Compare commits

..

385 Commits

Author SHA1 Message Date
Lucio Andrés Illanes Albornoz
22db646b91 assets/text/README.md: updated. 2020-03-28 10:37:39 +01:00
Lucio Andrés Illanes Albornoz
b77db5eb4b libgui/GuiCanvasWxBackend.py:resize(): correctly set self.cellSize on non-Windows platforms (via blowfish.) 2020-03-28 10:31:47 +01:00
Lucio Andrés Illanes Albornoz
8fabe15792 libgui/GuiCanvasWxBackend.py: only import WinDLL from ctypes if platform.system() == "Windows" (via blowfish.) 2020-03-27 18:13:26 +01:00
Lucio Andrés Illanes Albornoz
d545a7447e assets/audio/roarspoke15.wav: added.
libroar/RoarCanvasCommandsFile.py: melp?
roar.py: fix typo (via wr34k.)
2020-01-15 10:49:05 +01:00
Lucio Andrés Illanes Albornoz
c8a048403a Hard-wire default canvas size on New to [100, 30]. 2019-10-25 13:21:48 +02:00
Lucio Andrés Illanes Albornoz
0c66f94797 Cleanup, bugfixes & C++ backend implementation.
1) {About,Melp?} window: switch to green on black.
2) Assets window: scroll assets list on selected item update w/ <Cursor> or on deletion.
3) Canvas window: change default brush colours to [3, -1].
4) Canvas window: copy canvas cells given transparent cells from tools.
5) Canvas window: don't disable {re,un}do during object tool usage.
6) Canvas window: don't hide cursor during {re,un}do.
7) Canvas window: draw new cells using current brush background colour on resize.
8) Canvas window: fix memory leak on cell size updating.
9) Text tool: process [\r\n] in text pasted from clipboard.

assets/audio/roar{vap0r[1-8],viking[1-5]}.wav: added.
assets/text/README.txt: updated.
assets/tools/AnsiToMiRCART.py: added (for spoke.)
assets/tools/deploy-python.sh: updated.
2019-10-24 21:14:00 +02:00
Lucio Andrés Illanes Albornoz
90840bd0a0 Various bugfixes & usability improvements.
1) Canvas window: clear new canvases w/ [-1, -1] by default.
2) Canvas window: don't create new canvas on initialisation.
3) Canvas window: set default brush colours to [3, 9].
4) Erase tool: correctly fill non-text cells w/ background colour.
5) GUI: correctly show current operator name in status bar whilst active.
6) GUI: {de,in}crease canvas {height,width} w/ <Ctrl> & cursor keys.
7) GUI: disable tiling items unless current tool is object tool.
8) GUI: select tool w/ <F2-F10> accelerators.
2019-10-01 21:34:42 +02:00
Lucio Andrés Illanes Albornoz
bc969295dd Various bugfixes & usability improvements.
1) Backend: initial optimised cell rendering Python C module implementation skeleton.
2) Backend: raise alpha blending {fore,back}ground colour coefficient to {0.8,1.0 - 0.8}, resp.
3) Backend: reimplement cell rendering using Draw{Rectangle,Text}List().
4) Canvas window: eliminate {canvas,{scroll,tool}bar} flickering during resize.
5) Canvas window: fix cursor artifacts during resizing by masking cursor.
6) Canvas window: restore cursor after executing operations that remove it.
7) Import store: correctly parse non-conforming \u0003,<bg colour> sequences.
8) GUI: correctly save list of recently used files post-update.
2019-10-01 19:03:29 +02:00
Lucio Andrés Illanes Albornoz
e1bf3a3ad4 assets/images/roar.png: updated. 2019-09-28 20:25:03 +02:00
Lucio Andrés Illanes Albornoz
1fd0768912 Apply tool w/ mouse state on brush/colour update. 2019-09-28 20:12:53 +02:00
Lucio Andrés Illanes Albornoz
19957a2006 Implements automatic snapshotting & restoring from snapshots. 2019-09-28 19:45:45 +02:00
Lucio Andrés Illanes Albornoz
451a708d7a Minor cleanup & usability improvements.
1) Add format clarification to commands documentation.
2) Allow changing {canvas,brush} size w/ {<Ctrl> <Shift>,<Ctrl>} <Mouse wheel>.
3) Backend: {draw,produce,remove} cursor w/ list of coordinates instead of cells.
4) Update changing rendered cell size hotkey to <Ctrl> <Alt> <Mouse wheel>.
2019-09-28 12:08:52 +02:00
Lucio Andrés Illanes Albornoz
de96a7cdaa Various bugfixes.
1) Fix erroneous canvas repositioning on resize given no docked toolbars.
2) Correctly update scrollbar parameters when changing font (and hence cell) size.
2019-09-28 10:11:49 +02:00
Lucio Andrés Illanes Albornoz
4b98d1cdf1 Various bugfixes & usability improvements.
1)  Assets window: adds clear list context menu item.
2)  Assets window: allow deleting multiple selected items.
3)  Assets window: fix list view cursor key navigation.
4)  Backend: correctly blend transparent background cursor cells with canvas character cells.
5)  Backend: correctly determine cell size & set font size.
6)  Backend: correctly unmask cursor.
7)  Backend: disable font anti-aliasing on Windows.
8)  Backend: render transparent background cells as RGBA #303030FF.
9)  Canvas window: adds <F1> accelerator for `View melp?' menu item.
10) Canvas window: implement {dockable,floating} toolbars w/ wx' AUI framework.
11) Canvas window: separate tools toolbar from edit commands toolbar & dock both on right-hand side alongside each other.
12) Flip horizontally tool: flip characters, including some Unicode symbols.
13) Flip vertically tool: flip additional Unicode symbols.
14) Text tool: don't process keyboard events w/ either of <{Alt,AltGr,Ctrl}> modifiers.

assets/images/roar.png: updated.
2019-09-27 20:17:39 +02:00
Lucio Andrés Illanes Albornoz
2592fa9ad8 Tool bug fixes & usability improvements.
1) Circle, rectangle tool: initiate dragging with <Ctrl>, conclude w/ <[LR]MB> release.
2) Circle tool: fix artifacts and radius inference from brush size.
3) Line tool: fix origin point artifacts.
4) Rectangle tool: correctly determine target point.
2019-09-27 14:22:58 +02:00
Lucio Andrés Illanes Albornoz
fe6b79a2ab Implement {circle,rectangle} dragging w/ <Ctrl>.
assets/text/hotkeys.txt: updated.
assets/text/TODO: updated.
2019-09-26 23:34:01 +02:00
Lucio Andrés Illanes Albornoz
50edb55e0b Fix brush size toolbar items. 2019-09-26 23:15:56 +02:00
Lucio Andrés Illanes Albornoz
76e57bd081 Improve backend latency and throughput via batching.
assets/text/TODO: updated.
libtools/ToolLine.py: reflect brush width in pre-line dragging cursor.
2019-09-26 22:38:28 +02:00
Lucio Andrés Illanes Albornoz
86e2c9e904 Various bugfixes & usability improvements.
1) Correctly unmask cursor and dispatch delta patches on successful {re,un}do.
2) Don't prompt to save twice on exit via Exit {accelerator,menu item}.
3) Fix cursor artifacts by always resetting origin point on DC whilst unmasking cursor cells.
4) Fix {re,un}do {accelerator,{menu,toolbar} item} desynchronisation with actual canvas journal undo level.
5) Remove scattered remnants of initial implementation of unimplemented italic support.
6) Replace rendering transparent cursor w/ manual blending over wx.GraphicsContext() due to canvas bitmap masking & performance degradation.

assets/text/TODO: updated.
2019-09-26 14:06:11 +02:00
Lucio Andrés Illanes Albornoz
0750bc2261 assets/audio/roardaemon1.wav: added.
assets/text/TODO: updated.
2019-09-25 22:29:48 +02:00
Lucio Andrés Illanes Albornoz
c45a616442 Only allow setting selection w/ <Esc> post-selection. 2019-09-24 18:12:50 +02:00
Lucio Andrés Illanes Albornoz
e2183acf46 Update canvas filename on save as.
libroar/RoarCanvasCommandsFile.py:{_importFile,canvasSaveAs}(): fix return values.
2019-09-24 18:09:05 +02:00
Lucio Andrés Illanes Albornoz
279cca3313 Prompt to save changes on application exit. 2019-09-24 18:08:49 +02:00
Lucio Andrés Illanes Albornoz
286df901a7 assets/images/roar.png: updated. 2019-09-24 18:00:21 +02:00
Lucio Andrés Illanes Albornoz
50c9d69309 Fix underlined cells rendering line width. 2019-09-24 17:59:51 +02:00
Lucio Andrés Illanes Albornoz
fdcf242884 assets/audio/roarspoke1[234].wav: added. 2019-09-24 17:53:29 +02:00
Lucio Andrés Illanes Albornoz
b479e7ddbb Remove cursor before {re,un}doing to prevent artifacts.
assets/text/TODO: updated.
2019-09-24 17:45:18 +02:00
Lucio Andrés Illanes Albornoz
9eec4f58ab Increase roar window height to match default canvas size.
{assets/tools,lib{canvas,gui,roar,rtl,tools}}/*.py: remove Vim fold marker remnants.
2019-09-24 17:02:02 +02:00
Lucio Andrés Illanes Albornoz
a3d1ed3d96 libroar/RoarWindowMelp.py: set button accelerator. 2019-09-24 16:59:51 +02:00
Lucio Andrés Illanes Albornoz
84d20a4398 Adds GitHub & (hotkey) help dialogue menu items.
assets/text/hotkeys.txt: updated.
assets/text/TODO: updated.
libtools/ToolText.py: minor cleanup.
2019-09-24 16:46:02 +02:00
Lucio Andrés Illanes Albornoz
6ab7c443eb Erase back- w/ foreground colour on <RMB>.
assets/text/hotkeys.txt: updated.
assets/text/TODO: updated.
2019-09-24 15:44:54 +02:00
Lucio Andrés Illanes Albornoz
c083800b3b Don't show error when cancelling assets loading. 2019-09-24 15:13:48 +02:00
Lucio Andrés Illanes Albornoz
35f6910427 Load & store LRU file dialogue directory.
assets/text/TODO: updated.
2019-09-24 15:10:34 +02:00
Lucio Andrés Illanes Albornoz
c7dd80327d Fix canvas windows size regarding scrollbars.
assets/text/TODO: updated.
libroar/RoarClient.py: I FUCKING _HATE_ WXPYTHON
2019-09-24 14:39:37 +02:00
Lucio Andrés Illanes Albornoz
a2295d1cfc Various bugfixes & usability improvements.
1) Directly render characters to canvas DC w/ clipping.
2) Fix background colour toolbar icon bitmaps.
3) Line tool: correctly set brush colours whenever updated.
4) Line tool: fix artifacts bug.
5) Render tool cursor transparently at opacity 200 (out of 255.)

assets/text/TODO: updated.
2019-09-24 14:03:16 +02:00
Lucio Andrés Illanes Albornoz
e68cae09b5 assets/text/TODO: updated. 2019-09-23 22:46:11 +02:00
Lucio Andrés Illanes Albornoz
3eb4eaeea5 RoarWindowAbout.py: add website URL. 2019-09-23 22:32:37 +02:00
Lucio Andrés Illanes Albornoz
dc8b96146e Further shorten {file,operator,tool} status bar items.
assets/images/roar.png: updated.
2019-09-23 22:25:32 +02:00
Lucio Andrés Illanes Albornoz
e0a9c97343 assets/images/tool{Erase,PickColour}.png: updated. 2019-09-23 22:18:40 +02:00
Lucio Andrés Illanes Albornoz
c355e848b2 Implements pick colour tool.
assets/text/TODO: updated.
2019-09-23 22:12:20 +02:00
Lucio Andrés Illanes Albornoz
cefb982c1d Implements erase tool.
assets/text/TODO: updated.
2019-09-23 21:59:41 +02:00
Lucio Andrés Illanes Albornoz
1d650bd64b Allow purging MRU list manually & on failure.
assets/text/TODO: updated.
2019-09-23 21:37:52 +02:00
Lucio Andrés Illanes Albornoz
600cecfad1 Fix ANSI underline encoding, 2019-09-23 20:42:30 +02:00
Lucio Andrés Illanes Albornoz
b1de2eda79 Fix ANSI<->mIRC colour conversion. 2019-09-23 20:34:55 +02:00
Lucio Andrés Illanes Albornoz
9c03910420 Fix canvas windows size regarding scrollbars. 2019-09-23 20:24:18 +02:00
Lucio Andrés Illanes Albornoz
e6e0951029 Convert tabs to position-modulated run of whitespaces. 2019-09-23 19:56:10 +02:00
Lucio Andrés Illanes Albornoz
a48d7f3d32 Fix dirty flag updating on {new,open,resize}.
assets/text/hotkeys.txt: updated.
assets/text/TODO: updated.
2019-09-23 19:55:31 +02:00
Lucio Andrés Illanes Albornoz
bff70b409b {assets/tools,lib{canvas,gui,roar,rtl,tools}}/*.py: fix formatting. 2019-09-23 19:21:06 +02:00
Lucio Andrés Illanes Albornoz
0843b7d330 Allow cancelling & setting object selection w/ <Esc>. 2019-09-23 19:19:43 +02:00
Lucio Andrés Illanes Albornoz
724b460be1 assets/images/roar.png: updated. 2019-09-23 19:02:15 +02:00
Lucio Andrés Illanes Albornoz
6fcac2b3b9 libroar/RoarCanvasCommands.py:update(): shorten {file,operator,tool} status bar items.
assets/text/TODO: updated.
2019-09-23 18:59:18 +02:00
Lucio Andrés Illanes Albornoz
f504fa2b76 Various bugfixes & usability improvements.
1)  Add background colour toolbar beneath (foreground) colour toolbar.
2)  Add colour flipping command w/ {accelerator,{menu,toolbar} item}.
3)  Add {de,in}crease {brush,canvas} size accelerator.
4)  Add {hide,show} assets window toolbar item.
5)  Circle tool: draw outline with foreground colour.
6)  Circle tool: honour transparency.
7)  Fill tool: change comprehensive fill modifier key from <Shift> to <Ctrl>.
8)  Fill tool: fill with {back,fore}ground colour given <[RL]MB>
9)  Fix arrow keys cursor motion when scrolled down.
10  Instantly reflect {brush size,colour,tool} changes in canvas.
11) Object tool: honour transparency w/ non-external objects.
12) Object tool: update selection rectangle during <LMB> whilst dragging, set w/ release of <LMB>.
13) Rectangle tool: draw outline with foreground colour.
14) Rectangle tool: honour transparency.
15) Replace wx.ToolBar() w/ wx.lib.agw.aui.AuiToolBar() & custom wx.lib.agw.aui.AuiDefaultToolBarArt().
16) Restore scrolling position after resizing canvas.

.TODO: deleted.
assets/audio/roar{arab8,spoke11}.wav: added.
assets/text/hotkeys.txt: added to document hotkeys.
assets/text/requirements.txt, requirements.txt: moved.
assets/text/TODO: updated.
{assets/tools,lib{canvas,gui,roar,rtl,tools}}/*.py: remove Vim fold markers.
libroar/RoarCanvasCommandsFile.py:_importFile(): update wx.FileDialog() message.
libroar/RoarCanvasCommandsOperators.py:canvasOperator(): update invert colours {caption,label}.
2019-09-23 18:49:33 +02:00
Lucio Andrés Illanes Albornoz
eb3795a98e Initial {rotate,tile} operator implementation. 2019-09-23 09:21:57 +02:00
Lucio Andrés Illanes Albornoz
62e7edc852 assets/tools/deploy-python.sh: updated. 2019-09-19 13:40:58 +02:00
Lucio Andrés Illanes Albornoz
5320e45651 assets/audio/roar{arab7,spoke10}.wav: added. 2019-09-19 13:37:51 +02:00
Lucio Andrés Illanes Albornoz
74a0ef6df8 assets/text/TODO: updated. 2019-09-19 07:52:45 +02:00
Lucio Andrés Illanes Albornoz
25c42023f2 .env: added.
.vscode/{roar.code-workspace,settings.json}: added.
2019-09-18 21:12:42 +02:00
Lucio Andrés Illanes Albornoz
1a0e1bf2de assets/audio/roar{arab6,spoke9}.wav: added.
libroar/RoarWindowAbout.py: correctly pick random soundBitePathName.
2019-09-17 08:50:33 +02:00
Lucio Andrés Illanes Albornoz
93c05d6b09 libroar/RoarCanvasWindow.py:onKeyboardInput(): simulate LMB & advance to the right w/ space key except w/ Text tool. 2019-09-16 17:31:17 +02:00
Lucio Andrés Illanes Albornoz
d01cf7d167 assets/images/toolCursor.png: fix mode bits.
libroar/RoarCanvasWindow.py: remove trailing SP.
2019-09-16 17:17:04 +02:00
Lucio Andrés Illanes Albornoz
1c82e723c1 Implements foreground colour accelerators.
libgui/GuiFrame.py:onMenu(): don't call wx.SafeYield() because wxPython doesn't actually know what it wants from you EVER.
libroar/RoarCanvasCommands.py:update(): select tool corresponding to new foreground colour on change thereof.
libroar/RoarCanvasCommandsEdit.py:canvasColour(): specify <Control> <[0-9]> & <Control> <Shift> <[0-5]> accelerators.
libroar/RoarCanvasCommandsEdit.py:canvasColourAlpha(): specify <Control> <Shift> 6 accelerator.
2019-09-16 17:13:55 +02:00
Lucio Andrés Illanes Albornoz
3dae6dadde Decisively vanquish the lion's share of ftupid fucking fcrolling.
libgui/GuiCanvasWxBackend.py: rOAorAoARRAORA ROaroAR RAOAr roar RAOAAAR...
libroar/Roar{AssetsWindow,Canvas{CommandsOperators,Window}}.py: RoaWORAROORAOARORARARRA!!
libtools/Tool{,Circle,Fill,Line,Object,Rect,Text}.py: ROAOaRoaROAROAROOAAAARORORAOR?
assets/text/TODO: updated.
2019-09-16 16:54:07 +02:00
Lucio Andrés Illanes Albornoz
187dfa6355 libtools/ToolText.py:arabicCombiningRegEx: added.
libtools/ToolText.py:onKeyboardEvent(): skip combining Arabic characters.
2019-09-16 14:52:11 +02:00
Lucio Andrés Illanes Albornoz
deba33deba Initial implementation of Arabic character reshaping & handling.
libgui/GuiCanvasWxBackend.py:{arabicShapes{},_reshapeArabic()}: initial implementation.
libgui/GuiCanvasWxBackend.py:draw{CursorMaskWithJournal,Patch}(): update type signature.
libgui/GuiCanvasWxBackend.py:drawPatch(): call _reshapeArabic() on Arabic character patches.
libroar/Roar{Assets,Canvas}Window.py: pass updated set of arguments to backend.draw{CursorWithMask,Patch}().
libtools/ToolText.py:{arabicRegEx{},_checkRtl()}: initial implementation.
libtools/ToolText.py:_processKeyChar(): call _checkRtl().
libtools/ToolText.py:onKeyboardEvent(): initial implementation of RTL backspace support.
assets/text/TODO: updated.
2019-09-16 14:13:44 +02:00
Lucio Andrés Illanes Albornoz
366a958fdb libtools/ToolObject.py:_mouseEventTsTarget(): fix return value. 2019-09-16 11:42:50 +02:00
Lucio Andrés Illanes Albornoz
632e3a8ac1 libroar/RoarCanvasCommandsOperators.py:canvasOperator(): correctly {pass,set} parentCanvas.dirty.
assets/text/TODO: updated.
2019-09-16 11:38:14 +02:00
Lucio Andrés Illanes Albornoz
c93c8dd8b0 libroar/RoarCanvasCommandsTools.py:canvasTool(): call dropTarget.done() when setting new tool from external object as current tool. 2019-09-16 11:23:47 +02:00
Lucio Andrés Illanes Albornoz
5ad678bf4c Allow pasting text when using text tool.
libtools/ToolText.py:{_processKeyChar,onKeyboardEvent}(): split from onKeyboardEvent().
libtools/ToolText.py:onKeyboardEvent(): obtain & iteratively _processKeyChar() clipboard text on <Control> V.
libtools/ToolText.py:onKeyboardEvent(): dispatch cursor patch once after {drawing,pasting}.
assets/text/TODO: updated.
2019-09-16 11:15:23 +02:00
Lucio Andrés Illanes Albornoz
9752a8e6ce Honour RTL flow in text tool.
libtools/ToolText.py:rtlRegEx: added.
libtools/ToolText.py:onKeyboardEvent(): move backwards given RTL keyChar.
assets/text/TODO: updated.
2019-09-16 10:09:25 +02:00
Lucio Andrés Illanes Albornoz
99369626c4 Fully implement {{arrow keys,backspace,enter},arrow keys} in {text,} tool{,s}.
libroar/RoarCanvasWindow.py:applyTool(): delegate updating of brushPos to ToolText if current tool.
libroar/RoarCanvasWindow.py:applyTool(): fix function result.
libroar/RoarCanvasWindow.py:applyTool(): pass updated set of arguments to on{Keyboard,Mouse}Event().
libroar/RoarCanvasWindow.py:onKeyboardInput(): allow wrapping around canvas when receiving cursor key input.
libroar/RoarCanvasWindow.py:onKeyboardInput(): supply keyCode or None to applyTool().
libroar/RoarCanvas{CommandsTools,Window}.py: supply keyCode or None to applyTool().
libtools/Tool{,Circle,Fill,Line,Object,Rect}.py:on{Keyboard,Mouse}Event(): update type signature.
libtools/ToolText.py:onKeyboardEvent(): fully implement {arrow keys,backspace,enter}.
libtools/ToolText.py:onKeyboardEvent(): update cursor when necessary.
libtools/ToolText.py:onMouseEvent(): correctly set brushPos if mouseLeftDown or mouseRightDown.
libtools/ToolText.py:__init__(): removed.
assets/text/TODO: updated.
2019-09-16 09:55:30 +02:00
Lucio Andrés Illanes Albornoz
299a045aa9 libcanvas/CanvasExportStore.py:exportTextBuffer(): correctly encode {bold,underline} cell state changes. 2019-09-16 08:52:04 +02:00
Lucio Andrés Illanes Albornoz
72b96b7c77 libtools/ToolFill.py: honour cell background colour & character on LMB, cell background colour only on RMB. 2019-09-16 08:45:03 +02:00
Lucio Andrés Illanes Albornoz
70d7995b20 Fix underlined cells rendering.
libgui/GuiCanvasWxBackend.py: FUCK YOU WXPYTHON
assets/text/TODO: updated.
2019-09-16 08:39:55 +02:00
Lucio Andrés Illanes Albornoz
3c0606a390 liboperators/OperatorFlipVertical.py:{flipPairs,apply()}: correctly flip flippable text characters.
assets/text/TODO: updated.
2019-09-16 08:13:55 +02:00
Lucio Andrés Illanes Albornoz
aef7f629d0 libroar/RoarCanvasCommandsFile.py:canvasSaveAs(): fix self.canvasPathName reference.
libroar/RoarCanvasWindow.py:applyTool(): don't dispatch cursor patch if mapPoint == None.
libroar/RoarCanvasWindow.py:applyTool(): correctly handle cursor tool.
assets/text/TODO: updated.
2019-09-16 08:05:50 +02:00
Lucio Andrés Illanes Albornoz
df8ee6fedf Implements cursor tool & arrow keys navigation.
assets/images/toolCursor.png: added.
libroar/Roar{CanvasCommands{,Tools},Client}.py: adds cursor tool.
libroar/RoarCanvasWindow.py:applyTool(): dummy-handle cursor tool.
libroar/RoarCanvasWindow.py:onKeyboardInput(): update cursor position & cursor on {down,left,right,up} arrow key input.
assets/text/TODO: updated.
2019-09-15 21:33:19 +02:00
Lucio Andrés Illanes Albornoz
aed3f7157c Implements invert colours operator.
liboperators/OperatorInvert.py: initial implementation.
libroar/RoarCanvasCommandsOperators.py: adds OperatorInvert.
assets/text/TODO: updated.
2019-09-15 20:43:49 +02:00
Lucio Andrés Illanes Albornoz
af5363cf04 assets/images/roar.png: updated. 2019-09-15 20:31:40 +02:00
Lucio Andrés Illanes Albornoz
e71f11ddad libtools/ToolObject.py:_mouseEventTsSelect(): reset object{Map,Size} when resetting to TS_NONE. 2019-09-15 19:37:10 +02:00
Lucio Andrés Illanes Albornoz
78b567d42b Fix assets management canvas panel scrolling.
libgui/GuiWindow.py: ONCE, THERE WAS A _LETHALLY FUCKING WOUNDED_ WXPYTHON
libroar/RoarAssetsWindow.py: ON THE VERY EDGE OF ITS MISERABLE EXISTENCE
libroar/RoarCanvasWindow.py: BECAUSE A LION VICIOUSLY MAULED IT AND TORTURED IT FOR 392 DAYS AND TWO (2) SECONDS
2019-09-15 16:54:19 +02:00
Lucio Andrés Illanes Albornoz
9f72bd5a4c Inhibit {drag & drop,{un,re}do} during object tool usage.
libroar/RoarCanvasCommands.py:update(): disable {re,un}do {menu,toolbar} items on lastPanelState["undoInhibit"].
libroar/RoarCanvasWindow.py:applyTool(): call commands.update(undoInhibit=True) after active object tool usage.
libroar/RoarCanvasWindow.py:OnDropText(): inhibit during active object tool usage.
2019-09-15 16:04:38 +02:00
Lucio Andrés Illanes Albornoz
1487f2eb9a libroar/RoarCanvasWindow.py:onKeyboardInput(): enter pdb on <Shift> <Pause>. 2019-09-15 15:17:27 +02:00
Lucio Andrés Illanes Albornoz
d7ebed77f7 libroar/RoarCanvasWindow.py:RoarCanvasWindowDropTarget.{done,OnDropText,__init__}(): {honour,set} inProgress.
libroar/RoarCanvasWindow.py:applyTool(): call dropTarget.done() when resetting to last tool.
2019-09-15 13:57:41 +02:00
Lucio Andrés Illanes Albornoz
a40017b607 Implements object tool operators context menu.
libgui/GuiFrame.py: _FUCK_ _YOU_ WXPYTHON
libroar/RoarCanvasCommandsOperators.py: I _FUCKING_ _HATE_ WXPYTHON
libroar/RoarCanvasWindow.py: DIE IN A DITCH WXPYTHON YOU ROTTING PILE OF FUCKING HIPPOPOTAMUS SHIT
libroar/RoarClient.py: I HATE WXPYTHON I HATE WXPYTHON I HATE WXPYTHON I HATE WXPYTHON I HATE WXPYTHON
2019-09-15 13:52:44 +02:00
Lucio Andrés Illanes Albornoz
1c7524ba6f libroar/RoarCanvasWindow.py:onPaint(): invariably remove cursor prior to calling backend.onPaint().
libroar/RoarCanvasWindow.py:onScroll(): removed.
2019-09-15 12:58:39 +02:00
Lucio Andrés Illanes Albornoz
866ef19966 libroar/RoarCanvasCommands.py:update(): disable undo if on last undo item. 2019-09-15 12:52:27 +02:00
Lucio Andrés Illanes Albornoz
245925406e libgui/GuiFrame.py:{_init,load}Menu(): split from loadMenu(), implement nested (sub)menus.
libroar/RoarCanvasCommandsEdit.py: put {brush,canvas} size commands into submenu.
libroar/RoarCanvasCommandsFile.py: put {ex,im}port commands into submenu.
assets/text/TODO: updated.
2019-09-15 12:41:14 +02:00
Lucio Andrés Illanes Albornoz
7226639977 Replace RMB w/ LMB outside of region as end selection UI.
libtools/ToolObject.py: cleanup.
assets/text/TODO: updated.
2019-09-15 11:55:59 +02:00
Lucio Andrés Illanes Albornoz
e2f413e4ba Implements merged object tool & flip operators.
assets/images/toolObject.png: added.
liboperators/Operator{,Flip{Horizontal,Vertical}}.py: initial implementation.
libroar/RoarCanvasCommands.py: adds RoarCanvasCommandsOperators.
libroar/RoarCanvasCommandsOperators.py: initial implementation.
libroar/Roar{CanvasCommands{,Tools},Client}.py: replaces ToolSelect{Clone,Move} w/ ToolObject.
libroar/RoarCanvasWindow.py:RoarCanvasWindowDropTarget.OnDropText(): update ToolObject() invocation.
libroar/RoarCanvasWindow.py:{applyTool,onMouseInput}(): pass keyModifiers to Tool.onMouseEvent().
libroar/RoarCanvasWindow.py:applyTool(): only switch back to lastTool if current object tool contains an external object.
libroar/RoarCanvasWindow.py:onLeaveWindow(): disable hiding cursor for now.
libtools/Tool{,Circle,Fill,Line,Rect,Text}.py:onMouseEvent(): update type signature.
libtools/Tool{Object,Select{,Clone,Move}}.py: merged into libtools/ToolObject.py.
roar.py: add liboperators to sys.path[].
assets/text/TODO: updated.
2019-09-15 11:06:25 +02:00
Lucio Andrés Illanes Albornoz
d67dd317a1 libtools/Tool.py:Tool(): derive from object. 2019-09-14 17:11:08 +02:00
Lucio Andrés Illanes Albornoz
9cc6364a08 assets/images/roar.png: updated. 2019-09-14 16:29:51 +02:00
Lucio Andrés Illanes Albornoz
ddec3cefeb libroar/RoarWindowAbout.py, assets/tools/deploy-python.sh: updated. 2019-09-14 16:16:36 +02:00
Lucio Andrés Illanes Albornoz
25c9e88484 libgui/GuiCanvasWxBackend.py:resize(): set font size from cellSize[0] + 1 vs. hard-wired 8.
libroar/RoarCanvasWindow.py:{onMouseWheel,__init__}(): {de,in}crease cell size w/ <Ctrl> <Mouse Wheel>.
assets/text/TODO: updated.
2019-09-14 15:44:17 +02:00
Lucio Andrés Illanes Albornoz
0e4915351e libgui/GuiFrame.py:onMenu(): clear event queue after calling command function. 2019-09-14 15:20:20 +02:00
Lucio Andrés Illanes Albornoz
bdc8bcd494 Implements recently used {directory,files} in {assets,application} windows & file dialogues.
libgui/GuiFrame.py:GuiSubMenuDecorator(): added.
libgui/GuiFrame.py:loadMenus(): process submenu menu items.
libroar/RoarCanvasCommandsFile.py:{_{load,push}Recent,canvasOpenRecent}(): initial implementation.
libroar/RoarCanvasCommandsFile.py:_import{,File}(): return pathname along w/ rc.
libroar/RoarCanvasCommandsFile.py:canvas{Open,SaveAs}(): call _pushRecent() post-{open,save}.
libroar/RoarCanvasCommandsFile.py:__init__(): updated.
librtl/RtlPlatform.py: added.
librtl/RtlPlatform.py:getLocalConfPathName(): initial implementation.
roar.py: manually call _loadRecent() & _pushRecent() post-import.
assets/text/TODO: updated.
2019-09-14 15:16:57 +02:00
Lucio Andrés Illanes Albornoz
075bff0644 bcanvas/CanvasImportStore.py:importTextBuffer(): handle exceptions. 2019-09-14 11:52:24 +02:00
Lucio Andrés Illanes Albornoz
c049181ceb libroar/RoarAssetsWindow.py:{_importFiles, on{Load,Save}List,__init__}(): {pass,receive} lastDir {before,after} wx.FileDialog()s.
libroar/RoarCanvasCommandsFile.py:{_importFile,canvasExportAs{Ansi,Png},SaveAs,__init__}(): {pass,receive} lastDir {before,after} wx.FileDialog()s.
2019-09-14 11:49:08 +02:00
Lucio Andrés Illanes Albornoz
67f2d11240 libroar/RoarCanvasWindow.py:onScroll(): hide cursor when scrolling. 2019-09-14 11:33:13 +02:00
Lucio Andrés Illanes Albornoz
9e2d9d1419 .gitignore: ignore build/. 2019-09-14 11:29:26 +02:00
Lucio Andrés Illanes Albornoz
6d12c04349 setup.py: added.
requirements.txt: updated.
2019-09-14 11:23:29 +02:00
Lucio Andrés Illanes Albornoz
cab0478c39 roar.py: fix argument handling. 2019-09-14 11:21:40 +02:00
Lucio Andrés Illanes Albornoz
b1cc38a8e7 requirements.txt: added (via Civil.) 2019-09-14 11:05:05 +02:00
Lucio Andrés Illanes Albornoz
5123a016e8 libroar/RoarAssetsWindow.py:{_load_list,onLoadList}(): split from onLoadList().
roar.py: optionally load assets list from argv[2].
2019-09-14 10:52:05 +02:00
Lucio Andrés Illanes Albornoz
243311c091 libtools/ToolObject.py:onSelectEvent(): obtain correct canvas map cell given transparent object selection cell. 2019-09-14 10:49:11 +02:00
Lucio Andrés Illanes Albornoz
f65fe72cea libroar/RoarCanvasCommandsFile.py:canvasSave{,As}(): don't reset dirty on canvasSaveAs(). 2019-09-13 21:14:11 +02:00
Lucio Andrés Illanes Albornoz
74e2bf7f49 libgui/GuiCanvasWxBackend.py:GuiBufferedDC.__init__(): directly select buffer into DC.
libroar/RoarCanvasWindow.py:applyTool(): normalise mapPoint w/ viewRect if mapPoint != None.
2019-09-13 21:09:51 +02:00
Lucio Andrés Illanes Albornoz
2fa06043c6 libroar/RoarCanvasWindow.py:applyTool(): only call tool.onMouseEvent() if mouse state has changed since the last call & update lastCellState.
libroar/RoarCanvasWindow.py:{on{Enter,Leave}Window,__init__}(): reset lastCellState.
2019-09-13 20:20:26 +02:00
Lucio Andrés Illanes Albornoz
2c8fa89eda libgui/GuiCanvasWxBackend.py:{resize,__init__}(): restrict external font loading to Windows (via sym.)
assets/text/TODO: updated.
2019-09-13 10:03:39 +02:00
Lucio Andrés Illanes Albornoz
6e470c93d0 libgui/GuiCanvasWxBackend.py: render transparent cells as dark grey `░'. 2019-09-12 21:01:06 +02:00
Lucio Andrés Illanes Albornoz
a4a9c9a2d5 libtools/ToolSelectMove.py: exclude sub-cells of both of {select,src}Rect from being cleared.
libtools/ToolSelect.py: minor cleanup.
2019-09-12 20:09:11 +02:00
Lucio Andrés Illanes Albornoz
ec515d4ed4 libroar/RoarCanvasWindow.py:dispatchDeltaPatches(): remove cursor prior to updating canvas. 2019-09-12 17:03:21 +02:00
Lucio Andrés Illanes Albornoz
3e4644122b libtools/ToolSelect.py: fix function signatures. 2019-09-12 16:55:03 +02:00
Lucio Andrés Illanes Albornoz
31b45d1076 libcanvas/CanvasJournal.py:end(): purge patchesUndo if at patchesUndoLevel > 0.
assets/text/TODO: updated.
2019-09-12 16:39:17 +02:00
Lucio Andrés Illanes Albornoz
6e05238efa libcanvas/CanvasJournal.py:{begin,end,updateCurrentDeltas}(): {insert,delete,append} at patchesUndoLevel.
libgui/GuiWindow.py:{onEnterWindow,__init__}(): bind wx.EVT_ENTER_WINDOW.
libroar/RoarCanvasWindow.py:applyTool(): only call tool.onMouseEvent() if mouse has moved to another cell since the last dirtying call & update lastDirtyCell.
libroar/RoarCanvasWindow.py:{onEnterWindow,onLeaveWindow,__init__}(): reset lastDirtyCell.
libroar/RoarCanvasWindow.py:__init__():
assets/text/TODO: updated.
2019-09-12 16:24:53 +02:00
Lucio Andrés Illanes Albornoz
b6c063c5d3 Initial implementation of assets window & external object tool.
libgui/GuiFrame.py:GuiMiniFrame(): added as sub-class of wx.MiniFrame().
libgui/GuiWindow.py:RoarCanvasWindowDropTarget(): initial implementation.
libgui/GuiWindow.py:__init__(): receive & pass optional style parameter.
libgui/GuiWindow.py:__init__(): set drop target.
libroar/RoarAssetsWindow.py: initial implementation.
libroar/RoarClient.py:__init__(): add RoarAssetsWindow().
libtools/ToolObject.py: initial implementation.
assets/text/TODO: updated.
2019-09-12 12:49:53 +02:00
Lucio Andrés Illanes Albornoz
99e746f090 libgui/GuiCanvasWxBackend.py:_drawCharPatch(): fix underscore rendering.
libgui/GuiCanvasWxBackend.py:_get{Brush,Char}PatchColours(): fix {back,fore}ground colour {brush{,es},pen}.
assets/text/TODO: updated.
2019-09-12 11:26:17 +02:00
Lucio Andrés Illanes Albornoz
6dad3c7798 libtools/ToolText.py: ignore non-printable key events. 2019-09-12 10:28:56 +02:00
Lucio Andrés Illanes Albornoz
3f992ec44f assets/text/{arab-spokelion,spoke-arablion}.txt: ROAR! 2019-09-12 10:09:41 +02:00
Lucio Andrés Illanes Albornoz
9de3372a80 libtools/Tool{,Circle,Fill,Line,Rect,Select,Text}.py: sync function signature comments. 2019-09-12 10:08:14 +02:00
Lucio Andrés Illanes Albornoz
8485ab568d libroar/RoarCanvasCommandsEdit.py:canvasColourAlpha_(): fix signature. 2019-09-12 10:03:29 +02:00
Lucio Andrés Illanes Albornoz
7a4e1b23e9 libcanvas/CanvasExportStore.py:exportTextBuffer(): fix transparent colour processing.
libcanvas/CanvasImportStore.py:importTextBuffer(): fix transparent colour processing.
2019-09-12 10:02:41 +02:00
Lucio Andrés Illanes Albornoz
0f88a78639 libgui/GuiCanvasWxBackend.py:resize(): load font named by fontName instead of wx.FONTFAMILY_TELETYPE.
libgui/GuiCanvasWxBackend.py:__init__(): receive & set font{Name,PathName}.
libgui/GuiCanvasWxBackend.py:__init__(): call AddFontResourceW() in lieu of WxPython support FOR ANYTHING THAT GOES BEYOND BEING A BLOODY NON-STOP HINDRANCE.
2019-09-11 15:33:15 +02:00
Lucio Andrés Illanes Albornoz
418d16a81a libgui/GuiFrame.py:{load{Accel,Menu,ToolBar}s,__init__}(): replace lastId w/ wx.NewId(). 2019-09-11 14:45:59 +02:00
Lucio Andrés Illanes Albornoz
3be34bc657 libgui/GuiFrame.py:onMenu(): only dispatch events via itemsById[eventId] if present, skip otherwise. 2019-09-11 14:43:55 +02:00
Lucio Andrés Illanes Albornoz
e30628f28d libroar/RoarCanvasCommandsFile.py:_import(): handle FileNotFoundError exception. 2019-09-11 13:53:48 +02:00
Lucio Andrés Illanes Albornoz
6109e9b38c libgui/GuiCanvasWxBackend.py:GuiBufferedDC(): implement double-buffered wx.MemoryDC() honouring view{Rect,Size}.
libgui/GuiCanvasWxBackend.py:{getDeviceContext,onPaint}(): use GuiBufferedDC() if viewRect > (0, 0).
libroar/RoarCanvas{CommandsTools,Window}.py: updated.
assets/text/TODO: updated.
2019-09-11 09:39:36 +02:00
Lucio Andrés Illanes Albornoz
968e7d45c5 assets/images/logo[12].bmp: updated.
libroar/RoarWindowAbout.py: switch to wx.FlexGridSizer().
assets/text/TODO: updated.
2019-09-10 18:43:08 +02:00
Lucio Andrés Illanes Albornoz
af1ef072ab libroar/RoarWindowAbout.py: cleanup. 2019-09-10 13:24:47 +02:00
Lucio Andrés Illanes Albornoz
5f7f2b0f9c assets/tools/deploy-python.sh: updated. 2019-09-10 12:32:09 +02:00
Lucio Andrés Illanes Albornoz
1156f8b5c3 libroar/RoarCanvasCommands.py: fix base classes order. 2019-09-10 12:30:35 +02:00
Lucio Andrés Illanes Albornoz
bb23efac56 libgui/GuiFrame.py:loadAccels(): obtain from {menu,Toolbar}s[].
libroar/RoarCanvasCommands{,Edit,File,Help,Tools}.py: remove self.accels[].
libroar/RoarClient.py: updated.
2019-09-10 12:28:26 +02:00
Lucio Andrés Illanes Albornoz
309e8cf089 libtools/ToolText.py:onKeyboardEvent(): correctly return (rc, dirty). 2019-09-10 12:20:23 +02:00
Lucio Andrés Illanes Albornoz
fb274c0d66 libroar/RoarCanvas{CommandsTools,Window}.py: explicitly pass canvas.
libtools/Tool{,Circle,Fill,Line,Rect,Select,Text}.py: explicitly receive canvas.
2019-09-10 12:12:12 +02:00
Lucio Andrés Illanes Albornoz
f23101f511 libgui/GuiFrame.py: updated.
libroar/RoarCanvasCommands{,Edit,File,Help,Tools}.py: split from libroar/RoarCanvasInterface.py.
libroar/RoarCanvasWindow.py: updated.
libroar/RoarClient.py: updated.
libroar/RoarWindowAbout.py: renamed from libroar/RoarClientAboutWindow.py.
roar.py: updated.
2019-09-10 12:06:56 +02:00
Lucio Andrés Illanes Albornoz
01ca10be21 libroar/RoarCanvasInterface.py, libgui/GuiCanvasInterface.py: split from libgui/GuiCanvasInterface.py.
libroar/RoarClientAboutWindow.py: merged from libgui/GuiCanvasInterfaceAbout.py.
libgui/GuiFrame.py:Gui{Command{,List},Select}Decorator(): moved from libroar/RoarCanvasInterface.py.
libroar/RoarClient.py: updated.
2019-09-10 10:22:02 +02:00
Lucio Andrés Illanes Albornoz
8fd0294ef8 libroar/RoarClient.py, libgui/GuiFrame.py: split from libgui/GuiFrame.py.
libroar/RoarCanvasWindow.py, libgui/GuiWindow.py: split from libgui/GuiCanvasPanel.py.
roar.py: updated.
2019-09-10 10:14:12 +02:00
Lucio Andrés Illanes Albornoz
116e5915dc libgui/GuiCanvasInterface.py:_import(): call reset{Cursor,Undo}() on journal after importing. 2019-09-09 20:10:52 +02:00
Lucio Andrés Illanes Albornoz
c1ce141700 libcanvas/CanvasJournal.py:begin(): don't reset patchesUndo{[],Level}.
assets/text/TODO: updated.
2019-09-09 19:59:57 +02:00
Lucio Andrés Illanes Albornoz
5f00b60cdd assets/text/TODO: updated. 2019-09-09 19:43:07 +02:00
Lucio Andrés Illanes Albornoz
649f118b20 libgui/GuiCanvasInterfaceAbout.py: update legend. 2019-09-09 19:34:22 +02:00
Lucio Andrés Illanes Albornoz
b50879db5f assets/tools/deploy-python.sh: updated. 2019-09-09 19:33:16 +02:00
Lucio Andrés Illanes Albornoz
a4ee9e9083 libcanvas/CanvasImportStore.py:importTextBuffer(): correctly process mIRC foreground colour sequences.
assets/images/roar.png: updated.
assets/text/arab-puke.txt: removed.
assets/text/spoke-vxplion.txt: added.
2019-09-09 19:29:50 +02:00
Lucio Andrés Illanes Albornoz
bfd8e17b2b libgui/GuiCanvasPanel.py:{onPanelScroll,__init__}(): reset cursor on EVT_SCROLLWIN_LINE{DOWN,UP}. 2019-09-09 19:03:14 +02:00
Lucio Andrés Illanes Albornoz
c2f8fb272e assets/text/TODO: updated. 2019-09-09 18:47:39 +02:00
Lucio Andrés Illanes Albornoz
0fe6899d05 libcanvas/Canvas.py:dispatchPatchSingle(): cloned from dispatchPatch().
lib{canvas/Canvas{,Journal},gui/GuiCanvasPanel}.py: replace dirtyJournal & pushDeltas() w/ explicit {begin,end}().
libgui/GuiCanvasPanel.py:applyTool(): updated.
libgui/GuiCanvasPanel.py:dispatchPatchSingle(): cloned from dispatchPatch().
libtools/Tool{,Circle,Fill,Line,Rect,Select{,Clone,Move},Text}.py:on{Mouse,Keyboard}Event(): return rc, dirty.
libtools/ToolSelect{Clone,Move}.py:onSelectEvent(): return rc, dirty.
2019-09-09 18:43:33 +02:00
Lucio Andrés Illanes Albornoz
b3f587fc73 libgui/GuiCanvasInterface.py:canvasTool(): call applyTool() w/ new tool post-selection.
libgui/GuiCanvasPanel.py:{applyTool,onPanelInput}(): split from onPanelInput().
libtools/Tool{,Circle,Fill,Line,Rect,Select,Text}.py:on{Keyboard,Mouse}Event(): updated.
libtools/ToolFill.py:onMouseEvent(): display cursor.
libtools/ToolSelect{,Clone,Move}.py:onSelectEvent(): updated.
2019-09-09 18:18:54 +02:00
Lucio Andrés Illanes Albornoz
1fe5db9fa6 libgui/GuiCanvasPanel.py:{onPanelSize,__init__}(): call _updateScrollBars() on EVT_SIZE. 2019-09-09 17:34:50 +02:00
Lucio Andrés Illanes Albornoz
c2657eeefd libgui/GuiCanvasPanel.py:_updateScrollBars(): split from resize().
libgui/GuiCanvasPanel.py:_updateScrollBars(): reduce flickering induced by calling SetVirtualSize() when scrollbars are not required.
libgui/GuiCanvasPanel.py:{resize,update}(): updated.
libgui/GuiCanvasWxBackend.py:{reset,__init__}(): removes obsolete reset().
2019-09-09 17:27:37 +02:00
Lucio Andrés Illanes Albornoz
04430e7c0c lib{canvas,gui,rtl,tools}/*.py: remove trailing SP. 2019-09-09 12:46:52 +02:00
Lucio Andrés Illanes Albornoz
56a3b748cc libgui/GuiCanvasPanel.py:{onPanelEnterWindow,__init__}(): don't steal focus when entering window.
libgui/GuiCanvasPanel.py:__init__(): bind EVT_CHAR to self vs.  parentFrame.
libgui/GuiFrame.py:{onChar,__init__}(): forward EVT_CHAR to canvasPanel.onPanelInput().
2019-09-09 12:44:32 +02:00
Lucio Andrés Illanes Albornoz
88d69cb31e Initial canvas panel scrollbar implementation.
assets/text/TODO: updated.
libgui/GuiCanvasPanel.py:__init__(): provide self.winSize & call SetScrollRate().
libgui/GuiCanvasPanel.py:{_drawPatch,dispatch{DeltaPatches,Patch},onPanel{Input,LeaveWindow},resize,update}(): receive and/or pass viewRect.
libgui/GuiCanvasPanel.py:onPanelPaint(): updated.
libgui/GuiCanvasPanel.py:resize(): call SetVirtualSize().
libgui/GuiCanvasWxBackend.py:_{draw{Brush,Char}Patch,_get{Brush,Char}PatchColours,xlatePoint}(): updated.
libgui/GuiCanvasWxBackend.py:{draw{CursorMaskWithJournal,Patch},getDeviceContext,xlateEventPoint}(): receive and/or pass viewRect.
libgui/GuiCanvasWxBackend.py:getDeviceContext(): return ClientDC() if viewRect > (0, 0)
libgui/GuiCanvasWxBackend.py:onPanelPaintEvent(): blit subset of canvasBitmap into BufferedPaintDC() if viewRect > (0, 0).
libtools/Tool{,Circle,Fill,Line,Rect,Select{,Clone,Move},Text}.py: receive & pass viewRect to dispatchFn().
2019-09-09 12:30:25 +02:00
Lucio Andrés Illanes Albornoz
e54429e29e libgui/GuiCanvasInterface.py: soft-fail importing ImgurApiKey.
assets/text/TODO: updated.
2019-09-08 20:05:34 +02:00
Lucio Andrés Illanes Albornoz
52672796c6 libgui/GuiCanvasInterface.py:canvas{Brush,Canvas}Size(): correctly {de,in}crement {brush,canvas} height & width. 2019-09-08 19:24:55 +02:00
Lucio Andrés Illanes Albornoz
ff05b829b8 assets/text/{LICENCE,README.md,TODO}: convert to DOS file format for convenience on Windows. 2019-09-08 19:14:24 +02:00
Lucio Andrés Illanes Albornoz
629e03928e assets/tools/deploy-python.sh: updated. 2019-09-08 19:12:16 +02:00
Lucio Andrés Illanes Albornoz
fcd5173329 {libcanvas/Canvas,libgui/GuiCanvasPanel}.py: neither commit nor draw patches referencing cells outside of the current map. 2019-09-08 19:04:27 +02:00
Lucio Andrés Illanes Albornoz
b83684073e libgui/GuiCanvasInterface.py: minor cleanup. 2019-09-08 19:02:19 +02:00
Lucio Andrés Illanes Albornoz
f6e2dbd64b libgui/GuiCanvasInterface.py: minor cleanup. 2019-09-08 18:34:30 +02:00
Lucio Andrés Illanes Albornoz
c77813380a assets/text/ImgurApiKey.py.template, lib{canvas,gui,rtl,tools}/*.py: deXXXify. 2019-09-08 18:08:04 +02:00
Lucio Andrés Illanes Albornoz
6da84c05c5 {libcanvas/Canvas,libgui/GuiCanvas{Interface,Panel}}.py: implement dirty flag.
libgui/GuiCanvasInterface.py:canvas{Exit,New,Open,Save,Import{Ansi,FromClipboard,Sauce}}(): prompt to save canvas if dirty.
libgui/GuiCanvasInterface.py: minor cleanup.
libcanvas/Canvas.py:resize(): don't push undo patches when extending canvas.
2019-09-08 18:00:11 +02:00
Lucio Andrés Illanes Albornoz
d410ef9321 libtools/ToolText.py:onMouseEvent(): only set self.textPos on is{Left,Right}Down. 2019-09-08 17:02:41 +02:00
Lucio Andrés Illanes Albornoz
a628ed6471 Implements transparent colour.
libgui/Gui{CanvasInterface,{General,}Frame}.py: refactored.
{libcanvas/CanvasExportStore,libgui/GuiCanvas{Interface,WxBackend}}.py: implements transparent colour.
libgui/GuiCanvasPanel.py: updated.
libtools/ToolText.py: updated.
assets/text/TODO: updated.
2019-09-08 16:57:45 +02:00
Lucio Andrés Illanes Albornoz
8367c166f3 assets/tools/MiRCART{Reduce,ToAnsi}.py: fixed.
libcanvas/CanvasExportStore.py: fixed.
assets/text/TODO: updated.
2019-09-07 17:38:28 +02:00
Lucio Andrés Illanes Albornoz
f3140d4b3d libcanvas/CanvasColours.py, libgui/GuiCanvasColours.py: split from libcanvas/CanvasColours.py.
libcanvas/Canvas.py, libgui/GuiCanvasPanel.py: split from libcanvas/Canvas.py.
libcanvas/CanvasImportStore.py:import{Ansi{Buffer,File},SauceFile}(): fixed (via spoke.)
{libgui/Gui{CanvasInterface,Frame},libtools/Tool{Fill,Select,Text},roar}.py: updated.
libgui/GuiCanvasWxBackend.py: merged from libcanvas/CanvasBackend.py.
2019-09-07 10:39:26 +02:00
Lucio Andrés Illanes Albornoz
c28c2f87ab libcanvas/CanvasColours.py: renamed from libcanvas/Colours.py.
lib{canvas/Canvas{Backend,{Ex,Im}portStore},gui/GuiFrame}.py: updated.
2019-09-06 10:32:54 +02:00
Lucio Andrés Illanes Albornoz
34d9df756b libcanvas/Canvas{Ex,Im}portStore.py: cleanup.
assets/tools/MiRCARTReduce.py: fixed (for spoke.)
assets/tools/{MiRCARTTo{Ansi,PngFile},SAUCETo{Ansi,MiRCART}}.py: updated.
lib{canvas/Canvas,gui/Gui{CanvasInterface,Frame}}.py: updated.
lib{{canvas/Canvas{Backend,Journal},Colours},tools/ToolRect}.py: minor cleanup.
roar.py: updated.
2019-09-06 10:29:45 +02:00
Lucio Andrés Illanes Albornoz
bb4ba3ae46 libcanvas/Canvas.py, libgui/GuiCanvasInterface.py: correctly set ImgurApiKey.
libcanvas/CanvasExportStore.py:_exportFileToImgur(): fixed.
libcanvas/CanvasExportStore.py:exportPastebin(): fixed.
2019-09-05 17:13:35 +02:00
Lucio Andrés Illanes Albornoz
045b7a24ed Implements importing from {ANSI,SAUCE} and exporting to ANSI files.
assets/tools/{MiRCARTTo{Ansi,PngFile},SAUCETo{Ansi,MiRCART}}.py: reimplemented using Canvas{Ex,Im}portStore.
libcanvas/Canvas.py: minor cleanup.
libcanvas/CanvasExportStore.py:export{Ansi,Png}File(): merged from assets/tools/MiRCARTTo{Ansi,PngFile}.py.
libcanvas/CanvasImportStore.py:import{Ansi,Sauce}File(): merged from assets/tools/SAUCETo{Ansi,MiRCART}.py.
libcanvas/Colours.py:{AnsiBgToMiRCARTColours,AnsiFgToMiRCARTColours,AnsiFgBoldToMiRCARTColours}{}: merged from assets/tools/SAUCEToMiRCART.py.
libcanvas/Colours.py:{ColourMap{Bold,Normal}}[]: merged from assets/tools/MiRCARTToPngFile.py.
libcanvas/Colours.py: minor cleanup.
libgui/GuiCanvasInterface.py:canvas{ExportAsAnsi,Import{Ansi,Sauce}}(): implemented.
libgui/GuiFrame.py: adds CID_{EXPORT_AS_{ANSI,SAUCE},IMPORT_{ANSI,SAUCE}}.
assets/text/TODO: updated.
2019-09-05 16:55:07 +02:00
Lucio Andrés Illanes Albornoz
da8384f3bb libgui/GuiCanvasInterface.py:canvasExportAsPng(): correctly pass self.parentFrame to wx.FileDialog().
libgui/GuiCanvasInterface.py: minor cleanup.
assets/text/TODO: updated.
2019-09-04 16:36:57 +02:00
Lucio Andrés Illanes Albornoz
752af56774 {assets/tools,lib{canvas,gui,rtl,tools}}/*.py: update copyright legends.
assets/text/ImgurApiKey.py.template: updated from assets/text/MiRCARTImgurApiKey.py.template.
librtl/IrcClient.py: minor cleanup.
2019-09-04 16:23:59 +02:00
Lucio Andrés Illanes Albornoz
bd83513689 assets/tools/deploy-python.sh: updated. 2019-09-04 16:13:47 +02:00
Lucio Andrés Illanes Albornoz
b9f271297c Show toolbar tooltips.
libgui/GuiGeneralFrame.py:_initToolBars(): pass label to wx.ToolBar.Add{,Radio}Tool().
libgui/GuiGeneralFrame.py: minor cleanup.
assets/text/TODO: updated.
2019-09-04 16:09:46 +02:00
Lucio Andrés Illanes Albornoz
e3d23200b3 Implements {un,re}doing resizing.
libcanvas/Canvas.py:_dispatchDeltaPatches(): handle `resize' delta patches.
libcanvas/Canvas.py:_dispatchPatch(): only commit to journal given commitUndo.
libcanvas/Canvas.py:resize(): commit `resize' & {deleted,added} cell deltas to journal.
libcanvas/Canvas.py: minor cleanup.
libcanvas/Canvas{Backend,{Export,Import}Store,Journal}.py: minor cleanup.
assets/text/arab-puke.txt: added from mircart/pack2/.
assets/text/TODO: updated.
2019-09-04 15:52:54 +02:00
Lucio Andrés Illanes Albornoz
e407ab2d81 Implements {ex,im}port {to,from} clipboard.
libcanvas/CanvasExportStore.py:exportTextBuffer(): cloned from exportTextFile().
libcanvas/CanvasExportStore.py:exportTextFile(): correctly fetch cell colours & text.
libcanvas/CanvasImportStore.py:importTextFile{,Buffer}(): split from importTextFile().
libcanvas/CanvasImportStore.py: minor cleanup.
libgui/GuiCanvasInterface.py:canvas{ExportTo,ImportFrom}Clipboard(): implemented.
libgui/GuiFrame.py: adds CID_{EX,IM}PORT_CLIPB.
libgui/GuiFrame.py: minor cleanup.
libgui/GuiGeneralFrame.py: minor cleanup.
assets/text/TODO: updated.
2019-09-04 12:50:21 +02:00
Lucio Andrés Illanes Albornoz
84126f5d4e libgui/GuiCanvasInterface.py: merged from libcanvas/CanvasInterface.py.
libgui/GuiCanvasInterfaceAbout.py: split from libgui/GuiCanvasInterface.py.
libcanvas/Canvas.py, libgui/GuiFrame.py: updated.
assets/text/TODO: updated.
2019-09-04 11:47:09 +02:00
Lucio Andrés Illanes Albornoz
e03cf65028 assets/tools/SAUCEToMiRCART.py: added (for spoke.)
assets/text/TODO: updated.
2019-09-04 11:03:53 +02:00
Lucio Andrés Illanes Albornoz
74da2e43c9 ROAR! 2019-09-03 18:58:50 +02:00
Lucio Andrés Illanes Albornoz
f0cc23fcaa assets/text/TODO: merged from roar/. 2019-09-03 15:49:53 +02:00
Lucio Andrés Illanes Albornoz
9c528e8a6f MiRCART.py: melp? 2019-09-03 15:48:52 +02:00
Lucio Andrés Illanes Albornoz
71fd619709 libtools/MiRCARTToolSelect{,Clone,Move}.py: cleanup & bug fixes. 2019-09-03 15:48:48 +02:00
Lucio Andrés Illanes Albornoz
cf2c5a7042 Replaces & merges MiRCART-{nw,python,www} w/ original MiRCART as of Thu Jul 5 2018. 2019-09-01 16:34:00 +02:00
Lucio Andrés Illanes Albornoz
08b453b33f Bump to v1.1.6. 2019-08-28 11:35:42 +02:00
Lucio Andrés Illanes Albornoz
9420392b83 assets/html/index.html: fix asset pathnames.
assets/js/clipboard.js:import_ansi(): correctly handle {bold,faint,reset} SGR parameters.
assets/js/clipboard.js:import_ansi(): don't apply {bold,faint} to background colours.
assets/js/clipboard.js:import_ansi(): don't reset {background,foreground} colour & {bold,faint} flags on new row.
assets/js/clipboard.js:import_ansi(): emit missing cells if necessary.
assets/js/clipboard.js:import_ansi(): handle CUrsor Forward control sequence.
assets/js/color.js: removes ansi_bg_bold_import[].
2019-08-28 11:31:42 +02:00
Lucio Andrés Illanes Albornoz
01394e6137 Bump to v1.1.5. 2019-08-28 10:37:45 +02:00
Lucio Andrés Illanes Albornoz
6453c1ff03 MiRCART-nw/index.html: update symlink. 2019-08-28 10:37:27 +02:00
Lucio Andrés Illanes Albornoz
6e371adbbe Bump to v1.1.4. 2019-08-28 10:32:44 +02:00
Lucio Andrés Illanes Albornoz
035ab03e3c SAUCEToAnsi.py: updated. 2019-08-28 10:32:26 +02:00
Lucio Andrés Illanes Albornoz
80df97f972 Bump to v1.1.3.
assets/shell/bump-version.sh: updated.
2019-08-28 10:31:44 +02:00
Lucio Andrés Illanes Albornoz
c15a5b7a21 MiRCART-python/SAUCEToAnsi.py: added (for spoke.) 2019-08-28 10:29:28 +02:00
Lucio Andrés Illanes Albornoz
0d4a6432b2 assets/html/index.html: moved from index.html.
MiRCART-www/index.html: updates symlink.
2019-08-27 08:49:15 +02:00
Lucio Andrés Illanes Albornoz
6639336a25 assets/js/clipboard.js:import_ansi(): correctly support sequence of comma-separated SGR parameters.
assets/js/clipboard.js:import_ansi(): support {bold,faint,reset} SGR parameters.
assets/js/color.js: adds ansi_[bf]g_bold_import[].
2019-08-27 08:45:39 +02:00
Lucio Andrés Illanes Albornoz
2578c80819 assets/shell/bump-version.sh: removes MiRCART-cordoba support. 2019-08-27 08:01:35 +02:00
Lucio Andrés Illanes Albornoz
66fb77b9b5 assets/js/matrix.js:Matrix.prototype.{ansi,mirc}(): only reset {bg,fg}_ when processing a new row vs. new cell (via spoke.) 2019-08-27 07:59:35 +02:00
Lucio Andrés Illanes Albornoz
50b7e9c279 Removes MiRCART-cordoba. 2019-08-25 19:00:11 +02:00
Lucio Andrés Illanes Albornoz
6200254152 Implements {ex,im}porting {as,from} ANSI.
Bump to v1.1.2.
2019-08-25 18:39:49 +02:00
Lucio Andrés Illanes Albornoz
ecaaf75b6b MiRCARTCanvasImportStore.py:importTextFile(): open() w/ encoding="utf-8-sig" due to potentially present BOM.
MiRCARTToPngFile.py:export(): disable PIL font anti-aliasing.
2019-08-18 13:02:33 +02:00
Lucio Andrés Illanes Albornoz
b4e2286ecc assets/js/matrix.js:Matrix.prototype.mirc(): don't assume default foreground colour (via spoke.) 2019-07-13 23:53:24 +02:00
Lucio Andrés Illanes Albornoz
fd71785b3b MiRCART-python/MiRCARTReduce.py: added. 2019-04-11 14:54:29 +02:00
Lucio Andrés Illanes Albornoz
7d80e5331f MiRCART-python/IrcMiRCARTBot.py: updated.
assets/text/TODO: updated.
2019-02-06 19:10:35 +01:00
Lucio Andrés Illanes Albornoz
375324be9b MiRCARTToAnsi.py: fix colours. 2019-01-21 20:21:11 +01:00
Lucio Andrés Illanes Albornoz
a4fa91f894 MiRCARTToAnsi.py: added.
MiRCARTToPngFile.py: clean up _ColourMap{Bold,Normal}.
2019-01-21 10:23:19 +01:00
Lucio Andrés Illanes Albornoz
efd8b20671 IrcMiRCARTBot.py:_{dispatchPrivmsg,uploadToImgur}(): gracefully handle invalid responses from Imgur.com. 2018-12-03 11:51:20 +01:00
Lucio Andrés Illanes Albornoz
61f7f75755 Bump to v1.1.0. 2018-11-26 11:00:15 +01:00
Lucio Andrés Illanes Albornoz
c4e58ee096 assets/shell/deploy-nw.sh:deploy(): consistent package names. 2018-11-26 10:59:56 +01:00
Lucio Andrés Illanes Albornoz
8f65e1723e Bump to v1.0.9. 2018-11-26 10:55:55 +01:00
Lucio Andrés Illanes Albornoz
c53d1172a2 Fix LICENCEs. 2018-11-26 10:54:54 +01:00
Lucio Andrés Illanes Albornoz
1218de3bdf assets/shell/bump-version.sh:main(): correctly update version in {index,assets/html/help}.html. 2018-11-26 10:46:53 +01:00
Lucio Andrés Illanes Albornoz
3b981c3a34 Bump to v1.0.8. 2018-11-26 10:46:18 +01:00
Lucio Andrés Illanes Albornoz
2d4d14266e Renames asciiblaster{,*} to MiRCART. 2018-11-26 10:45:12 +01:00
Lucio Andrés Illanes Albornoz
bffd7711a1 help im trapped inside the commit message oh hey who are OH NO AHHHHHH STOP NO DONT DO THAT AHHHHHHHHHHHHHHHHHH IM BLEEDING 2018-11-26 10:35:48 +01:00
Lucio Andrés Illanes Albornoz
35f3323be1 assets/html/help.html: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
a39c103065 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
47604d6bde assets/shell/bump-version.sh:main(): update version in assets/html/help.html also. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
8ef33b1683 Bump to v1.0.7. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
b031c994b5 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
61c40b59c3 assets/{css,html}/*, index.html: clean up & correct {CSS,HTML}, pt. XII.
assets/text/TODO: updated.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
b6e0a93029 assets/html/help.html, index.html: add <meta ...charset="UTF-8"...> to make Firefox shut up. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f35850051c assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
bd46fb99c5 assets/html/help.html, index.html: remove <meta charset="utf-8" />. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
fad1dab3c6 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
ad87bc8b2f assets/{css,js,html}/*, index.html: clean up & correct {CSS,HTML}.
assets/text/TODO: updated.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0ad72d7fb0 assets/css/{ak.css,html/help.html}, index.html: clean up & correct {CSS,HTML}.
assets/text/TODO: updated.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
3903f61815 assets/css/{{help,nitelite}.css,html/help.html}: clean up & correct {CSS,HTML}. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
6cae432f46 index.html, assets/css/sally.css: clean up & correct {CSS,HTML}. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f012c628f2 assets/html/help.html:Tips on using the keyboard: updated.
assets/html/help.html:Notate bene: added.
assets/text/TODO: updated.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
6d4db48ef0 index.html, assets/js/{clipboard,lex,matrix,ui/controls}.js: removes non-functional ANSI export format.
index.html, assets/js/{clipboard,lex,matrix,ui/controls}.js: removes Irssi export format due to lack of interest on #MiRCart.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
1d224b2bb4 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f9100dacb0 index.html, assets/js/{app,gfx,ui/controls}.js: removes pointless brush [xy] mirroring `feature.'
index.html, assets/{css/sally.css,js/{matrix,ui/controls}.js}: removes dead {rotate[d],pixels} code.
index.html: cleanup UI layout.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
8ec55357e6 assets/{css/sally.css,js/ui/controls.js}, index.html: always show `advanced' UIs. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
85cad2879a assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
a02ebdd039 assets/doc/index.html, index.html: renamed to assets/html/help.html. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
cc7d8b1b17 assets/doc/irssi.txt: removed from repository.
assets/doc/{tips.txt,index.html}: merged.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2e682e0af1 assets/js/*: substitute VT w/ SP. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
baacc19a67 assets/*, index.html: remove {trailing,leading} SP. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
06fa555306 assets/js/ui/{brush,custom}.js, index.html: merged into brush.js.
assets/js/ui/{letters,palette}.js, index.html: merged into paletters.js.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0f2a234ec4 assets/js/{app,ui/{canvas,controls,tool,util}}.js: remove is_{android,desktop,i{pad,phone},mobile}. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
301a003549 .gitattributes: ignore asciiblaster-{cordoba,nw}/ concerning Github repository languages. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
48ce316417 Bump to v1.0.6. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
114ec3fb19 assets/shell/bump-version.sh: melp? 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
048b3677ba asciiblaster-cordoba/platforms/android/app/src/main/res/xml/config.xml: fix index.html pathname. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
74952c6367 Bump to v1.0.5. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
18342308b2 assets/shell/bump-version.sh: added to repository. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
e1d338d6c3 asciiblaster-cordoba/platforms/android/app/src/main/assets/www/assets/: WHO HATES APACHE _CÓRDOBA_?!?!?!
asciiblaster-cordoba/platforms/android/app/src/main/assets/www/assets/: WE HATE APACHE _CÓRDOBA_!"!$"*$%"&(!£*
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
dc45d81361 assets/shell/deploy-www.sh:deploy(): switch from TGZ back to ZIP. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
4b8c0f3469 assets/shell/deploy-all.sh:main(): fix typo. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f273a3c6b1 assets/shell/deploy-all.sh:main(): cd into asciiblaster-${_build}/ prior to calling deployment script.
assets/shell/deploy-nwjs.sh, asciiblaster-nw/deploy.sh: renamed to assets/shell/deploy-nw.sh.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
7afc5ea271 Bump to v1.0.4. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
401b7a408c asciiblaster-www/: added to repository.
.gitignore: ignore asciiblaster-www/releases/*.
asciiblaster-{cordoba,nw,www}/deploy.sh, assets/shell/: melp?
index.html: add version to <title>.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2287a8368b assets/js/ext/*, index.html: merges assets/js/ext/ into assets/js/. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0f5c8eae4a assets/js/ext/oktween.js, index.html: remove dead code. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
9bacadcb70 assets/*, index.html: removes shader feature due to lack of interest on #MiRCart & maintenance cost. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
8bb9982b3b assets/*, index.html: removes nopaint feature due to lack of interest on #MiRCart & maintenance cost. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
d7e7519592 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
233abaedef assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
9d4f1bf30a assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
07e693cc85 asciiblaster-cordoba/release.sh: added to automate GitHub standalone Apache Córdoba Android app releases.
asciiblaster-cordoba/releases/: replaced symlink with directory.
.gitignore: ignore asciiblaster-cordoba/releases/*.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
eb0bbac893 asciiblaster-cordoba/{config.xml,package.json}: fix index.html pathnames. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
80cacedd89 asciiblaster-cordoba/: preliminarily added for standalone Android app support via Apache Córdoba.
.gitignore: adds asciiblaster-cordoba/{node_modules,platforms/android/{.gradle,app/build,CordovaLib/build}}.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
b853878b4b Refactor & clean up repository tree. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
9f0357605b assets/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
b51f315bb7 assets/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
5424b2ba1e assets/release.sh:release(): fix lapse in logic. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
b0f140d657 assets/nwjs.manifest: added to repository.
assets/release.sh:release(): check SHA256 sum of downloaded NW.js archives.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
19200b8249 assets/release.sh: fix typo. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
d597715422 package.json: bump to v1.0.2. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f68fd2bcb9 assets/release.sh: obtain version number from package.json. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0f727db699 index.html:cutoff_warning_el: update warning message. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
1aeca12d04 assets/release.sh, package.json: bump to v1.0.1. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2da7965c99 js/matrix.js: disable irresponsibly obnoxious automatic cutoff `feature.'
js/clipboard.js: increase default cutoff warning threshold to 425.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0f8b211517 assets/release.sh: added to automate GitHub standalone NW.js app releases.
.gitignore: ignore releases/.
asciiblaster.cmd: added as convenience wrapper on Windows.
2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
90192929f6 package{,-lock}.json: added for standalone app support via NW.js. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
948147124f LICENCE: added from <http://jollo.org/licensing/public/LNT-1.txt>.
doc/index.html: update licence URL.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
3607fd59e8 js/ext/unicode.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
6a347c4786 js/ui/keys.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
fcea7308f2 js/ui/goodies.js: removed (unused.)
index.html: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
e5e254fb9b js/ui/nopaint.js: moved into js/ext/.
index.html: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
5d045d4de5 assets/sally.css, index.html, js/clipboard.js: remove gallery link. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
ac8fb74dc4 js/ui/controls.js: remove webcam code.
assets/sally.css, index.html: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
fea5e2a4c9 js/clipboard.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
60cb67e7a6 assets/fsex300-webfont.eot?: removed. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
042cb2a57b {css,img,fonts}/: merged into assets.
doc/index.html, index.html: updates URIs.
TODO: moved into assets/.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
6180783d2d css/sally.css, js/*: remove {gallery,send to IRC,upload,username}.
index.html, TODO: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
8e34feff55 js/{matrix,undo}.js: merged.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
4c629197a7 js/{draw,gfx}.js: merged.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
3c79ca0675 js/tool.js: moved into js/ui/.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
72aa916dc8 js/{blit,shader}.js: merged into js/gfx.js.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
88f8efe56c js/util.js: moved into js/ext/.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
735c016f94 js/ext/dataUriToBlob.js: removed.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
6be9cbd9b0 js/{lex,matrix}.js: encode to mIRC more efficiently, pt. II. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
b10c6fb6f7 TODO: updated. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
9fbfe7c26f js/clipboard.js: fix typo. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
af683e4f26 TODO: updated. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
6b5fc121c8 js/ext/FileSaver.js: removed.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
f7db3b30f4 index.html: readd js/undo.js. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
f1fd806573 js/dither.js: merged into js/ui/palette.js.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
fc652958e8 js/ext/{colorcode,png}.js: removed.
js/clipboard.js: reimplements to_json().
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
63531adce7 js/: split into js/{ext,ui,}.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
44e4b02068 js/lex.js: default to {bg,fg} when encoding to mIRC. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
21526e2421 TODO: added to repository. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
4a132b50df index.html: update {doc,gallery}_el links. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
86e849b122 js/lex.js: encode to mIRC more efficiently.
js/lex.js: remove unused fgOnly.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
7e264f8a8b js/ui/custom.js: allow deletion of brushes w/ <Shift> + LMB. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
2106b77586 js/user.js: update cookie domain. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
0eb96f9266 Initial commit. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
08e5856db9 Stash dead repository MiRCARTools. 2018-10-25 03:06:28 +02:00
Lucio Andrés Illanes Albornoz
54522ac329 ENNTool/ENNTool{,GL{CanvasPanel,TTFTexture}}.py: adds [-c x,y,z] & [-t float]. 2018-07-25 14:26:47 +02:00
Lucio Andrés Illanes Albornoz
455a3fdcba Stash dead repository MiRCARTools. 2018-07-05 15:21:30 +02:00
Lucio Andrés Illanes Albornoz
3c7560f6d7 Merge remote-tracking branch 'MiRCARTools/master' 2018-07-05 15:20:47 +02:00
Lucio Andrés Illanes Albornoz
624ab9ca71 IrcMiRCARTBot.py: adds optional -[46] force IPv[46] flag. 2018-06-28 18:18:48 +02:00
Lucio Andrés Illanes Albornoz
05d3d28f1d MiRCARTToPngFiles.sh: added. 2018-06-26 15:39:29 +02:00
Lucio Andrés Illanes Albornoz
7a1ec135b2 MiRCARTCanvasImportStore.py:importTextFile(): correctly open() pathName w/ encoding="utf-8" (via munki.) 2018-06-22 11:38:47 +02:00
Lucio Andrés Illanes Albornoz
8b483e262e IrcMiRCARTBot.py:_dispatchPrivmsg(): print message from website if upload fails.
IrcMiRCARTBot.py:_uploadToImgur(): additionally return responseHttp.text on failure.
2018-06-21 09:04:22 +02:00
Lucio Andrés Illanes Albornoz
8d6f6ef56e MiRCARTCanonicalise.py: correctly reset last{Attribs,Colours} on each new row (fixes issue #1.) 2018-06-17 09:45:00 +02:00
Lucio Andrés Illanes Albornoz
9a9d5d858d IrcMiRCARTBot.py:_dispatchPrivmsg(): lower rate limit to once every 5 seconds (via aaa, alghazi, amomp, anji, arab, astra, astra`, biobag, biobag__, bj0rn, blomp, boomp, bromp, brr, butts, buttvomit, c003y, Caku, chomp, chrono_, cooey, CosbyX, darkmage_, dboard, deh uman, dOm3r, eddb, efukt, ep^, er, era, eraser, erratic, erratic_, gnomp, gromp, interdom3, JEWS, kobach, l1tup, Lions, lul, lulz, lulzee, lulzy, Matthew, MercyX, moomp, mr_vile, muff, munki, n0v, nk9k, ooomp, OVH, pcap, pinchy, plop, pngbot, poccri, poomp, promp, pump-, pu mpbull, pyrex, rain, rObOtNiK, rondito, scd, SEEEEN, Shapes, snadge, spidy, spinsane, spoke, spomp, stomp, TACO, tetedupet, toohighto, tromp, twomp, vap0r, vapor, venus, virtuald, vixen, whomp, wreathman, wromp, yoomp, z0z0, zen_, and zoomp.) 2018-05-24 08:59:00 +02:00
Lucio Andrés Illanes Albornoz
95f7dd113c IrcMiRCARTBot.py, MiRCARTCanvasInterface.py: remove Imgur API key from repository (via aaa, alghazi, amomp, anji, arab, astra, astra`, biobag, biobag__, bj0rn, blomp, boomp, bromp, brr, butts, buttvomit, c003y, Caku, chomp, chrono_, cooey, CosbyX, darkmage_, dboard, dehuman, dOm3r, eddb, efukt, ep^, er, era, eraser, erratic, erratic_, gnomp, gromp, interdom3, JEWS, kobach, l1tup, Lions, lul, lulz, lulzee, lulzy, Matthew, MercyX, moomp, mr_vile, muff, munki, n0v, nk9k, ooomp, OVH, pcap, pinchy, plop, pngbot, poccri, poomp, promp, pump-, pumpbull, pyrex, rain, rObOtNiK, rondito, scd, SEEEEN, Shapes, snadge, spidy, spinsane, spoke, spomp, stomp, TACO, tetedupet, toohighto, tromp, twomp, vap0r, vapor, venus, virtuald, vixen, whomp, wreathman, wromp, yoomp, z0z0, zen_, and zoomp.) 2018-05-24 08:52:20 +02:00
Lucio Andrés Illanes Albornoz
99c0c14993 MiRCARTCheckLineLengths.sh: check mIRC art line lengths. 2018-05-02 17:08:45 +02:00
Lucio Andrés Illanes Albornoz
3b70d42784 MiRCARTCanonicalise.py: canonicalise mIRC art {from,to} file (for munki.) 2018-05-01 23:30:36 +02:00
Lucio Andrés Illanes Albornoz
8e91d1269e {IrcMiRCARTBot,MiRCARTToPngFile}.py: reduce memory usage by folding nested patch {coordinate,colour} list(s).
Followup to <b4a71505ffa68757931f633baf511f7863682e8e>.
2018-01-30 20:34:02 +01:00
Lucio Andrés Illanes Albornoz
6d5e081dfb Reduce memory usage by folding nested patch {coordinate,colour} list(s). 2018-01-30 11:31:28 +01:00
Lucio Andrés Illanes Albornoz
1a503979d1 IrcMiRCARTBot.py:_uploadToImgur(): prevent file object leak via `with' barrier.
MiRCARTCanvasExportStore.py:_exportFileToImgur(): prevent file object leak via `with' barrier.
2018-01-26 22:38:28 +01:00
Lucio Andrés Illanes Albornoz
c6ab27c08c Fixes memory leaks on {re,un}do state mutation & resize-related operations.
MiRCARTCanvas.py:__del__(): delete canvasMap w/ clear().
MiRCARTCanvasBackend.py:resize(): delete old canvasBitmap w/ Destroy().
MiRCARTCanvasJournal.py:reset{Cursor,Undo}(): delete patches{Cursor,Undo} w/ clear().
MiRCARTCanvasJournal.py:__del__(): provided explicitly to call reset{Cursor,Undo}().
MiRCARTFrame.py:__del__(): provided explicitly to delete panelCanvas.
2018-01-26 17:45:04 +01:00
Lucio Andrés Illanes Albornoz
579dd858ba MiRCARTCanvasInterface.py:canvasSave(): fix call parameters. 2018-01-25 15:04:37 +01:00
Lucio Andrés Illanes Albornoz
92124dc34f MiRCARTCanvas.py:resize(): clean up & fix.
MiRCARTCanvasInterface.py:_updateCanvasSize(): merged into ...Canvas.resize().
MiRCARTCanvasInterface.py:canvas{De,In}crCanvas{Height,Width}(): directly call ...Canvas.resize().
2018-01-25 15:02:09 +01:00
Lucio Andrés Illanes Albornoz
f77e6d5bf8 IrcClient.py:connect(): create file from socket w/ errors="replace" to prevent UnicodeDecodeErrors. 2018-01-22 21:06:46 +01:00
Lucio Andrés Illanes Albornoz
b385909b45 MiRCARTCanvasImportStore.py: correctly process ^C<colour>, sequences.
MiRCARTToPngFile.py: fix {bold,underline} processing.
2018-01-20 09:17:52 +01:00
Lucio Andrés Illanes Albornoz
0cf1040614 MiRCARTColours.py: fix permission bits. 2018-01-11 23:27:57 +01:00
Lucio Andrés Illanes Albornoz
802de6fa6e MiRCART{CanvasInterface,Frame}.py: complete set of {brush,canvas} size operations.
assets/tool{De,In}cr{Brush,Canvas}{H{,W},W}.png: added/updated.
2018-01-11 23:26:36 +01:00
Lucio Andrés Illanes Albornoz
4f4ecb4331 MiRCARTToPngFile.py:export(): treat `█' as whitespace w/ inverse colours. 2018-01-11 23:22:02 +01:00
Lucio Andrés Illanes Albornoz
1511002798 MiRCARTCanvasImportStore.py:_parseCharAsColourSpec(): correctly process mIRC colour control code sequences specifying one single colour.
MiRCARTToPngFile.py:export(): fix indentation.
2018-01-11 23:13:46 +01:00
Lucio Andrés Illanes Albornoz
d79ea00164 IrcMiRCARTBot.py: include black-on-black border around map.
IrcMiRCARTBot.py: normalise imported map.
2018-01-11 21:28:53 +01:00
Lucio Andrés Illanes Albornoz
9eee59b501 MiRCARTFrame.py: initially select red (4) colour toolbar item. 2018-01-11 03:02:18 +01:00
Lucio Andrés Illanes Albornoz
5380e899e4 MiRCARTFrame.py: add {in,de}crease {height,width} toolbar items.
assets/tool{De,In}crCanvas{H,W}.png: added.
2018-01-11 02:49:53 +01:00
Lucio Andrés Illanes Albornoz
4e19f91eb0 MiRCARTFrame.py: adds exit accelerator (hotkey) <Ctrl> X. 2018-01-11 02:37:20 +01:00
Lucio Andrés Illanes Albornoz
a33ff6b5df MiRCART{CanvasInterface,{,General}Frame}.py: add & sync {tools,colour selection} toolbar items as radio tools. 2018-01-11 02:34:32 +01:00
Lucio Andrés Illanes Albornoz
46258cde32 MiRCARTCanvas.py:MiRCARTCanvas.onPanelInput(): clip mapPoint to canvasSize. 2018-01-11 02:10:33 +01:00
Lucio Andrés Illanes Albornoz
508a3cd489 MiRCARTToolFill.py: don't process cells more than once. 2018-01-11 02:06:09 +01:00
Lucio Andrés Illanes Albornoz
44490912b2 MiRCARTCanvasInterface.py:_updateCanvasSize(): hide cursor before resizing. 2018-01-11 01:59:43 +01:00
Lucio Andrés Illanes Albornoz
2f85fa8cd1 MiRCARTToolFill.py: set qualifying colour from current cell. 2018-01-11 01:56:28 +01:00
Lucio Andrés Illanes Albornoz
bb243c7556 MiRCARTCanvasInterface.py:_updateCanvasSize(): reimplement (fixes <Alt> [WASD] bugs.) 2018-01-11 01:52:53 +01:00
Lucio Andrés Illanes Albornoz
d1936486ff MiRCART.py: import argv[1] into canvas if specified. 2018-01-11 01:18:50 +01:00
Lucio Andrés Illanes Albornoz
cf04a12690 MiRCARTCanvasInterface.py: prompt to save changes on exit given non-None canvasPathName. 2018-01-11 01:12:55 +01:00
Lucio Andrés Illanes Albornoz
02d3c7f6f4 assets/tool{Clone,Move}.png: updated. 2018-01-11 01:09:00 +01:00
Lucio Andrés Illanes Albornoz
ebdf7f410b MiRCARTCanvasInterface.py: sync menu item state when selecting tool. 2018-01-11 00:59:07 +01:00
Lucio Andrés Illanes Albornoz
4bc9b61f2c MiRCARTGeneralFrame.py: show accelerators (hotkeys) in menus. 2018-01-11 00:45:40 +01:00
Lucio Andrés Illanes Albornoz
f65603dd0c MiRCART.png: updated. 2018-01-11 00:31:25 +01:00
Lucio Andrés Illanes Albornoz
d26a322612 MiRCART{CanvasInterface,Frame}.py: adds (flood) fill tool.
MiRCARTToolFill.py: initial implementation.
assets/toolFill.png: added.
2018-01-11 00:27:50 +01:00
Lucio Andrés Illanes Albornoz
499296ba1d MiRCARTGeneralFrame.py: add toolbar windows to sizer w/ border width 3. 2018-01-10 22:31:52 +01:00
Lucio Andrés Illanes Albornoz
e53e8bf8e7 MiRCARTFrame.py: add canvas panel window to sizer w/ border width 14. 2018-01-10 22:16:48 +01:00
Lucio Andrés Illanes Albornoz
33df272b39 MiRCARTCanvas.py:MiRCARTCanvas.resize(): call SetSize() w/o changing position.
MiRCARTCanvas.py:MiRCARTCanvas.resize(): call SetMinSize() in addition to SetSize().
MiRCARTCanvas.py:MiRCARTCanvas.resize(): call Layout() on canvas panel window and its parents.
MiRCARTCanvasInterface.py: always call SetCursor() w/ self.parentCanvas.
MiRCARTFrame.py: moves colour toolbar items to separate toolbar.
MiRCARTFrame.py:MiRCARTFrame.__init__(): add canvas panel to and initialise sizer.
MiRCARTGeneralFrame.py: adds support for multiple toolbars via vertical box sizer.
2018-01-10 22:06:48 +01:00
Lucio Andrés Illanes Albornoz
8c0de9fbd3 MiRCARTToolSelectMove.py: clear source region prior to moving. 2018-01-10 19:20:35 +01:00
Lucio Andrés Illanes Albornoz
ad761d658e MiRCART{CanvasInterface,Frame}.py: adds clone & move (selection) tools.
MiRCARTTool{Clone,Move}Select.py: initial implementation.
assets/tool{Clone,Move}.png: added.
MiRCART.png: updated.
2018-01-10 18:29:16 +01:00
Lucio Andrés Illanes Albornoz
c9d6b90e68 MiRCART{anvas{,ImportStore,Interface},Frame}.py: pass & initialise from default canvas position, size, and cell size. 2018-01-10 15:04:59 +01:00
Lucio Andrés Illanes Albornoz
49ef54ef76 MiRCARTToolLine.py: correctly cache colours on first click. 2018-01-10 14:53:35 +01:00
Lucio Andrés Illanes Albornoz
2254a0638b MiRCART{Canvas{,Interface},Frame}.py: use dict() to cache canvas state in Frame. 2018-01-10 14:51:25 +01:00
Lucio Andrés Illanes Albornoz
402e542137 MiRCART{CanvasInterface,Frame,Tool*}.py: include current tool name in status bar text. 2018-01-10 05:05:59 +01:00
Lucio Andrés Illanes Albornoz
a320c904fe MiRCART{Canvas{,Interface},Frame}.py: remove undo level & show brush size in status bar. 2018-01-10 05:00:52 +01:00
Lucio Andrés Illanes Albornoz
3fa377791a MiRCARTToolLine.py: honour brush size. 2018-01-10 04:44:13 +01:00
Lucio Andrés Illanes Albornoz
e157f479ce MiRCART{Canvas,Frame}.py: include canvas width and height in status bar. 2018-01-10 04:36:39 +01:00
Lucio Andrés Illanes Albornoz
12027bc6b5 MiRCARTToolText.py: fix non-US ASCII character handling. 2018-01-10 04:31:20 +01:00
Lucio Andrés Illanes Albornoz
f9f910160f MiRCART{Canvas{,Interface},Frame}.py: merge on{StatusBar,Undo}Update() into onCanvasUpdate().
MiRCARTCanvas{Frame,Interface}.py: adds canvas{De,In}crCanvas{Height,Width}() & CID_*.
MiRCARTCanvasFrame.py:onCanvasUpdate(): updated.
2018-01-10 04:23:54 +01:00
Lucio Andrés Illanes Albornoz
3e03126006 MiRCARTCanvasInterface.py: split from MiRCARTCanvas.py.
MiRCART{Canvas,{,General}Frame}.py: updated.
2018-01-10 03:24:25 +01:00
Lucio Andrés Illanes Albornoz
398ee3af3d MiRCARTCanvas{Export,Import}Store.py: split from MiRCARTCanvasStore.py.
IrcMiRCARTBot.py, MiRCART{Canvas,Frame,ToPngFile}.py: updated.
2018-01-10 00:48:13 +01:00
Lucio Andrés Illanes Albornoz
cd5ea1fab5 MiRCARTCanvas{,Journal,Store}.py: dispatch patches from tool event handlers directly.
MiRCARTTool{,Circle,Line,Rect,Text}.py: updated to new interface.
2018-01-10 00:35:08 +01:00
Lucio Andrés Illanes Albornoz
ed8ccab26f MiRCART{Canvas{,Backend},Frame}.py: replace MemoryDC() w/ BufferedDC(canvasBitmap). 2018-01-09 22:23:27 +01:00
Lucio Andrés Illanes Albornoz
2e014cd7ec MiRCARTCanvasBackend.py: only update device context brushes & pen if necessary. 2018-01-09 21:37:58 +01:00
Lucio Andrés Illanes Albornoz
dbf6c76930 MiRCARTGeneralFrame.py: prepend script execution directory when loading toolbar item bitmaps. 2018-01-09 21:21:04 +01:00
Lucio Andrés Illanes Albornoz
363a86fe81 MiRCARTCanvas.py: enable double buffering to eliminate brush flickering. 2018-01-09 05:07:04 +01:00
Lucio Andrés Illanes Albornoz
dec1e4c0d4 MiRCART.png: updates screenshot.
MiRCARTCanvas.py:onPanelKeyboardInput(): only handle {no,shift}-modifier keyboard events.
MiRCARTFrame.py: fix {de,in}crease canvas size accelerators (hotkeys.)
2018-01-09 04:17:24 +01:00
Lucio Andrés Illanes Albornoz
38e90f96ca Initial release w/ circle, line, rectangle, and text tools. 2018-01-09 03:56:40 +01:00
Lucio Andrés Illanes Albornoz
2a7f281d29 MiRCART{General,}Frame.py, assets/*.png: adds icons for tools.
MiRCART{Frame,ToolText}.py: initial implementation of Text tool.
2018-01-08 20:28:43 +01:00
Lucio Andrés Illanes Albornoz
f431adb381 MiRCART.png: updates screenshot. 2018-01-08 17:47:58 +01:00
Lucio Andrés Illanes Albornoz
195ddd9af8 MiRCART{Canvas,Frame}.py: adds circle & line tools.
MiRCARTTool{Circle,Line}.py: initial implementation.
2018-01-08 17:44:54 +01:00
Lucio Andrés Illanes Albornoz
9b5c9be88b MiRCARTFrame.py: adds <Ctrl>-<{+,-}> {in,de}crease brush size accelerators (hotkeys). 2018-01-08 02:56:08 +01:00
Lucio Andrés Illanes Albornoz
1099341b5d MiRCARTCanvas{,Journal}.py: fix {un,re}do regarding brush sizes >(1,1). 2018-01-08 02:45:03 +01:00
Lucio Andrés Illanes Albornoz
e7b7e05234 MiRCARTGeneralFrame.py, MiRCARTFrame.py: cleanup. 2018-01-08 01:43:54 +01:00
Lucio Andrés Illanes Albornoz
30f3688a2c MiRCARTFrame.py:MiRCARTFrame.onFrameCommand(): don't decrease brush size below (1,1).
MiRCARTFrame.py:MiRCARTFrame.onFrameCommand(): remove debugging print()s.
2018-01-08 01:18:52 +01:00
Lucio Andrés Illanes Albornoz
da82a543b5 MiRCARTCanvas.py: disable double buffering. 2018-01-08 01:11:00 +01:00
Lucio Andrés Illanes Albornoz
54adf3a95c MiRCART{CanvasJournal,Frame,ToolRect}.py: implements variable brush size. 2018-01-08 00:33:47 +01:00
Lucio Andrés Illanes Albornoz
6a4356568b MiRCARTFrame.py:MiRCARTFrame.canvas{Export{AsPng,Imgur,Pastebin},New,Open,Save}(): add wx.CURSOR_WAIT guards. 2018-01-08 00:05:10 +01:00
Lucio Andrés Illanes Albornoz
39babc71a4 MiRCART{CanvasStore,Frame}.py: implements export to Imgur (from bitmap via PNG.) 2018-01-08 00:05:10 +01:00
Lucio Andrés Illanes Albornoz
84ab4eee5a Initial release sans tools. 2018-01-08 00:05:03 +01:00
Lucio Andrés Illanes Albornoz
f6911293c3 MiRCARTFrame:MiRCARTFrame.TID_SELECT, [CM]ID_*: adds selectable item type.
MiRCARTFrame:MiRCARTFrame.{_initMenus,__init__}(): automatically set menu item initial state.
2018-01-07 15:20:27 +01:00
Lucio Andrés Illanes Albornoz
1c5f869c40 MiRCARTFrame.py: remove obsolete MiRCARTTo{Pastebin,PngFile} imports.
MiRCARTFrame.py:MiRCARTFrame.__init__(): initially disable (grey out) Copy, Cut, Delete, Paste, Export to Pastebin, and Export as PNG menu items.
2018-01-07 15:07:37 +01:00
Lucio Andrés Illanes Albornoz
3c28fa0071 MiRCARTCanvasStore.py: split & merge MiRCART{FromTextFile,ToPastebin,ToTextFile}.py. 2018-01-07 15:07:22 +01:00
Lucio Andrés Illanes Albornoz
115991736a MiRCART.py: hand off to MiRCARTFrame().
MiRCARTCanvas.py: split from MiRCART.py.
MiRCARTCanvasJournal.py: split from MiRCART.py.
MiRCARTColours.py: split from MiRCART.py.
MiRCARTFrame.py: split from MiRCART.py.
MiRCARTFromTextFile.py: initial implementation.
MiRCARTToPastebin.py: initial implementation.
MiRCARTToPngFile.py, IrcMiRCARTBot.py: renamed MiRC2png to MiRCARTToPngFile.
MiRCARTToTextFile.py: split from MiRCART.py.
MiRCARTTool.py: split from MiRCART.py.
MiRCARTToolRect.py: split from MiRCART.py.
README.md: updated.
2018-01-07 03:45:18 +01:00
88 changed files with 2616 additions and 1329 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
PYTHONPATH="E:\Documents - Repositories\roar\libcanvas;E:\Documents - Repositories\roar\libgui;E:\Documents - Repositories\roar\liboperators;E:\Documents - Repositories\roar\libroar;E:\Documents - Repositories\roar\librtl;E:\Documents - Repositories\roar\libtools"

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
*.sw[op]
__pycache__/
build/
libgui/GuiCanvasWxBackendFast.exp
libgui/GuiCanvasWxBackendFast.lib
libgui/GuiCanvasWxBackendFast.obj
libgui/GuiCanvasWxBackendFast.pyd
librtl/ImgurApiKey.py
releases/

23
.vscode/c_cpp_properties.json vendored Executable file
View File

@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"C:/Python37/include/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "msvc-x64",
"compilerArgs": []
}
],
"version": 4
}

70
.vscode/launch.json vendored Executable file
View File

@ -0,0 +1,70 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File (Integrated Terminal)",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"port": 5678,
"host": "localhost",
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
]
},
{
"name": "Python: Module",
"type": "python",
"request": "launch",
"module": "enter-your-module-name-here",
"console": "integratedTerminal"
},
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"console": "integratedTerminal",
"args": [
"runserver",
"--noreload",
"--nothreading"
],
"django": true
},
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py"
},
"args": [
"run",
"--no-debugger",
"--no-reload"
],
"jinja": true
},
{
"name": "Python: Current File (External Terminal)",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "externalTerminal"
}
]
}

8
.vscode/roar.code-workspace vendored Executable file
View File

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "E:\\Documents - Repositories\\roar"
}
],
"settings": {}
}

5
.vscode/settings.json vendored Executable file
View File

@ -0,0 +1,5 @@
{
"python.analysis.disabled": ["unresolved-import"],
"python.pythonPath": "C:\\Python37\\python.exe",
"python.linting.enabled": false
}

15
.vscode/tasks.json vendored Executable file
View File

@ -0,0 +1,15 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build libgui/GuiCanvasWxBackendFast.pyd",
"type": "shell",
"command": "cd \"${workspaceFolder}/libgui\" && cmd /c cl /EHsc /LD /Ox /Wall /WX /IC:/Python37/include GuiCanvasWxBackendFast.cpp C:/Python37/libs/python37.lib /FeGuiCanvasWxBackendFast.pyd",
"problemMatcher": [
"$msCompile"
]
}
]
}

BIN
assets/audio/roararab6.wav Normal file

Binary file not shown.

BIN
assets/audio/roararab7.wav Normal file

Binary file not shown.

BIN
assets/audio/roararab8.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/audio/roarspoke9.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r1.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r2.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r3.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r4.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r5.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r6.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r7.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r8.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

0
assets/images/toolCursor.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

BIN
assets/images/toolErase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

View File

@ -1,7 +1,7 @@
# roar.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:
# roar.py -- mIRC art editor for Windows & Linux (unmaintained reference implementation, do not use)
* Prerequisites on Windows: install Python v3.7.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
* Prerequisites on Linux: python3 (v3.7.x) && python-wx{gtk2.8,tools} on Debian-family Linux distributions
* Screenshot:
![Screenshot](https://github.com/lalbornoz/roar/raw/master/assets/images/roar.png "Screenshot")

View File

@ -1,22 +1,20 @@
1) Implement ANSI CSI CU[BDPU] sequences & italic
2) Layers & layout (e.g. for comics, zines, etc.)
3) Implement instrumentation & unit tests, document
4) Open and toggle a reference image in the background
5) Client-Server or Peer-to-Peer realtime collaboration
6) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.)
7) Incremental auto{load,save} & {backup,restore} (needs Settings window)
8) Composition, parametrisation & keying of tools from higher-order operators (brushes, functions, filters, outlines, patterns & shaders) and unit tools
9) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...)
High-priority list:
1) unit tools: arrow, {cloud,speech bubble}, curve, measure, pick, polygon, triangle, unicode
2) text tool: a) honour RTL text flow b) navigating w/ cursor keys c) pasting text
3) operators: rotate, scale, shift, slice, tile
4) cleanup & refactor, switch to GTK
5) GUI:
a) replace logo w/ canvas panel in About dialogue
b) Settings/Settings window (e.g. autosave, hide cursor on leaving window, ...)
c) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3)
d) {copy,cut,delete,insert from,paste}, {edit asset in new canvas,import from {canvas,object}}
1) ANSI CSI CU[BDPU] sequences
3) Documentation, instrumentation & unit tests
4) Layers & layout (e.g. for comics, zines, etc.)
5) Open and toggle a reference image in the background
6) Client-Server or Peer-to-Peer realtime collaboration
7) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.)
8) Unit tools: arrow, {cloud,speech bubble}, curve, polygon
9) {record,replay} {keyboard,mouse,...} events in debugging builds
10) Integrate ENNTool code in the form of OpenGL-based animation window (see 13) and 14))
11) Composition, parametrisation & keying of tools from higher-order operators (brushes, functions, filters, outlines, patterns & shaders) and unit tools
12) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...)
13) GUI TODO list:
a) switch to Gtk
b) canvas preview in Open dialogue(s)
c) https://material.io/resources/icons/?style=baseline
d) replace logo w/ canvas panel in About dialogue, revisit melp? dialogue
e) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3)
f) Settings window (e.g. autosave parameters, cursor opacity, default colours, hide cursor on leaving window, keyboard/mouse map, show cell position tooltip on mouse hover, ...)
vim:ff=dos tw=0

41
assets/text/melp.txt Normal file
View File

@ -0,0 +1,41 @@
Keys or mouse actions separated by forward slashes (`/') indicate alternatives.
Keys or mouse actions separated by commas indicate separate commands.
Global commands:
<Ctrl> -, +/<Mouse wheel> Decrease/increase brush height and width
<Ctrl> <Mouse wheel> Decrease/increase canvas height and width
<Ctrl> <Up>, <Down> Decrease/increase canvas height
<Ctrl> <Left>, <Right> Decrease/increase canvas width
<Ctrl> <Alt> <Mouse wheel> Decrease/increase cell size
<Ctrl> 0-9 Set foreground colour to #0-9
<Ctrl> <Shift> 0-5, 6 Set foreground colour to #10-15 or transparent colour, resp.
<Ctrl> <Alt> 0-9 Set background colour to #0-9
<Ctrl> <Alt> <Shift> 0-5, 6 Set background colour to #10-15 or transparent colour, resp.
<Ctrl> I Flip colours
<F1> View melp?
<F2-F10> Switch to cursor, rectangle, circle, fill, line, text, object, erase, pick colour tool
<Ctrl> N New canvas
<Ctrl> O Open mIRC art file
<Ctrl> S Save canvas as mIRC art file
<Ctrl> X Exit
<Ctrl> Y, Z Redo, undo last action
<Shift> <Pause> Break into Python debugger
Canvas commands:
<Down>, <Left>, <Right>, <Up> Move canvas cursor
<LMB>/<Space> Apply current tool with foreground colour (with exceptions)
<RMB> Apply current tool with background colour (with exceptions)
Tool-specific commands:
(Circle, rectangle) <Ctrl> <LMB>, <RMB> Initiate circle/rectangle dragging irrespective of brush size
(Erase) <RMB> Erase background colour with foreground colour
(Fill) <Ctrl> <LMB>, <Space>/<RMB> Fill entire region with foreground/background colour ignoring character cells
(Line, object) <LMB>/<Space> Initiate line drawing/selection
(Object) <Ctrl> <LMB> Move selection instead of cloning
(Pick colour) <LMB>/<Space> Pick current cell's foreground colour
(Pick colour) <RMB> Pick current cell's background colour
(Text) <Backspace> Erase last cell and move backwards w/ wraparound
(Text) <Ctrl> V Paste text from clipboard
(Text) <Enter> Move to leftmost cell on next row w/ wraparound

View File

@ -0,0 +1 @@
.

15
assets/text/roadmap.txt Normal file
View File

@ -0,0 +1,15 @@
1) GUI: drag & drop file outside of canvas to open, into canvas as object w/ select tool
2) GUI: edit asset in new canvas, import from {canvas,object}
3) GUI: implement GuiCanvasWxBackendFast.c{,c}
4) GUI: select all
5) GUI: show line numbers w/ tooltip on accelerator
6) Operators: copy, cut, delete, insert from, paste
7) Operators: crop, scale, shift, slice operators
8) Tools: measure, triangle, unicode block elements tool
9) Tools: object tool: allow application of arbitrary tool to selection before setting
10) Tools: object tool: reimplement cloning correctly outside of object tool
11) Tools: reimplement in C++
12) Tools: text tool: finish Arabic/RTL text implementation
13) Tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB
vim:ff=dos tw=0

View File

@ -1,17 +1,17 @@
 3,6▟6,3▝
 3,6▟6,3▜▛3,6▟6,3▝
 3,6▟6,3▝▜3,6▟6,3▝▜▛3,6▟6,3▝
 3,6▟6,3▝3,8/\3,6▟6,3▜▛3,6▟6,3▝3,8/\3,6▟6,3▝
 9,1/\ 9,1/ 6,3▝▜▛3,6▟6,3▝8,8 3,6▟6,3▝▜▛3,6▟6,3▝
6,1(0o9 6) 6,1( 6,3▜▛3,6▟6,3▝8,8 3o _ o8 3,6▟6,3▝▜▛3,6▟
 9,1( \ 9,1) 6,3▝▜▛▜8,8 6(_3Y6_)6,3▛▝▛3,6▟6,3▝
 6,1|(__)/ 3,6▟6,3▝▜▛8,8 6\_/6,3▜▛3,6▟6,3▝
 1,3\\ 1,3.'0s3 6▜▛3,6▟6,3▝
 1,6\\ 1,6/6 0p6 1\ \ 6,3▜▛
 1,3\\/3 0o3 1\3 1\ \6▜
 1,6\6 0k6 1/)___|_|6,3▛
 1,3(_0e1__/__))) )))6▛
 12,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
 12,1
 2,1
 2,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲
3,6▟6,3▝15
3,6▟6,3▜▛3,6▟6,3▝15
3,6▟6,3▝▜3,6▟6,3▝▜▛3,6▟6,3▝15
3,6▟6,3▝3,8/\3,6▟6,3▜▛3,6▟6,3▝3,8/\3,6▟6,3▝15
9,1/\15 9,1/15 6,3▝▜▛3,6▟6,3▝8,8 3,6▟6,3▝▜▛3,6▟6,3▝15
6,1(0o9 6)15 6,1(15 6,3▜▛3,6▟6,3▝8,8 3o _ o8 3,6▟6,3▝▜▛3,6▟
9,1( \15 9,1)15 6,3▝▜▛▜8,8 6(_3Y6_)6,3▛▝▛3,6▟6,3▝15
6,1|(__)/15 3,6▟6,3▝▜▛8,8 6\_/6,3▜▛3,6▟6,3▝15
1,3\\15 1,3.'0s3 6▜▛3,6▟6,3▝15
1,6\\15 1,6/6 0p6 1\ \ 6,3▜▛15
1,3\\/3 0o3 1\3 1\ \6▜15
1,6\6 0k6 1/)___|_|6,3▛15
1,3(_0e1__/__))) )))6▛15
12,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲15
12,115
2,115
2,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲15

31
assets/tools/AnsiToMiRCART.py Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
#
# AnsiToMiRCART.py -- convert ANSI to mIRC art file (for spoke)
# Copyright (c) 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# This project is licensed under the terms of the MIT licence.
#
import os, sys
[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]]
from CanvasExportStore import CanvasExportStore
from CanvasImportStore import CanvasImportStore
#
# Entry point
def main(*argv):
if (len(sys.argv) - 1) != 2:
print("usage: {} <ANSI input file pathname> <mIRC art output file pathname>".format(sys.argv[0]), file=sys.stderr)
else:
canvasImportStore = CanvasImportStore()
rc, error = canvasImportStore.importAnsiFile(argv[1])
if rc:
canvasExportStore = CanvasExportStore()
with open(argv[2], "w", encoding="utf-8") as outFile:
canvasExportStore.exportTextFile(canvasImportStore.outMap, canvasImportStore.inSize, outFile)
else:
print("error: {}".format(error), file=sys.stderr)
if __name__ == "__main__":
main(*sys.argv)
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -19,17 +19,14 @@ class IrcMiRCARTBot(IrcClient):
"""IRC<->MiRC2png bot"""
imgurApiKey = ImgurApiKey.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(" "):
@ -38,20 +35,17 @@ class IrcMiRCARTBot(IrcClient):
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;
@ -78,17 +72,14 @@ class IrcMiRCARTBot(IrcClient):
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 "):
@ -155,19 +146,16 @@ class IrcMiRCARTBot(IrcClient):
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()
@ -188,13 +176,11 @@ class IrcMiRCARTBot(IrcClient):
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, localAddr=None, preferFamily=0, timeout=None): Connect to server and (re)initialise w/ optional timeout
def connect(self, localAddr=None, preferFamily=0, timeout=None):
self._log("Connecting to {}:{}...".format(self.serverHname, self.serverPort))
if super().connect(localAddr=localAddr, preferFamily=preferFamily, timeout=timeout):
@ -206,8 +192,7 @@ class IrcMiRCARTBot(IrcClient):
return True
else:
return False
# }}}
# {{{ dispatch(self): Read, parse, and dispatch single line from server
def dispatch(self):
while True:
if self.clientNextTimeout:
@ -245,10 +230,7 @@ class IrcMiRCARTBot(IrcClient):
self.clientHasPing = False
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
@ -277,12 +259,12 @@ if __name__ == "__main__":
optlist, argv = getopt(sys.argv[1:], "46l:")
optdict = dict(optlist)
if len(argv) < 1 or len(argv) > 6:
print("usage: {} [-4|-6] [-l <local hostname>] " \
"<IRC server hostname> " \
"[<IRC server port; defaults to 6667>] " \
"[<IRC bot nick name; defaults to pngbot>] " \
"[<IRC bot user name; defaults to pngbot>] " \
"[<IRC bot real name; defaults to pngbot>] " \
print("usage: {} [-4|-6] [-l <local hostname>] " \
"<IRC server hostname> " \
"[<IRC server port; defaults to 6667>] " \
"[<IRC bot nick name; defaults to pngbot>] " \
"[<IRC bot user name; defaults to pngbot>] " \
"[<IRC bot real name; defaults to pngbot>] " \
"[<IRC bot channel name; defaults to #MiRCART>] ".format(sys.argv[0]), file=sys.stderr)
else:
main(optdict, *argv)

View File

@ -27,13 +27,15 @@ deploy() {
-mindepth 1 \
-not -path "./${RELEASES_DNAME}/*" \
-not -path "./${RELEASES_DNAME}" \
-not -path "./.git/*" \
-not -path "./.git" \
-not -path './.*/*' \
-not -path './.*' \
-not -path '*/__pycache__/*' \
-not -path '*/__pycache__' \
-not -path './librtl/ImgurApiKey.py' \
-not -name '*.exp' \
-not -name '*.lib' \
-not -name '*.obj' \
-not -name '*.sw*' \
-not -name '.gitignore' \
-not -name "${0##*/}" |\
cpio --quiet -dLmp "${_release_dname}";
sed -i"" "s/__ROAR_RELEASE_VERSION__/${_release_version_long}/" "${_release_dname}/libroar/RoarWindowAbout.py";

View File

@ -6,102 +6,123 @@
from CanvasExportStore import CanvasExportStore
from CanvasImportStore import CanvasImportStore
from CanvasJournal import CanvasJournal
class Canvas():
# {{{ _commitPatch(self, patch)
def _commitPatch(self, patch):
self.map[patch[1]][patch[0]] = patch[2:]
# }}}
# {{{ dispatchPatch(self, isCursor, patch, commitUndo=True)
def dispatchPatch(self, isCursor, patch, commitUndo=True):
def applyPatch(self, patch, commitUndo=True):
if (patch[0] >= self.size[0]) or (patch[1] >= self.size[1]):
return False
else:
patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
if isCursor:
self.journal.pushCursor(patchDelta)
else:
if commitUndo:
self.journal.begin(); self.journal.updateCurrentDeltas(patch, patchDelta); self.journal.end();
self._commitPatch(patch)
if commitUndo:
self.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch)
return True
# }}}
# {{{ dispatchPatchSingle(self, isCursor, patch, commitUndo=True)
def dispatchPatchSingle(self, isCursor, patch, commitUndo=True):
if (patch[0] >= self.size[0]) or (patch[1] >= self.size[1]):
return False
def begin(self):
deltaItem = [[], []]; self.patchesUndo.insert(self.patchesUndoLevel, deltaItem);
def end(self):
if self.patchesUndo[self.patchesUndoLevel] == [[], []]:
del self.patchesUndo[self.patchesUndoLevel]
else:
patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
if isCursor:
self.journal.pushCursor(patchDelta)
else:
if commitUndo:
self.journal.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch)
return True
# }}}
# {{{ resize(self, newSize, commitUndo=True)
def resize(self, newSize, commitUndo=True):
if self.patchesUndoLevel > 0:
del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0;
def popCursor(self, reset=True):
patchesCursor = []
if len(self.patchesCursor):
patchesCursor = self.patchesCursor
if reset:
self.resetCursor()
return patchesCursor
def popUndo(self, redo=False):
patches = []
if not redo:
if self.patchesUndo[self.patchesUndoLevel] != None:
patches = self.patchesUndo[self.patchesUndoLevel][0]; self.patchesUndoLevel += 1;
else:
if self.patchesUndoLevel > 0:
self.patchesUndoLevel -= 1; patches = self.patchesUndo[self.patchesUndoLevel][1];
return patches
def pushCursor(self, patches):
self.patchesCursor = patches
def resetCursor(self):
self.patchesCursor = []
def resetUndo(self):
if self.patchesUndo != None:
self.patchesUndo.clear()
self.patchesUndo = [None]; self.patchesUndoLevel = 0;
def resize(self, brushColours, newSize, commitUndo=True):
newCells = []
if newSize != self.size:
if self.map == None:
self.map, oldSize = [], [0, 0]
else:
oldSize = self.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
self.journal.resetCursor()
if commitUndo:
self.journal.begin()
self.begin()
undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize]
self.journal.updateCurrentDeltas(redoPatches, undoPatches)
self.updateCurrentDeltas(redoPatches, undoPatches)
if deltaSize[0] < 0:
for numRow in range(oldSize[1]):
if commitUndo:
for numCol in range((oldSize[0] + deltaSize[0]), oldSize[0]):
self.journal.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
self.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
del self.map[numRow][-1:(deltaSize[0]-1):-1]
else:
for numRow in range(oldSize[1]):
self.map[numRow].extend([[1, 1, 0, " "]] * deltaSize[0])
self.map[numRow].extend([[*brushColours, 0, " "]] * deltaSize[0])
for numNewCol in range(oldSize[0], newSize[0]):
if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None)
self.dispatchPatch(False, [numNewCol, numRow, 1, 1, 0, " "], False)
self.updateCurrentDeltas([numNewCol, numRow, *brushColours, 0, " "], None)
newCells += [[numNewCol, numRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numRow, *brushColours, 0, " "], False)
if deltaSize[1] < 0:
if commitUndo:
for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]):
for numCol in range(oldSize[0] + deltaSize[0]):
self.journal.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
self.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
del self.map[-1:(deltaSize[1]-1):-1]
else:
for numNewRow in range(oldSize[1], newSize[1]):
self.map.extend([[[1, 1, 0, " "]] * newSize[0]])
self.map.extend([[[*brushColours, 0, " "]] * newSize[0]])
for numNewCol in range(newSize[0]):
if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None)
self.dispatchPatch(False, [numNewCol, numNewRow, 1, 1, 0, " "], False)
self.updateCurrentDeltas([numNewCol, numNewRow, *brushColours, 0, " "], None)
newCells += [[numNewCol, numNewRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numNewRow, *brushColours, 0, " "], False)
self.size = newSize
if commitUndo:
self.journal.end()
return True
self.end()
return True, newCells
else:
return False
# }}}
# {{{ update(self, newSize, newCanvas=None)
return False, newCells
def update(self, newSize, newCanvas=None):
for numRow in range(self.size[1]):
for numCol in range(self.size[0]):
if (newCanvas != None) \
and (numRow < len(newCanvas)) \
and (numCol < len(newCanvas[numRow])):
if (newCanvas != None) \
and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])):
self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]])
# }}}
#
# __init__(self, size): initialisation method
def updateCurrentDeltas(self, redoPatches, undoPatches):
self.patchesUndo[self.patchesUndoLevel][0].append(undoPatches)
self.patchesUndo[self.patchesUndoLevel][1].append(redoPatches)
def __del__(self):
self.resetCursor(); self.resetUndo();
def __init__(self, size):
self.dirtyCursor, self.map, self.size = False, None, size
self.exportStore, self.importStore, self.journal = CanvasExportStore(), CanvasImportStore(), CanvasJournal()
self.exportStore, self.importStore, self.map, self.size = CanvasExportStore(), CanvasImportStore(), None, size
self.patchesCursor, self.patchesUndo, self.patchesUndoLevel = [], [None], 0
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -4,11 +4,10 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
# {{{ AnsiBgToMiRCARTColours
AnsiBgToMiRCARTColours = {
107: 0, # Bright White
40: 1, # Black
104: 2, # Blue
44: 2, # Blue
42: 3, # Green
101: 4, # Red
41: 5, # Light Red
@ -18,13 +17,12 @@ AnsiBgToMiRCARTColours = {
102: 9, # Light Green
46: 10, # Cyan
106: 11, # Light Cyan
44: 12, # Light Blue
104: 12, # Light Blue
105: 13, # Light Pink
100: 14, # Grey
47: 15, # Light Grey
};
# }}}
# {{{ AnsiFgBoldToMiRCARTColours
AnsiFgBoldToMiRCARTColours = {
97: 0, # Bright White
30: 14, # Grey
@ -38,17 +36,16 @@ AnsiFgBoldToMiRCARTColours = {
92: 9, # Light Green
36: 11, # Light Cyan
96: 11, # Light Cyan
34: 12, # Light Blue
94: 12, # Light Blue
95: 13, # Light Pink
90: 14, # Grey
37: 0, # Bright White
};
# }}}
# {{{ AnsiFgToMiRCARTColours
AnsiFgToMiRCARTColours = {
97: 0, # Bright White
30: 1, # Black
94: 2, # Blue
34: 2, # Blue
32: 3, # Green
91: 4, # Red
31: 5, # Light Red
@ -58,13 +55,12 @@ AnsiFgToMiRCARTColours = {
92: 9, # Light Green
36: 10, # Cyan
96: 11, # Light Cyan
34: 12, # Light Blue
94: 12, # Light Blue
95: 13, # Light Pink
90: 14, # Grey
37: 15, # Light Grey
};
# }}}
# {{{ ColourMapBold: mIRC colour number to RGBA map given ^B (bold)
ColourMapBold = [
[255, 255, 255], # Bright White
[85, 85, 85], # Black
@ -83,8 +79,7 @@ ColourMapBold = [
[85, 85, 85], # Grey
[255, 255, 255], # Light Grey
]
# }}}
# {{{ ColourMapNormal: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline)
ColourMapNormal = [
[255, 255, 255], # Bright White
[0, 0, 0], # Black
@ -103,12 +98,11 @@ ColourMapNormal = [
[85, 85, 85], # Grey
[187, 187, 187], # Light Grey
]
# }}}
# {{{ MiRCARTToAnsiColours
MiRCARTToAnsiColours = [
97, # Bright White
30, # Black
94, # Light Blue
34, # Blue
32, # Green
91, # Red
31, # Light Red
@ -118,11 +112,10 @@ MiRCARTToAnsiColours = [
92, # Light Green
36, # Cyan
96, # Light Cyan
34, # Blue
94, # Light Blue
95, # Light Pink
90, # Grey
37, # Light Grey
];
# }}}
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -20,25 +20,20 @@ except ImportError:
haveUrllib = False
class CanvasExportStore():
# {{{ _CellState(): Cell state
class _CellState():
CS_NONE = 0x00
CS_BOLD = 0x01
CS_ITALIC = 0x02
CS_UNDERLINE = 0x04
# }}}
CS_UNDERLINE = 0x02
ImgurUploadUrl = "https://api.imgur.com/3/upload.json"
PastebinPostUrl = "https://pastebin.com/api/api_post.php"
# {{{ _drawUnderline(self, curPos, fillColour, fontSize, imgDraw)
def _drawUnderLine(self, curPos, fillColour, fontSize, imgDraw):
imgDraw.line( \
xy=(curPos[0], curPos[1] + (fontSize[1] - 2), \
curPos[0] + fontSize[0], curPos[1] + (fontSize[1] - 2)), \
fill=fillColour)
# }}}
# {{{ exportAnsiFile(self, canvasMap, canvasSize, outFile)
def exportAnsiFile(self, canvasMap, canvasSize, outFile):
outBuffer = ""
for inCurRow in range(len(canvasMap)):
@ -50,9 +45,12 @@ class CanvasExportStore():
outBuffer += "\u001b[1m"
if inCurCell[2] & self._CellState.CS_UNDERLINE:
outBuffer += "\u001b[4m"
elif (lastAttribs & self._CellState.CS_UNDERLINE) \
and ((inCurCell[2] & self._CellState.CS_UNDERLINE) == 0):
outBuffer += "\u001b[24m"
lastAttribs = inCurCell[2]
if lastColours == None or lastColours != inCurCell[:2]:
if (inCurCell[0] == -1) \
if (inCurCell[0] == -1) \
and (inCurCell[1] == -1):
outBuffer += "\u001b[39;49m{}".format(" ")
elif inCurCell[1] == -1:
@ -71,8 +69,7 @@ class CanvasExportStore():
return (True, None)
else:
return (False, "empty buffer generated")
# }}}
# {{{ exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType)
def exportBitmapToImgur(self, apiKey, canvasBitmap, imgName, imgTitle, imgType):
tmpPathName = tempfile.mkstemp()
os.close(tmpPathName[0])
@ -94,12 +91,10 @@ class CanvasExportStore():
imgurResult = (False, responseHttp.status_code, responseDict.get("data"))
os.remove(tmpPathName[1])
return imgurResult
# }}}
# {{{ exportBitmapToPngFile(self, canvasBitmap, outPathName, outType)
def exportBitmapToPngFile(self, canvasBitmap, outPathName, outType):
return canvasBitmap.ConvertToImage().SaveFile(outPathName, outType)
# }}}
# {{{ exportPastebin(self, apiDevKey, canvasMap, canvasSize, pasteName="", pastePrivate=0)
def exportPastebin(self, apiDevKey, canvasMap, canvasSize, pasteName="", pastePrivate=0):
if haveUrllib:
outFile = io.StringIO()
@ -117,8 +112,7 @@ class CanvasExportStore():
return (False, str(responseHttp.status_code))
else:
return (False, "missing requests and/or urllib3 module(s)")
# }}}
# {{{ exportPngFile(self, canvasMap, fontFilePath, fontSize, outPathName)
def exportPngFile(self, canvasMap, fontFilePath, fontSize, outPathName):
if havePIL:
inSize = (len(canvasMap[0]), len(canvasMap))
@ -161,20 +155,32 @@ class CanvasExportStore():
return (True, None)
else:
return (False, "missing PIL modules")
# }}}
# {{{ exportTextBuffer(self, canvasMap, canvasSize)
def exportTextBuffer(self, canvasMap, canvasSize):
outBuffer = ""
for canvasRow in range(canvasSize[1]):
canvasLastColours = [15, -1]
canvasLastAttrs, canvasLastColours = self._CellState.CS_NONE, [15, -1]
for canvasCol in range(canvasSize[0]):
canvasColAttrs = canvasMap[canvasRow][canvasCol][2]
canvasColColours = canvasMap[canvasRow][canvasCol][0:2]
canvasColText = canvasMap[canvasRow][canvasCol][3]
if (canvasColAttrs & self._CellState.CS_BOLD) \
and (not (canvasLastAttrs & self._CellState.CS_BOLD)):
outBuffer += "\u0002"; canvasLastAttrs = canvasLastAttrs | self._CellState.CS_BOLD;
if (not (canvasColAttrs & self._CellState.CS_BOLD)) \
and (canvasLastAttrs & self._CellState.CS_BOLD):
outBuffer += "\u0002"; canvasLastAttrs = canvasLastAttrs & ~self._CellState.CS_BOLD;
if (canvasColAttrs & self._CellState.CS_UNDERLINE) \
and (not (canvasLastAttrs & self._CellState.CS_UNDERLINE)):
outBuffer += "\u001f"; canvasLastAttrs = canvasLastAttrs | self._CellState.CS_UNDERLINE;
if (not (canvasColAttrs & self._CellState.CS_UNDERLINE)) \
and (canvasLastAttrs & self._CellState.CS_UNDERLINE):
outBuffer += "\u001f"; canvasLastAttrs = canvasLastAttrs & ~self._CellState.CS_UNDERLINE;
if canvasColColours[0] == -1:
canvasColColours[0] = canvasColColours[1]
if (canvasColColours[0] != canvasLastColours[0]) \
if (canvasColColours[0] != canvasLastColours[0]) \
and (canvasColColours[1] != canvasLastColours[1]):
if (canvasColColours[0] == -1) \
if (canvasColColours[0] == -1) \
and (canvasColColours[1] == -1):
outBuffer += "\u000f"
elif canvasColColours[1] == -1:
@ -200,11 +206,9 @@ class CanvasExportStore():
return (True, outBuffer)
else:
return (False, "empty buffer generated")
# }}}
# {{{ exportTextFile(self, canvasMap, canvasSize, outFile)
def exportTextFile(self, canvasMap, canvasSize, outFile):
rc, outBuffer = self.exportTextBuffer(canvasMap, canvasSize)
return outFile.write(outBuffer) if rc else (rc, outBuffer)
# }}}
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -9,20 +9,14 @@ from CanvasColours import AnsiBgToMiRCARTColours, AnsiFgToMiRCARTColours, AnsiFg
import io, os, re, struct, sys
class CanvasImportStore():
# {{{ _CellState(): Cell state
class _CellState():
CS_NONE = 0x00
CS_BOLD = 0x01
CS_ITALIC = 0x02
CS_UNDERLINE = 0x04
# }}}
CS_UNDERLINE = 0x02
# {{{ _flipCellStateBit(self, bit, cellState)
def _flipCellStateBit(self, bit, cellState):
return cellState & ~bit if cellState & bit else cellState | bit
# }}}
# {{{ importAnsiBuffer(self, inBuffer, encoding="cp437", width=None)
def importAnsiBuffer(self, inBuffer, encoding="cp437", width=None):
curBg, curBgAnsi, curBoldAnsi, curFg, curFgAnsi = -1, 30, False, 15, 37
done, outMap, outMaxCols = False, [[]], 0
@ -74,12 +68,10 @@ class CanvasImportStore():
return (True, None)
else:
return (False, "empty output map")
# }}}
# {{{ importAnsiFile(self, inPathName, encoding="cp437")
def importAnsiFile(self, inPathName, encoding="cp437"):
return self.importAnsiBuffer(open(inPathName, "rb").read(), encoding)
# }}}
# {{{ importSauceFile(self, inPathName, encoding="cp437")
def importSauceFile(self, inPathName, encoding="cp437"):
with open(inPathName, "rb") as inFile:
inFileStat = os.stat(inPathName)
@ -90,8 +82,7 @@ class CanvasImportStore():
return self.importAnsiBuffer(inFileData, encoding, width)
else:
return (False, "only character based ANSi SAUCE files are supported")
# }}}
# {{{ importTextBuffer(self, inFile)
def importTextBuffer(self, inFile):
try:
inLine, outMap, outMaxCols = inFile.readline(), [], 0
@ -110,19 +101,25 @@ class CanvasImportStore():
inCurColours = (int(m[2]), int(m[3]))
elif (m[2] != None) and (m[3] == None):
inCurColours = (int(m[2]), int(inCurColours[1]))
elif (m[2] == None) and (m[3] != None):
inCurColours = (int(inCurColours[0]), int(m[3]))
else:
inCurColours = (15, -1)
inCurCol += len(m[0])
else:
inCurColours = (15, -1); inCurCol += 1;
elif inChar == "\u0006":
inCellState = self._flipCellStateBit(self._CellState.CS_ITALIC, inCellState); inCurCol += 1;
inCurCol += 1
elif inChar == "\u000f":
inCellState |= self._CellState.CS_NONE; inCurColours = (15, -1); inCurCol += 1;
elif inChar == "\u0016":
inCurColours = (inCurColours[1], inCurColours[0]); inCurCol += 1;
elif inChar == "\u001f":
inCellState = self._flipCellStateBit(self._CellState.CS_UNDERLINE, inCellState); inCurCol += 1;
elif inChar == "\t":
for tabChar in range(8 - len(outMap[-1]) % 8):
outMap[-1].append([*inCurColours, inCellState, inChar])
inCurCol += 1
else:
outMap[-1].append([*inCurColours, inCellState, inChar]); inCurCol += 1;
inLine, outMaxCols = inFile.readline(), max(outMaxCols, len(outMap[-1]))
@ -137,15 +134,11 @@ class CanvasImportStore():
return (False, "empty output map")
except:
return (False, sys.exc_info()[1])
# }}}
# {{{ importTextFile(self, pathName)
def importTextFile(self, pathName):
with open(pathName, "r", encoding="utf-8-sig") as inFile:
return self.importTextBuffer(inFile)
# }}}
#
# __init__(self, inFile=None): initialisation method
def __init__(self, inFile=None):
self.inSize, self.outMap = None, None
if inFile != None:

View File

@ -1,77 +0,0 @@
#!/usr/bin/env python3
#
# CanvasJournal.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
class CanvasJournal():
# {{{ begin(self)
def begin(self):
deltaItem = [[], []]; self.patchesUndo.insert(self.patchesUndoLevel, deltaItem);
# }}}
# {{{ end(self)
def end(self):
if self.patchesUndo[self.patchesUndoLevel] == [[], []]:
del self.patchesUndo[self.patchesUndoLevel]
else:
if self.patchesUndoLevel > 0:
del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0;
# }}}
# {{{ popCursor(self)
def popCursor(self):
if len(self.patchesCursor):
patchesCursor = self.patchesCursor; self.patchesCursor = [];
return patchesCursor
else:
return []
# }}}
# {{{ popRedo(self)
def popRedo(self):
if self.patchesUndoLevel > 0:
self.patchesUndoLevel -= 1; patches = self.patchesUndo[self.patchesUndoLevel];
return patches[1]
else:
return []
# }}}
# {{{ popUndo(self)
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)
def pushCursor(self, patches):
self.patchesCursor.append(patches)
# }}}
# {{{ resetCursor(self)
def resetCursor(self):
if self.patchesCursor != None:
self.patchesCursor.clear()
self.patchesCursor = []
# }}}
# {{{ resetUndo(self)
def resetUndo(self):
if self.patchesUndo != None:
self.patchesUndo.clear()
self.patchesUndo = [None]; self.patchesUndoLevel = 0;
# }}}
# {{{ updateCurrentDeltas(self, redoPatches, undoPatches)
def updateCurrentDeltas(self, redoPatches, undoPatches):
self.patchesUndo[self.patchesUndoLevel][0].append(undoPatches)
self.patchesUndo[self.patchesUndoLevel][1].append(redoPatches)
# }}}
# {{{ __del__(self): destructor method
def __del__(self):
self.resetCursor(); self.resetUndo();
# }}}
#
# __init__(self): initialisation method
def __init__(self):
self.patchesCursor, self.patchesUndo, self. patchesUndoLevel = None, None, None
self.resetCursor(); self.resetUndo();
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,7 +5,7 @@
#
#
# Colours: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline)
# Colours: mIRC colour number to RGBA map given none of ^[BV_] (bold, reverse, underline)
#
Colours = [
[255, 255, 255, 255, "White"],

View File

@ -4,17 +4,19 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from ctypes import *
try:
import GuiCanvasWxBackendFast; haveGuiCanvasWxBackendFast = True;
except ImportError as e:
print("Failed to import GuiCanvasWxBackendFast: {}".format(e)); haveGuiCanvasWxBackendFast = False;
from GuiCanvasColours import Colours
import math, os, platform, wx
import math, os, platform, Rtl, wx
class GuiBufferedDC(wx.MemoryDC):
# {{{ __del__(self)
def __del__(self):
self.dc.Blit(0, 0, *self.viewSize, self, 0, 0)
self.SelectObject(wx.NullBitmap)
# }}}
# {{{ __init__(self, backend, buffer, clientSize, dc, viewRect)
def __init__(self, backend, buffer, clientSize, dc, viewRect):
super().__init__()
canvasSize = [a - b for a, b in zip(backend.canvasSize, viewRect)]
@ -24,161 +26,228 @@ class GuiBufferedDC(wx.MemoryDC):
viewSize = [m * n for m, n in zip(backend.cellSize, viewSize)]
self.SelectObject(buffer); self.SetDeviceOrigin(*viewRect);
self.dc, self.viewRect, self.viewSize = dc, viewRect, viewSize
# }}}
class GuiCanvasWxBackend():
# {{{ _drawBrushPatch(self, eventDc, patch, point)
def _drawBrushPatch(self, eventDc, patch, point):
absPoint = self._xlatePoint(point)
brushBg, brushFg, pen = self._getBrushPatchColours(patch)
self._setBrushDc(brushBg, brushFg, eventDc, pen)
eventDc.DrawRectangle(*absPoint, *self.cellSize)
# }}}
# {{{ _drawCharPatch(self, eventDc, patch, point)
def _drawCharPatch(self, eventDc, patch, point):
absPoint, fontBitmap = self._xlatePoint(point), wx.Bitmap(*self.cellSize)
brushBg, brushFg, pen = self._getCharPatchColours(patch)
fontDc = wx.MemoryDC(); fontDc.SelectObject(fontBitmap); fontDc.SetFont(self._font);
fontDc.SetBackground(brushBg); fontDc.SetBrush(brushFg); fontDc.SetPen(pen);
fontDc.SetTextForeground(wx.Colour(Colours[patch[0]][:4]))
fontDc.SetTextBackground(wx.Colour(Colours[patch[1]][:4]))
fontDc.DrawRectangle(0, 0, *self.cellSize)
if patch[3] == "_":
fontDc.SetPen(self._pens[patch[0]])
fontDc.DrawLine(0, self.cellSize[1] - 1, self.cellSize[0], self.cellSize[1] - 1)
else:
fontDc.DrawText(patch[3], 0, 0)
eventDc.Blit(*absPoint, *self.cellSize, fontDc, 0, 0)
# }}}
# {{{ _finiBrushesAndPens(self)
def _finiBrushesAndPens(self):
[brush.Destroy() for brush in self._brushes or []]
[pen.Destroy() for pen in self._pens or []]
self._brushes, self._lastBrushBg, self._lastBrushFg, self._lastPen, self._pens = None, None, None, None, None
# }}}
# {{{ _getBrushPatchColours(self, patch)
def _getBrushPatchColours(self, patch):
if (patch[0] != -1) and (patch[1] != -1):
brushBg, brushFg, pen = self._brushes[patch[1]], self._brushes[patch[1]], self._pens[patch[1]]
elif (patch[0] == -1) and (patch[1] == -1):
brushBg, brushFg, pen = self._brushAlpha, self._brushAlpha, self._penAlpha
elif patch[0] == -1:
brushBg, brushFg, pen = self._brushes[patch[1]], self._brushes[patch[1]], self._pens[patch[1]]
elif patch[1] == -1:
brushBg, brushFg, pen = self._brushAlpha, self._brushAlpha, self._penAlpha
return (brushBg, brushFg, pen)
# }}}
# {{{ _getCharPatchColours(self, patch)
def _getCharPatchColours(self, patch):
if (patch[0] != -1) and (patch[1] != -1):
brushBg, brushFg, pen = self._brushes[patch[1]], self._brushes[patch[1]], self._pens[patch[1]]
elif (patch[0] == -1) and (patch[1] == -1):
brushBg, brushFg, pen = self._brushAlpha, self._brushAlpha, self._penAlpha
elif patch[0] == -1:
brushBg, brushFg, pen = self._brushes[patch[1]], self._brushes[patch[1]], self._pens[patch[1]]
elif patch[1] == -1:
brushBg, brushFg, pen = self._brushAlpha, self._brushAlpha, self._penAlpha
return (brushBg, brushFg, pen)
# }}}
# {{{ _initBrushesAndPens(self)
def _initBrushesAndPens(self):
self._brushes, self._pens = [None for x in range(len(Colours))], [None for x in range(len(Colours))]
for mircColour in range(len(Colours)):
self._brushes[mircColour] = wx.Brush(wx.Colour(Colours[mircColour][:4]), wx.BRUSHSTYLE_SOLID)
self._pens[mircColour] = wx.Pen(wx.Colour(Colours[mircColour][:4]), 1)
self._brushAlpha = wx.Brush(wx.Colour(Colours[14][:4]), wx.BRUSHSTYLE_SOLID)
self._penAlpha = wx.Pen(wx.Colour(Colours[14][:4]), 1)
self._lastBrushBg, self._lastBrushFg, self._lastPen = None, None, None
# }}}
# {{{ _setBrushDc(self, brushBg, brushFg, dc, pen)
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, point)
def _xlatePoint(self, point):
return [a * b for a, b in zip(point, self.cellSize)]
# }}}
arabicShapes = {
u'\u0621': (u'\uFE80'),
u'\u0622': (u'\uFE81', None, None, u'\uFE82'),
u'\u0623': (u'\uFE83', None, None, u'\uFE84'),
u'\u0624': (u'\uFE85', None, None, u'\uFE86'),
u'\u0625': (u'\uFE87', None, None, u'\uFE88'),
u'\u0626': (u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A'),
u'\u0627': (u'\uFE8D', None, None, u'\uFE8E'),
u'\u0628': (u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90'),
u'\u0629': (u'\uFE93', None, None, u'\uFE94'),
u'\u062A': (u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96'),
u'\u062B': (u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A'),
u'\u062C': (u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E'),
u'\u062D': (u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2'),
u'\u062E': (u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6'),
u'\u062F': (u'\uFEA9', None, None, u'\uFEAA'),
u'\u0630': (u'\uFEAB', None, None, u'\uFEAC'),
u'\u0631': (u'\uFEAD', None, None, u'\uFEAE'),
u'\u0632': (u'\uFEAF', None, None, u'\uFEB0'),
u'\u0633': (u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2'),
u'\u0634': (u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6'),
u'\u0635': (u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA'),
u'\u0636': (u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE'),
u'\u0637': (u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2'),
u'\u0638': (u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6'),
u'\u0639': (u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA'),
u'\u063A': (u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE'),
u'\u0640': (u'\u0640', None, None, None),
u'\u0641': (u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2'),
u'\u0642': (u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6'),
u'\u0643': (u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA'),
u'\u0644': (u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE'),
u'\u0645': (u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2'),
u'\u0646': (u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6'),
u'\u0647': (u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA'),
u'\u0648': (u'\uFEED', None, None, u'\uFEEE'),
u'\u0649': (u'\uFEEF', None, None, u'\uFEF0'),
u'\u064A': (u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2'),
}
# {{{ drawCursorMaskWithJournal(self, canvasJournal, eventDc, viewRect)
def drawCursorMaskWithJournal(self, canvasJournal, eventDc, viewRect):
[self.drawPatch(eventDc, patch, viewRect) for patch in canvasJournal.popCursor()]
# }}}
# {{{ drawPatch(self, eventDc, patch, viewRect)
def drawPatch(self, eventDc, patch, viewRect):
point = [m - n for m, n in zip(patch[:2], viewRect)]
if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]:
if patch[5] == " ":
if patch[3] == -1:
self._drawCharPatch(eventDc, [*patch[2:-1], ""], point)
else:
self._drawBrushPatch(eventDc, patch[2:], point)
class _CellState():
CS_NONE = 0x00
CS_BOLD = 0x01
CS_UNDERLINE = 0x02
def _blendColours(self, bg, fg):
return [int((fg * 0.8) + (bg * (1.0 - 0.8))) for bg, fg in zip(Colours[bg][:3], Colours[fg][:3])]
def _finiBrushesAndPens(self):
for wxObject in Rtl.flatten([
(self._brushAlpha,), (*(self._brushes or ()),), (self._penAlpha,), (*(self._pens or ()),),
*[[self._brushesBlend[bg][fg] for fg in self._brushesBlend[bg].keys()] for bg in self._brushesBlend.keys()],
*[[self._pensBlend[bg][fg] for fg in self._pensBlend[bg].keys()] for bg in self._pensBlend.keys()]]):
if wxObject != None:
wxObject.Destroy()
self._brushAlpha, self._brushes, self._brushesBlend, self._lastBrush, self._lastPen, self._penAlpha, self._pens, self._pensBlend = None, [], {}, None, None, None, [], {}
def _initBrushesAndPens(self):
self._brushes, self._brushesBlend, self._lastBrush, self._lastPen, self._pens, self._pensBlend = [], {}, None, None, [], {}
self._brushAlpha, self._penAlpha = wx.Brush(wx.Colour(48, 48, 48, 255), wx.BRUSHSTYLE_SOLID), wx.Pen(wx.Colour(48, 48, 48, 255), 1)
for mircColour in range(len(Colours)):
self._brushes += [wx.Brush(wx.Colour(Colours[mircColour][:4]), wx.BRUSHSTYLE_SOLID)]; self._brushesBlend[mircColour] = {};
self._pens += [wx.Pen(wx.Colour(Colours[mircColour][:4]), 1)]; self._pensBlend[mircColour] = {};
for mircColourFg in range(len(Colours)):
colourBlend = self._blendColours(mircColour, mircColourFg)
self._brushesBlend[mircColour][mircColourFg] = wx.Brush(wx.Colour(colourBlend), wx.BRUSHSTYLE_SOLID)
self._pensBlend[mircColour][mircColourFg] = wx.Pen(wx.Colour(colourBlend), 1)
def _reshapeArabic(self, canvas, eventDc, isCursor, patch, point):
lastCell, patches = point[0], []
while True:
if ((lastCell + 1) >= (canvas.size[0] - 1)) \
or (not canvas.map[point[1]][lastCell + 1][3] in self.arabicShapes):
break
else:
self._drawCharPatch(eventDc, patch[2:], point)
return True
lastCell += 1
connect = False
for runX in range(lastCell, point[0], -1):
runCell = list(canvas.map[point[1]][runX])
if runX == lastCell:
if self.arabicShapes[runCell[3]][1] != None:
runCell[3] = self.arabicShapes[runCell[3]][1]; connect = True;
else:
runCell[3] = self.arabicShapes[runCell[3]][0]; connect = False;
else:
if connect and (self.arabicShapes[runCell[3]][2] != None):
runCell[3] = self.arabicShapes[runCell[3]][2]; connect = True;
elif connect and (self.arabicShapes[runCell[3]][3] != None):
runCell[3] = self.arabicShapes[runCell[3]][3]; connect = False;
elif not connect and (self.arabicShapes[runCell[3]][1] != None):
runCell[3] = self.arabicShapes[runCell[3]][1]; connect = True;
else:
runCell[3] = self.arabicShapes[runCell[3]][0]; connect = False;
patches += [[runX, point[1], *runCell]]
runCell = list(patch[2:])
if connect and (self.arabicShapes[patch[5]][3] != None):
runCell[3] = self.arabicShapes[patch[5]][3]
else:
return False
# }}}
# {{{ getDeviceContext(self, clientSize, parentWindow, viewRect)
def getDeviceContext(self, clientSize, parentWindow, viewRect):
runCell[3] = self.arabicShapes[patch[5]][0]
patches += [[*point, *runCell]]
return patches
def _setBrushColours(self, dc, isCursor, patch, patchBg):
if ((patch[0] != -1) and (patch[1] != -1)) \
or ((patch[0] == -1) and (patch[1] != -1)):
if not isCursor:
brush, pen = self._brushes[patch[1]], self._pens[patch[1]]
else:
bg = patchBg[1] if patchBg[1] != -1 else 14
brush, pen = self._brushesBlend[bg][patch[1]], self._pensBlend[bg][patch[1]]
else:
if not isCursor:
brush, pen = self._brushAlpha, self._penAlpha
else:
bg = patchBg[1] if patchBg[1] != -1 else 14
brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14]
return brush, pen
def drawPatches(self, canvas, eventDc, patches, isCursor=False):
patchesRender = []
for patch in patches:
point = patch[:2]
if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]:
if patch[5] in self.arabicShapes:
for patchReshaped in self._reshapeArabic(canvas, eventDc, isCursor, patch, point):
patchesRender += [patchReshaped]
else:
patchesRender += [patch]
if haveGuiCanvasWxBackendFast:
GuiCanvasWxBackendFast.drawPatches(self.canvasBitmap, canvas.map, canvas.size, eventDc, isCursor, patchesRender); return;
numPatch, textBg = 0, wx.Colour(0, 0, 0, 0)
rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender)
textList, coords, foregrounds, backgrounds = [], [], [], []
eventDc.SetFont(self._font)
for patchRender in patchesRender:
if (patchRender[5] == " ") and (patchRender[3] == -1):
text, textFg = "", wx.Colour(0, 0, 0, 255)
elif False and isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][3] != " "):
patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]]
text, textFg = canvas.map[patchRender[1]][patchRender[0]][3], wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif False and isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE):
patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]]
text, textFg = "_", wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif patchRender[5] != " ":
text, textFg = patchRender[5], wx.Colour(Colours[patchRender[2]][:4])
elif patchRender[4] & self._CellState.CS_UNDERLINE:
text, textFg = "_", wx.Colour(Colours[patchRender[2]][:4])
else:
text = None
brush, pen = self._setBrushColours(eventDc, isCursor, patchRender[2:], canvas.map[patchRender[1]][patchRender[0]])
rectangles[numPatch] = [patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1], self.cellSize[0], self.cellSize[1]];
pens[numPatch] = pen; brushes[numPatch] = brush;
if text != None:
textList += [text]; coords += [[patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1]]]; foregrounds += [textFg]; backgrounds += [textBg];
numPatch += 1
eventDc.DrawRectangleList(rectangles, pens, brushes)
eventDc.DrawTextList(textList, coords, foregrounds, backgrounds)
def getDeviceContext(self, clientSize, parentWindow, viewRect=None):
if viewRect == None:
viewRect = parentWindow.GetViewStart()
if viewRect == (0, 0):
eventDc = wx.BufferedDC(wx.ClientDC(parentWindow), self.canvasBitmap)
else:
eventDc = GuiBufferedDC(self, self.canvasBitmap, clientSize, wx.ClientDC(parentWindow), viewRect)
self._lastBrushBg, self._lastBrushFg, self._lastPen = None, None, None
self._lastBrush, self._lastPen = None, None
return eventDc
# }}}
# {{{ onPaint(self, clientSize, panelWindow, viewRect)
def onPaint(self, clientSize, panelWindow, viewRect):
if self.canvasBitmap != None:
if viewRect == (0, 0):
eventDc = wx.BufferedPaintDC(panelWindow, self.canvasBitmap)
else:
eventDc = GuiBufferedDC(self, self.canvasBitmap, clientSize, wx.PaintDC(panelWindow), viewRect)
# }}}
# {{{ resize(self, canvasSize, cellSize):
def resize(self, canvasSize, cellSize):
winSize = [a * b for a, b in zip(canvasSize, cellSize)]
def resize(self, canvasSize):
if platform.system() == "Windows":
self._font = wx.TheFontList.FindOrCreateFont(self.fontSize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, self.fontName)
fontInfoDesc = self._font.GetNativeFontInfoDesc().split(";"); fontInfoDesc[12] = "3";
self._font.SetNativeFontInfo(";".join(fontInfoDesc))
dc = wx.MemoryDC()
dc.SetFont(self._font); self.cellSize = dc.GetTextExtent("_");
dc.Destroy()
else:
self._font = wx.Font(self.fontSize, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
dc = wx.MemoryDC()
dc.SetFont(self._font); self.cellSize = dc.GetTextExtent("_");
dc.Destroy()
winSize = [a * b for a, b in zip(canvasSize, self.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)
oldDc.SelectObject(wx.NullBitmap); newDc.SelectObject(wx.NullBitmap);
self.canvasBitmap.Destroy(); self.canvasBitmap = newBitmap;
self.canvasSize, self.cellSize = canvasSize, cellSize
if platform.system() == "Windows":
self._font = wx.TheFontList.FindOrCreateFont(cellSize[0] + 1, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, self.fontName)
else:
self._font = wx.Font(cellSize[0] + 1, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
# }}}
# {{{ xlateEventPoint(self, event, eventDc, viewRect)
self.canvasSize = canvasSize;
if haveGuiCanvasWxBackendFast:
GuiCanvasWxBackendFast.resize(tuple(self.cellSize), self._font, tuple(winSize))
def xlateEventPoint(self, event, eventDc, viewRect):
eventPoint = event.GetLogicalPosition(eventDc)
rectX, rectY = eventPoint.x - (eventPoint.x % self.cellSize[0]), eventPoint.y - (eventPoint.y % self.cellSize[1])
mapX, mapY = int(rectX / self.cellSize[0] if rectX else 0), int(rectY / self.cellSize[1] if rectY else 0)
return [m + n for m, n in zip((mapX, mapY), viewRect)]
# }}}
# {{{ __del__(self): destructor method
def __del__(self):
if self.canvasBitmap != None:
self.canvasBitmap.Destroy(); self.canvasBitmap = None;
self._finiBrushesAndPens()
# }}}
#
# __init__(self, canvasSize, cellSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf")): initialisation method
def __init__(self, canvasSize, cellSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf")):
def __init__(self, canvasSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf"), fontSize=8):
if haveGuiCanvasWxBackendFast:
GuiCanvasWxBackendFast.init(wx)
self._brushes, self._font, self._lastBrush, self._lastPen, self._pens = None, None, None, None, None
self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName = None, None, fontName, fontPathName
self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName, self.fontSize = None, None, fontName, fontPathName, fontSize
if platform.system() == "Windows":
from ctypes import WinDLL
WinDLL("gdi32.dll").AddFontResourceW(self.fontPathName.encode("utf16"))
self._initBrushesAndPens(); self.resize(canvasSize, cellSize);
self._initBrushesAndPens(); self.resize(canvasSize);
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -0,0 +1,577 @@
/*
* GuiCanvasWxBackendFast.cpp
* Copyright (c) 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
*/
#ifdef _MSC_VER
#pragma warning(disable : 4514)
#pragma warning(disable : 4530)
#pragma warning(disable : 4577)
#pragma warning(disable : 4706)
#pragma warning(disable : 4710)
#pragma warning(disable : 4711)
#pragma warning(disable : 4820)
#pragma warning(disable : 5045)
#endif /* _MSC_VER_ */
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <chrono>
#include <cmath>
#include <map>
#include <vector>
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
/*
* Private types
*/
typedef uint32_t COLOUR;
#define COLOUR_ALPHA(colour) (((colour) >> 24) & 0xff)
typedef uint64_t COORD;
typedef struct point_s {
COORD x, y;
} POINT;
#define POINT_EMPTY {0ULL, 0ULL}
typedef enum cell_attrs_e {
CATTR_NONE = 0x00,
CATTR_BOLD = 0x01,
CATTR_UNDERLINE = 0x02,
} CELL_ATTRS;
typedef struct cell_s {
CELL_ATTRS attrs;
COLOUR bg, fg;
POINT p;
wchar_t txt[8];
} CELL;
#define CELL_EMPTY {CATTR_NONE, 0UL, 0UL, POINT_EMPTY, {L'\0',}}
typedef struct rect_s {
POINT p0, p1;
} RECT;
#define RECT_EMPTY {POINT_EMPTY, POINT_EMPTY}
#define RECT_HEIGHT(r) ((r).p1.y - (r).p0.y)
#define RECT_WIDTH(r) ((r).p1.x - (r).p0.x)
typedef struct size_s {
uint64_t h, w;
} SIZE;
#define SIZE_EMPTY {0ULL, 0ULL}
typedef std::vector<std::vector<uint8_t>> COLOUR_LIST;
typedef std::vector<std::vector<COLOUR>> CHAR_MAP_ITEM;
typedef std::map<wchar_t, CHAR_MAP_ITEM> CHAR_MAP;
/*
* Private constants and variables
*/
#define BITMAP_BPS 24
#define BITMAP_BPS_BYTES 3
#define BLEND_ALPHA_COEFFICIENT 0.8
static PyObject *s_bitmap = NULL, *s_dc = NULL, *s_dc_tmp = NULL, *s_font = NULL, *s_wx = NULL, *s_wx_NullBitmap = NULL;
static uint8_t *s_bitmap_buffer = NULL;
static size_t s_bitmap_buffer_size = 0;
static SIZE s_bitmap_size = SIZE_EMPTY, s_cell_size = SIZE_EMPTY;
static CHAR_MAP s_char_map;
static PyObject *s_colour_black = NULL, *s_colour_white = NULL;
static PyObject *s_error = NULL;
static COLOUR_LIST s_colours = {
{255, 255, 255}, // Bright White
{0, 0, 0}, // Black
{0, 0, 187}, // Light Blue
{0, 187, 0}, // Green
{255, 85, 85}, // Red
{187, 0, 0}, // Light Red
{187, 0, 187}, // Pink
{187, 187, 0}, // Yellow
{255, 255, 85}, // Light Yellow
{85, 255, 85}, // Light Green
{0, 187, 187}, // Cyan
{85, 255, 255}, // Light Cyan
{85, 85, 255}, // Blue
{255, 85, 255}, // Light Pink
{85, 85, 85}, // Grey
{187, 187, 187}, // Light Grey
};
static COLOUR_LIST s_colours_bold = {
{255, 255, 255}, // Bright White
{85, 85, 85}, // Black
{85, 85, 255}, // Light Blue
{85, 255, 85}, // Green
{255, 85, 85}, // Red
{255, 85, 85}, // Light Red
{255, 85, 255}, // Pink
{255, 255, 85}, // Yellow
{255, 255, 85}, // Light Yellow
{85, 255, 85}, // Light Green
{85, 255, 255}, // Cyan
{85, 255, 255}, // Light Cyan
{85, 85, 255}, // Blue
{255, 85, 255}, // Light Pink
{85, 85, 85}, // Grey
{255, 255, 255}, // Light Grey
};
/*
* Private preprocessor macros
*/
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define PYTHON_TRY(expr, msg) \
[&](){bool rc = (bool)(expr); if (!rc) { PyErr_SetString(s_error, msg); }; return rc;}()
#define PYTHON_TRY_NOMEMORY(expr, msg) \
[&](){bool rc = (bool)(expr); if (!rc) { PyErr_SetString(PyExc_MemoryError, msg); }; return rc;}()
/*
* N.B. required due to absence of Python_CallMethodV()
*/
#define COMMA ,
#define PYTHON_WRAP_METHOD(fn, fmt, args1, args2) \
static bool \
python_##fn(PyObject *obj, const char *default_error, PyObject **presult, args1) \
{ \
bool rc = true; \
PyObject *result; \
\
if ((result = PyObject_CallMethod(obj, #fn, fmt, args2))) { \
if (!presult) { \
Py_XDECREF(result); \
} else { \
*presult = result; \
} \
} else { \
rc = false; \
setErrorFromLast(default_error ? default_error \
: "Failed to call " # fn "()"); \
} \
return rc; \
}
#define PYTHON_WRAP_METHOD0(fn) \
static bool \
python_##fn(PyObject *obj, const char *default_error, PyObject **presult) \
{ \
bool rc = true; \
PyObject *result; \
\
if ((result = PyObject_CallMethod(obj, #fn, ""))) { \
if (!presult) { \
Py_XDECREF(result); \
} else { \
*presult = result; \
} \
} else { \
rc = false; \
setErrorFromLast(default_error ? default_error \
: "Failed to call " # fn "()"); \
} \
return rc; \
}
/*
* Static private subroutine prototypes
*/
static COLOUR blendColours(COLOUR bg, COLOUR fg);
static void cellDraw(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, RECT *prect);
static bool cellDrawPixel(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, COLOUR colour, RECT *prect, COORD rx, COORD ry);
static bool cellDrawText(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, CHAR_MAP& char_map, RECT *prect);
static bool cellFetch(const COLOUR_LIST& colours, const COLOUR_LIST& colours_bold, PyObject *object, bool fromCanvas, POINT canvasPoint, CELL *pcell);
static void setErrorFromLast(const char *default_fmt, ...);
#ifdef TIMING
static std::chrono::system_clock::time_point timeBegin();
static double timeDelta(std::chrono::system_clock::time_point t0);
#endif /* TIMING */
static bool updateCharMap(SIZE cell_size, CHAR_MAP& char_map, wchar_t wch);
PYTHON_WRAP_METHOD(Bitmap, "lll", unsigned long long width COMMA unsigned long long height COMMA unsigned long long bits, width COMMA height COMMA bits);
PYTHON_WRAP_METHOD(Blit, "OllllOll", PyObject *self COMMA unsigned long long xdest COMMA unsigned long long ydest COMMA unsigned long long width COMMA unsigned long long height COMMA PyObject *source COMMA unsigned long long xsrc COMMA unsigned long long ysrc, self COMMA xdest COMMA ydest COMMA width COMMA height COMMA source COMMA xsrc COMMA ysrc);
PYTHON_WRAP_METHOD(Colour, "lll", unsigned long long red COMMA unsigned long long green COMMA unsigned long long blue, red COMMA green COMMA blue);
PYTHON_WRAP_METHOD(CopyFromBuffer, "Ol", PyObject *data COMMA unsigned long long format, data COMMA format);
PYTHON_WRAP_METHOD(CopyToBuffer, "Ol", PyObject *data COMMA unsigned long long format, data COMMA format);
PYTHON_WRAP_METHOD(DrawText, "u#ll", wchar_t *text COMMA size_t text_size COMMA unsigned long long x COMMA unsigned long long y, text COMMA text_size COMMA x COMMA y);
PYTHON_WRAP_METHOD0(MemoryDC);
PYTHON_WRAP_METHOD(SelectObject, "O", PyObject *bitmap, bitmap);
PYTHON_WRAP_METHOD(SetFont, "O", PyObject *font, font);
PYTHON_WRAP_METHOD(SetTextBackground, "O", PyObject *colour, colour);
PYTHON_WRAP_METHOD(SetTextForeground, "O", PyObject *colour, colour);
/*
* Static private subroutines
*/
static COLOUR
blendColours(COLOUR bg, COLOUR fg)
{
return (COLOUR)
(std::llround(((fg & 0xff) * BLEND_ALPHA_COEFFICIENT) + ((bg & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) |
(std::llround((((fg >> 8) & 0xff) * BLEND_ALPHA_COEFFICIENT) + (((bg >> 8) & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) << 8) |
(std::llround((((fg >> 16) & 0xff) * BLEND_ALPHA_COEFFICIENT) + (((bg >> 16) & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) << 16));
}
static void
cellDraw(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, RECT *prect)
{
for (COORD ry = 0; ry < cell_size.h; ry++) {
for (COORD rx = 0; rx < cell_size.w; rx++)
cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.bg, prect, rx, ry);
}
if (cell.attrs & CATTR_UNDERLINE) {
for (COORD rx = 0, ry = (cell_size.h - 1); rx < cell_size.w; rx++)
cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.fg, prect, rx, ry);
}
}
static bool
cellDrawPixel(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, COLOUR colour, RECT *prect, COORD rx, COORD ry)
{
COORD offset, x_, y_;
bool rc = false;
x_ = (cell.p.x * cell_size.w) + rx, y_ = (cell.p.y * cell_size.h) + ry;
offset = ((y_ * bitmap_size.w) + x_) * bitmap_bps_bytes;
if ((x_ < bitmap_size.w) && (y_ < bitmap_size.h)) {
prect->p0.x = MIN(prect->p0.x > 0 ? prect->p0.x : x_, x_);
prect->p0.y = MIN(prect->p0.y > 0 ? prect->p0.y : y_, y_);
prect->p1.x = MAX(prect->p1.x, x_+ 1); prect->p1.y = MAX(prect->p1.y, y_+ 1);
bitmap_buffer[offset] = colour & 0xff;
bitmap_buffer[offset + 1] = (colour >> 8) & 0xff;
bitmap_buffer[offset + 2] = (colour >> 16) & 0xff;
rc = true;
}
return rc;
}
static bool
cellDrawText(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, CHAR_MAP& char_map, RECT *prect)
{
CHAR_MAP::iterator char_map_item;
COLOUR colour;
bool rc = true;
for (size_t nch = 0; (nch < (sizeof(cell.txt) / sizeof(cell.txt[0]))) && (cell.txt[nch]); nch++) {
if ((char_map_item = char_map.find(cell.txt[nch])) == char_map.end()) {
if (updateCharMap(cell_size, char_map, cell.txt[nch]))
char_map_item = char_map.find(cell.txt[nch]);
else {
rc = false; break;
}
}
for (COORD ry = 0; ry < cell_size.h; ry++) {
for (COORD rx = 0; rx < cell_size.w; rx++) {
if ((char_map_item != char_map.end())
&& (char_map_item->second[ry][rx] != (COLOUR)0x0L))
colour = cell.fg;
else
colour = cell.bg;
cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, colour, prect, rx, ry);
}
}
}
if (cell.attrs & CATTR_UNDERLINE) {
for (COORD rx = 0, ry = (cell_size.h - 1); rx < cell_size.w; rx++)
cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.fg, prect, rx, ry);
}
return rc;
}
static bool
cellFetch(const COLOUR_LIST& colours, const COLOUR_LIST& colours_bold, PyObject *object, bool fromCanvas, POINT canvasPoint, CELL *pcell)
{
long bg, fg;
PyObject *canvasMapRow, *cellObject = NULL, *txt;
Py_ssize_t offset, txt_len;
const COLOUR_LIST *pcolours;
bool rc = false;
if (fromCanvas) {
offset = -2;
if ((canvasMapRow = PyList_GetItem(object, (Py_ssize_t)canvasPoint.y)))
cellObject = PyList_GetItem(canvasMapRow, (Py_ssize_t)canvasPoint.x);
} else
cellObject = object, offset = 0;
if (cellObject && PyList_Check(cellObject) && (PyList_Size(cellObject) == 6 + offset)) {
if (!fromCanvas) {
pcell->p.x = PyLong_AsUnsignedLongLong(PyList_GetItem(cellObject, 0));
pcell->p.y = PyLong_AsUnsignedLongLong(PyList_GetItem(cellObject, 1));
}
fg = PyLong_AsLong(PyList_GetItem(cellObject, 2 + offset));
bg = PyLong_AsLong(PyList_GetItem(cellObject, 3 + offset));
pcell->attrs = (CELL_ATTRS)PyLong_AsUnsignedLong(PyList_GetItem(cellObject, 4 + offset));
if (pcell->attrs & CATTR_BOLD)
pcolours = &colours_bold;
else
pcolours = &colours;
pcell->bg = (bg == -1) ? 0xff000000 : (COLOUR)((colours[(uint8_t)bg][0]) | ((colours[(uint8_t)bg][1] << 8) & 0xff00) | ((colours[(uint8_t)bg][2] << 16) & 0xff0000));
pcell->fg = (fg == -1) ? pcell->bg : (COLOUR)(((*pcolours)[(uint8_t)fg][0]) | (((*pcolours)[(uint8_t)fg][1] << 8) & 0xff00) | (((*pcolours)[(uint8_t)fg][2] << 16) & 0xff0000));
txt = PyList_GetItem(cellObject, 5 + offset);
if ((txt_len = PyUnicode_AsWideChar(txt, pcell->txt, sizeof(pcell->txt) / sizeof(pcell->txt[0]))) > 0) {
if (txt_len < (sizeof(pcell->txt) / sizeof(pcell->txt[0])))
pcell->txt[txt_len] = L'\0';
rc = true;
}
}
return rc;
}
static void
setErrorFromLast(const char *default_fmt, ...)
{
va_list ap;
static char default_buf[1024];
PyObject *exc_traceback, *exc_type, *exc_value = NULL;
PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
if (exc_value)
PyErr_SetObject(s_error, exc_value);
else {
va_start(ap, default_fmt);
vsnprintf_s(default_buf, sizeof(default_buf), default_fmt, ap);
va_end(ap);
PyErr_SetString(s_error, default_buf);
}
}
#ifdef TIMING
static std::chrono::system_clock::time_point
timeBegin()
{
return std::chrono::system_clock::now();
}
static double
timeDelta(std::chrono::system_clock::time_point t0)
{
return ((std::chrono::duration<double>)(std::chrono::system_clock::now() - t0)).count();
}
#endif /* TIMING */
static bool
updateCharMap(SIZE cell_size, CHAR_MAP& char_map, wchar_t wch)
{
PyObject *bitmap, *mv = NULL;
Py_buffer buffer;
uint8_t *char_buffer = NULL;
size_t char_buffer_size;
bool rc = false;
PyBuffer_FillInfo(&buffer, 0, NULL, 0, false, PyBUF_WRITABLE);
char_buffer_size = s_cell_size.w * s_cell_size.h * BITMAP_BPS_BYTES;
if (python_Bitmap(s_wx, NULL, &bitmap, s_cell_size.w, s_cell_size.h, BITMAP_BPS)
&& python_SelectObject(s_dc_tmp, NULL, NULL, bitmap)
&& python_SetFont(s_dc_tmp, NULL, NULL, s_font)
&& python_SetTextBackground(s_dc_tmp, NULL, NULL, s_colour_black)
&& python_SetTextForeground(s_dc_tmp, NULL, NULL, s_colour_white)
&& python_DrawText(s_dc_tmp, NULL, NULL, &wch, 1, 0, 0)
&& PYTHON_TRY_NOMEMORY((char_buffer = (uint8_t *)malloc(char_buffer_size)), "Failed to allocate character bitmap buffer")
&& PYTHON_TRY(PyBuffer_FillInfo(&buffer, 0, char_buffer, (Py_ssize_t)char_buffer_size, false, PyBUF_WRITABLE) == 0, "Failed to create Py_buffer")
&& PYTHON_TRY((mv = PyMemoryView_FromBuffer(&buffer)), "Failed to create Py_buffer memory view")
&& python_CopyToBuffer(bitmap, NULL, NULL, mv, 0)) {
char_map[wch] = CHAR_MAP_ITEM(cell_size.h);
for (COORD ry = 0; ry < cell_size.h; ry++) {
for (COORD rx = 0; rx < cell_size.w; rx++)
char_map[wch][ry].push_back(
(((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES)]) & 0xff) |
(((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES) + 1] << 8) & 0xff00) |
(((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES) + 2] << 16) & 0xff0000));
}
rc = true;
}
if (s_dc_tmp)
python_SelectObject(s_dc_tmp, NULL, NULL, s_wx_NullBitmap);
Py_XDECREF(bitmap);
if (char_buffer) {
free(char_buffer);
}
Py_XDECREF(mv); PyBuffer_Release(&buffer);
return rc;
}
/*
* Private Python module subroutine prototypes
*/
static PyObject *GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args);
static PyObject *GuiCanvasWxBackendFast_init(PyObject *self, PyObject *args);
static PyObject *GuiCanvasWxBackendFast_resize(PyObject *self, PyObject *args);
/*
* Private Python module subroutines
*/
static PyObject *
GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args)
{
PyObject *bitmap, *canvas_map, *canvas_size_obj, *eventDc, *patches;
Py_buffer buffer;
SIZE canvas_size;
CELL cell, cell_canvas;
bool isCursor, skip, status = true;
PyObject *iter, *iter_cur, *mv = NULL, *rc = NULL;
RECT rect = RECT_EMPTY;
(void)self;
PyBuffer_FillInfo(&buffer, 0, NULL, 0, false, PyBUF_WRITABLE);
#ifdef TIMING
auto t0 = timeBegin();
#endif /* TIMING */
if (PYTHON_TRY(PyArg_ParseTuple(args, "OOOOpO", &bitmap, &canvas_map, &canvas_size_obj, &eventDc, &isCursor, &patches), "Invalid arguments")
&& PYTHON_TRY((iter = PyObject_GetIter(patches)), "Failed to get patches iterator object")) {
canvas_size.w = PyLong_AsUnsignedLong(PyList_GetItem(canvas_size_obj, 0));
canvas_size.h = PyLong_AsUnsignedLong(PyList_GetItem(canvas_size_obj, 1));
while (iter_cur = PyIter_Next(iter)) {
skip = false, status = true;
if (PYTHON_TRY(cellFetch(s_colours, s_colours_bold, iter_cur, false, POINT_EMPTY, &cell), "Failed to get patch cell")) {
if (isCursor) {
if (!(skip = !cellFetch(s_colours, s_colours_bold, canvas_map, true, cell.p, &cell_canvas))) {
cell.attrs = cell_canvas.attrs;
if (COLOUR_ALPHA(cell.bg) != 0xff) {
cell.fg = blendColours(cell_canvas.fg, cell.bg); cell.bg = blendColours(cell_canvas.bg, cell.bg);
if ((cell_canvas.txt[0] == L' ') && (COLOUR_ALPHA(cell_canvas.bg) == 0xff))
cell.txt[0] = L'\u2591', cell.txt[1] = L'\0';
else
memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt));
} else if (COLOUR_ALPHA(cell_canvas.bg) == 0xff) {
cell.fg = cell_canvas.fg, cell.bg = cell_canvas.bg;
if (cell_canvas.txt[0] == L' ')
cell.txt[0] = L'\u2591', cell.txt[1] = L'\0';
else
memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt));
} else {
cell.fg = cell_canvas.fg, cell.bg = cell_canvas.bg;
memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt));
}
}
} else if ((cell.txt[0] == L' ') && (COLOUR_ALPHA(cell.bg) == 0xff))
cell.bg = 0x00000000, cell.txt[0] = L'\u2591', cell.txt[1] = L'\0';
if (status && !skip) {
if (cell.txt[0] != L' ')
status = cellDrawText(BITMAP_BPS_BYTES, s_bitmap_buffer, s_bitmap_size, cell, s_cell_size, s_char_map, &rect);
else
cellDraw(BITMAP_BPS_BYTES, s_bitmap_buffer, s_bitmap_size, cell, s_cell_size, &rect);
}
}
Py_XDECREF(iter_cur);
}
Py_XDECREF(iter);
if (status
&& PYTHON_TRY(PyBuffer_FillInfo(&buffer, 0, s_bitmap_buffer, (Py_ssize_t)s_bitmap_buffer_size, false, PyBUF_WRITABLE) == 0, "Failed to create Py_buffer")
&& PYTHON_TRY((mv = PyMemoryView_FromBuffer(&buffer)), "Failed to create Py_buffer memory view")
&& python_CopyFromBuffer(s_bitmap, NULL, NULL, mv, 0)
&& python_Blit((PyObject *)eventDc->ob_type, NULL, NULL, eventDc, rect.p0.x, rect.p0.y, RECT_WIDTH(rect), RECT_HEIGHT(rect), s_dc, rect.p0.x, rect.p0.y)) {
Py_INCREF(Py_True), rc = Py_True;
}
}
#ifdef TIMING
printf("drawing took %.2f ms\n", timeDelta(t0) * 1000);
#endif /* TIMING */
Py_XDECREF(mv); PyBuffer_Release(&buffer);
return rc;
}
static PyObject *
GuiCanvasWxBackendFast_init(PyObject *self, PyObject *args)
{
PyObject *colour_black = NULL, *colour_white = NULL, *dc = NULL, *dc_tmp = NULL, *wx = NULL, *wx_NullBitmap = NULL;
PyObject *rc = NULL, *wx_dict;
(void)self;
if (PYTHON_TRY(PyArg_ParseTuple(args, "O", &wx), "Invalid arguments")
&& PYTHON_TRY((wx_dict = PyModule_GetDict(wx)), "Failed to get wx module dictionary")
&& python_Colour(wx, NULL, &colour_black, 0, 0, 0)
&& python_Colour(wx, NULL, &colour_white, 255, 255, 255)
&& python_MemoryDC(wx, NULL, &dc)
&& python_MemoryDC(wx, NULL, &dc_tmp)
&& PYTHON_TRY((wx_NullBitmap = PyObject_GetAttrString(wx, "NullBitmap")), "Failed to get wx.NullBitmap attribute")) {
s_colour_black = colour_black, s_colour_white = colour_white, s_dc = dc, s_dc_tmp = dc_tmp;
s_wx = wx; Py_INCREF(wx_NullBitmap), s_wx_NullBitmap = wx_NullBitmap;
Py_INCREF(Py_True), rc = Py_True;
}
if (!rc) {
Py_XDECREF(colour_black); Py_XDECREF(colour_white); Py_XDECREF(dc); Py_XDECREF(dc_tmp); Py_XDECREF(wx_NullBitmap);
}
return rc;
}
static PyObject *
GuiCanvasWxBackendFast_resize(PyObject *self, PyObject *args)
{
uint8_t *bitmap_buffer_new = NULL;
size_t bitmap_buffer_size_new;
PyObject *bitmap_new = NULL;
SIZE bitmap_size_new, cell_size_new;
PyObject *cellSize, *cellSizeHeightObj, *cellSizeWidthObj, *font, *winSize, *winSizeHeightObj, *winSizeWidthObj, *rc = NULL;
(void)self;
if (PYTHON_TRY(PyArg_ParseTuple(args, "OOO", &cellSize, &font, &winSize) && PyTuple_Check(cellSize) && PyTuple_Check(winSize), "Invalid arguments")) {
cellSizeWidthObj = PyTuple_GetItem(cellSize, 0); cellSizeHeightObj = PyTuple_GetItem(cellSize, 1);
cell_size_new.w = PyLong_AsUnsignedLong(cellSizeWidthObj); cell_size_new.h = PyLong_AsUnsignedLong(cellSizeHeightObj);
winSizeWidthObj = PyTuple_GetItem(winSize, 0); bitmap_size_new.w = PyLong_AsUnsignedLong(winSizeWidthObj);
winSizeHeightObj = PyTuple_GetItem(winSize, 1); bitmap_size_new.h = PyLong_AsUnsignedLong(winSizeHeightObj);
bitmap_buffer_size_new = bitmap_size_new.h * bitmap_size_new.w * BITMAP_BPS_BYTES;
if (python_Bitmap(s_wx, NULL, &bitmap_new, bitmap_size_new.w, bitmap_size_new.h, BITMAP_BPS)
&& (s_bitmap ? python_SelectObject(s_dc, NULL, NULL, s_wx_NullBitmap) : true)
&& python_SelectObject(s_dc, NULL, NULL, bitmap_new)
&& PYTHON_TRY_NOMEMORY((bitmap_buffer_new = (uint8_t *)malloc(bitmap_buffer_size_new)), "Failed to allocate bitmap buffer")) {
if (s_bitmap_buffer)
free(s_bitmap_buffer);
s_bitmap_buffer = bitmap_buffer_new;
Py_XDECREF(s_bitmap); s_bitmap = bitmap_new;
s_bitmap_buffer_size = bitmap_buffer_size_new, s_bitmap_size = bitmap_size_new;
if ((cell_size_new.h != s_cell_size.h) || (cell_size_new.w != s_cell_size.w))
s_char_map.clear();
s_cell_size = cell_size_new; Py_INCREF(font), s_font = font; Py_INCREF(Py_True), rc = Py_True;
}
}
if (!rc) {
if (bitmap_buffer_new)
free(bitmap_buffer_new);
Py_XDECREF(bitmap_new); Py_XDECREF(font);
}
return rc;
}
/*
* Python C/C++ extension footer
*/
static PyMethodDef
GuiCanvasWxBackendFast_methods[] = {
{"drawPatches", GuiCanvasWxBackendFast_drawPatches, METH_VARARGS, "drawPatches"},
{"init", GuiCanvasWxBackendFast_init, METH_VARARGS, "init"},
{"resize", GuiCanvasWxBackendFast_resize, METH_VARARGS, "resize"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef
GuiCanvasWxBackendFastmodule = {
PyModuleDef_HEAD_INIT, "GuiCanvasWxBackendFast", NULL, -1, GuiCanvasWxBackendFast_methods,
};
PyMODINIT_FUNC
PyInit_GuiCanvasWxBackendFast(void)
{
PyObject *m = NULL;
m = PyModule_Create(&GuiCanvasWxBackendFastmodule);
s_error = PyErr_NewException("GuiCanvasWxBackendFast.error", NULL, NULL);
Py_XINCREF(s_error);
if (PyModule_AddObject(m, "error", s_error) < 0) {
Py_XDECREF(s_error); Py_CLEAR(s_error); Py_DECREF(m); m = NULL;
}
return m;
}

View File

@ -4,11 +4,10 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
import os, sys, wx
import os, sys, wx, wx.lib.agw.aui
#
# Decorators
# {{{ GuiCommandDecorator(targetObject)
def GuiCommandDecorator(caption, label, icon, accel, initialState):
def GuiCommandDecoratorOuter(targetObject):
if callable(targetObject):
@ -17,8 +16,7 @@ def GuiCommandDecorator(caption, label, icon, accel, initialState):
targetObject.attrDict = {"caption": caption, "label": label, "icon": icon, "accel": accel, "initialState": initialState, "id": None}
return targetObject
return GuiCommandDecoratorOuter
# }}}
# {{{ GuiCommandListDecorator(targetObject)
def GuiCommandListDecorator(idx, caption, label, icon, accel, initialState):
def GuiCommandListDecoratorOuter(targetObject):
if callable(targetObject):
@ -27,8 +25,7 @@ def GuiCommandListDecorator(idx, caption, label, icon, accel, initialState):
targetObject.attrList.insert(0, {"caption": caption, "label": label, "icon": icon, "accel": accel, "initialState": initialState, "id": None, "idx": idx})
return targetObject
return GuiCommandListDecoratorOuter
# }}}
# {{{ GuiSelectDecorator(targetObject)
def GuiSelectDecorator(idx, caption, label, icon, accel, initialState):
def GuiSelectDecoratorOuter(targetObject):
if callable(targetObject):
@ -38,8 +35,7 @@ def GuiSelectDecorator(idx, caption, label, icon, accel, initialState):
targetObject.attrList.insert(0, {"caption": caption, "label": label, "icon": icon, "accel": accel, "initialState": initialState, "id": None, "idx": idx})
return targetObject
return GuiSelectDecoratorOuter
# }}}
# {{{ GuiSubMenuDecorator(targetObject)
def GuiSubMenuDecorator(caption, label, icon, accel, initialState):
def GuiSubMenuDecoratorOuter(targetObject):
if callable(targetObject):
@ -49,7 +45,18 @@ def GuiSubMenuDecorator(caption, label, icon, accel, initialState):
targetObject.attrDict = {"caption": caption, "label": label, "icon": icon, "accel": accel, "initialState": initialState, "id": None, "menu":None}
return targetObject
return GuiSubMenuDecoratorOuter
# }}}
class GuiToolBarArtProvider(wx.lib.agw.aui.AuiDefaultToolBarArt):
def DrawBackground(self, dc, wnd, _rect, horizontal=True):
dc.SetBrush(wx.Brush(wx.Colour(240, 240, 240, 0), wx.BRUSHSTYLE_SOLID)); dc.SetPen(wx.Pen(wx.Colour(240, 240, 240, 0), 1));
dc.DrawRectangle(*_rect)
dc.SetPen(wx.Pen(wx.Colour(180, 180, 180, 0), 1))
dc.DrawLine(0, _rect[3]-1, _rect[2], _rect[3]-1); dc.DrawLine(_rect[2]-1, 0, _rect[2]-1, _rect[3]-1);
dc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 0), 1))
dc.DrawLine(0, 0, _rect[2]-1, 0); dc.DrawLine(0, 0, 0, _rect[3]-1);
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#
# Non-items (0xf000-0xffff)
@ -57,13 +64,11 @@ NID_MENU_SEP = 0xf000
NID_TOOLBAR_HSEP = 0xf001
class GuiFrame(wx.Frame):
# {{{ _initIcon(self, iconPathName)
def _initIcon(self, iconPathName):
icon = wx.Icon()
icon.CopyFromBitmap(wx.Bitmap(iconPathName, wx.BITMAP_TYPE_ANY))
self.SetIcon(icon)
# }}}
# {{{ _initMenu(self, menuItem, menuWindow)
def _initMenu(self, menuItem, menuWindow):
if menuItem == NID_MENU_SEP:
menuWindow.AppendSeparator()
@ -87,10 +92,8 @@ class GuiFrame(wx.Frame):
menuItemWindow.Check(menuItem.attrDict["initialState"])
else:
menuItemWindow.Enable(menuItem.attrDict["initialState"])
# }}}
# {{{ loadAccels(self, menus, toolBars)
def loadAccels(self, menus, toolBars):
def loadAccels(self, accelsIn, menus, toolBars):
def loadAccels_(accels):
nonlocal accelTableEntries
accels_ = []
@ -110,10 +113,9 @@ class GuiFrame(wx.Frame):
self.itemsById[accel.attrDict["id"]] = accel
self.Bind(wx.EVT_MENU, self.onMenu, id=accel.attrDict["id"])
accelTableEntries = []
[loadAccels_(menu[1:]) for menu in menus]; [loadAccels_(toolBar) for toolBar in toolBars];
[loadAccels_(accel) for accel in accelsIn]; [loadAccels_(menu[1:]) for menu in menus]; [loadAccels_(toolBar) for toolBar in toolBars];
self.SetAcceleratorTable(wx.AcceleratorTable(accelTableEntries))
# }}}
# {{{ loadBitmap(self, basePathName, descr, size=(16, 16))
def loadBitmap(self, basePathName, descr, size=(16, 16)):
if descr == None:
descr = ["", None, wx.ArtProvider.GetBitmap(wx.ART_HELP, wx.ART_TOOLBAR, size)]
@ -126,8 +128,7 @@ class GuiFrame(wx.Frame):
elif len(descr) == 3:
descr = ("", None, descr[2])
return descr
# }}}
# {{{ loadMenus(self, menus)
def loadMenus(self, menus):
self.menuBar = wx.MenuBar()
for menu in menus:
@ -142,11 +143,11 @@ class GuiFrame(wx.Frame):
self._initMenu(menuItem, menuWindow)
self.menuBar.Append(menuWindow, menu[0])
self.SetMenuBar(self.menuBar)
# }}}
# {{{ loadToolBars(self, toolBars)
def loadToolBars(self, toolBars):
for toolBar in toolBars:
self.toolBars.append(wx.ToolBar(self.panelSkin, -1, style=wx.TB_FLAT | wx.HORIZONTAL | wx.TB_NODIVIDER))
self.toolBars.append(wx.lib.agw.aui.AuiToolBar(self, -1))
self.toolBars[-1].SetArtProvider(GuiToolBarArtProvider())
self.toolBars[-1].SetToolBitmapSize((16, 16))
for toolBarItem in toolBar:
if toolBarItem == NID_TOOLBAR_HSEP:
@ -156,58 +157,53 @@ class GuiFrame(wx.Frame):
toolBarItem.attrDict["id"] = wx.NewId()
self.itemsById[toolBarItem.attrDict["id"]] = toolBarItem
if hasattr(toolBarItem, "isSelect"):
toolBarItemWindow = self.toolBars[-1].AddRadioTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["caption"], toolBarItem.attrDict["icon"][2], shortHelp=toolBarItem.attrDict["label"])
toolBarItemWindow = self.toolBars[-1].AddRadioTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["caption"], toolBarItem.attrDict["icon"][2], wx.NullBitmap, short_help_string=toolBarItem.attrDict["caption"])
else:
toolBarItemWindow = self.toolBars[-1].AddTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["caption"], toolBarItem.attrDict["icon"][2], shortHelp=toolBarItem.attrDict["label"])
self.toolBarItemsById[toolBarItem.attrDict["id"]] = toolBarItemWindow
toolBarItemWindow = self.toolBars[-1].AddTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["caption"], toolBarItem.attrDict["icon"][2], wx.NullBitmap, wx.ITEM_NORMAL, short_help_string=toolBarItem.attrDict["caption"])
self.toolBarItemsById[toolBarItem.attrDict["id"]] = (self.toolBars[-1], toolBarItemWindow,)
self.Bind(wx.EVT_TOOL, self.onMenu, toolBarItemWindow)
self.Bind(wx.EVT_TOOL_RCLICKED, self.onMenu, toolBarItemWindow)
if toolBarItem.attrDict["initialState"] != None:
if hasattr(toolBarItem, "isSelect"):
toolBarItemWindow.Toggle(toolBarItem.attrDict["initialState"])
if toolBarItem.attrDict["initialState"]:
self.toolBars[-1].ToggleTool(toolBarItemWindow, True)
else:
toolBarItemWindow.Enable(toolBarItem.attrDict["initialState"])
self.toolBars[-1].EnableTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["initialState"])
self.toolBars[-1].Refresh()
self.toolBarPanes, row = [], 0
for toolBar in self.toolBars:
self.sizerSkin.Add(toolBar, 0, wx.ALIGN_LEFT | wx.ALL, 3)
toolBar.Realize(); toolBar.Fit();
# }}}
# {{{ addWindow(self, window, border=14, expand=False)
def addWindow(self, window, border=14, expand=False):
flags = wx.ALL; flags = flags | wx.EXPAND if expand else flags;
self.sizerSkin.Add(window, 0, flags, border); self.sizerSkin.Fit(self.panelSkin);
# }}}
# {{{ onChar(self, event)
self.toolBarPanes += [wx.lib.agw.aui.AuiPaneInfo().ToolbarPane().CaptionVisible(False).CloseButton(False).Dockable(True).Floatable(True).Gripper(True).Row(row).Top()]
self.auiManager.AddPane(toolBar, self.toolBarPanes[-1]); row += 1;
self.auiManager.Update()
def addWindow(self, window):
self.auiManager.AddPane(window, wx.lib.agw.aui.AuiPaneInfo().CaptionVisible(False).Centre().CloseButton(False).Dockable(False).Floatable(False).Gripper(False))
self.auiManager.Update()
def onChar(self, event):
event.Skip()
# }}}
# {{{ onMenu(self, event)
def onMenu(self, event):
eventId = event.GetId()
if eventId in self.itemsById:
self.itemsById[eventId](event); wx.SafeYield();
self.itemsById[eventId](event)
else:
event.Skip()
# }}}
# {{{ onMouseWheel(self, event)
def onMouseWheel(self, event):
event.Skip()
# }}}
#
# __init__(self, iconPathName, size, parent=None, title=""): initialisation method
def __init__(self, iconPathName, size, parent=None, title=""):
super().__init__(parent, wx.ID_ANY, title, size=size)
self.itemsById, self.menuItemsById, self.toolBarItemsById = {}, {}, {}
self.panelSkin, self.sizerSkin, self.toolBars = wx.Panel(self, wx.ID_ANY), wx.BoxSizer(wx.VERTICAL), []
self.sizerSkin.AddSpacer(5); self.panelSkin.SetSizer(self.sizerSkin); self.panelSkin.SetAutoLayout(1);
self.auiManager = wx.lib.agw.aui.AuiManager(); self.auiManager.SetManagedWindow(self);
self.itemsById, self.menuItemsById, self.toolBarItemsById, self.toolBars = {}, {}, {}, []
self._initIcon(iconPathName); self.statusBar = self.CreateStatusBar();
self.sizerSkin.Fit(self.panelSkin); self.SetFocus(); self.Show(True);
self.SetFocus(); self.Show(True);
for event, f in ((wx.EVT_CHAR, self.onChar), (wx.EVT_MENU, self.onMenu), (wx.EVT_MOUSEWHEEL, self.onMouseWheel)):
self.Bind(event, f)
class GuiMiniFrame(wx.MiniFrame):
#
# __init__(self, parent, size, title, pos=wx.DefaultPosition): initialisation method
def __init__(self, parent, size, title, pos=wx.DefaultPosition):
super().__init__(parent, id=wx.ID_ANY, pos=pos, size=size, title=title)

View File

@ -7,69 +7,57 @@
import wx
class GuiWindow(wx.ScrolledWindow):
# {{{ _updateScrollBars(self)
def _updateScrollBars(self):
if self.size != None:
clientSize = self.GetClientSize()
if (self.scrollStep != None) and (self.size != None):
self.SetScrollRate(*self.scrollStep); clientSize = self.GetClientSize();
if (self.size[0] > clientSize[0]) or (self.size[1] > clientSize[1]):
self.scrollFlag = True; super().SetVirtualSize(self.size);
elif self.scrollFlag \
and ((self.size[0] <= clientSize[0]) or (self.size[1] <= clientSize[1])):
self.scrollFlag = False; super().SetVirtualSize((0, 0));
# }}}
# {{{ onClose(self, event)
def onClose(self, event):
self.Destroy()
# }}}
# {{{ onEnterWindow(self, event)
def onEnterWindow(self, event):
event.Skip()
# }}}
# {{{ onKeyboardInput(self, event)
def onKeyboardInput(self, event):
return False
# }}}
# {{{ onLeaveWindow(self, event)
def onLeaveWindow(self, event):
event.Skip()
# }}}
# {{{ onMouseInput(self, event)
def onMouseInput(self, event):
return False
# }}}
# {{{ onPaint(self, event)
def onPaint(self, event):
event.Skip()
# }}}
# {{{ onScroll(self, event)
def onScroll(self, event):
event.Skip()
# }}}
# {{{ onSize(self, event)
def onSize(self, event):
self._updateScrollBars(); event.Skip();
# }}}
# {{{ resize(self, newSize)
def resize(self, newSize):
self.size = newSize; self._updateScrollBars();
self.SetMinSize(self.size); self.SetSize(wx.DefaultCoord, wx.DefaultCoord, *self.size);
self.SetMinSize(self.parent.GetSize()); self.SetSize(wx.DefaultCoord, wx.DefaultCoord, *self.parent.GetSize())
curWindow = self
while curWindow != None:
curWindow.Layout(); curWindow = curWindow.GetParent();
# }}}
#
# __init__(self, parent, pos, scrollStep, style=0): initialisation method
def __init__(self, parent, pos, scrollStep, style=0):
def __init__(self, parent, pos, style=0):
super().__init__(parent, pos=pos, style=style) if style != 0 else super().__init__(parent, pos=pos)
self.pos, self.scrollFlag, self.scrollStep, self.size = pos, False, scrollStep, None
self.parent = parent
self.pos, self.scrollFlag, self.scrollStep, self.size = pos, False, None, None
for eventType, f in (
(wx.EVT_CHAR, self.onKeyboardInput), (wx.EVT_CLOSE, self.onClose), (wx.EVT_ENTER_WINDOW, self.onEnterWindow),
(wx.EVT_LEAVE_WINDOW, self.onLeaveWindow), (wx.EVT_LEFT_DOWN, self.onMouseInput), (wx.EVT_MOTION, self.onMouseInput),
(wx.EVT_PAINT, self.onPaint), (wx.EVT_RIGHT_DOWN, self.onMouseInput), (wx.EVT_SCROLLWIN_LINEDOWN, self.onScroll),
(wx.EVT_SCROLLWIN_LINEUP, self.onScroll), (wx.EVT_SIZE, self.onSize)):
self.Bind(eventType, f)
self.SetScrollRate(*self.scrollStep); self._updateScrollBars();
self._updateScrollBars()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,12 +5,9 @@
#
class Operator(object):
#
# apply(self, region)
def apply(self, region):
pass
# __init__(self, *args): initialisation method
def __init__(self, *args):
pass

View File

@ -6,16 +6,26 @@
from Operator import Operator
# TODO <https://en.wikipedia.org/wiki/Box_Drawing_(Unicode_block)>
class OperatorFlipHorizontal(Operator):
name = "Flip horizontally"
flipPairs = {
"/":"\\", "":"",
"":"", "":"", "":"", "":"",
"":"", "":"", "":"",
}
#
# apply(self, region)
def apply(self, region):
region.reverse(); return region;
region.reverse()
for numRow in range(len(region)):
for numCol in range(len(region[numRow])):
if region[numRow][numCol][3] in self.flipPairs:
region[numRow][numCol][3] = self.flipPairs[region[numRow][numCol][3]]
return region
# __init__(self, *args): initialisation method
def __init__(self, *args):
pass
for flipPairKey in list(self.flipPairs.keys()):
self.flipPairs[self.flipPairs[flipPairKey]] = flipPairKey
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -6,18 +6,27 @@
from Operator import Operator
# TODO <https://en.wikipedia.org/wiki/Box_Drawing_(Unicode_block)>
class OperatorFlipVertical(Operator):
name = "Flip"
flipPairs = {
"(":")", "/":"\\", "":"", "[":"]", "{":"}", "<":">", "`":"'",
"":"", "":"",
"":"", "":"",
"":"", "":"", "":"",
}
#
# apply(self, region)
def apply(self, region):
for numRow in range(len(region)):
region[numRow].reverse()
for numCol in range(len(region[numRow])):
if region[numRow][numCol][3] in self.flipPairs:
region[numRow][numCol][3] = self.flipPairs[region[numRow][numCol][3]]
return region
# __init__(self, *args): initialisation method
def __init__(self, *args):
pass
for flipPairKey in list(self.flipPairs.keys()):
self.flipPairs[self.flipPairs[flipPairKey]] = flipPairKey
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -9,15 +9,12 @@ from Operator import Operator
class OperatorInvert(Operator):
name = "Invert colours"
#
# apply(self, region)
def apply(self, region):
for numRow in range(len(region)):
for numCol in range(len(region[numRow])):
region[numRow][numCol][0:2] = [(~r & (16 - 1) if r > 0 else r) for r in region[numRow][numCol][0:2]]
return region
# __init__(self, *args): initialisation method
def __init__(self, *args):
pass

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
#
# OperatorRotate.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from Operator import Operator
import math
class OperatorRotate(Operator):
name = "Rotate"
def apply2(self, mapPoint, mousePoint, regionOld, region):
if self.originPoint == None:
self.originPoint = list(mousePoint)
delta = [b - a for a, b in zip(self.originPoint, mousePoint)]
radius = math.sqrt(math.pow(delta[0], 2) + math.pow(delta[1], 2))
if radius >= 10:
regionSize = (len(region[0]), len(region))
theta = math.atan2(-delta[1], delta[0]); cos, sin = math.cos(theta), math.sin(theta);
for numCol in range(regionSize[0]):
for numRow in range(regionSize[1]):
numRow_, numCol_ = (numRow / regionSize[1]) * 2 - 1, (numCol / regionSize[0]) * 2 - 1
b, a = (numCol_ * sin) + (numRow_ * cos), (numCol_ * cos) - (numRow_ * sin)
numRow_, numCol_ = int((b + 1) / 2 * regionSize[1]), int((a + 1) / 2 * regionSize[0])
if (numRow_ < regionSize[1]) and (numCol_ < regionSize[0]):
region[numRow][numCol] = list(regionOld[numRow_][numCol_])
return region
else:
return region
def __init__(self, *args):
self.originPoint = None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
#
# OperatorTile.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from Operator import Operator
import copy
class OperatorTile(Operator):
name = "Tile"
def apply2(self, mapPoint, mousePoint, regionOld, region):
if self.lastPoint == None:
self.lastPoint = list(mapPoint)
if self.tileObject == None:
self.tileObject = copy.deepcopy(region)
delta = [b - a for a, b in zip(self.lastPoint, mapPoint)]
if delta[1] > 0:
for numNewRow in range(delta[1]):
newRow = copy.deepcopy(self.tileObject[len(region) % len(self.tileObject)])
if len(newRow) < len(region[0]):
for numNewCol in range(len(newRow), len(region[0])):
newRow += [list(self.tileObject[len(region) % len(self.tileObject)][numNewCol % len(self.tileObject[len(region) % len(self.tileObject)])])]
region += [newRow]
if delta[0] > 0:
for numRow in range(len(region)):
for numNewCol in range(len(region[numRow]), len(region[numRow]) + delta[0]):
region[numRow] += [list(self.tileObject[numRow % len(self.tileObject)][numNewCol % len(self.tileObject[numRow % len(self.tileObject)])])]
self.lastPoint = list(mapPoint)
return region
def __init__(self, *args):
self.lastPoint, self.tileObject = None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -7,15 +7,10 @@
from Canvas import Canvas
from GuiFrame import GuiMiniFrame
from GuiWindow import GuiWindow
from RtlPlatform import getLocalConfPathName
import json, os, sys, wx
class RoarAssetsWindow(GuiMiniFrame):
# {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect)
def _drawPatch(self, eventDc, isCursor, patch, viewRect):
if not isCursor:
self.backend.drawPatch(eventDc, patch, viewRect)
# }}}
# {{{ _import(self, f, pathName)
def _import(self, f, pathName):
rc = False
self.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
@ -28,22 +23,22 @@ class RoarAssetsWindow(GuiMiniFrame):
rc, error, newMap, newPathName, newSize = False, str(e), None, None, None
self.SetCursor(wx.Cursor(wx.NullCursor))
return rc, error, canvas, newMap, newPathName, newSize
# }}}
# {{{ _importFiles(self, f, wildcard)
def _importFiles(self, f, wildcard):
resultList = []
with wx.FileDialog(self, "Load...", os.getcwd(), "", wildcard, wx.FD_MULTIPLE | wx.FD_OPEN) as dialog:
if self.lastDir != None:
dialog.SetDirectory(self.lastDir)
if dialog.ShowModal() == wx.ID_CANCEL:
resultList += [[False, "(cancelled)", None, None, None, None]]
resultList += [[None, "(cancelled)", None, None, None, None]]
else:
for pathName in dialog.GetPaths():
resultList += [self._import(f, pathName)]
self.lastDir = os.path.dirname(pathName)
lastDir = os.path.dirname(pathName)
if self.lastDir != lastDir:
self.lastDir = lastDir; self._storeLastDir(self.lastDir);
return resultList
# }}}
# {{{ _load_list(self, pathName)
def _load_list(self, pathName):
try:
with open(pathName, "r") as fileObject:
@ -84,8 +79,40 @@ class RoarAssetsWindow(GuiMiniFrame):
self.SetCursor(wx.Cursor(wx.NullCursor))
with wx.MessageDialog(self, "Error: {}".format(str(e)), "", wx.OK | wx.OK_DEFAULT) as dialog:
dialogChoice = dialog.ShowModal()
# }}}
# {{{ _updateScrollBars(self)
def _loadLastDir(self):
localConfFileName = getLocalConfPathName("RecentAssetsDir.txt")
if os.path.exists(localConfFileName):
with open(localConfFileName, "r", encoding="utf-8") as inFile:
self.lastDir = inFile.read().rstrip("\r\n")
def _removeAsset(self, idx):
del self.canvasList[idx]; self.listView.DeleteItem(idx);
itemCount = self.listView.GetItemCount()
if itemCount > 0:
self.listView.Select(self.currentIndex, on=0)
for numCanvas in [n for n in sorted(self.canvasList.keys()) if n >= idx]:
self.canvasList[numCanvas - 1] = self.canvasList[numCanvas]; del self.canvasList[numCanvas];
[self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)]
if (idx == 0) or (idx >= itemCount):
idx = 0 if itemCount > 0 else None
else:
idx = idx if idx < itemCount else None
self.currentIndex = idx
if self.currentIndex != None:
self.listView.Select(self.currentIndex, on=1)
self.drawCanvas(self.canvasList[self.currentIndex][0])
else:
self.currentIndex = None
[self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE_USEHEADER) for col in (0, 1)]
self.drawCanvas(Canvas((0, 0)))
return self.currentIndex
def _storeLastDir(self, pathName):
localConfFileName = getLocalConfPathName("RecentAssetsDir.txt")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
print(pathName, file=outFile)
def _updateScrollBars(self):
clientSize = self.panelCanvas.GetClientSize()
if self.currentIndex != None:
@ -99,27 +126,26 @@ class RoarAssetsWindow(GuiMiniFrame):
elif self.scrollFlag \
and ((panelSize[0] <= clientSize[0]) or (panelSize[1] <= clientSize[1])):
self.scrollFlag = False; super(wx.ScrolledWindow, self.panelCanvas).SetVirtualSize((0, 0));
# }}}
# {{{ drawCanvas(self, canvas)
def drawCanvas(self, canvas):
panelSize = [a * b for a, b in zip(canvas.size, self.cellSize)]
panelSize = [a * b for a, b in zip(canvas.size, self.backend.cellSize)]
self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize);
curWindow = self.panelCanvas
while curWindow != None:
curWindow.Layout(); curWindow = curWindow.GetParent();
self.backend.resize(canvas.size, self.cellSize)
viewRect = self.panelCanvas.GetViewStart();
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
self.backend.resize(canvas.size)
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
# {{{ onPaint(self, event)
patches += [[numCol, numRow, *canvas.map[numRow][numCol]]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
def onPaint(self, event):
self.backend.onPaint(self.panelCanvas.GetClientSize(), self.panelCanvas, self.panelCanvas.GetViewStart())
# }}}
# {{{ onPanelLeftDown(self, event)
def onPanelLeftDown(self, event):
self.panelCanvas.SetFocus()
if (self.currentIndex != None):
@ -129,60 +155,58 @@ class RoarAssetsWindow(GuiMiniFrame):
dropSource.SetData(textDataObject)
result = dropSource.DoDragDrop(True)
event.Skip()
# }}}
# {{{ onPanelPaint(self, event)
def onPanelPaint(self, event):
self.backend.onPaint(self.panelCanvas.GetClientSize(), self.panelCanvas, self.panelCanvas.GetViewStart())
# }}}
# {{{ onPanelSize(self, event)
def onPanelSize(self, event):
self._updateScrollBars(); event.Skip();
# }}}
# {{{ resize(self, canvas, newSize)
def resize(self, canvas, newSize):
oldSize = [0, 0] if canvas.map == None else canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
if canvas.resize(newSize, False):
panelSize = [a * b for a, b in zip(canvas.size, self.cellSize)]
if canvas.resize((1, 1,), newSize, False):
panelSize = [a * b for a, b in zip(canvas.size, self.backend.cellSize)]
self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize);
curWindow = self.panelCanvas
while curWindow != None:
curWindow.Layout(); curWindow = curWindow.GetParent();
self.backend.resize(newSize, self.cellSize)
viewRect = self.panelCanvas.GetViewStart(); eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect);
self.backend.resize(newSize)
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
if deltaSize[0] > 0:
for numRow in range(oldSize[1]):
for numNewCol in range(oldSize[0], newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numRow, 1, 1, 0, " "], viewRect)
patches += [[numNewCol, numRow, 1, 1, 0, " "]]
if deltaSize[1] > 1:
for numNewRow in range(oldSize[1], newSize[1]):
for numNewCol in range(newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect)
# }}}
# {{{ update(self, canvas, newSize, newCanvas=None)
patches += [[numNewCol, numNewRow, 1, 1, 0, " "]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
def update(self, canvas, newSize, newCanvas=None):
self.resize(canvas, newSize);
canvas.update(newSize, newCanvas); viewRect = self.panelCanvas.GetViewStart();
viewRect = self.panelCanvas.GetViewStart();
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
canvas.update(newSize, newCanvas);
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
patches += [[numCol, numRow, *canvas.map[numRow][numCol]]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
# {{{ onImportAnsi(self, event)
def onImportAnsi(self, event):
event.Skip()
# }}}
# {{{ onImportFromClipboard(self, event)
def onImportFromClipboard(self, event):
event.Skip()
# }}}
# {{{ onImportSauce(self, event)
def onImportSauce(self, event):
event.Skip()
# }}}
# {{{ onChar(self, event)
def onChar(self, event):
if (event.GetModifiers() == wx.MOD_NONE) \
and (event.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP)):
@ -190,24 +214,35 @@ class RoarAssetsWindow(GuiMiniFrame):
return wx.PostEvent(self.listView, event)
else:
event.Skip()
# }}}
# {{{ onListViewChar(self, event)
def onClearList(self, event):
while len(self.canvasList):
self._removeAsset(list(self.canvasList.keys())[0])
def onListViewChar(self, event):
index, rc = self.listView.GetFirstSelected(), False
if index != -1:
keyChar, keyModifiers = event.GetKeyCode(), event.GetModifiers()
if (keyChar, keyModifiers) == (wx.WXK_DELETE, wx.MOD_NONE):
self.currentIndex, rc = index, True; self.onRemove(None);
if not rc:
event.Skip()
# }}}
# {{{ onListViewItemSelected(self, event)
keyCode = event.GetKeyCode()
if (event.GetModifiers() == wx.MOD_NONE) \
and (keyCode in (wx.WXK_DOWN, wx.WXK_UP)) \
and (self.currentIndex != None):
self.listView.Select(self.currentIndex, on=0)
id = +1 if keyCode == wx.WXK_DOWN else -1
self.currentIndex = (self.currentIndex + id) % len(self.canvasList)
self.listView.Select(self.currentIndex, on=1)
self.listView.EnsureVisible(self.currentIndex)
else:
index, rc = self.listView.GetFirstSelected(), False
if index != -1:
keyChar, keyModifiers = event.GetKeyCode(), event.GetModifiers()
if (keyChar, keyModifiers) == (wx.WXK_DELETE, wx.MOD_NONE):
self.currentIndex, rc = index, True; self.onRemove(None);
if not rc:
event.Skip()
def onListViewItemSelected(self, event):
self.currentIndex = event.GetItem().GetId()
item = [self.listView.GetItem(self.currentIndex, col).GetText() for col in (0, 1)]
self.drawCanvas(self.canvasList[self.currentIndex][0])
# }}}
# {{{ onListViewRightDown(self, event)
def onListViewRightDown(self, event):
eventPoint = event.GetPosition()
if self.currentIndex == None:
@ -218,15 +253,18 @@ class RoarAssetsWindow(GuiMiniFrame):
self.contextMenuItems[4].Enable(False)
else:
self.contextMenuItems[4].Enable(True)
if len(self.canvasList) == 0:
self.contextMenuItems[7].Enable(False)
else:
self.contextMenuItems[7].Enable(True)
self.PopupMenu(self.contextMenu, eventPoint)
# }}}
# {{{ onLoad(self, event)
def onLoad(self, event):
def importmIRC(canvas, pathName):
rc, error = canvas.importStore.importTextFile(pathName)
return (rc, error, canvas.importStore.outMap, pathName, canvas.importStore.inSize)
for rc, error, canvas, newMap, newPathName, newSize in self._importFiles(importmIRC, "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*"):
if rc:
if rc == True:
self.currentIndex = self.listView.GetItemCount()
self.canvasList[self.currentIndex] = [canvas, newPathName]
self.listView.InsertItem(self.currentIndex, "")
@ -241,43 +279,35 @@ class RoarAssetsWindow(GuiMiniFrame):
self.listView.SetFocus()
[self.listView.SetItem(self.currentIndex, col, label) for col, label in zip((0, 1), (os.path.basename(newPathName), "{}x{}".format(*newSize)))]
[self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)]
else:
elif rc == False:
with wx.MessageDialog(self, "Error: {}".format(error), "", wx.CANCEL | wx.OK | wx.OK_DEFAULT) as dialog:
dialogChoice = dialog.ShowModal()
if dialogChoice == wx.ID_CANCEL:
break
# }}}
# {{{ onLoadList(self, event)
def onLoadList(self, event):
rc = True
with wx.FileDialog(self, "Load from list...", os.getcwd(), "", "List files (*.lst)|*.lst|Text files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog:
if self.lastDir != None:
dialog.SetDirectory(self.lastDir)
if dialog.ShowModal() != wx.ID_CANCEL:
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName);
pathName = dialog.GetPath()
self.lastDir = os.path.dirname(pathName); self._storeLastDir(self.lastDir);
self._load_list(pathName)
# }}}
# {{{ onRemove(self, event)
def onRemove(self, event):
del self.canvasList[self.currentIndex]; self.listView.DeleteItem(self.currentIndex);
itemCount = self.listView.GetItemCount()
if itemCount > 0:
for numCanvas in [n for n in sorted(self.canvasList.keys()) if n >= self.currentIndex]:
self.canvasList[numCanvas - 1] = self.canvasList[numCanvas]; del self.canvasList[numCanvas];
[self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE) for col in (0, 1)]
if (self.currentIndex == 0) or (self.currentIndex >= itemCount):
self.currentIndex = 0 if itemCount > 0 else None
items = [self.listView.GetFirstSelected()]
while True:
item = self.listView.GetNextSelected(items[-1])
if item != -1:
items += [item]
else:
self.currentIndex = self.currentIndex if self.currentIndex < itemCount else None
if self.currentIndex != None:
self.listView.Select(self.currentIndex, on=1)
self.drawCanvas(self.canvasList[self.currentIndex][0])
else:
self.currentIndex = None
[self.listView.SetColumnWidth(col, wx.LIST_AUTOSIZE_USEHEADER) for col in (0, 1)]
self.drawCanvas(Canvas((0, 0)))
# }}}
# {{{ onSaveList(self, event)
break
while len(items):
self._removeAsset(items[0]); del items[0]; items = [i - 1 for i in items];
if self.currentIndex != None:
self.listView.EnsureVisible(self.currentIndex)
def onSaveList(self, event):
rc = True
if len(self.canvasList):
@ -285,7 +315,8 @@ class RoarAssetsWindow(GuiMiniFrame):
if self.lastDir != None:
dialog.SetDirectory(self.lastDir)
if dialog.ShowModal() != wx.ID_CANCEL:
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName);
pathName = dialog.GetPath()
self.lastDir = os.path.dirname(pathName); self._storeLastDir(self.lastDir);
with open(pathName, "w") as fileObject:
for pathName in [self.canvasList[k][1] for k in self.canvasList.keys()]:
print(pathName, file=fileObject)
@ -295,17 +326,15 @@ class RoarAssetsWindow(GuiMiniFrame):
if not rc:
with wx.MessageDialog(self, "Error: {}".format(error), "", wx.OK | wx.OK_DEFAULT) as dialog:
dialogChoice = dialog.ShowModal()
# }}}
#
# __init__(self, backend, cellSize, parent, pos=None, size=(400, 400), title="Assets"): initialisation method
def __init__(self, backend, cellSize, parent, pos=None, size=(400, 400), title="Assets"):
def __init__(self, backend, parent, pos=None, size=(400, 400), title="Assets"):
if pos == None:
parentRect = parent.GetScreenRect(); pos = (parentRect.x + parentRect.width, parentRect.y);
super().__init__(parent, size, title, pos=pos)
self.backend, self.canvasList, self.lastDir = backend((0, 0), cellSize), {}, None
self.cellSize, self.currentIndex, self.leftDown, self.parent, self.scrollFlag = cellSize, None, False, parent, False
self.backend, self.canvasList, self.lastDir = backend((0, 0)), {}, None
self.currentIndex, self.leftDown, self.parent, self.scrollFlag = None, False, parent, False
self.Bind(wx.EVT_CHAR, self.onChar)
self._loadLastDir()
self.contextMenu, self.contextMenuItems = wx.Menu(), []
for text, f in (
@ -316,7 +345,9 @@ class RoarAssetsWindow(GuiMiniFrame):
("&Remove", self.onRemove),
(None, None),
("Load from l&ist...", self.onLoadList),
("Sa&ve as list...", self.onSaveList),):
("Sa&ve as list...", self.onSaveList),
(None, None),
("Cl&ear list", self.onClearList),):
if (text, f) == (None, None):
self.contextMenu.AppendSeparator()
else:
@ -330,7 +361,8 @@ class RoarAssetsWindow(GuiMiniFrame):
self.listView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onListViewItemSelected)
self.listView.Bind(wx.EVT_RIGHT_DOWN, self.onListViewRightDown)
self.panelCanvas = GuiWindow(self, (0, 0), cellSize, wx.BORDER_SUNKEN)
self.panelCanvas = GuiWindow(self, (0, 0), wx.BORDER_SUNKEN)
self.panelCanvas.Bind(wx.EVT_CHAR, self.onChar)
self.panelCanvas.Bind(wx.EVT_LEFT_DOWN, self.onPanelLeftDown)
self.panelCanvas.Bind(wx.EVT_PAINT, self.onPanelPaint)
self.panelCanvas.Bind(wx.EVT_SIZE, self.onPanelSize)

View File

@ -5,41 +5,101 @@
#
from GuiCanvasColours import Colours
from GuiFrame import NID_TOOLBAR_HSEP
from GuiFrame import NID_MENU_SEP, NID_TOOLBAR_HSEP
from RoarCanvasCommandsEdit import RoarCanvasCommandsEdit
from RoarCanvasCommandsFile import RoarCanvasCommandsFile
from RoarCanvasCommandsHelp import RoarCanvasCommandsHelp
from RoarCanvasCommandsOperators import RoarCanvasCommandsOperators
from RoarCanvasCommandsTools import RoarCanvasCommandsTools
from ToolObject import ToolObject
import os, wx
class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCanvasCommandsTools, RoarCanvasCommandsOperators, RoarCanvasCommandsHelp):
# {{{ _initColourBitmaps(self)
def _initColourBitmaps(self):
for numColour in range(len(RoarCanvasCommandsEdit.canvasColour.attrList)):
if numColour < len(Colours):
toolBitmapColour = Colours[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)
RoarCanvasCommandsEdit.canvasColour.attrList[numColour]["icon"] = ["", None, toolBitmap]
toolBitmapColours = ((0, 0, 0, 255), (255, 255, 255, 255))
toolBitmap = wx.Bitmap((16, 16))
toolBitmapDc = wx.MemoryDC(); toolBitmapDc.SelectObject(toolBitmap);
toolBitmapBrush = [wx.Brush(wx.Colour(c), wx.BRUSHSTYLE_SOLID) for c in toolBitmapColours]
toolBitmapDc.SetBrush(toolBitmapBrush[1])
toolBitmapDc.SetBackground(toolBitmapBrush[1])
toolBitmapDc.SetPen(wx.Pen(wx.Colour(toolBitmapColours[1]), 1))
toolBitmapDc.DrawRectangle(0, 0, 8, 8)
toolBitmapDc.DrawRectangle(8, 8, 16, 16)
RoarCanvasCommandsEdit.canvasColourAlpha.attrList[0]["icon"] = ["", None, toolBitmap]
# }}}
def _initColourBitmaps_(cmd, cmdAlpha, div):
for numColour in range(len(cmd.attrList)):
if numColour < len(Colours):
toolBitmapColour = Colours[numColour][0:4]
toolBitmap = wx.Bitmap((16, 16))
toolBitmapDc = wx.MemoryDC(); toolBitmapDc.SelectObject(toolBitmap);
toolBitmapBrush = wx.Brush(wx.Colour([*[int(c / div) for c in toolBitmapColour[:3]], 255]), wx.BRUSHSTYLE_SOLID)
toolBitmapDc.SetBrush(toolBitmapBrush)
toolBitmapDc.SetBackground(toolBitmapBrush)
toolBitmapDc.SetPen(wx.Pen(toolBitmapColour, 1))
toolBitmapDc.DrawRectangle(0, 0, 16, 16)
cmd.attrList[numColour]["icon"] = ["", None, toolBitmap]
toolBitmapColours = ((0, 0, 0, 255), (255, 255, 255, 255))
toolBitmap = wx.Bitmap((16, 16))
toolBitmapDc = wx.MemoryDC(); toolBitmapDc.SelectObject(toolBitmap);
toolBitmapBrush = [wx.Brush(wx.Colour(c), wx.BRUSHSTYLE_SOLID) for c in toolBitmapColours]
toolBitmapDc.SetBrush(toolBitmapBrush[1])
toolBitmapDc.SetBackground(toolBitmapBrush[1])
toolBitmapDc.SetPen(wx.Pen(wx.Colour(toolBitmapColours[1]), 1))
toolBitmapDc.DrawRectangle(0, 0, 8, 8)
toolBitmapDc.DrawRectangle(8, 8, 16, 16)
cmdAlpha.attrList[0]["icon"] = ["", None, toolBitmap]
_initColourBitmaps_(RoarCanvasCommandsEdit.canvasColour, RoarCanvasCommandsEdit.canvasColourAlpha, 1.0)
_initColourBitmaps_(RoarCanvasCommandsEdit.canvasColourBackground, RoarCanvasCommandsEdit.canvasColourAlphaBackground, 1.5)
def _initInterface(self):
accels = ()
menus = (
("&File",
self.canvasNew, self.canvasOpen, self.canvasOpenRecent, self.canvasRestore, self.canvasSave, self.canvasSaveAs, NID_MENU_SEP,
("&Export...", self.canvasExportAsAnsi, self.canvasExportToClipboard, self.canvasExportImgur, self.canvasExportPastebin, self.canvasExportAsPng,),
("&Import...", self.canvasImportAnsi, self.canvasImportFromClipboard, self.canvasImportSauce,),
NID_MENU_SEP,
self.canvasExit,
),
("&Edit",
self.canvasUndo, self.canvasRedo, NID_MENU_SEP,
self.canvasCut, self.canvasCopy, self.canvasPaste,
self.canvasDelete, NID_MENU_SEP,
("Brush size", self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_MENU_SEP,
self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),),
("Canvas size", self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_MENU_SEP,
self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False),),
self.canvasColoursFlip,
NID_MENU_SEP,
self.canvasBrush(self.canvasBrush, 0), NID_MENU_SEP,
self.canvasAssetsWindowHide, self.canvasAssetsWindowShow,
),
("&Tools",
self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6),
),
("&Operators",
self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2), self.canvasOperator(self.canvasOperator, 3), self.canvasOperator(self.canvasOperator, 4),
),
("&Help",
self.canvasMelp, NID_MENU_SEP, self.canvasNewIssueGitHub, self.canvasVisitGitHub, NID_MENU_SEP, self.canvasAbout,
),
)
toolBars = (
(self.canvasNew, self.canvasOpen, self.canvasSave, self.canvasSaveAs, NID_TOOLBAR_HSEP,
self.canvasUndo, self.canvasRedo, NID_TOOLBAR_HSEP,
self.canvasCut, self.canvasCopy, self.canvasPaste, self.canvasDelete, NID_TOOLBAR_HSEP,
self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, NID_TOOLBAR_HSEP,
),
(self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6),),
(self.canvasColour(self.canvasColour, 0), self.canvasColour(self.canvasColour, 1), self.canvasColour(self.canvasColour, 2), self.canvasColour(self.canvasColour, 3),
self.canvasColour(self.canvasColour, 4), self.canvasColour(self.canvasColour, 5), self.canvasColour(self.canvasColour, 6), self.canvasColour(self.canvasColour, 7),
self.canvasColour(self.canvasColour, 8), self.canvasColour(self.canvasColour, 9), self.canvasColour(self.canvasColour, 10), self.canvasColour(self.canvasColour, 11),
self.canvasColour(self.canvasColour, 12), self.canvasColour(self.canvasColour, 13), self.canvasColour(self.canvasColour, 14), self.canvasColour(self.canvasColour, 15),
self.canvasColourAlpha(self.canvasColourAlpha, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP,
self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), NID_TOOLBAR_HSEP,
self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),
),
(self.canvasColourBackground(self.canvasColourBackground, 0), self.canvasColourBackground(self.canvasColourBackground, 1), self.canvasColourBackground(self.canvasColourBackground, 2), self.canvasColourBackground(self.canvasColourBackground, 3),
self.canvasColourBackground(self.canvasColourBackground, 4), self.canvasColourBackground(self.canvasColourBackground, 5), self.canvasColourBackground(self.canvasColourBackground, 6), self.canvasColourBackground(self.canvasColourBackground, 7),
self.canvasColourBackground(self.canvasColourBackground, 8), self.canvasColourBackground(self.canvasColourBackground, 9), self.canvasColourBackground(self.canvasColourBackground, 10), self.canvasColourBackground(self.canvasColourBackground, 11),
self.canvasColourBackground(self.canvasColourBackground, 12), self.canvasColourBackground(self.canvasColourBackground, 13), self.canvasColourBackground(self.canvasColourBackground, 14), self.canvasColourBackground(self.canvasColourBackground, 15),
self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP,
self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_TOOLBAR_HSEP,
self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False),
),
)
return accels, menus, toolBars
# {{{ update(self, **kwargs)
def update(self, **kwargs):
self.lastPanelState.update(kwargs); textItems = [];
if "cellPos" in self.lastPanelState:
@ -47,83 +107,64 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan
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"]))
textItems.append("B: {: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(
Colours[self.lastPanelState["colours"][0]][4] if self.lastPanelState["colours"][0] != -1 else "Transparent",
Colours[self.lastPanelState["colours"][1]][4] if self.lastPanelState["colours"][1] != -1 else "Transparent"))
textItems.append("FG: {:02d} ({}), BG: {:02d} ({})".format(self.lastPanelState["colours"][0], Colours[self.lastPanelState["colours"][0]][4] if self.lastPanelState["colours"][0] != -1 else "Transparent", self.lastPanelState["colours"][1], Colours[self.lastPanelState["colours"][1]][4] if self.lastPanelState["colours"][1] != -1 else "Transparent"))
toolBar = self.parentFrame.toolBarItemsById[self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"]][0]
toolBarBg = self.parentFrame.toolBarItemsById[self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"]][0]
if self.lastPanelState["colours"][0] != -1:
toolBar.ToggleTool(self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"], True); toolBar.Refresh()
else:
toolBar.ToggleTool(self.canvasColourAlpha(self.canvasColourAlpha, 0).attrDict["id"], True); toolBar.Refresh()
if self.lastPanelState["colours"][1] != -1:
toolBarBg.ToggleTool(self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"], True); toolBarBg.Refresh()
else:
toolBarBg.ToggleTool(self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0).attrDict["id"], True); toolBarBg.Refresh()
if "pathName" in self.lastPanelState:
if self.lastPanelState["pathName"] != None:
basePathName = os.path.basename(self.lastPanelState["pathName"])
textItems.append("Current file: {}".format(basePathName))
textItems.append("F: {}".format(basePathName))
self.parentFrame.SetTitle("{} - roar".format(basePathName))
else:
self.parentFrame.SetTitle("roar")
if "toolName" in self.lastPanelState:
textItems.append("Current tool: {}".format(self.lastPanelState["toolName"]))
if "dirty" in self.lastPanelState \
and self.lastPanelState["dirty"]:
if "currentTool" in self.lastPanelState:
self.parentFrame.menuItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]].Check(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]][0]
toolBar.ToggleTool(self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"], True); toolBar.Refresh();
if (self.lastPanelState["currentTool"] != None) and (self.lastPanelState["currentTool"].__class__ == ToolObject):
self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(True)
else:
self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(False)
textItems.append("T: {}".format(self.lastPanelState["currentTool"].name if (self.lastPanelState["currentTool"] != None) else "Cursor"))
if ("operator" in self.lastPanelState) and (self.lastPanelState["operator"] != None):
textItems.append("O: {}".format(self.lastPanelState["operator"]))
if ("dirty" in self.lastPanelState) and self.lastPanelState["dirty"]:
textItems.append("*")
if ("backupStatus" in self.lastPanelState) and (self.lastPanelState["backupStatus"] == True):
textItems.append("Saving backup...")
self.parentFrame.statusBar.SetStatusText(" | ".join(textItems))
if ("undoInhibit" in self.lastPanelState) \
and (self.lastPanelState["undoInhibit"]):
for item in (self.canvasRedo, self.canvasUndo):
self.parentFrame.menuItemsById[item.attrDict["id"]].Enable(False)
toolBar = self.parentFrame.toolBarItemsById[item.attrDict["id"]].GetToolBar()
toolBar.EnableTool(item.attrDict["id"], False)
elif "undoLevel" in self.lastPanelState:
if "undoLevel" in self.lastPanelState:
if (self.lastPanelState["undoLevel"] >= 0) \
and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.journal.patchesUndo) - 1)):
and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.patchesUndo) - 1)):
self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasUndo.attrDict["id"], True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasUndo.attrDict["id"], True); toolBar.Refresh();
else:
self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasUndo.attrDict["id"], False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasUndo.attrDict["id"], False); toolBar.Refresh();
if self.lastPanelState["undoLevel"] > 0:
self.parentFrame.menuItemsById[self.canvasRedo.attrDict["id"]].Enable(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasRedo.attrDict["id"], True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasRedo.attrDict["id"], True); toolBar.Refresh();
else:
self.parentFrame.menuItemsById[self.canvasRedo.attrDict["id"]].Enable(False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasRedo.attrDict["id"], False)
# }}}
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasRedo.attrDict["id"], False); toolBar.Refresh();
#
# __init__(self, parentCanvas, parentFrame):
def __init__(self, parentCanvas, parentFrame):
menus, toolBars = [], []
[classObject.__init__(self) for classObject in self.__class__.__bases__]
self._initColourBitmaps(); self.accels, self.menus, self.toolBars = self._initInterface();
self.canvasPathName, self.lastPanelState, self.parentCanvas, self.parentFrame = None, {}, parentCanvas, parentFrame
for classObject in self.__class__.__bases__:
classObject.__init__(self)
if len(self.menus):
menus += self.menus
if len(self.toolBars):
toolBars += self.toolBars
self._initColourBitmaps()
# XXX
toolBars.append(
[self.canvasNew, self.canvasOpen, self.canvasSave, self.canvasSaveAs, NID_TOOLBAR_HSEP,
self.canvasUndo, self.canvasRedo, NID_TOOLBAR_HSEP,
self.canvasCut, self.canvasCopy, self.canvasPaste, self.canvasDelete, NID_TOOLBAR_HSEP,
self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_TOOLBAR_HSEP,
self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False), NID_TOOLBAR_HSEP,
self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 6), self.canvasTool(self.canvasTool, 4),
])
# XXX
toolBars.append(
[self.canvasColour(self.canvasColour, 0), self.canvasColour(self.canvasColour, 1), self.canvasColour(self.canvasColour, 2), self.canvasColour(self.canvasColour, 3),
self.canvasColour(self.canvasColour, 4), self.canvasColour(self.canvasColour, 5), self.canvasColour(self.canvasColour, 6), self.canvasColour(self.canvasColour, 7),
self.canvasColour(self.canvasColour, 8), self.canvasColour(self.canvasColour, 9), self.canvasColour(self.canvasColour, 10), self.canvasColour(self.canvasColour, 11),
self.canvasColour(self.canvasColour, 12), self.canvasColour(self.canvasColour, 13), self.canvasColour(self.canvasColour, 14), self.canvasColour(self.canvasColour, 15),
self.canvasColourAlpha(self.canvasColourAlpha, 0), NID_TOOLBAR_HSEP,
self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_TOOLBAR_HSEP,
self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),
])
self.menus, self.toolBars = menus, toolBars
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,25 +4,30 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator, NID_MENU_SEP
from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator
import wx
class RoarCanvasCommandsEdit():
# {{{ canvasAssetsWindowHide(self, event)
@GuiCommandDecorator("Hide assets window", "Hide assets window", None, None, False)
@GuiCommandDecorator("Hide assets window", "Hide assets window", ["toolHideAssetsWindow.png"], None, False)
def canvasAssetsWindowHide(self, event):
self.parentFrame.assetsWindow.Show(False)
self.parentFrame.menuItemsById[self.canvasAssetsWindowHide.attrDict["id"]].Enable(False)
self.parentFrame.menuItemsById[self.canvasAssetsWindowShow.attrDict["id"]].Enable(True)
# }}}
# {{{ canvasAssetsWindowShow(self, event)
@GuiCommandDecorator("Show assets window", "Show assets window", None, None, False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasAssetsWindowHide.attrDict["id"]][0]
toolBar.EnableTool(self.canvasAssetsWindowHide.attrDict["id"], False)
toolBar.EnableTool(self.canvasAssetsWindowShow.attrDict["id"], True)
toolBar.Refresh()
@GuiCommandDecorator("Show assets window", "Show assets window", ["toolShowAssetsWindow.png"], None, False)
def canvasAssetsWindowShow(self, event):
self.parentFrame.assetsWindow.Show(True)
self.parentFrame.menuItemsById[self.canvasAssetsWindowHide.attrDict["id"]].Enable(True)
self.parentFrame.menuItemsById[self.canvasAssetsWindowShow.attrDict["id"]].Enable(False)
# }}}
# {{{ canvasBrush(self, f, idx)
toolBar = self.parentFrame.toolBarItemsById[self.canvasAssetsWindowHide.attrDict["id"]][0]
toolBar.EnableTool(self.canvasAssetsWindowHide.attrDict["id"], True)
toolBar.EnableTool(self.canvasAssetsWindowShow.attrDict["id"], False)
toolBar.Refresh()
@GuiSelectDecorator(0, "Solid brush", "Solid brush", None, None, True)
def canvasBrush(self, f, idx):
def canvasBrush_(self, event):
@ -30,14 +35,13 @@ class RoarCanvasCommandsEdit():
setattr(canvasBrush_, "attrDict", f.attrList[idx])
setattr(canvasBrush_, "isSelect", True)
return canvasBrush_
# }}}
# {{{ canvasBrushSize(self, f, dimension, incrFlag)
@GuiCommandListDecorator(0, "Decrease brush width", "Decrease brush width", ["toolDecrBrushW.png"], None, None)
@GuiCommandListDecorator(1, "Decrease brush height", "Decrease brush height", ["toolDecrBrushH.png"], None, None)
@GuiCommandListDecorator(2, "Decrease brush size", "Decrease brush size", ["toolDecrBrushHW.png"], None, None)
@GuiCommandListDecorator(2, "Decrease brush size", "Decrease brush size", ["toolDecrBrushHW.png"], [wx.ACCEL_CTRL, ord("-")], None)
@GuiCommandListDecorator(3, "Increase brush width", "Increase brush width", ["toolIncrBrushW.png"], None, None)
@GuiCommandListDecorator(4, "Increase brush height", "Increase brush height", ["toolIncrBrushH.png"], None, None)
@GuiCommandListDecorator(5, "Increase brush size", "Increase brush size", ["toolIncrBrushHW.png"], None, None)
@GuiCommandListDecorator(5, "Increase brush size", "Increase brush size", ["toolIncrBrushHW.png"], [wx.ACCEL_CTRL, ord("+")], None)
def canvasBrushSize(self, f, dimension, incrFlag):
def canvasBrushSize_(event):
if (dimension < 2) and not incrFlag:
@ -49,15 +53,17 @@ class RoarCanvasCommandsEdit():
self.update(brushSize=self.parentCanvas.brushSize)
elif dimension == 2:
[self.canvasBrushSize(f, dimension_, incrFlag)(None) for dimension_ in [0, 1]]
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
setattr(canvasBrushSize_, "attrDict", f.attrList[dimension + (0 if not incrFlag else 3)])
return canvasBrushSize_
# }}}
# {{{ canvasCanvasSize(self, f, dimension, incrFlag)
@GuiCommandListDecorator(0, "Decrease canvas height", "Decrease canvas height", ["toolDecrCanvasH.png"], None, None)
@GuiCommandListDecorator(1, "Decrease canvas width", "Decrease canvas width", ["toolDecrCanvasW.png"], None, None)
@GuiCommandListDecorator(0, "Decrease canvas height", "Decrease canvas height", ["toolDecrCanvasH.png"], [wx.ACCEL_CTRL, wx.WXK_UP], None)
@GuiCommandListDecorator(1, "Decrease canvas width", "Decrease canvas width", ["toolDecrCanvasW.png"], [wx.ACCEL_CTRL, wx.WXK_LEFT], None)
@GuiCommandListDecorator(2, "Decrease canvas size", "Decrease canvas size", ["toolDecrCanvasHW.png"], None, None)
@GuiCommandListDecorator(3, "Increase canvas height", "Increase canvas height", ["toolIncrCanvasH.png"], None, None)
@GuiCommandListDecorator(4, "Increase canvas width", "Increase canvas width", ["toolIncrCanvasW.png"], None, None)
@GuiCommandListDecorator(3, "Increase canvas height", "Increase canvas height", ["toolIncrCanvasH.png"], [wx.ACCEL_CTRL, wx.WXK_DOWN], None)
@GuiCommandListDecorator(4, "Increase canvas width", "Increase canvas width", ["toolIncrCanvasW.png"], [wx.ACCEL_CTRL, wx.WXK_RIGHT], None)
@GuiCommandListDecorator(5, "Increase canvas size", "Increase canvas size", ["toolIncrCanvasHW.png"], None, None)
def canvasCanvasSize(self, f, dimension, incrFlag):
def canvasCanvasSize_(event):
@ -77,24 +83,23 @@ class RoarCanvasCommandsEdit():
[self.canvasCanvasSize(f, dimension_, incrFlag)(None) for dimension_ in [0, 1]]
setattr(canvasCanvasSize_, "attrDict", f.attrList[dimension + (0 if not incrFlag else 3)])
return canvasCanvasSize_
# }}}
# {{{ canvasColour(self, f, idx)
@GuiSelectDecorator(0, "Colour #00", "Colour #00 (Bright White)", None, None, False)
@GuiSelectDecorator(1, "Colour #01", "Colour #01 (Black)", None, None, False)
@GuiSelectDecorator(2, "Colour #02", "Colour #02 (Blue)", None, None, False)
@GuiSelectDecorator(3, "Colour #03", "Colour #03 (Green)", None, None, False)
@GuiSelectDecorator(4, "Colour #04", "Colour #04 (Red)", None, None, False)
@GuiSelectDecorator(5, "Colour #05", "Colour #05 (Light Red)", None, None, False)
@GuiSelectDecorator(6, "Colour #06", "Colour #06 (Pink)", None, None, False)
@GuiSelectDecorator(7, "Colour #07", "Colour #07 (Yellow)", None, None, False)
@GuiSelectDecorator(8, "Colour #08", "Colour #08 (Light Yellow)", None, None, False)
@GuiSelectDecorator(9, "Colour #09", "Colour #09 (Light Green)", None, None, False)
@GuiSelectDecorator(10, "Colour #10", "Colour #10 (Cyan)", None, None, False)
@GuiSelectDecorator(11, "Colour #11", "Colour #11 (Light Cyan)", None, None, False)
@GuiSelectDecorator(12, "Colour #12", "Colour #12 (Light Blue)", None, None, False)
@GuiSelectDecorator(13, "Colour #13", "Colour #13 (Light Pink)", None, None, False)
@GuiSelectDecorator(14, "Colour #14", "Colour #14 (Grey)", None, None, False)
@GuiSelectDecorator(15, "Colour #15", "Colour #15 (Light Grey)", None, None, False)
@GuiSelectDecorator(0, "Colour #00", "Colour #00 (Bright White)", None, [wx.ACCEL_CTRL, ord("0")], False)
@GuiSelectDecorator(1, "Colour #01", "Colour #01 (Black)", None, [wx.ACCEL_CTRL, ord("1")], False)
@GuiSelectDecorator(2, "Colour #02", "Colour #02 (Blue)", None, [wx.ACCEL_CTRL, ord("2")], False)
@GuiSelectDecorator(3, "Colour #03", "Colour #03 (Green)", None, [wx.ACCEL_CTRL, ord("3")], False)
@GuiSelectDecorator(4, "Colour #04", "Colour #04 (Red)", None, [wx.ACCEL_CTRL, ord("4")], False)
@GuiSelectDecorator(5, "Colour #05", "Colour #05 (Light Red)", None, [wx.ACCEL_CTRL, ord("5")], False)
@GuiSelectDecorator(6, "Colour #06", "Colour #06 (Pink)", None, [wx.ACCEL_CTRL, ord("6")], False)
@GuiSelectDecorator(7, "Colour #07", "Colour #07 (Yellow)", None, [wx.ACCEL_CTRL, ord("7")], False)
@GuiSelectDecorator(8, "Colour #08", "Colour #08 (Light Yellow)", None, [wx.ACCEL_CTRL, ord("8")], False)
@GuiSelectDecorator(9, "Colour #09", "Colour #09 (Light Green)", None, [wx.ACCEL_CTRL, ord("9")], False)
@GuiSelectDecorator(10, "Colour #10", "Colour #10 (Cyan)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("0")], False)
@GuiSelectDecorator(11, "Colour #11", "Colour #11 (Light Cyan)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("1")], False)
@GuiSelectDecorator(12, "Colour #12", "Colour #12 (Light Blue)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("2")], False)
@GuiSelectDecorator(13, "Colour #13", "Colour #13 (Light Pink)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("3")], False)
@GuiSelectDecorator(14, "Colour #14", "Colour #14 (Grey)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("4")], False)
@GuiSelectDecorator(15, "Colour #15", "Colour #15 (Light Grey)", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("5")], False)
def canvasColour(self, f, idx):
def canvasColour_(event):
if event.GetEventType() == wx.wxEVT_TOOL:
@ -102,12 +107,14 @@ class RoarCanvasCommandsEdit():
elif event.GetEventType() == wx.wxEVT_TOOL_RCLICKED:
self.parentCanvas.brushColours[1] = idx
self.update(colours=self.parentCanvas.brushColours)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
setattr(canvasColour_, "attrDict", f.attrList[idx])
setattr(canvasColour_, "isSelect", True)
return canvasColour_
# }}}
# {{{ canvasColourAlpha(self, f, idx)
@GuiSelectDecorator(0, "Transparent colour", "Transparent colour", None, None, False)
@GuiSelectDecorator(0, "Transparent colour", "Transparent colour", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("6")], False)
def canvasColourAlpha(self, f, idx):
def canvasColourAlpha_(event):
if event.GetEventType() == wx.wxEVT_TOOL:
@ -115,60 +122,82 @@ class RoarCanvasCommandsEdit():
elif event.GetEventType() == wx.wxEVT_TOOL_RCLICKED:
self.parentCanvas.brushColours[1] = -1
self.update(colours=self.parentCanvas.brushColours)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
setattr(canvasColourAlpha_, "attrDict", f.attrList[idx])
setattr(canvasColourAlpha_, "isSelect", True)
return canvasColourAlpha_
# }}}
# {{{ canvasCopy(self, event)
@GuiSelectDecorator(0, "Transparent colour", "Transparent colour", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("6")], False)
def canvasColourAlphaBackground(self, f, idx):
def canvasColourAlphaBackground_(event):
self.parentCanvas.brushColours[1] = -1
self.update(colours=self.parentCanvas.brushColours)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
setattr(canvasColourAlphaBackground_, "attrDict", f.attrList[idx])
setattr(canvasColourAlphaBackground_, "isSelect", True)
return canvasColourAlphaBackground_
@GuiSelectDecorator(0, "Colour #00", "Colour #00 (Bright White)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("0")], False)
@GuiSelectDecorator(1, "Colour #01", "Colour #01 (Black)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("1")], False)
@GuiSelectDecorator(2, "Colour #02", "Colour #02 (Blue)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("2")], False)
@GuiSelectDecorator(3, "Colour #03", "Colour #03 (Green)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("3")], False)
@GuiSelectDecorator(4, "Colour #04", "Colour #04 (Red)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("4")], False)
@GuiSelectDecorator(5, "Colour #05", "Colour #05 (Light Red)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("5")], False)
@GuiSelectDecorator(6, "Colour #06", "Colour #06 (Pink)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("6")], False)
@GuiSelectDecorator(7, "Colour #07", "Colour #07 (Yellow)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("7")], False)
@GuiSelectDecorator(8, "Colour #08", "Colour #08 (Light Yellow)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("8")], False)
@GuiSelectDecorator(9, "Colour #09", "Colour #09 (Light Green)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("9")], False)
@GuiSelectDecorator(10, "Colour #10", "Colour #10 (Cyan)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("0")], False)
@GuiSelectDecorator(11, "Colour #11", "Colour #11 (Light Cyan)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("1")], False)
@GuiSelectDecorator(12, "Colour #12", "Colour #12 (Light Blue)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("2")], False)
@GuiSelectDecorator(13, "Colour #13", "Colour #13 (Light Pink)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("3")], False)
@GuiSelectDecorator(14, "Colour #14", "Colour #14 (Grey)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("4")], False)
@GuiSelectDecorator(15, "Colour #15", "Colour #15 (Light Grey)", None, [wx.ACCEL_CTRL | wx.ACCEL_ALT | wx.ACCEL_SHIFT, ord("5")], False)
def canvasColourBackground(self, f, idx):
def canvasColourBackground_(event):
self.parentCanvas.brushColours[1] = idx
self.update(colours=self.parentCanvas.brushColours)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
setattr(canvasColourBackground_, "attrDict", f.attrList[idx])
setattr(canvasColourBackground_, "isSelect", True)
return canvasColourBackground_
@GuiCommandDecorator("Flip colours", "Flip colours", ["toolColoursFlip.png"], [wx.ACCEL_CTRL, ord("I")], True)
def canvasColoursFlip(self, event):
self.parentCanvas.brushColours = [self.parentCanvas.brushColours[1], self.parentCanvas.brushColours[0]]
self.update(colours=self.parentCanvas.brushColours)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, *self.parentCanvas.lastMouseState, self.currentTool, viewRect, force=True)
@GuiCommandDecorator("Copy", "&Copy", ["", wx.ART_COPY], None, False)
def canvasCopy(self, event):
pass
# }}}
# {{{ canvasCut(self, event)
@GuiCommandDecorator("Cut", "Cu&t", ["", wx.ART_CUT], None, False)
def canvasCut(self, event):
pass
# }}}
# {{{ canvasDelete(self, event)
@GuiCommandDecorator("Delete", "De&lete", ["", wx.ART_DELETE], None, False)
def canvasDelete(self, event):
pass
# }}}
# {{{ canvasPaste(self, event)
@GuiCommandDecorator("Paste", "&Paste", ["", wx.ART_PASTE], None, False)
def canvasPaste(self, event):
pass
# }}}
# {{{ canvasRedo(self, event)
@GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False)
def canvasRedo(self, event):
self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo())
self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel)
# }}}
# {{{ canvasUndo(self, event)
self.parentCanvas.undo(redo=True); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel);
@GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False)
def canvasUndo(self, event):
self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popUndo())
self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel)
# }}}
#
# __init__(self)
def __init__(self):
self.menus = (
("&Edit",
self.canvasUndo, self.canvasRedo, NID_MENU_SEP,
self.canvasCut, self.canvasCopy, self.canvasPaste,
self.canvasDelete, NID_MENU_SEP,
("Canvas size", self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_MENU_SEP,
self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False),),
("Brush size", self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_MENU_SEP,
self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),),
NID_MENU_SEP,
self.canvasBrush(self.canvasBrush, 0), NID_MENU_SEP,
self.canvasAssetsWindowHide, self.canvasAssetsWindowShow,
),
)
self.toolBars = ()
self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel);
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -5,35 +5,35 @@
#
try:
from ImgurApiKey import ImgurApiKey
haveImgurApiKey = True
from ImgurApiKey import ImgurApiKey; haveImgurApiKey = True;
except ImportError:
haveImgurApiKey = False
try:
import base64, json, requests, urllib.request
haveUrllib = True
import base64, json, requests, urllib.request; haveUrllib = True;
except ImportError:
haveUrllib = False
from GuiFrame import GuiCommandDecorator, GuiSubMenuDecorator, NID_MENU_SEP
from RtlPlatform import getLocalConfPathName
import io, os, wx
import datetime, io, os, stat, wx
class RoarCanvasCommandsFile():
# {{{ _import(self, f, newDirty, pathName)
def _import(self, f, newDirty, pathName):
def _import(self, f, newDirty, pathName, emptyPathName=False):
rc = False
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
try:
rc, error, newMap, newPathName, newSize = f(pathName)
if rc:
self.parentCanvas.update(newSize, False, newMap, dirty=newDirty)
self.parentCanvas.dirty = newDirty
self.parentCanvas.update(newSize, False, newMap)
self.canvasPathName = newPathName
if not emptyPathName:
self.canvasPathName = newPathName
else:
self.canvasPathName = None
self.parentCanvas._snapshotsReset()
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
self.parentCanvas.canvas.journal.resetCursor()
self.parentCanvas.canvas.journal.resetUndo()
self.parentCanvas.canvas.resetCursor(); self.parentCanvas.canvas.resetUndo();
except FileNotFoundError as e:
rc, error, newMap, newPathName, newSize = False, str(e), None, None, None
if not rc:
@ -41,27 +41,18 @@ class RoarCanvasCommandsFile():
dialogChoice = dialog.ShowModal()
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return rc, newPathName
# }}}
# {{{ _importFile(self, f, newDirty, wildcard)
def _importFile(self, f, newDirty, wildcard):
with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", wildcard, wx.FD_OPEN) as dialog:
def _importFile(self, f, newDirty, wildcard, emptyPathName=False):
with wx.FileDialog(self.parentCanvas, "Open...", os.getcwd(), "", wildcard, wx.FD_OPEN) as dialog:
if self.lastDir != None:
dialog.SetDirectory(self.lastDir)
if dialog.ShowModal() == wx.ID_CANCEL:
return False, None
elif self._promptSaveChanges():
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName);
return self._import(f, newDirty, pathName)
# }}}
# {{{ _loadRecent(self)
def _loadRecent(self):
localConfFileName = getLocalConfPathName("Recent.lst")
if os.path.exists(localConfFileName):
with open(localConfFileName, "r", encoding="utf-8") as inFile:
for lastFile in inFile.readlines():
self._pushRecent(lastFile.rstrip("\r\n"), False)
# }}}
# {{{ _promptSaveChanges(self)
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName); self._recentDirSave(self.lastDir);
return self._import(f, newDirty, pathName, emptyPathName=emptyPathName)
return False, None
def _promptSaveChanges(self):
if self.parentCanvas.dirty:
message = "Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)")
@ -77,34 +68,82 @@ class RoarCanvasCommandsFile():
return False
else:
return True
# }}}
# {{{ _pushRecent(self, pathName, serialise=True)
def _pushRecent(self, pathName, serialise=True):
def _recentDirLoad(self):
localConfFileName = getLocalConfPathName("RecentDir.txt")
if os.path.exists(localConfFileName):
with open(localConfFileName, "r", encoding="utf-8") as inFile:
self.lastDir = inFile.read().rstrip("\r\n")
def _recentDirSave(self, pathName):
localConfFileName = getLocalConfPathName("RecentDir.txt")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
print(pathName, file=outFile)
def _recentLoad(self):
localConfFileName = getLocalConfPathName("Recent.lst")
if os.path.exists(localConfFileName):
with open(localConfFileName, "r", encoding="utf-8") as inFile:
for lastFile in inFile.readlines():
self._recentPush(lastFile.rstrip("\r\n"), False)
def _recentPush(self, pathName, serialise=True):
menuItemId = wx.NewId()
if not pathName in [l["pathName"] for l in self.lastFiles]:
numLastFiles = len(self.lastFiles) if self.lastFiles != None else 0
if (numLastFiles + 1) > 8:
self.canvasOpenRecent.attrDict["menu"].Delete(self.lastFiles[0]["menuItemId"])
del self.lastFiles[0]
menuItemWindow = self.canvasOpenRecent.attrDict["menu"].Append(menuItemId, "{}".format(pathName), pathName)
menuItemWindow = self.canvasOpenRecent.attrDict["menu"].Insert(self.canvasOpenRecent.attrDict["menu"].GetMenuItemCount() - 2, menuItemId, pathName, pathName)
self.parentFrame.menuItemsById[self.canvasOpenRecent.attrDict["id"]].Enable(True)
self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasOpenRecent(event, pathName), menuItemWindow)
self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}]
if serialise:
localConfFileName = getLocalConfPathName("Recent.lst")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
for lastFile in [l["pathName"] for l in self.lastFiles]:
print(lastFile, file=outFile)
# }}}
self._recentSave()
def _recentSave(self):
localConfFileName = getLocalConfPathName("Recent.lst")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
for lastFile in [l["pathName"] for l in self.lastFiles]:
print(lastFile, file=outFile)
def _snapshotsPop(self, pathName):
if pathName in self.snapshots.keys():
self.canvasRestore.attrDict["menu"].Delete(self.snapshots[pathName]["menuItemId"])
del self.snapshots[pathName]
def _snapshotsPush(self, pathName):
menuItemId = wx.NewId()
if not (pathName in self.snapshots.keys()):
label = datetime.datetime.fromtimestamp(os.stat(pathName)[stat.ST_MTIME]).strftime("%c")
menuItemWindow = self.canvasRestore.attrDict["menu"].Insert(self.canvasRestore.attrDict["menu"].GetMenuItemCount() - 2, menuItemId, label)
self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(True)
self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasRestore(event, pathName), menuItemWindow)
self.snapshots[pathName] = {"menuItemId":menuItemId, "menuItemWindow":menuItemWindow}
def _snapshotsReset(self):
for pathName in list(self.snapshots.keys()):
self._snapshotsPop(pathName)
if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById:
self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(False)
@GuiCommandDecorator("Clear list", "&Clear list", None, None, False)
def canvasClearRecent(self, event):
if self.lastFiles != None:
for lastFile in self.lastFiles:
self.canvasOpenRecent.attrDict["menu"].Delete(lastFile["menuItemId"])
self.lastFiles = []
localConfFileName = getLocalConfPathName("Recent.lst")
if os.path.exists(localConfFileName):
os.unlink(localConfFileName)
self.parentFrame.menuItemsById[self.canvasOpenRecent.attrDict["id"]].Enable(False)
# {{{ canvasExit(self, event)
@GuiCommandDecorator("Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None)
def canvasExit(self, event):
if self._promptSaveChanges():
self.parentFrame.Close(True)
# }}}
if not self.exiting:
if self._promptSaveChanges():
self.exiting = True; self.parentFrame.Close(True);
# {{{ canvasExportAsAnsi(self, event)
@GuiCommandDecorator("Export as ANSI...", "Export as &ANSI...", None, None, None)
def canvasExportAsAnsi(self, event):
with wx.FileDialog(self.parentFrame, "Save As...", os.getcwd(), "", "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog:
@ -113,14 +152,13 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName);
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir);
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
with open(outPathName, "w", encoding="utf-8") as outFile:
self.parentCanvas.canvas.exportStore.exportAnsiFile(self.parentCanvas.canvas.map, self.parentCanvas.canvas.size, outFile)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True
# }}}
# {{{ canvasExportAsPng(self, event)
@GuiCommandDecorator("Export as PNG...", "Export as PN&G...", None, None, None)
def canvasExportAsPng(self, event):
with wx.FileDialog(self.parentFrame, "Save As...", os.getcwd(), "", "PNG (*.png)|*.png|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog:
@ -129,17 +167,20 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName);
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir);
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
self.parentCanvas.cursorHide()
self.parentCanvas.canvas.exportStore.exportBitmapToPngFile(self.parentCanvas.backend.canvasBitmap, outPathName, wx.BITMAP_TYPE_PNG)
self.parentCanvas.cursorShow()
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True
# }}}
# {{{ canvasExportImgur(self, event)
@GuiCommandDecorator("Export to Imgur...", "Export to I&mgur...", None, None, haveImgurApiKey and haveUrllib)
def canvasExportImgur(self, event):
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
self.parentCanvas.cursorHide()
rc, status, result = self.parentCanvas.canvas.exportStore.exportBitmapToImgur(self.imgurApiKey, self.parentCanvas.backend.canvasBitmap, "", "", wx.BITMAP_TYPE_PNG)
self.parentCanvas.cursorShow()
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
if rc:
if not wx.TheClipboard.IsOpened():
@ -147,12 +188,11 @@ class RoarCanvasCommandsFile():
wx.MessageBox("Exported to Imgur: {}".format(result), "Export to Imgur", wx.ICON_INFORMATION | wx.OK)
else:
wx.MessageBox("Failed to export to Imgur: {}".format(result), "Export to Imgur", wx.ICON_EXCLAMATION | wx.OK)
# }}}
# {{{ canvasExportPastebin(self, event)
@GuiCommandDecorator("Export to Pastebin...", "Export to Pasteb&in...", None, None, haveUrllib)
def canvasExportPastebin(self, event):
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
pasteStatus, pasteResult = self.parentCanvas.canvas.exportStore.exportPastebin("253ce2f0a45140ee0a44ca99aa49260", self.parentCanvas.canvas.map, self.parentCanvas.canvas.size)
pasteStatus, pasteResult = self.parentCanvas.canvas.exportStore.exportPastebin("", self.parentCanvas.canvas.map, self.parentCanvas.canvas.size)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
if pasteStatus:
if not wx.TheClipboard.IsOpened():
@ -162,8 +202,7 @@ class RoarCanvasCommandsFile():
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)
# }}}
# {{{ canvasExportToClipboard(self, event)
@GuiCommandDecorator("Export to clipboard", "Export to &clipboard", None, None, None)
def canvasExportToClipboard(self, event):
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
@ -173,17 +212,14 @@ class RoarCanvasCommandsFile():
wx.TheClipboard.Close()
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True
# }}}
# {{{ canvasImportAnsi(self, event)
@GuiCommandDecorator("Import ANSI...", "Import &ANSI...", None, None, None)
def canvasImportAnsi(self, event):
def canvasImportAnsi_(pathName):
rc, error = self.parentCanvas.canvas.importStore.importAnsiFile(pathName)
return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName, self.parentCanvas.canvas.importStore.inSize)
self._importFile(canvasImportAnsi_, True, "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*")
# }}}
# {{{ canvasImportFromClipboard(self, event)
self._importFile(canvasImportAnsi_, True, "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", emptyPathName=True)
@GuiCommandDecorator("Import from clipboard", "Import from &clipboard", None, None, None)
def canvasImportFromClipboard(self, event):
def canvasImportFromClipboard_(pathName):
@ -197,31 +233,26 @@ class RoarCanvasCommandsFile():
else:
rc, error = False, "Clipboard does not contain text data and/or cannot be opened"
return (rc, error, self.parentCanvas.canvas.importStore.outMap, None, self.parentCanvas.canvas.importStore.inSize)
if self._promptSaveChanges():
self._import(canvasImportFromClipboard_, True, None)
# }}}
# {{{ canvasImportSauce(self, event)
self._import(canvasImportFromClipboard_, True, None, emptyPathName=True)
@GuiCommandDecorator("Import SAUCE...", "Import &SAUCE...", None, None, None)
def canvasImportSauce(self, event):
def canvasImportSauce_(pathName):
rc, error = self.parentCanvas.canvas.importStore.importSauceFile(pathName)
return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName, self.parentCanvas.canvas.importStore.inSize)
self._importFile(canvasImportSauce_, True, "SAUCE files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*")
# }}}
self._importFile(canvasImportSauce_, True, "SAUCE files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", emptyPathName=True)
# {{{ canvasNew(self, event, newCanvasSize=None)
@GuiCommandDecorator("New", "&New", ["", wx.ART_NEW], [wx.ACCEL_CTRL, ord("N")], None)
def canvasNew(self, event, newCanvasSize=None):
def canvasImportEmpty(pathName):
nonlocal newCanvasSize
if newCanvasSize == None:
newCanvasSize = list(self.parentCanvas.canvas.size)
newMap = [[[1, 1, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])]
newCanvasSize = [100, 30]
newMap = [[[*self.parentCanvas.brushColours, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])]
return (True, "", newMap, None, newCanvasSize)
if self._promptSaveChanges():
self._import(canvasImportEmpty, False, None)
# }}}
# {{{ canvasOpen(self, event)
@GuiCommandDecorator("Open", "&Open", ["", wx.ART_FILE_OPEN], [wx.ACCEL_CTRL, ord("O")], None)
def canvasOpen(self, event):
def canvasImportmIRC(pathName):
@ -229,17 +260,36 @@ class RoarCanvasCommandsFile():
return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName, self.parentCanvas.canvas.importStore.inSize)
rc, newPathName = self._importFile(canvasImportmIRC, False, "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*")
if rc:
self._pushRecent(newPathName)
# }}}
# {{{ canvasOpenRecent(self, event, pathName=None)
self._recentPush(newPathName)
@GuiSubMenuDecorator("Open Recent", "Open &Recent", None, None, False)
def canvasOpenRecent(self, event, pathName=None):
def canvasImportmIRC(pathName_):
rc, error = self.parentCanvas.canvas.importStore.importTextFile(pathName_)
return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName_, self.parentCanvas.canvas.importStore.inSize)
if self._promptSaveChanges():
rc, newPathName = self._import(canvasImportmIRC, False, pathName)
if not rc:
numLastFile = [i for i in range(len(self.lastFiles)) if self.lastFiles[i]["pathName"] == pathName][0]
self.canvasOpenRecent.attrDict["menu"].Delete(self.lastFiles[numLastFile]["menuItemId"]); del self.lastFiles[numLastFile];
self._recentSave()
@GuiSubMenuDecorator("Restore Snapshot", "Res&tore Snapshot", None, None, False)
def canvasRestore(self, event, pathName=None):
def canvasImportmIRC(pathName_):
rc, error = self.parentCanvas.canvas.importStore.importTextFile(pathName)
return (rc, error, self.parentCanvas.canvas.importStore.outMap, self.canvasPathName, self.parentCanvas.canvas.importStore.inSize)
if self._promptSaveChanges():
rc, newPathName = self._import(canvasImportmIRC, False, self.canvasPathName)
@GuiCommandDecorator("Restore from file", "Restore from &file", None, None, False)
def canvasRestoreFile(self, event):
def canvasImportmIRC(pathName):
rc, error = self.parentCanvas.canvas.importStore.importTextFile(pathName)
return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName, self.parentCanvas.canvas.importStore.inSize)
self._import(canvasImportmIRC, False, pathName)
# }}}
# {{{ canvasSave(self, event)
if self._promptSaveChanges():
rc, newPathName = self._import(canvasImportmIRC, False, self.canvasPathName)
@GuiCommandDecorator("Save", "&Save", ["", wx.ART_FILE_SAVE], [wx.ACCEL_CTRL, ord("S")], None)
def canvasSave(self, event, newDirty=False):
if self.canvasPathName == None:
@ -256,8 +306,7 @@ class RoarCanvasCommandsFile():
return True
except IOError as error:
return False
# }}}
# {{{ canvasSaveAs(self, event)
@GuiCommandDecorator("Save As...", "Save &As...", ["", wx.ART_FILE_SAVE_AS], None, None)
def canvasSaveAs(self, event):
with wx.FileDialog(self.parentCanvas, "Save As", os.getcwd(), "", "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dialog:
@ -266,24 +315,16 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
self.canvasPathName = dialog.GetPath(); self.lastDir = os.path.dirname(self.canvasPathName);
if self.canvasSave(event, newDirty=True):
self._pushRecent(pathName)
# }}}
self.canvasPathName = dialog.GetPath(); self.lastDir = os.path.dirname(self.canvasPathName); self._recentDirSave(self.lastDir);
if self.canvasSave(event, newDirty=False):
self.update(pathName=self.canvasPathName)
self._recentPush(self.canvasPathName)
return True
else:
return False
#
# __init__(self)
def __init__(self):
self.imgurApiKey, self.lastFiles, self.lastDir = ImgurApiKey.imgurApiKey if haveImgurApiKey else None, [], None
self.menus = (
("&File",
self.canvasNew, self.canvasOpen, self.canvasOpenRecent, self.canvasSave, self.canvasSaveAs, NID_MENU_SEP,
("&Export...", self.canvasExportAsAnsi, self.canvasExportToClipboard, self.canvasExportImgur, self.canvasExportPastebin, self.canvasExportAsPng,),
("&Import...", self.canvasImportAnsi, self.canvasImportFromClipboard, self.canvasImportSauce,),
NID_MENU_SEP,
self.canvasExit,
),
)
self.toolBars = ()
self.exiting, self.lastFiles, self.lastDir, self.snapshots = False, [], None, {}
self.imgurApiKey = ImgurApiKey.imgurApiKey if haveImgurApiKey else None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -6,17 +6,24 @@
from GuiFrame import GuiCommandDecorator
from RoarWindowAbout import RoarWindowAbout
from RoarWindowMelp import RoarWindowMelp
import webbrowser, wx
class RoarCanvasCommandsHelp():
# {{{ canvasAbout(self, event)
@GuiCommandDecorator("About", "&About", None, None, True)
@GuiCommandDecorator("About roar", "&About roar", None, None, True)
def canvasAbout(self, event):
RoarWindowAbout(self.parentFrame)
# }}}
#
# __init__(self)
def __init__(self):
self.menus, self.toolBars = (("&Help", self.canvasAbout,),), ()
@GuiCommandDecorator("View melp?", "View &melp?", None, [wx.MOD_NONE, wx.WXK_F1], True)
def canvasMelp(self, event):
RoarWindowMelp(self.parentFrame)
@GuiCommandDecorator("Open &issue on GitHub", "Open &issue on GitHub", None, None, True)
def canvasNewIssueGitHub(self, event):
webbrowser.open("https://github.com/lalbornoz/roar/issues/new")
@GuiCommandDecorator("Visit GitHub website", "Visit &GitHub website", None, None, True)
def canvasVisitGitHub(self, event):
webbrowser.open("https://www.github.com/lalbornoz/roar")
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,63 +4,29 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from GuiFrame import GuiCommandListDecorator
from OperatorFlipHorizontal import OperatorFlipHorizontal
from OperatorFlipVertical import OperatorFlipVertical
from OperatorInvert import OperatorInvert
from GuiFrame import GuiCommandListDecorator
from OperatorRotate import OperatorRotate
from OperatorTile import OperatorTile
from ToolObject import ToolObject
import copy, wx
class RoarCanvasCommandsOperators():
# {{{ canvasOperator(self, f, idx)
@GuiCommandListDecorator(0, "Flip", "&Flip", None, None, None)
@GuiCommandListDecorator(1, "Flip horizontally", "Flip &horizontally", None, None, None)
@GuiCommandListDecorator(2, "Invert", "&Invert", None, None, None)
@GuiCommandListDecorator(2, "Invert colours", "&Invert colours", None, None, None)
@GuiCommandListDecorator(3, "Rotate", "&Rotate", None, None, None)
@GuiCommandListDecorator(4, "Tile", "&Tile", None, None, False)
def canvasOperator(self, f, idx):
def canvasOperator_(event):
applyOperator = [OperatorFlipVertical, OperatorFlipHorizontal, OperatorInvert][idx]()
if (self.currentTool.__class__ == ToolObject) \
and (self.currentTool.toolState >= self.currentTool.TS_SELECT):
region = self.currentTool.getRegion(self.parentCanvas.canvas)
else:
region = self.parentCanvas.canvas.map
region = applyOperator.apply(copy.deepcopy(region))
if (self.currentTool.__class__ == ToolObject) \
and (self.currentTool.toolState >= self.currentTool.TS_SELECT):
viewRect = self.parentCanvas.GetViewStart()
if self.parentCanvas.popupEventDc == None:
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
else:
eventDc = self.parentCanvas.popupEventDc
self.currentTool.setRegion(self.parentCanvas.canvas, None, region, [len(region[0]), len(region)], self.currentTool.external)
self.currentTool.onSelectEvent(self.parentCanvas.canvas, (0, 0), self.parentCanvas.dispatchPatchSingle, eventDc, True, wx.MOD_NONE, None, self.currentTool.targetRect, viewRect)
else:
viewRect = self.parentCanvas.GetViewStart()
if self.parentCanvas.popupEventDc == None:
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
else:
eventDc = self.parentCanvas.popupEventDc
self.parentCanvas.canvas.journal.begin()
dirty = False
for numRow in range(len(region)):
for numCol in range(len(region[numRow])):
if not dirty:
dirty = True
self.parentCanvas.dispatchPatchSingle(eventDc, False, [numCol, numRow, *region[numRow][numCol]], viewRect)
self.parentCanvas.canvas.journal.end()
self.parentCanvas.commands.update(dirty=dirty, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel)
self.currentOperator = [OperatorFlipVertical, OperatorFlipHorizontal, OperatorInvert, OperatorRotate, OperatorTile][idx]()
self.operatorState = None
self.parentCanvas.applyOperator(self.currentTool, self.parentCanvas.brushPos, None, False, self.currentOperator, self.parentCanvas.GetViewStart())
setattr(canvasOperator_, "attrDict", f.attrList[idx])
return canvasOperator_
# }}}
#
# __init__(self)
def __init__(self):
self.menus = (
("&Operators",
self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2),
),
)
self.toolBars = ()
self.currentOperator, self.operatorState = None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -6,51 +6,42 @@
from GuiFrame import GuiSelectDecorator
from ToolCircle import ToolCircle
from ToolErase import ToolErase
from ToolFill import ToolFill
from ToolLine import ToolLine
from ToolObject import ToolObject
from ToolPickColour import ToolPickColour
from ToolRect import ToolRect
from ToolText import ToolText
import wx
class RoarCanvasCommandsTools():
# {{{ canvasTool(self, f, idx)
@GuiSelectDecorator(0, "Circle", "&Circle", ["toolCircle.png"], [wx.ACCEL_CTRL, ord("C")], False)
@GuiSelectDecorator(1, "Cursor", "C&ursor", ["toolCursor.png"], [wx.ACCEL_CTRL, ord("U")], False)
@GuiSelectDecorator(2, "Fill", "&Fill", ["toolFill.png"], [wx.ACCEL_CTRL, ord("F")], False)
@GuiSelectDecorator(3, "Line", "&Line", ["toolLine.png"], [wx.ACCEL_CTRL, ord("L")], False)
@GuiSelectDecorator(4, "Object", "&Object", ["toolObject.png"], [wx.ACCEL_CTRL, ord("E")], False)
@GuiSelectDecorator(5, "Rectangle", "&Rectangle", ["toolRect.png"], [wx.ACCEL_CTRL, ord("R")], True)
@GuiSelectDecorator(6, "Text", "&Text", ["toolText.png"], [wx.ACCEL_CTRL, ord("T")], False)
@GuiSelectDecorator(0, "Circle", "&Circle", ["toolCircle.png"], [wx.MOD_NONE, wx.WXK_F4], False)
@GuiSelectDecorator(1, "Cursor", "C&ursor", ["toolCursor.png"], [wx.MOD_NONE, wx.WXK_F2], False)
@GuiSelectDecorator(2, "Erase", "&Erase", ["toolErase.png"], [wx.MOD_NONE, wx.WXK_F9], False)
@GuiSelectDecorator(3, "Fill", "&Fill", ["toolFill.png"], [wx.MOD_NONE, wx.WXK_F5], False)
@GuiSelectDecorator(4, "Line", "&Line", ["toolLine.png"], [wx.MOD_NONE, wx.WXK_F6], False)
@GuiSelectDecorator(5, "Object", "&Object", ["toolObject.png"], [wx.MOD_NONE, wx.WXK_F8], False)
@GuiSelectDecorator(6, "Pick colour", "&Pick colour", ["toolPickColour.png"], [wx.MOD_NONE, wx.WXK_F10], False)
@GuiSelectDecorator(7, "Rectangle", "&Rectangle", ["toolRect.png"], [wx.MOD_NONE, wx.WXK_F3], True)
@GuiSelectDecorator(8, "Text", "&Text", ["toolText.png"], [wx.MOD_NONE, wx.WXK_F7], False)
def canvasTool(self, f, idx):
def canvasTool_(event):
self.lastTool, self.currentTool = self.currentTool, [ToolCircle, None, ToolFill, ToolLine, ToolObject, ToolRect, ToolText][idx]
if (self.currentTool.__class__ == ToolObject) \
and (self.currentTool.toolState > self.currentTool.TS_NONE) \
and self.currentTool.external:
self.parentCanvas.dropTarget.done()
self.lastTool, self.currentTool = self.currentTool, [ToolCircle, None, ToolErase, ToolFill, ToolLine, ToolObject, ToolPickColour, ToolRect, ToolText][idx]
if self.currentTool != None:
self.currentTool = self.currentTool()
self.parentFrame.menuItemsById[self.canvasTool.attrList[idx]["id"]].Check(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[idx]["id"]].GetToolBar()
toolBar.ToggleTool(self.canvasTool.attrList[idx]["id"], True)
if self.currentTool != None:
self.update(toolName=self.currentTool.name)
viewRect = self.parentCanvas.GetViewStart()
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect)
self.parentCanvas.applyTool(eventDc, True, None, None, self.parentCanvas.brushPos, False, False, False, self.currentTool, viewRect)
else:
self.update(toolName="Cursor")
self.currentOperator, self.operatorState = None, None
self.update(currentTool=self.currentTool, currentToolIdx=idx, operator=None)
self.parentCanvas.applyTool(None, True, None, None, None, self.parentCanvas.brushPos, False, False, False, self.currentTool, None, force=True)
setattr(canvasTool_, "attrDict", f.attrList[idx])
setattr(canvasTool_, "isSelect", True)
return canvasTool_
# }}}
#
# __init__(self)
def __init__(self):
self.menus = (
("&Tools",
self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 6), self.canvasTool(self.canvasTool, 4),
),
)
self.toolBars = ()
self.currentTool, self.lastTool = None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -5,16 +5,16 @@
#
from GuiWindow import GuiWindow
from Rtl import natural_sort
from RtlPlatform import getLocalConfPathName
from ToolObject import ToolObject
import json, wx, sys
import time
from ToolText import ToolText
import copy, hashlib, json, os, pdb, re, time, wx, sys
class RoarCanvasWindowDropTarget(wx.TextDropTarget):
# {{{ done(self)
def done(self):
self.inProgress = False
# }}}
# {{{ OnDropText(self, x, y, data)
def OnDropText(self, x, y, data):
rc = False
if ((self.parent.commands.currentTool.__class__ != ToolObject) \
@ -22,209 +22,340 @@ class RoarCanvasWindowDropTarget(wx.TextDropTarget):
and (not self.inProgress):
try:
dropMap, dropSize = json.loads(data)
viewRect = self.parent.GetViewStart()
rectX, rectY = x - (x % self.parent.backend.cellSize[0]), y - (y % self.parent.backend.cellSize[1])
mapX, mapY = int(rectX / self.parent.backend.cellSize[0] if rectX else 0), int(rectY / self.parent.backend.cellSize[1] if rectY else 0)
mapPoint = [m + n for m, n in zip((mapX, mapY), viewRect)]
viewRect = self.parent.GetViewStart(); mapPoint = [m + n for m, n in zip((mapX, mapY), viewRect)];
self.parent.commands.lastTool, self.parent.commands.currentTool = self.parent.commands.currentTool, ToolObject()
self.parent.commands.currentTool.setRegion(self.parent.canvas, mapPoint, dropMap, dropSize, external=True)
self.parent.commands.update(toolName=self.parent.commands.currentTool.name)
self.parent.parent.menuItemsById[self.parent.commands.canvasOperator.attrList[4]["id"]].Enable(True)
self.parent.commands.update(currentTool=self.parent.commands.currentTool, currentToolIdx=5)
eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, viewRect)
self.parent.applyTool(eventDc, True, None, None, self.parent.brushPos, False, False, False, self.parent.commands.currentTool, viewRect)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
self.parent.applyTool(eventDc, True, None, None, None, self.parent.brushPos, False, False, False, self.parent.commands.currentTool, viewRect)
eventDc.SetDeviceOrigin(*eventDcOrigin)
rc = True; self.inProgress = True;
except:
with wx.MessageDialog(self.parent, "Error: {}".format(sys.exc_info()[1]), "", wx.OK | wx.OK_DEFAULT) as dialog:
dialogChoice = dialog.ShowModal()
return rc
# }}}
# {{{ __init__(self, parent)
def __init__(self, parent):
super().__init__(); self.inProgress, self.parent = False, parent;
# }}}
class RoarCanvasWindow(GuiWindow):
# {{{ _drawPatch(self, eventDc, isCursor, patch, viewRect)
def _drawPatch(self, eventDc, isCursor, patch, viewRect):
if not self.canvas.dirtyCursor:
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.canvas.dirtyCursor = True
if self.backend.drawPatch(eventDc, patch, viewRect) and isCursor:
patchDeltaCell = self.canvas.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
self.canvas.journal.pushCursor(patchDelta)
# }}}
def _applyPatches(self, eventDc, patches, patchesCursor, rc, commitUndo=True, dirty=True, eventDcResetOrigin=True, hideCursor=True):
if rc:
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
if eventDcResetOrigin:
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
if hideCursor and (((patches != None) and (len(patches) > 0)) or ((patchesCursor != None) and (len(patchesCursor) > 0))):
self.cursorHide(eventDc, False, True)
if (patches != None) and (len(patches) > 0):
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
if dirty and not self.dirty:
self.dirty = True
if commitUndo:
self.canvas.begin()
for patch in patches if patches != None else []:
self.canvas.applyPatch(patch, commitUndo=commitUndo)
if commitUndo:
self.canvas.end()
if hideCursor and (patchesCursor != None):
self.cursorShow(eventDc, False, patchesCursor=patchesCursor)
if eventDcResetOrigin:
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.commands.update(cellPos=self.brushPos, dirty=self.dirty, undoLevel=self.canvas.patchesUndoLevel)
return eventDc
# {{{ applyTool(self, eventDc, eventMouse, keyChar, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect)
def applyTool(self, eventDc, eventMouse, keyChar, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect):
if mapPoint != None:
mapPoint = [a + b for a, b in zip(mapPoint, viewRect)]
dirty, self.canvas.dirtyCursor, rc = False, False, False
self.canvas.journal.begin()
def _snapshotsReset(self):
self._snapshotFiles, self._snapshotsUpdateLast = [], time.time()
self.commands._snapshotsReset()
if self.commands.canvasPathName != None:
canvasPathName = os.path.abspath(self.commands.canvasPathName)
canvasFileName = os.path.basename(canvasPathName)
canvasPathNameHash = hashlib.sha1(canvasPathName.encode()).hexdigest()
self._snapshotsDirName = os.path.join(getLocalConfPathName(), "{}_{}".format(canvasFileName, canvasPathNameHash))
if os.path.exists(self._snapshotsDirName):
for snapshotFile in natural_sort([f for f in os.listdir(self._snapshotsDirName) \
if (re.match(r'snapshot\d+\.txt$', f)) and os.path.isfile(os.path.join(self._snapshotsDirName, f))]):
self.commands._snapshotsPush(os.path.join(self._snapshotsDirName, snapshotFile))
else:
self._snapshotsDirName = None
def _snapshotsUpdate(self):
if self._snapshotsDirName != None:
t = time.time()
if (t > self._snapshotsUpdateLast) and ((t - self._snapshotsUpdateLast) >= (5 * 60)):
try:
if not os.path.exists(self._snapshotsDirName):
os.makedirs(self._snapshotsDirName)
self._snapshotFiles = natural_sort([f for f in os.listdir(self._snapshotsDirName)
if (re.match(r'snapshot\d+\.txt$', f)) and os.path.isfile(os.path.join(self._snapshotsDirName, f))])
if self._snapshotFiles != []:
snapshotsCount, snapshotIndex = len(self._snapshotFiles), abs(int(re.match(r'snapshot(\d+)\.txt$', self._snapshotFiles[-1])[1])) + 1
else:
snapshotsCount, snapshotIndex = 0, 1
snapshotPathName = os.path.join(self._snapshotsDirName, "snapshot{}.txt".format(snapshotIndex));
self.commands.update(snapshotStatus=True)
with open(snapshotPathName, "w", encoding="utf-8") as outFile:
self.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
self.canvas.exportStore.exportTextFile(self.canvas.map, self.canvas.size, outFile)
self.SetCursor(wx.Cursor(wx.NullCursor))
self.commands.update(snapshotStatus=False); self._snapshotsUpdateLast = time.time();
self._snapshotFiles += [os.path.basename(snapshotPathName)];
self.commands._snapshotsPush(snapshotPathName)
if len(self._snapshotFiles) > 72:
for snapshotFile in self._snapshotFiles[:len(self._snapshotFiles) - 8]:
self.commands._snapshotsPop(os.path.join(self._snapshotsDirName, snapshotFile))
os.remove(os.path.join(self._snapshotsDirName, snapshotFile)); snapshotsCount -= 1;
except:
print("Exception during _snapshotsUpdate(): {}".format(sys.exc_info()[1]))
def _windowEraseBackground(self, eventDc):
viewRect = self.GetViewStart()
canvasSize, panelSize = [a * b for a, b in zip(self.backend.canvasSize, self.backend.cellSize)], self.GetSize()
if viewRect != (0, 0):
viewRect = [a * b for a, b in zip(self.backend.cellSize, viewRect)]
canvasSize = [a - b for a, b in zip(canvasSize, viewRect)]
rectangles, pens, brushes = [], [], []
if panelSize[0] > canvasSize[0]:
brushes += [self.bgBrush]; pens += [self.bgPen];
rectangles += [[canvasSize[0], 0, panelSize[0] - canvasSize[0], panelSize[1]]]
if panelSize[1] > canvasSize[1]:
brushes += [self.bgBrush]; pens += [self.bgPen];
rectangles += [[0, canvasSize[1], panelSize[0], panelSize[1] - canvasSize[1]]]
if len(rectangles) > 0:
eventDc.DrawRectangleList(rectangles, pens, brushes)
def applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect):
eventDc, patches, patchesCursor, rc = self.backend.getDeviceContext(self.GetClientSize(), self), None, None, True
if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT):
region = currentTool.getRegion(self.canvas)
else:
region = self.canvas.map
if hasattr(operator, "apply2"):
self.commands.update(operator=self.commands.currentOperator.name)
if mouseLeftDown:
self.commands.operatorState = True if self.commands.operatorState == None else self.commands.operatorState
region = operator.apply2(mapPoint, mousePoint, region, copy.deepcopy(region))
elif self.commands.operatorState != None:
self.commands.currentOperator = None; self.commands.update(operator=None); rc = False;
else:
region = operator.apply(copy.deepcopy(region)); self.commands.currentOperator = None;
if rc:
if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT):
currentTool.setRegion(self.canvas, None, region, [len(region[0]), len(region)], currentTool.external)
rc, patches, patchesCursor = currentTool.onSelectEvent(self.canvas, (0, 0), True, wx.MOD_NONE, None, currentTool.targetRect)
patchesCursor = [] if patchesCursor == None else patchesCursor
patchesCursor += currentTool._drawSelectRect(currentTool.targetRect)
self._applyPatches(eventDc, patches, patchesCursor, rc)
else:
patches = []
for numRow in range(len(region)):
for numCol in range(len(region[numRow])):
patches += [[numCol, numRow, *region[numRow][numCol]]]
self._applyPatches(eventDc, patches, patchesCursor, rc)
if (patches != None) and (len(patches) > 0):
self._snapshotsUpdate()
return rc
def applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect, force=False):
patches, patchesCursor, rc = None, None, False
if viewRect == None:
viewRect = self.GetViewStart()
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
if eventMouse:
if ((mapPoint[0] < self.canvas.size[0]) \
and (mapPoint[1] < self.canvas.size[1])) \
and ((self.lastCellState == None) \
or (self.lastCellState != [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)])):
self.brushPos = list(mapPoint)
self.lastCellState = [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)]
self.lastCellState = None if force else self.lastCellState
if ((mapPoint[0] < self.canvas.size[0]) and (mapPoint[1] < self.canvas.size[1])) \
and ((self.lastCellState == None) or (self.lastCellState != [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)])):
self.brushPos = list(mapPoint) if tool.__class__ != ToolText else self.brushPos
if tool != None:
rc, dirty = tool.onMouseEvent(self.brushColours, self.brushSize, self.canvas, self.dispatchPatchSingle, eventDc, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
rc, patches, patchesCursor = tool.onMouseEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown)
else:
self.dispatchPatchSingle(eventDc, True, [*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "] , viewRect)
rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]]
self.lastCellState = [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)]
else:
if tool != None:
rc, dirty = tool.onKeyboardEvent(self.brushColours, self.brushSize, self.canvas, self.dispatchPatchSingle, eventDc, keyChar, keyModifiers, self.brushPos, viewRect)
else:
self.dispatchPatchSingle(eventDc, True, [*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "] , viewRect)
if dirty:
self.dirty = True
self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel)
else:
self.commands.update(cellPos=mapPoint if mapPoint else self.brushPos)
self.canvas.journal.end()
if rc and (tool.__class__ == ToolObject):
if tool.toolState > tool.TS_NONE:
self.commands.update(undoInhibit=True)
elif tool.toolState == tool.TS_NONE:
if tool.external:
self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool
self.commands.update(toolName=self.commands.currentTool.name, undoInhibit=False)
self.dropTarget.done()
else:
self.commands.update(undoInhibit=False)
return rc
# }}}
# {{{ dispatchDeltaPatches(self, deltaPatches)
def dispatchDeltaPatches(self, deltaPatches):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
if self.canvas.dirtyCursor:
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.canvas.dirtyCursor = False
for patch in deltaPatches:
if patch == None:
continue
elif patch[0] == "resize":
del eventDc; self.resize(patch[1:], False); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart());
else:
self.canvas._commitPatch(patch); self.backend.drawPatch(eventDc, patch, self.GetViewStart());
# }}}
# {{{ dispatchPatch(self, eventDc, isCursor, patch, viewRect)
def dispatchPatch(self, eventDc, isCursor, patch, viewRect):
if self.canvas.dispatchPatch(isCursor, patch, False if isCursor else True):
self._drawPatch(eventDc, isCursor, patch, viewRect)
# }}}
# {{{ dispatchPatchSingle(self, eventDc, isCursor, patch, viewRect)
def dispatchPatchSingle(self, eventDc, isCursor, patch, viewRect):
if self.canvas.dispatchPatchSingle(isCursor, patch, False if isCursor else True):
self._drawPatch(eventDc, isCursor, patch, viewRect)
# }}}
# {{{ resize(self, newSize, commitUndo=True)
def resize(self, newSize, commitUndo=True):
oldSize = [0, 0] if self.canvas.map == None else self.canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
if self.canvas.resize(newSize, commitUndo):
super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)])
self.backend.resize(newSize, self.backend.cellSize)
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
if deltaSize[0] > 0:
for numRow in range(oldSize[1]):
for numNewCol in range(oldSize[0], newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numRow, 1, 1, 0, " "], viewRect)
if deltaSize[1] > 1:
for numNewRow in range(oldSize[1], newSize[1]):
for numNewCol in range(newSize[0]):
self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect)
self.commands.update(size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel)
# }}}
# {{{ update(self, newSize, commitUndo=True, newCanvas=None)
def update(self, newSize, commitUndo=True, newCanvas=None):
self.resize(newSize, commitUndo)
self.canvas.update(newSize, newCanvas)
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
for numRow in range(newSize[1]):
for numCol in range(newSize[0]):
self.backend.drawPatch(eventDc, [numCol, numRow, *self.canvas.map[numRow][numCol]], self.GetViewStart())
# }}}
rc, patches, patchesCursor = tool.onKeyboardEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, keyChar, keyCode, keyModifiers, self.brushPos)
elif mapPoint != None:
rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]]
if rc:
for patch in patches if patches != None else []:
if ((patch[2] == -1) and (patch[3] == -1)) \
and (patch[0] < self.canvas.size[0]) \
and (patch[1] < self.canvas.size[1]):
patch[2:] = self.canvas.map[patch[1]][patch[0]]
self._applyPatches(eventDc, patches, patchesCursor, rc)
if (tool.__class__ == ToolObject) and (tool.external, tool.toolState) == (True, tool.TS_NONE):
self.dropTarget.done(); self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool;
self.commands.update(currentTool=self.commands.currentTool)
if (patches != None) and (len(patches) > 0):
self._snapshotsUpdate()
return rc
def cursorHide(self, eventDc=None, eventDcResetOrigin=True, reset=False):
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
if eventDcResetOrigin:
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patchesCursor = self.canvas.popCursor(reset=reset); patchesCursor_ = [];
for cursorCell in [p[:2] for p in patchesCursor]:
if (cursorCell[0] < self.canvas.size[0]) and (cursorCell[1] < self.canvas.size[1]):
patchesCursor_ += [[*cursorCell, *self.canvas.map[cursorCell[1]][cursorCell[0]]]]
if len(patchesCursor_) > 0:
self.backend.drawPatches(self.canvas, eventDc, patchesCursor_, False)
if eventDcResetOrigin:
eventDc.SetDeviceOrigin(*eventDcOrigin)
return eventDc
def cursorShow(self, eventDc=None, eventDcResetOrigin=True, patchesCursor=None):
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
if eventDcResetOrigin:
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
if patchesCursor == None:
patchesCursor = self.canvas.popCursor(reset=False)
elif len(patchesCursor) > 0:
self.canvas.pushCursor(patchesCursor)
if (patchesCursor != None) and (len(patchesCursor) > 0):
self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
if eventDcResetOrigin:
eventDc.SetDeviceOrigin(*eventDcOrigin)
def onEnterWindow(self, event):
self.lastCellState = None
# {{{ onKeyboardInput(self, event)
def onKeyboardInput(self, event):
keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers()
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
if (keyCode == wx.WXK_PAUSE) \
and (keyModifiers == wx.MOD_SHIFT):
import pdb; pdb.set_trace()
if (keyCode, keyModifiers,) == (wx.WXK_PAUSE, wx.MOD_SHIFT,):
pdb.set_trace()
elif keyCode in (wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP):
if (keyCode == wx.WXK_DOWN) and (self.brushPos[1] < (self.canvas.size[1] - 1)):
self.brushPos = [self.brushPos[0], self.brushPos[1] + 1]
elif (keyCode == wx.WXK_LEFT) and (self.brushPos[0] > 0):
self.brushPos = [self.brushPos[0] - 1, self.brushPos[1]]
elif (keyCode == wx.WXK_RIGHT) and (self.brushPos[0] < (self.canvas.size[0] - 1)):
self.brushPos = [self.brushPos[0] + 1, self.brushPos[1]]
elif (keyCode == wx.WXK_UP) and (self.brushPos[1] > 0):
self.brushPos = [self.brushPos[0], self.brushPos[1] - 1]
if keyCode == wx.WXK_DOWN:
self.brushPos[1] = (self.brushPos[1] + 1) % self.canvas.size[1]
elif keyCode == wx.WXK_LEFT:
self.brushPos[0] = (self.brushPos[0] - 1) if (self.brushPos[0] > 0) else (self.canvas.size[0] - 1)
elif keyCode == wx.WXK_RIGHT:
self.brushPos[0] = (self.brushPos[0] + 1) % self.canvas.size[0]
elif keyCode == wx.WXK_UP:
self.brushPos[1] = (self.brushPos[1] - 1) if (self.brushPos[1] > 0) else (self.canvas.size[1] - 1)
self.commands.update(cellPos=self.brushPos)
self.applyTool(eventDc, True, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect)
else:
if not self.applyTool(eventDc, False, chr(event.GetUnicodeKey()), keyModifiers, None, None, None, None, self.commands.currentTool, viewRect):
self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect)
elif (chr(event.GetUnicodeKey()) == " ") and (self.commands.currentTool.__class__ != ToolText):
if not self.applyTool(eventDc, True, None, None, event.GetModifiers(), self.brushPos, False, True, False, self.commands.currentTool, viewRect):
event.Skip()
# }}}
# {{{ onEnterWindow(self, event)
def onEnterWindow(self, event):
self.lastCellState = None
# }}}
# {{{ onLeaveWindow(self, event)
else:
self.brushPos[0] = (self.brushPos[0] + 1) if (self.brushPos[0] < (self.canvas.size[0] - 1)) else 0
self.commands.update(cellPos=self.brushPos)
self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect)
elif not self.applyTool(eventDc, False, chr(event.GetUnicodeKey()), keyCode, keyModifiers, None, None, None, None, self.commands.currentTool, viewRect):
event.Skip()
def onLeaveWindow(self, event):
if False:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, self.GetViewStart())
self.cursorHide()
self.lastCellState = None
# }}}
# {{{ onMouseInput(self, event)
def onMouseInput(self, event):
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
mouseDragging, mouseLeftDown, mouseRightDown = event.Dragging(), event.LeftIsDown(), event.RightIsDown()
self.lastMouseState = [mouseDragging, mouseLeftDown, mouseRightDown]
mapPoint = self.backend.xlateEventPoint(event, eventDc, viewRect)
if mouseRightDown \
if viewRect != (0, 0):
mapPoint = [a + b for a, b in zip(mapPoint, viewRect)]
if self.commands.currentOperator != None:
self.applyOperator(self.commands.currentTool, mapPoint, mouseLeftDown, event.GetLogicalPosition(eventDc), self.commands.currentOperator, viewRect)
elif mouseRightDown \
and (self.commands.currentTool.__class__ == ToolObject) \
and (self.commands.currentTool.toolState >= self.commands.currentTool.TS_SELECT):
self.popupEventDc = eventDc; self.PopupMenu(self.operatorsMenu); self.popupEventDc = None;
elif not self.applyTool(eventDc, True, None, event.GetModifiers(), mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, self.commands.currentTool, viewRect):
elif not self.applyTool(eventDc, True, None, None, event.GetModifiers(), mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, self.commands.currentTool, viewRect):
event.Skip()
# }}}
# {{{ onMouseWheel(self, event)
def onMouseWheel(self, event):
if event.GetModifiers() == wx.MOD_CONTROL:
cd = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1
newCellSize = [cs + cd for cs in self.backend.cellSize]
if (newCellSize[0] > 0) and (newCellSize[1] > 0):
self.backend.cellSize = newCellSize
delta, modifiers = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1, event.GetModifiers()
if modifiers == (wx.MOD_CONTROL | wx.MOD_ALT):
newFontSize = self.backend.fontSize + delta
if newFontSize > 0:
self.Freeze()
self.backend.fontSize = newFontSize; self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize;
super().resize([a * b for a, b in zip(self.canvas.size, self.backend.cellSize)])
self.backend.resize(self.canvas.size, self.backend.cellSize)
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
patches = []
for numRow in range(self.canvas.size[1]):
for numCol in range(len(self.canvas.map[numRow])):
self._drawPatch(eventDc, False, [numCol, numRow, *self.canvas.map[numRow][numCol]], viewRect)
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]]
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
self.cursorHide(eventDc, False, False)
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
self.cursorShow(eventDc, False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.Thaw(); self._windowEraseBackground(wx.ClientDC(self));
elif modifiers == (wx.MOD_CONTROL | wx.MOD_SHIFT):
self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None)
elif modifiers == wx.MOD_CONTROL:
self.commands.canvasBrushSize(self.commands.canvasBrushSize, 2, 1 if delta > 0 else 0)(None)
else:
event.Skip()
# }}}
# {{{ onPaint(self, event)
def onPaint(self, event):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart())
# }}}
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
self.backend.onPaint(self.GetClientSize(), self, viewRect)
del eventDc; self._windowEraseBackground(wx.PaintDC(self));
#
# __init__(self, backend, canvas, cellSize, commands, parent, parentFrame, pos, scrollStep, size): initialisation method
def __init__(self, backend, canvas, cellSize, commands, parent, parentFrame, pos, scrollStep, size):
super().__init__(parent, pos, scrollStep)
self.size = size
self.backend, self.canvas, self.cellSize, self.commands, self.parentFrame = backend(self.size, cellSize), canvas, cellSize, commands(self, parentFrame), parentFrame
self.brushColours, self.brushPos, self.brushSize, self.dirty, self.lastCellState = [4, 1], [0, 0], [1, 1], False, None
self.popupEventDc = None
self.dropTarget = RoarCanvasWindowDropTarget(self)
def resize(self, newSize, commitUndo=True, dirty=True, freeze=True):
if freeze:
self.Freeze()
viewRect = self.GetViewStart()
oldSize = [0, 0] if self.canvas.map == None else self.canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
rc, newCells = self.canvas.resize(self.brushColours, newSize, commitUndo)
if rc:
self.backend.resize(newSize); self.scrollStep = self.backend.cellSize;
super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)])
self._applyPatches(None, newCells, None, True, commitUndo=False, dirty=True, hideCursor=False)
self.Scroll(*viewRect); self.dirty = dirty;
self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.patchesUndoLevel)
if commitUndo:
self._snapshotsUpdate()
if freeze:
self.cursorShow(); self.Thaw(); self._windowEraseBackground(wx.ClientDC(self));
def undo(self, redo=False):
freezeFlag, patches, patchesDelta = False, [], self.canvas.popUndo(redo)
for patch in [p for p in patchesDelta if p != None]:
if patch[0] == "resize":
if not freezeFlag:
self.Freeze(); freezeFlag = True;
self.resize(patch[1:], False, freeze=False)
else:
patches += [patch]
eventDc = self._applyPatches(None, patches, None, True, commitUndo=False, hideCursor=False)
self.cursorShow(eventDc, True, None)
if freezeFlag:
self.Thaw(); self._windowEraseBackground(wx.ClientDC(self));
def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True):
self.resize(newSize, commitUndo, dirty); self.canvas.update(newSize, newCanvas);
patches = []
for numRow in range(newSize[1]):
for numCol in range(newSize[0]):
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]]
self._applyPatches(None, patches, None, True, dirty=False)
def __init__(self, backend, canvas, commands, parent, pos, size):
super().__init__(parent, pos); self.parent, self.size = parent, size;
self.backend, self.canvas, self.commands = backend(self.size), canvas, commands(self, parent)
self.bgBrush, self.bgPen = wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID), wx.Pen(self.GetBackgroundColour(), 1)
self.brushColours, self.brushPos, self.brushSize, = [3, -1], [0, 0], [1, 1]
self.dirty, self.lastCellState, self.lastMouseState = False, None, [False, False, False]
self.dropTarget, self.popupEventDc = RoarCanvasWindowDropTarget(self), None
for event, handler in ((wx.EVT_ERASE_BACKGROUND, lambda event: None,), (wx.EVT_MOUSEWHEEL, self.onMouseWheel,),):
self.Bind(event, handler)
self.SetDropTarget(self.dropTarget)
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel)
self._snapshotsReset()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -15,50 +15,61 @@ from glob import glob
import os, random, sys, wx
class RoarClient(GuiFrame):
# {{{ _getIconPathName(self)
def _getIconPathName(self):
iconPathNames = glob(os.path.join("assets", "images", "logo*.bmp"))
return iconPathNames[random.randint(0, len(iconPathNames) - 1)]
# }}}
# {{{ _initToolBitmaps(self, toolBars)
def _initToolBitmaps(self, toolBars):
basePathName = os.path.join(os.path.dirname(sys.argv[0]), "assets", "images")
for toolBar in toolBars:
for toolBarItem in [i for i in toolBar if i != NID_TOOLBAR_HSEP]:
toolBarItem.attrDict["icon"] = self.loadBitmap(basePathName, toolBarItem.attrDict["icon"])
# }}}
# {{{ onChar(self, event)
def onChar(self, event):
self.canvasPanel.onKeyboardInput(event)
# }}}
# {{{ onMouseWheel(self, event)
def onClose(self, event):
if not self.canvasPanel.commands.exiting:
closeFlag = self.canvasPanel.commands._promptSaveChanges()
else:
closeFlag = True
if closeFlag:
event.Skip();
def onMouseWheel(self, event):
self.canvasPanel.GetEventHandler().ProcessEvent(event)
# }}}
#
# __init__(self, parent, defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), defaultCellSize=(7, 14), size=(840, 630), title=""): initialisation method
def __init__(self, parent, defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), defaultCellSize=(7, 14), size=(840, 630), title=""):
def onSize(self, event):
pass
def __init__(self, parent, defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), size=(840, 640), title=""):
super().__init__(self._getIconPathName(), size, parent, title)
self.canvas = Canvas(defaultCanvasSize)
self.canvasPanel = RoarCanvasWindow(GuiCanvasWxBackend, self.canvas, defaultCellSize, RoarCanvasCommands, self.panelSkin, self, defaultCanvasPos, defaultCellSize, defaultCanvasSize)
self.loadAccels(self.canvasPanel.commands.menus, self.canvasPanel.commands.toolBars)
self.canvasPanel = RoarCanvasWindow(GuiCanvasWxBackend, self.canvas, RoarCanvasCommands, self, defaultCanvasPos, defaultCanvasSize)
self.loadAccels(self.canvasPanel.commands.accels, self.canvasPanel.commands.menus, self.canvasPanel.commands.toolBars)
self.loadMenus(self.canvasPanel.commands.menus)
self._initToolBitmaps(self.canvasPanel.commands.toolBars)
self.loadToolBars(self.canvasPanel.commands.toolBars)
self.canvasPanel.commands.canvasNew(None)
self.canvasPanel.commands.canvasTool(self.canvasPanel.commands.canvasTool, 1)(None)
self.canvasPanel.commands.update(brushSize=self.canvasPanel.brushSize, colours=self.canvasPanel.brushColours)
self.addWindow(self.canvasPanel, expand=True)
self.assetsWindow = RoarAssetsWindow(GuiCanvasWxBackend, defaultCellSize, self)
self.addWindow(self.canvasPanel)
self.assetsWindow = RoarAssetsWindow(GuiCanvasWxBackend, self)
self.canvasPanel.commands.canvasAssetsWindowShow(None)
# XXX
self.canvasPanel.operatorsMenu = wx.Menu()
for menuItem in self.canvasPanel.commands.menus[3][1:]:
menuItemWindow = self.canvasPanel.operatorsMenu.Append(menuItem.attrDict["id"], menuItem.attrDict["label"], menuItem.attrDict["caption"])
self.Bind(wx.EVT_MENU, self.onMenu, menuItemWindow)
self.canvasPanel.commands.canvasClearRecent.attrDict["id"] = wx.NewId()
self.canvasPanel.commands.canvasOpenRecent.attrDict["menu"].AppendSeparator()
self.canvasPanel.commands.canvasRestore.attrDict["menu"].AppendSeparator()
self.canvasPanel.commands.canvasRestoreFile.attrDict["id"] = wx.NewId()
menuItemWindow = self.canvasPanel.commands.canvasOpenRecent.attrDict["menu"].Append(self.canvasPanel.commands.canvasClearRecent.attrDict["id"], self.canvasPanel.commands.canvasClearRecent.attrDict["label"], self.canvasPanel.commands.canvasClearRecent.attrDict["caption"])
menuItemWindow = self.canvasPanel.commands.canvasRestore.attrDict["menu"].Append(self.canvasPanel.commands.canvasRestoreFile.attrDict["id"], self.canvasPanel.commands.canvasRestoreFile.attrDict["label"], self.canvasPanel.commands.canvasRestoreFile.attrDict["caption"])
self.canvasPanel.commands.canvasOpenRecent.attrDict["menu"].Bind(wx.EVT_MENU, self.canvasPanel.commands.canvasClearRecent, menuItemWindow)
self.canvasPanel.commands.canvasRestore.attrDict["menu"].Bind(wx.EVT_MENU, self.canvasPanel.commands.canvasRestoreFile, menuItemWindow)
self.Bind(wx.EVT_CLOSE, self.onClose); self.Bind(wx.EVT_SIZE, self.onSize);
self.toolBarPanes[0].BestSize(0, 0).Right(); self.toolBarPanes[1].BestSize(0, 0).Right(); self.auiManager.Update();
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -8,22 +8,19 @@ from glob import glob
import os, random, wx, wx.adv
class RoarWindowAbout(wx.Dialog):
# {{{ onButtonRoar(self, event)
def onButtonRoar(self, event):
self.Destroy()
# }}}
#
# __init__(self, parent, minSize=(320, 300), title="About roar")
def __init__(self, parent, minSize=(320, 300), title="About roar"):
super().__init__(parent, size=minSize, title=title)
self.panel, self.sizer, self.sizerV = wx.Panel(self), wx.FlexGridSizer(2, 2, 4, 4), wx.BoxSizer(wx.VERTICAL)
self.panel.SetBackgroundColour(wx.Colour(0, 0, 0)); self.panel.SetForegroundColour(wx.Colour(0, 187, 0));
logoPathNames = glob(os.path.join("assets", "images", "logo*.bmp"))
logoPathName = logoPathNames[random.randint(0, len(logoPathNames) - 1)]
self.logo = wx.StaticBitmap(self.panel, -1, wx.Bitmap(logoPathName))
self.title = wx.StaticText(self.panel, label="roar -- mIRC art editor for Windows && Linux\n__ROAR_RELEASE_VERSION__\nhttps://www.github.com/lalbornoz/roar/\nCopyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>", style=wx.ALIGN_CENTER)
self.title = wx.StaticText(self.panel, label="roar -- mIRC art editor for Windows && Linux\n__ROAR_RELEASE_VERSION__\nhttps://www.github.com/lalbornoz/roar/\nCopyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>\nhttps://www.lucioillanes.de\n", style=wx.ALIGN_CENTER)
self.title.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL, underline=False))
labelsText = ["&roar!", "&ROAR!", "&roaaaaaaar!", "&ROAROARAOR", "_&ROAR_"]
labelText = labelsText[random.randint(0, len(labelsText) - 1)]
@ -39,7 +36,7 @@ class RoarWindowAbout(wx.Dialog):
self.SetTitle(title)
soundBitePathNames = glob(os.path.join("assets", "audio", "roar*.wav"))
soundBitePathName = soundBitePathNames[random.randint(0, len(logoPathNames) - 1)]
soundBitePathName = soundBitePathNames[random.randint(0, len(soundBitePathNames) - 1)]
self.soundBite = wx.adv.Sound(soundBitePathName)
if self.soundBite.IsOk():
self.soundBite.Play(wx.adv.SOUND_ASYNC)

32
libroar/RoarWindowMelp.py Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
#
# RoarWindowMelp.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
import os, wx
class RoarWindowMelp(wx.Dialog):
def onButtonRoar(self, event):
self.Destroy()
def __init__(self, parent, minSize=(320, 300), title="melp?"):
super().__init__(parent, size=minSize, title=title)
self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL)
self.panel.SetBackgroundColour(wx.Colour(0, 0, 0)); self.panel.SetForegroundColour(wx.Colour(0, 187, 0));
with open(os.path.join("assets", "text", "melp.txt"), "r") as fileObject:
helpLabel = "".join(fileObject.readlines())
self.title = wx.StaticText(self.panel, label=helpLabel, style=wx.ALIGN_LEFT)
self.title.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, underline=False))
self.buttonRoar = wx.Button(self.panel, label="&explodes.")
self.buttonRoar.Bind(wx.EVT_BUTTON, self.onButtonRoar)
self.sizer.AddMany(((self.title, 1, wx.ALL | wx.CENTER | wx.EXPAND, 4), (self.buttonRoar, 0, wx.ALL | wx.CENTER, 4),))
self.panel.SetSizerAndFit(self.sizer)
newSize = self.sizer.ComputeFittingWindowSize(self)
self.SetSize((newSize[0] + 128, newSize[1],)); self.Center();
self.SetTitle(title)
self.ShowModal()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -11,13 +11,11 @@ import select, socket, time
class IrcClient:
"""Non-blocking abstraction over the IRC protocol"""
# {{{ close(self): Close connection to server
def close(self):
if self.clientSocket != None:
self.clientSocket.close()
self.clientSocket = self.clientSocketFile = None;
# }}}
# {{{ connect(self, localAddr=None, preferFamily=socket.AF_INET, timeout=None): Connect to server and register w/ optional timeout
def connect(self, localAddr=None, preferFamily=socket.AF_INET, timeout=None):
gaiInfo = socket.getaddrinfo(self.serverHname, self.serverPort,
preferFamily, socket.SOCK_STREAM, socket.IPPROTO_TCP)
@ -41,8 +39,7 @@ class IrcClient:
self.queue("NICK", self.clientNick)
self.queue("USER", self.clientIdent, "0", "0", self.clientGecos)
return True
# }}}
# {{{ 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):
@ -51,8 +48,7 @@ class IrcClient:
else:
msg += args[argNum] + " "
self.clientQueue.append((msg + "\r\n").encode())
# }}}
# {{{ readline(self, timeout=30): Read and parse single line from server into canonicalised list, honouring timers
def readline(self, timeout=30):
if self.clientNextTimeout:
timeNow = time.time()
@ -85,8 +81,7 @@ class IrcClient:
else:
msg = [""] + msg[0:]
return msg
# }}}
# {{{ unqueue(self, timeout=15): Send all queued lines to server, honouring timers
def unqueue(self, timeout=15):
while self.clientQueue:
msg = self.clientQueue[0]; msgLen = len(msg); msgBytesSent = 0;
@ -111,10 +106,7 @@ class IrcClient:
msg = msg[msgBytesSent:]; msgLen -= msgBytesSent;
del self.clientQueue[0]
return True
# }}}
#
# __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos): initialisation method
def __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos):
self.clientGecos, self.clientIdent, self.clientNick = clientGecos, clientIdent, clientNick
self.clientNextTimeout, self.clientQueue, self.clientSocket, self.clientSocketFile = None, None, None, None

18
librtl/Rtl.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
#
# Rtl.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# This project is licensed under the terms of the MIT licence.
#
import re
def flatten(l):
return [li for l_ in l for li in l_]
def natural_sort(l):
convert = lambda text: int(text) if text.isdigit() else text.lower()
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -7,10 +7,8 @@
import os, platform
# {{{ getLocalConfPathName(*args)
def getLocalConfPathName(*args):
vname = "LOCALAPPDATA" if platform.system() == "Windows" else "HOME"
return os.path.join(os.getenv(vname), "roar", *args)
# }}}
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,13 +5,10 @@
#
class Tool(object):
# {{{ onKeyboardEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyChar, keyModifiers, mapPoint, viewRect)
def onKeyboardEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyChar, keyModifiers, mapPoint, viewRect):
return False, False
# }}}
# {{{ onMouseEvent(self, brushColours, brushSize, dispatchFn, eventDc, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
return False, False
# }}}
def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint):
return False, None, None
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
return False, None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,35 +5,60 @@
#
from Tool import Tool
import wx
class ToolCircle(Tool):
name = "Circle"
TS_NONE = 0
TS_ORIGIN = 1
#
# onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
brushColours, dirty = brushColours.copy(), False
if mouseLeftDown:
brushColours[1] = brushColours[0]
elif mouseRightDown:
brushColours[0] = brushColours[1]
else:
brushColours[1] = brushColours[0]
_brushSize = brushSize[0] * 2
originPoint, radius = (_brushSize / 2, _brushSize / 2), _brushSize
def _drawCircle(self, brushColours, canvas, mapPoint, originPoint, radius):
cells, patches = [], []
for brushY in range(-radius, radius + 1):
cells += [[]]
for brushX in range(-radius, radius + 1):
if ((brushX ** 2) + (brushY ** 2) < (((radius ** 2) + radius) * 0.8)):
patch = [ \
mapPoint[0] + int(originPoint[0] + brushX), \
mapPoint[1] + int(originPoint[1] + brushY), \
*brushColours, 0, " "]
if mouseLeftDown or mouseRightDown:
if not dirty:
dirty = True
dispatchFn(eventDc, False, patch, viewRect); dispatchFn(eventDc, True, patch, viewRect);
cells[-1] += [[mapPoint[i] + int(originPoint[i] + o) for i, o in zip((0, 1,), (brushX, brushY,))]]
if cells[-1] == []:
del cells[-1]
for numRow in range(len(cells)):
for numCol in range(len(cells[numRow])):
point = cells[numRow][numCol]
if (point[0] >= 0) and (point[1] >= 0):
if ((numRow == 0) or (numRow == (len(cells) - 1))) \
or ((numCol == 0) or (numCol == (len(cells[numRow]) - 1))):
patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "]
elif ((numRow > 0) and (cells[numRow][numCol][0] < cells[numRow - 1][0][0])) \
or ((numRow < len(cells)) and (cells[numRow][numCol][0] < cells[numRow + 1][0][0])):
patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "]
elif ((numRow > 0) and (cells[numRow][numCol][0] > cells[numRow - 1][-1][0])) \
or ((numRow < len(cells)) and (cells[numRow][numCol][0] > cells[numRow + 1][-1][0])):
patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "]
else:
dispatchFn(eventDc, True, patch, viewRect)
return True, dirty
patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "]
patches += [patch]
return patches
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
brushColours, brushSize = [brushColours[1], brushColours[0]] if mouseRightDown else brushColours, brushSize[0] * 2
if self.toolState == self.TS_NONE:
originPoint, radius, targetPoint = list(mapPoint), brushSize, (brushSize / 2,) * 2
if (keyModifiers == wx.MOD_CONTROL) and (mouseLeftDown or mouseRightDown):
self.brushColours, isCursor, self.originPoint, self.toolState = brushColours, True, originPoint, self.TS_ORIGIN
else:
isCursor = not (mouseLeftDown or mouseRightDown)
elif self.toolState == self.TS_ORIGIN:
if mapPoint[0] > self.originPoint[0]:
brushSize += (mapPoint[0] - self.originPoint[0]); brushSize = brushSize + (brushSize % 2);
brushColours, originPoint, radius, targetPoint = self.brushColours, self.originPoint, brushSize, (brushSize / 2,) * 2
if not (mouseLeftDown or mouseRightDown):
self.brushColours, isCursor, self.originPoint, self.toolState = None, False, None, self.TS_NONE
else:
isCursor = True
patches = self._drawCircle(brushColours, canvas, originPoint, targetPoint, radius)
return True, patches if not isCursor else None, patches if isCursor else None
def __init__(self, *args):
super().__init__(*args); self.brushColours, self.originPoint, self.toolState = None, None, self.TS_NONE;
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

32
libtools/ToolErase.py Normal file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
#
# ToolErase.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from Tool import Tool
class ToolErase(Tool):
name = "Erase"
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
brushColours, brushSize, isCursor, patches, patchesCursor = list(brushColours), list(brushSize), not (mouseLeftDown or mouseRightDown), [], []
if brushSize[0] > 1:
brushSize[0] *= 2
for brushRow in range(brushSize[1]):
for brushCol in range(brushSize[0]):
if mouseLeftDown:
patches += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "]]
elif mouseRightDown \
and ((mapPoint[0] + brushCol) < canvas.size[0]) \
and ((mapPoint[1] + brushRow) < canvas.size[1]) \
and (canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][1] == brushColours[1]):
if canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][3] == " ":
patches += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[0], brushColours[0], *canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][2:]]]
else:
patches += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][0], brushColours[0], *canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][2:]]]
else:
patchesCursor += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "]]
return True, patches if not isCursor else None, patchesCursor
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,26 +5,22 @@
#
from Tool import Tool
import wx
class ToolFill(Tool):
name = "Fill"
#
# onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
dirty, pointsDone, pointStack, testColour, = False, [], [list(mapPoint)], canvas.map[mapPoint[1]][mapPoint[0]][0:2]
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
isCursor, patches, pointsDone, pointStack, testChar, testColour = not (mouseLeftDown or mouseRightDown), [], [], [list(mapPoint)], canvas.map[mapPoint[1]][mapPoint[0]][3], canvas.map[mapPoint[1]][mapPoint[0]][0:2]
if mouseLeftDown or mouseRightDown:
if mouseRightDown:
brushColours = [brushColours[1], brushColours[0]]
fillColour = brushColours[0] if mouseLeftDown else brushColours[1]
while len(pointStack) > 0:
point = pointStack.pop()
pointCell = canvas.map[point[1]][point[0]]
if (pointCell[0:2] == testColour) \
if ((pointCell[1] == testColour[1]) and ((pointCell[3] == testChar) or (keyModifiers == wx.MOD_CONTROL))) \
or ((pointCell[3] == " ") and (pointCell[1] == testColour[1])):
if not point in pointsDone:
if not dirty:
dirty = True
dispatchFn(eventDc, False, [*point, brushColours[0], brushColours[0], 0, " "], viewRect)
patches += [[*point, fillColour, fillColour, 0, " "]]
if point[0] > 0:
pointStack.append([point[0] - 1, point[1]])
if point[0] < (canvas.size[0] - 1):
@ -35,8 +31,7 @@ class ToolFill(Tool):
pointStack.append([point[0], point[1] + 1])
pointsDone += [point]
else:
patch = [mapPoint[0], mapPoint[1], brushColours[0], brushColours[0], 0, " "]
dispatchFn(eventDc, True, patch, viewRect)
return True, dirty
patches = [[mapPoint[0], mapPoint[1], brushColours[0], brushColours[0], 0, " "]]
return True, patches if not isCursor else None, patches if isCursor else None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -11,10 +11,8 @@ class ToolLine(Tool):
TS_NONE = 0
TS_ORIGIN = 1
# {{{ _getLine(self, brushColours, brushSize, dispatchFn, eventDc, isCursor, originPoint, targetPoint, viewRect)
def _getLine(self, brushColours, brushSize, dispatchFn, eventDc, isCursor, originPoint, targetPoint, viewRect):
dirty = False
originPoint, targetPoint = originPoint.copy(), targetPoint.copy()
def _getLine(self, brushColours, brushSize, isCursor, originPoint, targetPoint):
originPoint, patches, targetPoint = originPoint.copy(), [], 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]
@ -24,36 +22,28 @@ class ToolLine(Tool):
lineXX, lineXY, lineYX, lineYY = 0, lineYSign, lineXSign, 0
pointDelta = [pointDelta[1], pointDelta[0]]
lineD = 2 * pointDelta[1] - pointDelta[0]; lineY = 0;
pointsDone = []
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, viewRect); dispatchFn(eventDc, True, patch, viewRect);
else:
if not dirty:
dirty = True
dispatchFn(eventDc, True, patch, viewRect)
if not ([originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, originPoint[1] + lineX * lineXY + lineY * lineYY] in pointsDone):
patches += [[ \
originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, \
originPoint[1] + lineX * lineXY + lineY * lineYY, \
*brushColours, 0, " "]]
pointsDone += [[originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, originPoint[1] + lineX * lineXY + lineY * lineYY]]
if lineD > 0:
lineD -= pointDelta[0]; lineY += 1;
lineD += pointDelta[1]
return dirty
# }}}
# {{{ _pointDelta(self, a, b)
return patches
def _pointDelta(self, a, b):
return [a2 - a1 for a1, a2 in zip(a, b)]
# }}}
# {{{ _pointSwap(self, a, b)
def _pointSwap(self, a, b):
return [b, a]
# }}}
#
# onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
brushColours, dirty = brushColours.copy(), False
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
brushColours, isCursor, patches, rc = brushColours.copy(), not (mouseLeftDown or mouseRightDown), [], False
if mouseLeftDown:
brushColours[1] = brushColours[0]
elif mouseRightDown:
@ -62,20 +52,24 @@ class ToolLine(Tool):
brushColours[1] = brushColours[0]
if self.toolState == self.TS_NONE:
if mouseLeftDown or mouseRightDown:
self.toolColours, self.toolOriginPoint, self.toolState = brushColours, list(mapPoint), self.TS_ORIGIN
dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "], viewRect)
self.toolOriginPoint, self.toolState = list(mapPoint), self.TS_ORIGIN
isCursor, patches, rc = True, [], True
for brushCol in range(brushSize[0]):
if ((mapPoint[0] + brushCol) < canvas.size[0]) \
and (mapPoint[1] < canvas.size[1]):
patches += [[mapPoint[0] + brushCol, mapPoint[1], *brushColours, 0, " "]]
elif self.toolState == self.TS_ORIGIN:
originPoint, targetPoint = self.toolOriginPoint, list(mapPoint)
dirty = self._getLine(self.toolColours, brushSize, dispatchFn, eventDc, mouseLeftDown or mouseRightDown, originPoint, targetPoint, viewRect)
if mouseLeftDown or mouseRightDown:
self.toolColours, self.toolOriginPoint, self.toolState = None, None, self.TS_NONE
else:
return False, dirty
return True, dirty
patches = self._getLine(brushColours, brushSize, False, originPoint, targetPoint)
self.toolOriginPoint, self.toolState = None, self.TS_NONE
else:
patches = self._getLine(brushColours, brushSize, True, originPoint, targetPoint)
rc = True
return rc, patches if not isCursor else None, patches if isCursor else None
# __init__(self, *args): initialisation method
def __init__(self, *args):
super().__init__(*args)
self.toolColours, self.toolOriginPoint, self.toolState = None, None, self.TS_NONE
self.toolOriginPoint, self.toolState = None, self.TS_NONE
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -14,21 +14,21 @@ class ToolObject(Tool):
TS_SELECT = 2
TS_TARGET = 3
# {{{ _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, selectRect, viewRect)
def _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, selectRect, viewRect):
def _dispatchSelectEvent(self, canvas, keyModifiers, mapPoint, mouseLeftDown, selectRect):
if mouseLeftDown:
disp, isCursor = [mapPoint[m] - self.lastAtPoint[m] for m in [0, 1]], True
newTargetRect = [[selectRect[n][m] + disp[m] for m in [0, 1]] for n in [0, 1]]
self.lastAtPoint = list(mapPoint)
else:
disp, isCursor, newTargetRect = [0, 0], True, selectRect.copy()
dirty = self.onSelectEvent(canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect, viewRect)
self._drawSelectRect(newTargetRect, dispatchFn, eventDc, viewRect)
rc, patches, patchesCursor = self.onSelectEvent(canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect)
patchesCursor = [] if patchesCursor == None else patchesCursor
patchesCursor += self._drawSelectRect(newTargetRect)
self.targetRect = newTargetRect
return dirty
# }}}
# {{{ _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect)
def _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect):
return rc, patches, patchesCursor
def _drawSelectRect(self, rect):
patches = []
rectFrame = [[rect[m][n] for n in [0, 1]] for m in (0, 1)]
if rectFrame[0][0] > rectFrame[1][0]:
rectFrame[0][0], rectFrame[1][0] = rectFrame[1][0], rectFrame[0][0]
@ -37,29 +37,21 @@ class ToolObject(Tool):
curColours, rectFrame = [0, 0], [[rectFrame[m[0]][n] + m[1] for n in [0, 1]] for m in [[0, -1], [1, +1]]]
for rectX in range(rectFrame[0][0], rectFrame[1][0] + 1):
curColours = [1, 1] if curColours == [0, 0] else [0, 0]
dispatchFn(eventDc, True, [rectX, rectFrame[0][1], *curColours, 0, " "], viewRect)
dispatchFn(eventDc, True, [rectX, rectFrame[1][1], *curColours, 0, " "], viewRect)
patches += [[rectX, rectFrame[0][1], *curColours, 0, " "], [rectX, rectFrame[1][1], *curColours, 0, " "]]
for rectY in range(rectFrame[0][1], rectFrame[1][1] + 1):
curColours = [1, 1] if curColours == [0, 0] else [0, 0]
dispatchFn(eventDc, True, [rectFrame[0][0], rectY, *curColours, 0, " "], viewRect)
dispatchFn(eventDc, True, [rectFrame[1][0], rectY, *curColours, 0, " "], viewRect)
# }}}
# {{{ _mouseEventTsNone(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
def _mouseEventTsNone(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect):
self.substract = False
if self.external:
dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "], viewRect)
else:
if mouseLeftDown:
self.targetRect, self.toolState = [list(mapPoint), []], self.TS_ORIGIN
else:
dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "], viewRect)
return False
# }}}
# {{{ _mouseEventTsOrigin(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
def _mouseEventTsOrigin(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect):
patches += [[rectFrame[0][0], rectY, *curColours, 0, " "], [rectFrame[1][0], rectY, *curColours, 0, " "]]
return patches
def _mouseEventTsNone(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown):
patchesCursor = [[*mapPoint, brushColours[0], brushColours[0], 0, " "]]; self.substract = False;
if (not self.external) and mouseLeftDown:
self.targetRect, self.toolState = [list(mapPoint), []], self.TS_ORIGIN
return True, None, patchesCursor
def _mouseEventTsOrigin(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown):
self.targetRect[1] = list(mapPoint)
if mouseLeftDown:
if not mouseLeftDown:
if self.targetRect[0][0] > self.targetRect[1][0]:
self.targetRect[0][0], self.targetRect[1][0] = self.targetRect[1][0], self.targetRect[0][0]
if self.targetRect[0][1] > self.targetRect[1][1]:
@ -70,14 +62,10 @@ class ToolObject(Tool):
for numCol in range((self.targetRect[1][0] - self.targetRect[0][0]) + 1):
rectX, rectY = self.targetRect[0][0] + numCol, self.targetRect[0][1] + numRow
self.objectMap[numRow].append(canvas.map[rectY][rectX])
self._drawSelectRect(self.targetRect, dispatchFn, eventDc, viewRect)
else:
self._drawSelectRect(self.targetRect, dispatchFn, eventDc, viewRect)
return False
# }}}
# {{{ _mouseEventTsSelect(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
def _mouseEventTsSelect(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect):
dirty = False
return True, None, self._drawSelectRect(self.targetRect)
def _mouseEventTsSelect(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown):
rc, patches, patchesCursor = False, None, None
if mouseLeftDown:
if (mapPoint[0] >= (self.targetRect[0][0] - 1)) \
and (mapPoint[0] <= (self.targetRect[1][0] + 1)) \
@ -85,74 +73,79 @@ class ToolObject(Tool):
and (mapPoint[1] <= (self.targetRect[1][1] + 1)):
self.lastAtPoint, self.toolState = list(mapPoint), self.TS_TARGET
else:
dirty = self.onSelectEvent(canvas, (0, 0), dispatchFn, eventDc, False, keyModifiers, self.targetRect.copy(), self.targetRect, viewRect)
self._drawSelectRect(self.targetRect, dispatchFn, eventDc, viewRect)
rc, patches, patchesCursor = self.onSelectEvent(canvas, (0, 0), False, keyModifiers, self.targetRect.copy(), self.targetRect)
patchesCursor = [] if patchesCursor == None else patchesCursor
patchesCursor += self._drawSelectRect(self.targetRect)
self.objectMap, self.objectSize, self.targetRect, self.toolState = None, None, None, self.TS_NONE
else:
dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, self.targetRect, viewRect)
return dirty
# }}}
# {{{ _mouseEventTsTarget(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
def _mouseEventTsTarget(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect):
rc, patches, patchesCursor = self._dispatchSelectEvent(canvas, keyModifiers, mapPoint, mouseLeftDown, self.targetRect)
return rc, patches, patchesCursor
def _mouseEventTsTarget(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown):
rc, patches, patchesCursor = False, None, None
if (keyModifiers == wx.MOD_CONTROL) and (self.srcRect == self.targetRect):
self.substract = True
dirty = False
if mouseLeftDown:
dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, self.targetRect, viewRect)
rc, patches, patchesCursor = self._dispatchSelectEvent(canvas, keyModifiers, mapPoint, mouseLeftDown, self.targetRect)
else:
self.toolState = self.TS_SELECT
return True, dirty
# }}}
return rc, patches, patchesCursor
# {{{ getRegion(self, canvas)
def getRegion(self, canvas):
return self.objectMap
# }}}
# {{{ onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
dirty = False
if self.toolState == self.TS_NONE:
dirty = self._mouseEventTsNone(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_SELECT:
dirty = self._mouseEventTsSelect(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_ORIGIN:
dirty = self._mouseEventTsOrigin(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_TARGET:
dirty = self._mouseEventTsTarget(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint):
if (ord(keyChar) == wx.WXK_ESCAPE) and (self.toolState >= self.TS_SELECT):
rc, patches, patchesCursor = self.onSelectEvent(canvas, (0, 0), False, keyModifiers, self.targetRect.copy(), self.targetRect)
patchesCursor = [] if patchesCursor == None else patchesCursor
patchesCursor += self._drawSelectRect(self.targetRect)
self.objectMap, self.objectSize, self.targetRect, self.toolState = None, None, None, self.TS_NONE
else:
return False, dirty
return True, dirty
# }}}
# {{{ onSelectEvent(self, canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect, viewRect)
def onSelectEvent(self, canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect, viewRect):
dirty = False
rc, patches, patchesCursor = False, None, None
return rc, patches, patchesCursor
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
if self.toolState == self.TS_NONE:
rc, patches, patchesCursor = self._mouseEventTsNone(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
elif self.toolState == self.TS_SELECT:
rc, patches, patchesCursor = self._mouseEventTsSelect(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
elif self.toolState == self.TS_ORIGIN:
rc, patches, patchesCursor = self._mouseEventTsOrigin(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
elif self.toolState == self.TS_TARGET:
rc, patches, patchesCursor = self._mouseEventTsTarget(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
else:
rc, patches, patchesCursor = False, None, None
return rc, patches, patchesCursor
def onSelectEvent(self, canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect):
patches = []
if self.external:
for numRow in range(len(self.objectMap)):
for numCol in range(len(self.objectMap[numRow])):
rectX, rectY = selectRect[0][0] + numCol, selectRect[0][1] + numRow
dirty = False if isCursor else True
cellNew = self.objectMap[numRow][numCol]
if (cellNew[1] == -1) and (cellNew[3] == " "):
if ((rectY + disp[1]) < canvas.size[1]) and ((rectX + disp[0]) < canvas.size[0]):
cellNew = canvas.map[rectY + disp[1]][rectX + disp[0]]
dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellNew], viewRect)
patches += [[rectX + disp[0], rectY + disp[1], *cellNew]]
else:
if self.substract:
for numRow in range(self.srcRect[0][1], self.srcRect[1][1]):
for numCol in range(self.srcRect[0][0], self.srcRect[1][0]):
if ((numCol < selectRect[0][0]) or (numCol > selectRect[1][0])) \
or ((numRow < selectRect[0][1]) or (numRow > selectRect[1][1])):
dirty = False if isCursor else True
dispatchFn(eventDc, isCursor, [numCol, numRow, 1, 1, 0, " "], viewRect)
patches += [[numCol, numRow, 1, 1, 0, " "]]
for numRow in range(len(self.objectMap)):
for numCol in range(len(self.objectMap[numRow])):
cellOld = self.objectMap[numRow][numCol]
rectX, rectY = selectRect[0][0] + numCol, selectRect[0][1] + numRow
dirty = False if isCursor else True
dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellOld], viewRect)
return dirty
# }}}
# {{{ setRegion(self, canvas, mapPoint, objectMap, objectSize, external=True)
cellNew = self.objectMap[numRow][numCol]
if (cellNew[1] == -1) and (cellNew[3] == " "):
if ((rectY + disp[1]) < canvas.size[1]) and ((rectX + disp[0]) < canvas.size[0]):
cellNew = canvas.map[rectY + disp[1]][rectX + disp[0]]
patches += [[rectX + disp[0], rectY + disp[1], *cellNew]]
return True, patches if not isCursor else None, patches if isCursor else None
def setRegion(self, canvas, mapPoint, objectMap, objectSize, external=True):
self.external, self.toolState = external, self.TS_SELECT
if mapPoint != None:
@ -162,13 +155,11 @@ class ToolObject(Tool):
elif self.objectSize != objectSize:
if self.objectSize == None:
self.objectSize = objectSize
self.targetRect[1] = [t + d for t, d in zip(self.targetRect[1], (a - b for a, b in zip(self.objectSize, objectSize)))]
self.targetRect[1] = [t + d for t, d in zip(self.targetRect[1], (b - a for a, b in zip(self.objectSize, objectSize)))]
if self.srcRect == None:
self.srcRect = self.targetRect
self.objectMap, self.objectSize = objectMap, objectSize
# }}}
# __init__(self, *args): initialisation method
def __init__(self, *args):
super().__init__(*args)
self.external, self.lastAtPoint, self.srcRect, self.substract, \

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
#
# ToolPickColour.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from Tool import Tool
class ToolPickColour(Tool):
name = "Pick colour"
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
if (mapPoint[0] < canvas.size[0]) and (mapPoint[1] < canvas.size[1]):
if mouseLeftDown:
if canvas.map[mapPoint[1]][mapPoint[0]][3] == " ":
brushColours[0] = canvas.map[mapPoint[1]][mapPoint[0]][1]
else:
brushColours[0] = canvas.map[mapPoint[1]][mapPoint[0]][0]
elif mouseRightDown:
brushColours[1] = canvas.map[mapPoint[1]][mapPoint[0]][1]
return True, None, [[*mapPoint, *brushColours, 0, ""]]
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,32 +5,43 @@
#
from Tool import Tool
import wx
class ToolRect(Tool):
name = "Rectangle"
TS_NONE = 0
TS_ORIGIN = 1
#
# onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
brushColours, dirty = brushColours.copy(), False
if mouseLeftDown:
brushColours[1] = brushColours[0]
elif mouseRightDown:
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 = [mapPoint[0] + brushCol, mapPoint[1] + brushRow, *brushColours, 0, " "]
if mouseLeftDown or mouseRightDown:
if not dirty:
dirty = True
dispatchFn(eventDc, False, patch, viewRect); dispatchFn(eventDc, True, patch, viewRect);
def _drawRect(self, brushColours, canvas, rect):
patches = []
for brushRow in range((rect[3] - rect[1]) + 1):
for brushCol in range((rect[2] - rect[0]) + 1):
if (brushCol in [0, (rect[2] - rect[0])]) or (brushRow in [0, (rect[3] - rect[1])]):
patchColours = [brushColours[0]] * 2
else:
dispatchFn(eventDc, True, patch, viewRect)
return True, dirty
patchColours = [brushColours[1]] * 2
patches += [[rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "]]
return patches
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
brushColours = [brushColours[1], brushColours[0]] if mouseRightDown else brushColours
brushSize, patches = list(brushSize), []; brushSize[0] *= 2 if brushSize[0] > 1 else brushSize[0];
if self.toolState == self.TS_NONE:
if (keyModifiers == wx.MOD_CONTROL) and (mouseLeftDown or mouseRightDown):
self.brushColours, isCursor, self.originPoint, self.toolState = list(brushColours), True, list(mapPoint), self.TS_ORIGIN
else:
isCursor = not (mouseLeftDown or mouseRightDown)
rect = [*mapPoint, *([a + b for a, b in zip(brushSize, mapPoint)] if brushSize[0] > 1 else mapPoint)]
elif self.toolState == self.TS_ORIGIN:
rect = [*self.originPoint, *mapPoint]
if not (mouseLeftDown or mouseRightDown):
brushColours, isCursor, self.brushColours, self.originPoint, self.toolState = self.brushColours, False, None, None, self.TS_NONE
else:
isCursor = True
patches = self._drawRect(brushColours, canvas, rect)
return True, patches if not isCursor else None, patches if isCursor else None
def __init__(self, *args):
super().__init__(*args); self.brushColours, self.originPoint, self.toolState = None, None, self.TS_NONE;
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,43 +5,127 @@
#
from Tool import Tool
import string, wx
import re, string, wx
class ToolText(Tool):
name = "Text"
arabicCombiningRegEx = r'^[\u064B-\u065F\uFE70-\uFE72\uFE74\uFE76-\uFE7F]+$'
arabicRegEx = r'^[\u0621-\u063A\u0640-\u064A]+$'
rtlRegEx = r'^[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]+$'
#
# onKeyboardEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyChar, keyModifiers, mapPoint, viewRect)
def onKeyboardEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyChar, keyModifiers, mapPoint, viewRect):
if (ord(keyChar) != wx.WXK_NONE) \
and (not keyChar in set("\t\n\v\f\r")) \
and ((ord(keyChar) >= 32) if ord(keyChar) < 127 else True) \
and (keyModifiers in (wx.MOD_NONE, wx.MOD_SHIFT)):
rc, dirty = True, True
if self.textPos == None:
self.textPos = list(mapPoint)
dispatchFn(eventDc, False, [*self.textPos, *brushColours, 0, keyChar], viewRect)
if self.textPos[0] < (canvas.size[0] - 1):
self.textPos[0] += 1
elif self.textPos[1] < (canvas.size[1] - 1):
self.textPos[0] = 0; self.textPos[1] += 1;
else:
self.textPos = [0, 0]
def _checkRtl(self, canvas, brushPos, keyChar):
rtlFlag = False
if (keyChar != None) and re.match(self.rtlRegEx, keyChar):
rtlFlag = True
else:
rc, dirty = False, False
return rc, dirty
lastX, lastY = brushPos[0], brushPos[1]
while True:
if canvas.map[lastY][lastX][3] == " ":
if (lastX + 1) >= canvas.size[0]:
if lastY == 0:
break
else:
lastX, lastY = 0, lastY - 1
else:
lastX += 1
elif re.match(self.arabicRegEx, canvas.map[lastY][lastX][3]):
rtlFlag = True
if (lastX + 1) >= canvas.size[0]:
if lastY == 0:
break
else:
lastX, lastY = 0, lastY - 1
else:
lastX += 1
else:
break
return rtlFlag
#
# onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
def onMouseEvent(self, brushColours, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, viewRect):
def _processKeyChar(self, brushColours, brushPos, canvas, keyChar, keyModifiers):
patches, rc = [], False
if (ord(keyChar) != wx.WXK_NONE) \
and (not keyChar in set("\t\n\v\f\r")) \
and ((ord(keyChar) >= 32) if ord(keyChar) < 127 else True):
patches += [[*brushPos, *brushColours, 0, keyChar]]
if not self._checkRtl(canvas, brushPos, keyChar):
if brushPos[0] < (canvas.size[0] - 1):
brushPos[0] += 1
elif brushPos[1] < (canvas.size[1] - 1):
brushPos[0], brushPos[1] = 0, brushPos[1] + 1
else:
brushPos[0], brushPos[1] = 0, 0
else:
if brushPos[0] > 0:
brushPos[0] -= 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = canvas.size[0] - 1, brushPos[1] - 1
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, canvas.size[1] - 1
rc = True
return rc, patches
def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint):
patches, patchesCursor, rc = [], [], False
if re.match(self.arabicCombiningRegEx, keyChar):
rc = True
elif keyCode == wx.WXK_CONTROL_V:
rc = True
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) and wx.TheClipboard.Open():
inBuffer = wx.TextDataObject()
if wx.TheClipboard.GetData(inBuffer):
brushPosOriginX = brushPos[0]
for inBufferChar in list(inBuffer.GetText()):
if inBufferChar in set("\r\n"):
if brushPos[1] < (canvas.size[1] - 1):
brushPos[0], brushPos[1] = brushPosOriginX, brushPos[1] + 1
else:
brushPos[0], brushPos[1] = brushPosOriginX, 0
elif not re.match(self.arabicCombiningRegEx, inBufferChar):
rc_, patches_ = self._processKeyChar(brushColours, brushPos, canvas, inBufferChar, 0)
patches += patches_
rc = True if rc_ else rc
if rc:
patchesCursor += [[*brushPos, *brushColours, 0, "_"]]
wx.TheClipboard.Close()
else:
rc, error = False, "Clipboard does not contain text data and/or cannot be opened"
elif keyCode == wx.WXK_BACK:
if ((brushPos[0] + 1) >= canvas.size[0]):
lastBrushPos = [0, brushPos[1] - 1] if brushPos[1] > 0 else [0, 0]
else:
lastBrushPos = [brushPos[0] + 1, brushPos[1]]
if not self._checkRtl(canvas, lastBrushPos, None):
patches += [[*brushPos, *brushColours, 0, " "]]
if brushPos[0] > 0:
brushPos[0] -= 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = canvas.size[0] - 1, brushPos[1] - 1
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, canvas.size[1] - 1
else:
if brushPos[0] < (canvas.size[0] - 1):
brushPos[0] += 1
elif brushPos[1] > 0:
brushPos[0], brushPos[1] = 0, brushPos[1] - 1
else:
brushPos[0], brushPos[1] = canvas.size[0] - 1, 0
rc = True; patchesCursor += [[*brushPos, *brushColours, 0, "_"]];
elif keyCode == wx.WXK_RETURN:
if brushPos[1] < (canvas.size[1] - 1):
brushPos[0], brushPos[1] = 0, brushPos[1] + 1
else:
brushPos[0], brushPos[1] = 0, 0
rc = True; patchesCursor += [[*brushPos, *brushColours, 0, "_"]];
elif not (keyModifiers in (wx.MOD_ALT, wx.MOD_ALTGR, wx.MOD_CONTROL)):
rc, patches_ = self._processKeyChar(brushColours, brushPos, canvas, keyChar, keyModifiers)
patches += patches_
if rc:
patchesCursor += [[*brushPos, *brushColours, 0, "_"]]
return rc, patches, patchesCursor
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
if mouseLeftDown or mouseRightDown:
self.textPos = list(mapPoint)
dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, "_"], viewRect)
return True, False
# __init__(self, *args): initialisation method
def __init__(self, *args):
super().__init__(*args)
self.textColours = self.textPos = None
brushPos[0], brushPos[1] = atPoint[0], atPoint[1]
return True, None, [[*brushPos, *brushColours, 0, "_"]]
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -1 +0,0 @@
.

1
requirements.txt Symbolic link
View File

@ -0,0 +1 @@
assets/text/requirements.txt

13
roar.py
View File

@ -17,21 +17,26 @@ import wx
def main(*argv):
localConfDirName = getLocalConfPathName()
if not os.path.exists(localConfDirName):
os.makedirs(localConfirName)
os.makedirs(localConfDirName)
wxApp, roarClient = wx.App(False), RoarClient(None)
argv0, argv = argv[0], argv[1:]
roarClient.canvasPanel.commands._loadRecent()
roarClient.canvasPanel.commands._recentDirLoad(); roarClient.canvasPanel.commands._recentLoad();
if len(argv) >= 1:
if (len(argv) >= 2) and (argv[1].endswith(".lst")):
roarClient.assetsWindow._load_list(argv[1])
roarClient.canvasPanel.commands.canvasPathName = argv[0]
roarClient.canvasPanel._snapshotsReset()
rc, error = roarClient.canvasPanel.canvas.importStore.importTextFile(argv[0])
if rc:
roarClient.canvasPanel.update(roarClient.canvasPanel.canvas.importStore.inSize, False, roarClient.canvasPanel.canvas.importStore.outMap)
roarClient.canvasPanel.update(roarClient.canvasPanel.canvas.importStore.inSize, False, roarClient.canvasPanel.canvas.importStore.outMap, dirty=False)
roarClient.canvasPanel.commands.update(pathName=argv[0], undoLevel=-1)
roarClient.canvasPanel.commands._pushRecent(argv[0])
roarClient.canvasPanel.commands._recentPush(argv[0])
roarClient.canvasPanel.commands.canvasTool(roarClient.canvasPanel.commands.canvasTool, 1)(None)
else:
print("error: {}".format(error), file=sys.stderr)
else:
roarClient.canvasPanel.commands.canvasNew(None)
roarClient.canvasPanel.commands.canvasTool(roarClient.canvasPanel.commands.canvasTool, 1)(None)
wxApp.MainLoop()
if __name__ == "__main__":
main(*sys.argv)