Compare commits

..

310 Commits

Author SHA1 Message Date
Lucio Andrés Illanes Albornoz
55dfbbb13f 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
738b1db74e 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
498a20cd28 assets/images/roar.png: updated. 2019-09-15 20:31:40 +02:00
Lucio Andrés Illanes Albornoz
cad73fd135 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
0d5f588108 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
209b908fb4 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
80b6b151db libroar/RoarCanvasWindow.py:onKeyboardInput(): enter pdb on <Shift> <Pause>. 2019-09-15 15:17:27 +02:00
Lucio Andrés Illanes Albornoz
bc50a0ca43 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
9c6b7fa9b2 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
dd5dcdcdd4 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
9d045fe455 libroar/RoarCanvasCommands.py:update(): disable undo if on last undo item. 2019-09-15 12:52:27 +02:00
Lucio Andrés Illanes Albornoz
eda8034075 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
4b89180efe 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
b024dd855a 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
743383437e libtools/Tool.py:Tool(): derive from object. 2019-09-14 17:11:08 +02:00
Lucio Andrés Illanes Albornoz
f995d7b54f assets/images/roar.png: updated. 2019-09-14 16:29:51 +02:00
Lucio Andrés Illanes Albornoz
d565a7f7ef libroar/RoarWindowAbout.py, assets/tools/deploy-python.sh: updated. 2019-09-14 16:16:36 +02:00
Lucio Andrés Illanes Albornoz
3b47af399a 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
270a19ea40 libgui/GuiFrame.py:onMenu(): clear event queue after calling command function. 2019-09-14 15:20:20 +02:00
Lucio Andrés Illanes Albornoz
31babe1994 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
1d927bf2b9 bcanvas/CanvasImportStore.py:importTextBuffer(): handle exceptions. 2019-09-14 11:52:24 +02:00
Lucio Andrés Illanes Albornoz
f6afcd735a 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
3298e9925b libroar/RoarCanvasWindow.py:onScroll(): hide cursor when scrolling. 2019-09-14 11:33:13 +02:00
Lucio Andrés Illanes Albornoz
342af59ffe .gitignore: ignore build/. 2019-09-14 11:29:26 +02:00
Lucio Andrés Illanes Albornoz
c923890fde setup.py: added.
requirements.txt: updated.
2019-09-14 11:23:29 +02:00
Lucio Andrés Illanes Albornoz
2bbdec394f roar.py: fix argument handling. 2019-09-14 11:21:40 +02:00
Lucio Andrés Illanes Albornoz
771f124314 requirements.txt: added (via Civil.) 2019-09-14 11:05:05 +02:00
Lucio Andrés Illanes Albornoz
a5b37f1d2b 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
79f5e4e9e3 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
52a575cb47 libroar/RoarCanvasCommandsFile.py:canvasSave{,As}(): don't reset dirty on canvasSaveAs(). 2019-09-13 21:14:11 +02:00
Lucio Andrés Illanes Albornoz
5a3cdaca89 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
480dc6231e 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
dc88c0ad80 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
f7608cf4b7 libgui/GuiCanvasWxBackend.py: render transparent cells as dark grey `░'. 2019-09-12 21:01:06 +02:00
Lucio Andrés Illanes Albornoz
9ce04b2b9d 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
16689cf97f libroar/RoarCanvasWindow.py:dispatchDeltaPatches(): remove cursor prior to updating canvas. 2019-09-12 17:03:21 +02:00
Lucio Andrés Illanes Albornoz
b6c27ad434 libtools/ToolSelect.py: fix function signatures. 2019-09-12 16:55:03 +02:00
Lucio Andrés Illanes Albornoz
f16b75d590 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
8b3be4e35f 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
33e5645736 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
3867f34306 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
58765e187c libtools/ToolText.py: ignore non-printable key events. 2019-09-12 10:28:56 +02:00
Lucio Andrés Illanes Albornoz
133f99371b assets/text/{arab-spokelion,spoke-arablion}.txt: ROAR! 2019-09-12 10:09:41 +02:00
Lucio Andrés Illanes Albornoz
0399753457 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
48f24e3200 libroar/RoarCanvasCommandsEdit.py:canvasColourAlpha_(): fix signature. 2019-09-12 10:03:29 +02:00
Lucio Andrés Illanes Albornoz
ed966f7154 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
d60acb7501 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
5e9efd5175 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
c66bde16d0 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
5c55c49769 libroar/RoarCanvasCommandsFile.py:_import(): handle FileNotFoundError exception. 2019-09-11 13:53:48 +02:00
Lucio Andrés Illanes Albornoz
474f3be4a7 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
05da368849 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
345b2ede5e libroar/RoarWindowAbout.py: cleanup. 2019-09-10 13:24:47 +02:00
Lucio Andrés Illanes Albornoz
cc61090c5e assets/tools/deploy-python.sh: updated. 2019-09-10 12:32:09 +02:00
Lucio Andrés Illanes Albornoz
1d8b7cb1d3 libroar/RoarCanvasCommands.py: fix base classes order. 2019-09-10 12:30:35 +02:00
Lucio Andrés Illanes Albornoz
988e6199c0 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
df41c40b80 libtools/ToolText.py:onKeyboardEvent(): correctly return (rc, dirty). 2019-09-10 12:20:23 +02:00
Lucio Andrés Illanes Albornoz
0f29c7d3b0 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
723b1e86d0 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
41e070803a 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
0aceb126f3 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
548f63b4e7 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
6311c32bde 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
d7421197fd assets/text/TODO: updated. 2019-09-09 19:43:07 +02:00
Lucio Andrés Illanes Albornoz
e750f20521 libgui/GuiCanvasInterfaceAbout.py: update legend. 2019-09-09 19:34:22 +02:00
Lucio Andrés Illanes Albornoz
ce2fc61332 assets/tools/deploy-python.sh: updated. 2019-09-09 19:33:16 +02:00
Lucio Andrés Illanes Albornoz
e988b29c82 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
c88d2221e2 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
3b47b4afa9 assets/text/TODO: updated. 2019-09-09 18:47:39 +02:00
Lucio Andrés Illanes Albornoz
1b31d34d25 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
d342a9d804 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
dc84921d47 libgui/GuiCanvasPanel.py:{onPanelSize,__init__}(): call _updateScrollBars() on EVT_SIZE. 2019-09-09 17:34:50 +02:00
Lucio Andrés Illanes Albornoz
43cf20a78d 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
eff71ac7da lib{canvas,gui,rtl,tools}/*.py: remove trailing SP. 2019-09-09 12:46:52 +02:00
Lucio Andrés Illanes Albornoz
9a905f2f41 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
dfdd374bb0 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
8af07b61ba libgui/GuiCanvasInterface.py: soft-fail importing ImgurApiKey.
assets/text/TODO: updated.
2019-09-08 20:05:34 +02:00
Lucio Andrés Illanes Albornoz
b58e005b20 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
6182cabaeb 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
f2d30b3331 assets/tools/deploy-python.sh: updated. 2019-09-08 19:12:16 +02:00
Lucio Andrés Illanes Albornoz
f6559de704 {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
6bd8815640 libgui/GuiCanvasInterface.py: minor cleanup. 2019-09-08 19:02:19 +02:00
Lucio Andrés Illanes Albornoz
941a29e9ca libgui/GuiCanvasInterface.py: minor cleanup. 2019-09-08 18:34:30 +02:00
Lucio Andrés Illanes Albornoz
44e91956d1 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
318b4436f3 {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
34dddd3e2b 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
c9102f3462 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
391ee21612 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
8abf95abee 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
c7ef599185 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
997042ad90 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
80ff03e4b1 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
bffcc644f2 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
e481c8026c 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
dba97c9097 {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
48013617b2 assets/tools/deploy-python.sh: updated. 2019-09-04 16:13:47 +02:00
Lucio Andrés Illanes Albornoz
d193120312 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
c81275bf2f 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
0aa80899ff 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
c8429b3db2 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
d41f9468b7 assets/tools/SAUCEToMiRCART.py: added (for spoke.)
assets/text/TODO: updated.
2019-09-04 11:03:53 +02:00
Lucio Andrés Illanes Albornoz
2063ca86fc ROAR! 2019-09-03 18:58:50 +02:00
Lucio Andrés Illanes Albornoz
16b7d89b1d assets/text/TODO: merged from roar/. 2019-09-03 15:49:53 +02:00
Lucio Andrés Illanes Albornoz
807f59210c MiRCART.py: melp? 2019-09-03 15:48:52 +02:00
Lucio Andrés Illanes Albornoz
12089db11f libtools/MiRCARTToolSelect{,Clone,Move}.py: cleanup & bug fixes. 2019-09-03 15:48:48 +02:00
Lucio Andrés Illanes Albornoz
e9612855cb 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
ef91be0f48 Bump to v1.1.6. 2019-08-28 11:35:42 +02:00
Lucio Andrés Illanes Albornoz
2f62e20509 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
f6a4e17463 Bump to v1.1.5. 2019-08-28 10:37:45 +02:00
Lucio Andrés Illanes Albornoz
d03a5b9294 MiRCART-nw/index.html: update symlink. 2019-08-28 10:37:27 +02:00
Lucio Andrés Illanes Albornoz
9bd706cbe0 Bump to v1.1.4. 2019-08-28 10:32:44 +02:00
Lucio Andrés Illanes Albornoz
155a15eeb4 SAUCEToAnsi.py: updated. 2019-08-28 10:32:26 +02:00
Lucio Andrés Illanes Albornoz
a7da0aa679 Bump to v1.1.3.
assets/shell/bump-version.sh: updated.
2019-08-28 10:31:44 +02:00
Lucio Andrés Illanes Albornoz
72f9657608 MiRCART-python/SAUCEToAnsi.py: added (for spoke.) 2019-08-28 10:29:28 +02:00
Lucio Andrés Illanes Albornoz
1542c66c64 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
f276aa34f5 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
179b6b7c2d assets/shell/bump-version.sh: removes MiRCART-cordoba support. 2019-08-27 08:01:35 +02:00
Lucio Andrés Illanes Albornoz
f63ee53191 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
2efca3371f Removes MiRCART-cordoba. 2019-08-25 19:00:11 +02:00
Lucio Andrés Illanes Albornoz
922b9b6c5d 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
a002cb96bb 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
e9a90f5000 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
76db3cacd5 MiRCART-python/MiRCARTReduce.py: added. 2019-04-11 14:54:29 +02:00
Lucio Andrés Illanes Albornoz
ab41c9a56d MiRCART-python/IrcMiRCARTBot.py: updated.
assets/text/TODO: updated.
2019-02-06 19:10:35 +01:00
Lucio Andrés Illanes Albornoz
400615c134 MiRCARTToAnsi.py: fix colours. 2019-01-21 20:21:11 +01:00
Lucio Andrés Illanes Albornoz
add023fa5f MiRCARTToAnsi.py: added.
MiRCARTToPngFile.py: clean up _ColourMap{Bold,Normal}.
2019-01-21 10:23:19 +01:00
Lucio Andrés Illanes Albornoz
ffdc3cb6e5 IrcMiRCARTBot.py:_{dispatchPrivmsg,uploadToImgur}(): gracefully handle invalid responses from Imgur.com. 2018-12-03 11:51:20 +01:00
Lucio Andrés Illanes Albornoz
fbf415816d Bump to v1.1.0. 2018-11-26 11:00:15 +01:00
Lucio Andrés Illanes Albornoz
8d22469df6 assets/shell/deploy-nw.sh:deploy(): consistent package names. 2018-11-26 10:59:56 +01:00
Lucio Andrés Illanes Albornoz
c6afbb6978 Bump to v1.0.9. 2018-11-26 10:55:55 +01:00
Lucio Andrés Illanes Albornoz
47046c87ae Fix LICENCEs. 2018-11-26 10:54:54 +01:00
Lucio Andrés Illanes Albornoz
b9cd1c4b45 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
3ed735c6b5 Bump to v1.0.8. 2018-11-26 10:46:18 +01:00
Lucio Andrés Illanes Albornoz
2c0b0169b7 Renames asciiblaster{,*} to MiRCART. 2018-11-26 10:45:12 +01:00
Lucio Andrés Illanes Albornoz
d11efefd79 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
f2fa2d94ee assets/html/help.html: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
692e97f7da assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
7e707d16cb 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
0b6376e542 Bump to v1.0.7. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
21909c5cd0 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2d33adc1e3 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
847c6b8cd9 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
9adcda2209 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
f93e3beebf assets/html/help.html, index.html: remove <meta charset="utf-8" />. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
80c9f49de5 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
72cb6696b5 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
80b89add02 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
795b0a9813 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
87ab2ddeda index.html, assets/css/sally.css: clean up & correct {CSS,HTML}. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
e7ea7e8c92 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
678ba6fcf1 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
7a26476aec assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
c100fc5cc5 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
7e5b6ec1a7 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
a4bf6cbb8b assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
252769b2b2 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
8398a70e61 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
dc555966d9 assets/js/*: substitute VT w/ SP. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
551dfdaf68 assets/*, index.html: remove {trailing,leading} SP. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
cfdb8a151a 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
07b0380242 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
1e46357a6a .gitattributes: ignore asciiblaster-{cordoba,nw}/ concerning Github repository languages. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
3814aba9b4 Bump to v1.0.6. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
9aaf6fdd3b assets/shell/bump-version.sh: melp? 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
7eb8d20ed4 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
57a11b7a4f Bump to v1.0.5. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
a7f26952c0 assets/shell/bump-version.sh: added to repository. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
1e797b7aa5 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
d05e39476c 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
b036bf73b5 assets/shell/deploy-all.sh:main(): fix typo. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
4d09571092 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
114e34eef0 Bump to v1.0.4. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
3f354c93c4 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
e7d499553b 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
094866c78b assets/js/ext/oktween.js, index.html: remove dead code. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2e9fd149bc 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
f7dcd7fd5a 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
8c7e7f2d0c assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
85b7aabda2 assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
257295cfad assets/text/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
2092c08444 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
621bcbebef asciiblaster-cordoba/{config.xml,package.json}: fix index.html pathnames. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
84426cdf4f 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
0080b3cb6b Refactor & clean up repository tree. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
9c4cc5bd99 assets/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
bd9c7f69a7 assets/TODO: updated. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
1e68c8a885 assets/release.sh:release(): fix lapse in logic. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
809f234d6e 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
c2a7c22f31 assets/release.sh: fix typo. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
52f19e004f package.json: bump to v1.0.2. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
dbbaf9e9dc assets/release.sh: obtain version number from package.json. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
fdac8395c4 index.html:cutoff_warning_el: update warning message. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
27399ee6f6 assets/release.sh, package.json: bump to v1.0.1. 2018-11-26 10:23:26 +01:00
Lucio Andrés Illanes Albornoz
0cc52708df 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
82d30822d5 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
cf84575774 package{,-lock}.json: added for standalone app support via NW.js. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
a3ad4733b1 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
f15900dd75 js/ext/unicode.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
5d8921b826 js/ui/keys.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
6029f1ef4e js/ui/goodies.js: removed (unused.)
index.html: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
1c06878651 js/ui/nopaint.js: moved into js/ext/.
index.html: updated.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
9716bbc5d5 assets/sally.css, index.html, js/clipboard.js: remove gallery link. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
2704c413c4 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
69dcc0677e js/clipboard.js: cleanup. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
a9723b648c assets/fsex300-webfont.eot?: removed. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
29b72c23fd {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)
63c7f80c37 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)
b81dc2ccb6 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)
9af73954ce 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)
1302616ec0 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)
8981d25c07 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)
4da8cb31f7 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)
c26d4a123a js/ext/dataUriToBlob.js: removed.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
f9ee0ee4a8 js/{lex,matrix}.js: encode to mIRC more efficiently, pt. II. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
e4ed3c9b9e TODO: updated. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
9a1328e8a5 js/clipboard.js: fix typo. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
b830611891 TODO: updated. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
78c5b3b93e 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)
a64ff6f582 index.html: readd js/undo.js. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
15347846e5 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)
f27a25beda 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)
0e9d65cd52 js/: split into js/{ext,ui,}.
index.html: updates script URIs.
2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
40a95d41cd js/lex.js: default to {bg,fg} when encoding to mIRC. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
3937d2b3dd TODO: added to repository. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
088f740ccb index.html: update {doc,gallery}_el links. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
125dbca1d5 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)
ca64311767 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)
d9770b4f2b js/user.js: update cookie domain. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz (arab, vxp)
449a186b8e Initial commit. 2018-11-26 10:22:19 +01:00
Lucio Andrés Illanes Albornoz
fed2fbe90e Stash dead repository MiRCARTools. 2018-10-25 03:06:28 +02:00
Lucio Andrés Illanes Albornoz
b42eb4e7da 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
7b0d65f333 Stash dead repository MiRCARTools. 2018-07-05 15:21:30 +02:00
Lucio Andrés Illanes Albornoz
8c8766f703 Merge remote-tracking branch 'MiRCARTools/master' 2018-07-05 15:20:47 +02:00
Lucio Andrés Illanes Albornoz
fd8c9a4267 IrcMiRCARTBot.py: adds optional -[46] force IPv[46] flag. 2018-06-28 18:18:48 +02:00
Lucio Andrés Illanes Albornoz
85d260835b MiRCARTToPngFiles.sh: added. 2018-06-26 15:39:29 +02:00
Lucio Andrés Illanes Albornoz
6593aa594e 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
e7b6324866 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
5f99672c08 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
7011dc91dd 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
19b29d1714 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
e5ef8503c6 MiRCARTCheckLineLengths.sh: check mIRC art line lengths. 2018-05-02 17:08:45 +02:00
Lucio Andrés Illanes Albornoz
cbc12828e5 MiRCARTCanonicalise.py: canonicalise mIRC art {from,to} file (for munki.) 2018-05-01 23:30:36 +02:00
Lucio Andrés Illanes Albornoz
5f0328d1d2 {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
b4a71505ff Reduce memory usage by folding nested patch {coordinate,colour} list(s). 2018-01-30 11:31:28 +01:00
Lucio Andrés Illanes Albornoz
7475f7108d 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
85cbffbadb 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
0e8de3b4dd MiRCARTCanvasInterface.py:canvasSave(): fix call parameters. 2018-01-25 15:04:37 +01:00
Lucio Andrés Illanes Albornoz
55b88412c9 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
7192b66805 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
0724c80868 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
ea9f827099 MiRCARTColours.py: fix permission bits. 2018-01-11 23:27:57 +01:00
Lucio Andrés Illanes Albornoz
d6f72e17f7 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
4a093c25a7 MiRCARTToPngFile.py:export(): treat `█' as whitespace w/ inverse colours. 2018-01-11 23:22:02 +01:00
Lucio Andrés Illanes Albornoz
154d2b32a5 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
67a824779e 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
485d97ff99 MiRCARTFrame.py: initially select red (4) colour toolbar item. 2018-01-11 03:02:18 +01:00
Lucio Andrés Illanes Albornoz
25a0a696f6 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
cafb53b283 MiRCARTFrame.py: adds exit accelerator (hotkey) <Ctrl> X. 2018-01-11 02:37:20 +01:00
Lucio Andrés Illanes Albornoz
e336730d80 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
fe5e453187 MiRCARTCanvas.py:MiRCARTCanvas.onPanelInput(): clip mapPoint to canvasSize. 2018-01-11 02:10:33 +01:00
Lucio Andrés Illanes Albornoz
0342f6626d MiRCARTToolFill.py: don't process cells more than once. 2018-01-11 02:06:09 +01:00
Lucio Andrés Illanes Albornoz
ca0f76360f MiRCARTCanvasInterface.py:_updateCanvasSize(): hide cursor before resizing. 2018-01-11 01:59:43 +01:00
Lucio Andrés Illanes Albornoz
390dae6b8d MiRCARTToolFill.py: set qualifying colour from current cell. 2018-01-11 01:56:28 +01:00
Lucio Andrés Illanes Albornoz
1a9c08a3fd MiRCARTCanvasInterface.py:_updateCanvasSize(): reimplement (fixes <Alt> [WASD] bugs.) 2018-01-11 01:52:53 +01:00
Lucio Andrés Illanes Albornoz
77eb660f35 MiRCART.py: import argv[1] into canvas if specified. 2018-01-11 01:18:50 +01:00
Lucio Andrés Illanes Albornoz
b0794ccaf9 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
321ec8ffd9 assets/tool{Clone,Move}.png: updated. 2018-01-11 01:09:00 +01:00
Lucio Andrés Illanes Albornoz
d8f8f47543 MiRCARTCanvasInterface.py: sync menu item state when selecting tool. 2018-01-11 00:59:07 +01:00
Lucio Andrés Illanes Albornoz
b6040ef482 MiRCARTGeneralFrame.py: show accelerators (hotkeys) in menus. 2018-01-11 00:45:40 +01:00
Lucio Andrés Illanes Albornoz
8a01500846 MiRCART.png: updated. 2018-01-11 00:31:25 +01:00
Lucio Andrés Illanes Albornoz
24de84093d 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
426c1f990f MiRCARTGeneralFrame.py: add toolbar windows to sizer w/ border width 3. 2018-01-10 22:31:52 +01:00
Lucio Andrés Illanes Albornoz
325ae6883f 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
4ce163d906 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
5fd20d31b1 MiRCARTToolSelectMove.py: clear source region prior to moving. 2018-01-10 19:20:35 +01:00
Lucio Andrés Illanes Albornoz
7e7df8a31c 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
cd9a81dbf4 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
2330eb8c69 MiRCARTToolLine.py: correctly cache colours on first click. 2018-01-10 14:53:35 +01:00
Lucio Andrés Illanes Albornoz
f20174037c 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
b486d3966e 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
62ff843d03 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
7acb234404 MiRCARTToolLine.py: honour brush size. 2018-01-10 04:44:13 +01:00
Lucio Andrés Illanes Albornoz
08fcfa3290 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
b91ba78abd MiRCARTToolText.py: fix non-US ASCII character handling. 2018-01-10 04:31:20 +01:00
Lucio Andrés Illanes Albornoz
1b00bb3b2f 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
7abd7679d2 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
36b9a5eb82 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
bbec7d1eb1 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
d3e67a7c18 MiRCART{Canvas{,Backend},Frame}.py: replace MemoryDC() w/ BufferedDC(canvasBitmap). 2018-01-09 22:23:27 +01:00
Lucio Andrés Illanes Albornoz
317ed41b13 MiRCARTCanvasBackend.py: only update device context brushes & pen if necessary. 2018-01-09 21:37:58 +01:00
Lucio Andrés Illanes Albornoz
45dae7798a MiRCARTGeneralFrame.py: prepend script execution directory when loading toolbar item bitmaps. 2018-01-09 21:21:04 +01:00
Lucio Andrés Illanes Albornoz
674afe9bfa MiRCARTCanvas.py: enable double buffering to eliminate brush flickering. 2018-01-09 05:07:04 +01:00
Lucio Andrés Illanes Albornoz
cc64e955e9 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
14a4192442 Initial release w/ circle, line, rectangle, and text tools. 2018-01-09 03:56:40 +01:00
Lucio Andrés Illanes Albornoz
9e3af62f97 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
fa08cc52ae MiRCART.png: updates screenshot. 2018-01-08 17:47:58 +01:00
Lucio Andrés Illanes Albornoz
1edb7cc626 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
7aecb6fd58 MiRCARTFrame.py: adds <Ctrl>-<{+,-}> {in,de}crease brush size accelerators (hotkeys). 2018-01-08 02:56:08 +01:00
Lucio Andrés Illanes Albornoz
330d4c78fe 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
baa84ce859 MiRCARTGeneralFrame.py, MiRCARTFrame.py: cleanup. 2018-01-08 01:43:54 +01:00
Lucio Andrés Illanes Albornoz
e763dc8909 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
dddce0757b MiRCARTCanvas.py: disable double buffering. 2018-01-08 01:11:00 +01:00
Lucio Andrés Illanes Albornoz
26937149fb MiRCART{CanvasJournal,Frame,ToolRect}.py: implements variable brush size. 2018-01-08 00:33:47 +01:00
Lucio Andrés Illanes Albornoz
54ce9975e2 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
6b67a713e5 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
74e3f41000 Initial release sans tools. 2018-01-08 00:05:03 +01:00
Lucio Andrés Illanes Albornoz
e195bcde47 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
45f2f80050 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
a58781ed7a MiRCARTCanvasStore.py: split & merge MiRCART{FromTextFile,ToPastebin,ToTextFile}.py. 2018-01-07 15:07:22 +01:00
Lucio Andrés Illanes Albornoz
4abf68e168 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 1322 additions and 2609 deletions

1
.env
View File

@ -1 +0,0 @@
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,9 +1,5 @@
*.sw[op]
__pycache__/
build/
libgui/GuiCanvasWxBackendFast.exp
libgui/GuiCanvasWxBackendFast.lib
libgui/GuiCanvasWxBackendFast.obj
libgui/GuiCanvasWxBackendFast.pyd
librtl/ImgurApiKey.py
releases/

View File

@ -1,23 +0,0 @@
{
"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
View File

@ -1,70 +0,0 @@
{
// 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"
}
]
}

View File

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

View File

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

15
.vscode/tasks.json vendored
View File

@ -1,15 +0,0 @@
{
// 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"
]
}
]
}

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.

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.

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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

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

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

View File

@ -1,7 +1,7 @@
# 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:
# 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:
`pip install requests urllib3 wxPython`
* Prerequisites on Linux: python3 (v3.7.x) && python-wx{gtk2.8,tools} on Debian-family Linux distributions
* Prerequisites on Linux: python3 && 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,20 +1,22 @@
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, ...)
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}}
vim:ff=dos tw=0

View File

@ -1,41 +0,0 @@
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

@ -1 +0,0 @@
.

View File

@ -1,15 +0,0 @@
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▝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
 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╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲

View File

@ -1,31 +0,0 @@
#!/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,14 +19,17 @@ 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(" "):
@ -35,17 +38,20 @@ 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;
@ -72,14 +78,17 @@ 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 "):
@ -146,16 +155,19 @@ 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()
@ -176,11 +188,13 @@ 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):
@ -192,7 +206,8 @@ 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:
@ -230,7 +245,10 @@ 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
@ -259,12 +277,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,15 +27,13 @@ deploy() {
-mindepth 1 \
-not -path "./${RELEASES_DNAME}/*" \
-not -path "./${RELEASES_DNAME}" \
-not -path './.*/*' \
-not -path './.*' \
-not -path "./.git/*" \
-not -path "./.git" \
-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,123 +6,102 @@
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:]
# }}}
def applyPatch(self, patch, commitUndo=True):
# {{{ dispatchPatch(self, isCursor, patch, commitUndo=True)
def dispatchPatch(self, isCursor, 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 commitUndo:
self.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch)
if isCursor:
self.journal.pushCursor(patchDelta)
else:
if commitUndo:
self.journal.begin(); self.journal.updateCurrentDeltas(patch, patchDelta); self.journal.end();
self._commitPatch(patch)
return True
def begin(self):
deltaItem = [[], []]; self.patchesUndo.insert(self.patchesUndoLevel, deltaItem);
def end(self):
if self.patchesUndo[self.patchesUndoLevel] == [[], []]:
del self.patchesUndo[self.patchesUndoLevel]
# }}}
# {{{ 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
else:
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 = []
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 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.begin()
self.journal.begin()
undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize]
self.updateCurrentDeltas(redoPatches, undoPatches)
self.journal.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.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
self.journal.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([[*brushColours, 0, " "]] * deltaSize[0])
self.map[numRow].extend([[1, 1, 0, " "]] * deltaSize[0])
for numNewCol in range(oldSize[0], newSize[0]):
if commitUndo:
self.updateCurrentDeltas([numNewCol, numRow, *brushColours, 0, " "], None)
newCells += [[numNewCol, numRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numRow, *brushColours, 0, " "], False)
self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None)
self.dispatchPatch(False, [numNewCol, numRow, 1, 1, 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.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]])
self.journal.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([[[*brushColours, 0, " "]] * newSize[0]])
self.map.extend([[[1, 1, 0, " "]] * newSize[0]])
for numNewCol in range(newSize[0]):
if commitUndo:
self.updateCurrentDeltas([numNewCol, numNewRow, *brushColours, 0, " "], None)
newCells += [[numNewCol, numNewRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numNewRow, *brushColours, 0, " "], False)
self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None)
self.dispatchPatch(False, [numNewCol, numNewRow, 1, 1, 0, " "], False)
self.size = newSize
if commitUndo:
self.end()
return True, newCells
self.journal.end()
return True
else:
return False, newCells
return False
# }}}
# {{{ update(self, newSize, newCanvas=None)
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]])
# }}}
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();
#
# __init__(self, size): initialisation method
def __init__(self, size):
self.exportStore, self.importStore, self.map, self.size = CanvasExportStore(), CanvasImportStore(), None, size
self.patchesCursor, self.patchesUndo, self.patchesUndoLevel = [], [None], 0
self.dirtyCursor, self.map, self.size = False, None, size
self.exportStore, self.importStore, self.journal = CanvasExportStore(), CanvasImportStore(), CanvasJournal()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

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

View File

@ -20,20 +20,25 @@ except ImportError:
haveUrllib = False
class CanvasExportStore():
# {{{ _CellState(): Cell state
class _CellState():
CS_NONE = 0x00
CS_BOLD = 0x01
CS_UNDERLINE = 0x02
CS_ITALIC = 0x02
CS_UNDERLINE = 0x04
# }}}
ImgurUploadUrl = "https://api.imgur.com/3/upload.json"
PastebinPostUrl = "https://pastebin.com/api/api_post.php"
# {{{ _drawUnderline(self, curPos, 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)):
@ -45,12 +50,9 @@ 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:
@ -69,7 +71,8 @@ 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])
@ -91,10 +94,12 @@ 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()
@ -112,7 +117,8 @@ 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))
@ -155,32 +161,20 @@ 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]):
canvasLastAttrs, canvasLastColours = self._CellState.CS_NONE, [15, -1]
canvasLastColours = [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:
@ -206,9 +200,11 @@ 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,14 +9,20 @@ 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_UNDERLINE = 0x02
CS_ITALIC = 0x02
CS_UNDERLINE = 0x04
# }}}
# {{{ _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
@ -68,10 +74,12 @@ 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)
@ -82,7 +90,8 @@ 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
@ -101,25 +110,19 @@ 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":
inCurCol += 1
inCellState = self._flipCellStateBit(self._CellState.CS_ITALIC, inCellState); 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]))
@ -134,11 +137,15 @@ 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

@ -0,0 +1,77 @@
#!/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 ^[BV_] (bold, reverse, underline)
# Colours: mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline)
#
Colours = [
[255, 255, 255, 255, "White"],

View File

@ -4,19 +4,17 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
try:
import GuiCanvasWxBackendFast; haveGuiCanvasWxBackendFast = True;
except ImportError as e:
print("Failed to import GuiCanvasWxBackendFast: {}".format(e)); haveGuiCanvasWxBackendFast = False;
from ctypes import *
from GuiCanvasColours import Colours
import math, os, platform, Rtl, wx
import math, os, platform, 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)]
@ -26,228 +24,161 @@ 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():
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'),
}
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])]
# {{{ _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):
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, [], {}
[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._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)
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 += [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)
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)]
# }}}
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:
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;
# {{{ 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:
runCell[3] = self.arabicShapes[runCell[3]][0]; connect = False;
self._drawBrushPatch(eventDc, patch[2:], point)
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]
self._drawCharPatch(eventDc, patch[2:], point)
return True
else:
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()
return False
# }}}
# {{{ getDeviceContext(self, clientSize, parentWindow, viewRect)
def getDeviceContext(self, clientSize, parentWindow, viewRect):
if viewRect == (0, 0):
eventDc = wx.BufferedDC(wx.ClientDC(parentWindow), self.canvasBitmap)
else:
eventDc = GuiBufferedDC(self, self.canvasBitmap, clientSize, wx.ClientDC(parentWindow), viewRect)
self._lastBrush, self._lastPen = None, None
self._lastBrushBg, self._lastBrushFg, self._lastPen = None, 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)
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)]
# }}}
# {{{ resize(self, canvasSize, cellSize):
def resize(self, canvasSize, cellSize):
winSize = [a * b for a, b in zip(canvasSize, cellSize)]
if self.canvasBitmap == None:
self.canvasBitmap = wx.Bitmap(winSize)
else:
oldDc = wx.MemoryDC(); oldDc.SelectObject(self.canvasBitmap);
newDc = wx.MemoryDC(); newBitmap = wx.Bitmap(winSize); newDc.SelectObject(newBitmap);
newDc.Blit(0, 0, *self.canvasBitmap.GetSize(), oldDc, 0, 0)
oldDc.SelectObject(wx.NullBitmap); newDc.SelectObject(wx.NullBitmap);
oldDc.SelectObject(wx.NullBitmap)
self.canvasBitmap.Destroy(); self.canvasBitmap = newBitmap;
self.canvasSize = canvasSize;
if haveGuiCanvasWxBackendFast:
GuiCanvasWxBackendFast.resize(tuple(self.cellSize), self._font, tuple(winSize))
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)
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()
# }}}
def __init__(self, canvasSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf"), fontSize=8):
if haveGuiCanvasWxBackendFast:
GuiCanvasWxBackendFast.init(wx)
#
# __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")):
self._brushes, self._font, self._lastBrush, self._lastPen, self._pens = None, None, None, None, None
self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName, self.fontSize = None, None, fontName, fontPathName, fontSize
self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName = None, None, fontName, fontPathName
if platform.system() == "Windows":
from ctypes import WinDLL
WinDLL("gdi32.dll").AddFontResourceW(self.fontPathName.encode("utf16"))
self._initBrushesAndPens(); self.resize(canvasSize);
self._initBrushesAndPens(); self.resize(canvasSize, cellSize);
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -1,577 +0,0 @@
/*
* 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,10 +4,11 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
import os, sys, wx, wx.lib.agw.aui
import os, sys, wx
#
# Decorators
# {{{ GuiCommandDecorator(targetObject)
def GuiCommandDecorator(caption, label, icon, accel, initialState):
def GuiCommandDecoratorOuter(targetObject):
if callable(targetObject):
@ -16,7 +17,8 @@ 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):
@ -25,7 +27,8 @@ 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):
@ -35,7 +38,8 @@ 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):
@ -45,18 +49,7 @@ 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)
@ -64,11 +57,13 @@ 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()
@ -92,8 +87,10 @@ class GuiFrame(wx.Frame):
menuItemWindow.Check(menuItem.attrDict["initialState"])
else:
menuItemWindow.Enable(menuItem.attrDict["initialState"])
# }}}
def loadAccels(self, accelsIn, menus, toolBars):
# {{{ loadAccels(self, menus, toolBars)
def loadAccels(self, menus, toolBars):
def loadAccels_(accels):
nonlocal accelTableEntries
accels_ = []
@ -113,9 +110,10 @@ class GuiFrame(wx.Frame):
self.itemsById[accel.attrDict["id"]] = accel
self.Bind(wx.EVT_MENU, self.onMenu, id=accel.attrDict["id"])
accelTableEntries = []
[loadAccels_(accel) for accel in accelsIn]; [loadAccels_(menu[1:]) for menu in menus]; [loadAccels_(toolBar) for toolBar in toolBars];
[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)]
@ -128,7 +126,8 @@ 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:
@ -143,11 +142,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.lib.agw.aui.AuiToolBar(self, -1))
self.toolBars[-1].SetArtProvider(GuiToolBarArtProvider())
self.toolBars.append(wx.ToolBar(self.panelSkin, -1, style=wx.TB_FLAT | wx.HORIZONTAL | wx.TB_NODIVIDER))
self.toolBars[-1].SetToolBitmapSize((16, 16))
for toolBarItem in toolBar:
if toolBarItem == NID_TOOLBAR_HSEP:
@ -157,53 +156,58 @@ 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], wx.NullBitmap, short_help_string=toolBarItem.attrDict["caption"])
toolBarItemWindow = self.toolBars[-1].AddRadioTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["caption"], toolBarItem.attrDict["icon"][2], shortHelp=toolBarItem.attrDict["label"])
else:
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,)
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
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"):
if toolBarItem.attrDict["initialState"]:
self.toolBars[-1].ToggleTool(toolBarItemWindow, True)
toolBarItemWindow.Toggle(toolBarItem.attrDict["initialState"])
else:
self.toolBars[-1].EnableTool(toolBarItem.attrDict["id"], toolBarItem.attrDict["initialState"])
self.toolBars[-1].Refresh()
self.toolBarPanes, row = [], 0
toolBarItemWindow.Enable(toolBarItem.attrDict["initialState"])
for toolBar in self.toolBars:
self.sizerSkin.Add(toolBar, 0, wx.ALIGN_LEFT | wx.ALL, 3)
toolBar.Realize(); toolBar.Fit();
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()
# }}}
# {{{ 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)
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)
self.itemsById[eventId](event); wx.SafeYield();
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.auiManager = wx.lib.agw.aui.AuiManager(); self.auiManager.SetManagedWindow(self);
self.itemsById, self.menuItemsById, self.toolBarItemsById, self.toolBars = {}, {}, {}, []
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._initIcon(iconPathName); self.statusBar = self.CreateStatusBar();
self.SetFocus(); self.Show(True);
self.sizerSkin.Fit(self.panelSkin); 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,57 +7,69 @@
import wx
class GuiWindow(wx.ScrolledWindow):
# {{{ _updateScrollBars(self)
def _updateScrollBars(self):
if (self.scrollStep != None) and (self.size != None):
self.SetScrollRate(*self.scrollStep); clientSize = self.GetClientSize();
if self.size != None:
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();
# }}}
def __init__(self, parent, pos, style=0):
#
# __init__(self, parent, pos, scrollStep, style=0): initialisation method
def __init__(self, parent, pos, scrollStep, style=0):
super().__init__(parent, pos=pos, style=style) if style != 0 else super().__init__(parent, pos=pos)
self.parent = parent
self.pos, self.scrollFlag, self.scrollStep, self.size = pos, False, None, None
self.pos, self.scrollFlag, self.scrollStep, self.size = pos, False, scrollStep, 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._updateScrollBars()
self.SetScrollRate(*self.scrollStep); self._updateScrollBars();
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

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

View File

@ -6,26 +6,16 @@
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()
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
region.reverse(); return region;
# __init__(self, *args): initialisation method
def __init__(self, *args):
for flipPairKey in list(self.flipPairs.keys()):
self.flipPairs[self.flipPairs[flipPairKey]] = flipPairKey
pass
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -6,27 +6,18 @@
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):
for flipPairKey in list(self.flipPairs.keys()):
self.flipPairs[self.flipPairs[flipPairKey]] = flipPairKey
pass
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -9,12 +9,15 @@ 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

@ -1,35 +0,0 @@
#!/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

@ -1,36 +0,0 @@
#!/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,10 +7,15 @@
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))
@ -23,22 +28,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 += [[None, "(cancelled)", None, None, None, None]]
resultList += [[False, "(cancelled)", None, None, None, None]]
else:
for pathName in dialog.GetPaths():
resultList += [self._import(f, pathName)]
lastDir = os.path.dirname(pathName)
if self.lastDir != lastDir:
self.lastDir = lastDir; self._storeLastDir(self.lastDir);
self.lastDir = os.path.dirname(pathName)
return resultList
# }}}
# {{{ _load_list(self, pathName)
def _load_list(self, pathName):
try:
with open(pathName, "r") as fileObject:
@ -79,40 +84,8 @@ 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()
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)
# }}}
# {{{ _updateScrollBars(self)
def _updateScrollBars(self):
clientSize = self.panelCanvas.GetClientSize()
if self.currentIndex != None:
@ -126,26 +99,27 @@ 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.backend.cellSize)]
panelSize = [a * b for a, b in zip(canvas.size, self.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)
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
self.backend.resize(canvas.size, self.cellSize)
viewRect = self.panelCanvas.GetViewStart();
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
patches += [[numCol, numRow, *canvas.map[numRow][numCol]]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
# {{{ onPaint(self, event)
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):
@ -155,58 +129,60 @@ 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((1, 1,), newSize, False):
panelSize = [a * b for a, b in zip(canvas.size, self.backend.cellSize)]
if canvas.resize(newSize, False):
panelSize = [a * b for a, b in zip(canvas.size, self.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)
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
self.backend.resize(newSize, self.cellSize)
viewRect = self.panelCanvas.GetViewStart(); eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect);
if deltaSize[0] > 0:
for numRow in range(oldSize[1]):
for numNewCol in range(oldSize[0], newSize[0]):
patches += [[numNewCol, numRow, 1, 1, 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]):
patches += [[numNewCol, numNewRow, 1, 1, 0, " "]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "], viewRect)
# }}}
# {{{ update(self, canvas, newSize, newCanvas=None)
def update(self, canvas, newSize, newCanvas=None):
self.resize(canvas, newSize);
canvas.update(newSize, newCanvas);
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
canvas.update(newSize, newCanvas); viewRect = self.panelCanvas.GetViewStart();
viewRect = self.panelCanvas.GetViewStart();
eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas, viewRect)
for numRow in range(canvas.size[1]):
for numCol in range(canvas.size[0]):
patches += [[numCol, numRow, *canvas.map[numRow][numCol]]]
self.backend.drawPatches(canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.backend.drawPatch(eventDc, [numCol, numRow, *canvas.map[numRow][numCol]], viewRect)
# }}}
# {{{ 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)):
@ -214,35 +190,24 @@ class RoarAssetsWindow(GuiMiniFrame):
return wx.PostEvent(self.listView, event)
else:
event.Skip()
def onClearList(self, event):
while len(self.canvasList):
self._removeAsset(list(self.canvasList.keys())[0])
# }}}
# {{{ onListViewChar(self, event)
def onListViewChar(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()
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)
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:
@ -253,18 +218,15 @@ 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 == True:
if rc:
self.currentIndex = self.listView.GetItemCount()
self.canvasList[self.currentIndex] = [canvas, newPathName]
self.listView.InsertItem(self.currentIndex, "")
@ -279,35 +241,43 @@ 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)]
elif rc == False:
else:
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); self._storeLastDir(self.lastDir);
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName);
self._load_list(pathName)
# }}}
# {{{ onRemove(self, event)
def onRemove(self, event):
items = [self.listView.GetFirstSelected()]
while True:
item = self.listView.GetNextSelected(items[-1])
if item != -1:
items += [item]
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
else:
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)
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)
def onSaveList(self, event):
rc = True
if len(self.canvasList):
@ -315,8 +285,7 @@ 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); self._storeLastDir(self.lastDir);
pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName);
with open(pathName, "w") as fileObject:
for pathName in [self.canvasList[k][1] for k in self.canvasList.keys()]:
print(pathName, file=fileObject)
@ -326,15 +295,17 @@ class RoarAssetsWindow(GuiMiniFrame):
if not rc:
with wx.MessageDialog(self, "Error: {}".format(error), "", wx.OK | wx.OK_DEFAULT) as dialog:
dialogChoice = dialog.ShowModal()
# }}}
def __init__(self, backend, parent, pos=None, size=(400, 400), title="Assets"):
#
# __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"):
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)), {}, None
self.currentIndex, self.leftDown, self.parent, self.scrollFlag = None, False, parent, False
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.Bind(wx.EVT_CHAR, self.onChar)
self._loadLastDir()
self.contextMenu, self.contextMenuItems = wx.Menu(), []
for text, f in (
@ -345,9 +316,7 @@ class RoarAssetsWindow(GuiMiniFrame):
("&Remove", self.onRemove),
(None, None),
("Load from l&ist...", self.onLoadList),
("Sa&ve as list...", self.onSaveList),
(None, None),
("Cl&ear list", self.onClearList),):
("Sa&ve as list...", self.onSaveList),):
if (text, f) == (None, None):
self.contextMenu.AppendSeparator()
else:
@ -361,8 +330,7 @@ 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), wx.BORDER_SUNKEN)
self.panelCanvas.Bind(wx.EVT_CHAR, self.onChar)
self.panelCanvas = GuiWindow(self, (0, 0), cellSize, wx.BORDER_SUNKEN)
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,101 +5,41 @@
#
from GuiCanvasColours import Colours
from GuiFrame import NID_MENU_SEP, NID_TOOLBAR_HSEP
from GuiFrame import 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):
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
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]
# }}}
# {{{ update(self, **kwargs)
def update(self, **kwargs):
self.lastPanelState.update(kwargs); textItems = [];
if "cellPos" in self.lastPanelState:
@ -107,64 +47,83 @@ 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("B: {:02d}x{:02d}".format(*self.lastPanelState["brushSize"]))
textItems.append("Brush: {:02d}x{:02d}".format(*self.lastPanelState["brushSize"]))
if "colours" in self.lastPanelState:
textItems.append("FG: {:02d} ({}), BG: {:02d} ({})".format(self.lastPanelState["colours"][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()
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"))
if "pathName" in self.lastPanelState:
if self.lastPanelState["pathName"] != None:
basePathName = os.path.basename(self.lastPanelState["pathName"])
textItems.append("F: {}".format(basePathName))
textItems.append("Current file: {}".format(basePathName))
self.parentFrame.SetTitle("{} - roar".format(basePathName))
else:
self.parentFrame.SetTitle("roar")
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"]:
if "toolName" in self.lastPanelState:
textItems.append("Current tool: {}".format(self.lastPanelState["toolName"]))
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 "undoLevel" in self.lastPanelState:
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 (self.lastPanelState["undoLevel"] >= 0) \
and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.patchesUndo) - 1)):
and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.journal.patchesUndo) - 1)):
self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasUndo.attrDict["id"], True); toolBar.Refresh();
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasUndo.attrDict["id"], True)
else:
self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasUndo.attrDict["id"], False); toolBar.Refresh();
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasUndo.attrDict["id"], False)
if self.lastPanelState["undoLevel"] > 0:
self.parentFrame.menuItemsById[self.canvasRedo.attrDict["id"]].Enable(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasRedo.attrDict["id"], True); toolBar.Refresh();
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasRedo.attrDict["id"], True)
else:
self.parentFrame.menuItemsById[self.canvasRedo.attrDict["id"]].Enable(False)
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasRedo.attrDict["id"], False); toolBar.Refresh();
toolBar = self.parentFrame.toolBarItemsById[self.canvasRedo.attrDict["id"]].GetToolBar()
toolBar.EnableTool(self.canvasRedo.attrDict["id"], False)
# }}}
#
# __init__(self, parentCanvas, parentFrame):
def __init__(self, parentCanvas, parentFrame):
[classObject.__init__(self) for classObject in self.__class__.__bases__]
self._initColourBitmaps(); self.accels, self.menus, self.toolBars = self._initInterface();
menus, toolBars = [], []
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,30 +4,25 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator
from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator, NID_MENU_SEP
import wx
class RoarCanvasCommandsEdit():
@GuiCommandDecorator("Hide assets window", "Hide assets window", ["toolHideAssetsWindow.png"], None, False)
# {{{ canvasAssetsWindowHide(self, event)
@GuiCommandDecorator("Hide assets window", "Hide assets window", None, 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)
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)
# }}}
# {{{ canvasAssetsWindowShow(self, event)
@GuiCommandDecorator("Show assets window", "Show assets window", None, 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)
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()
# }}}
# {{{ canvasBrush(self, f, idx)
@GuiSelectDecorator(0, "Solid brush", "Solid brush", None, None, True)
def canvasBrush(self, f, idx):
def canvasBrush_(self, event):
@ -35,13 +30,14 @@ 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"], [wx.ACCEL_CTRL, ord("-")], None)
@GuiCommandListDecorator(2, "Decrease brush size", "Decrease brush size", ["toolDecrBrushHW.png"], None, 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"], [wx.ACCEL_CTRL, ord("+")], None)
@GuiCommandListDecorator(5, "Increase brush size", "Increase brush size", ["toolIncrBrushHW.png"], None, None)
def canvasBrushSize(self, f, dimension, incrFlag):
def canvasBrushSize_(event):
if (dimension < 2) and not incrFlag:
@ -53,17 +49,15 @@ 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_
@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)
# }}}
# {{{ 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(2, "Decrease canvas size", "Decrease canvas size", ["toolDecrCanvasHW.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(3, "Increase canvas height", "Increase canvas height", ["toolIncrCanvasH.png"], None, None)
@GuiCommandListDecorator(4, "Increase canvas width", "Increase canvas width", ["toolIncrCanvasW.png"], None, None)
@GuiCommandListDecorator(5, "Increase canvas size", "Increase canvas size", ["toolIncrCanvasHW.png"], None, None)
def canvasCanvasSize(self, f, dimension, incrFlag):
def canvasCanvasSize_(event):
@ -83,23 +77,24 @@ 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_
@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)
# }}}
# {{{ 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)
def canvasColour(self, f, idx):
def canvasColour_(event):
if event.GetEventType() == wx.wxEVT_TOOL:
@ -107,14 +102,12 @@ 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_
@GuiSelectDecorator(0, "Transparent colour", "Transparent colour", None, [wx.ACCEL_CTRL | wx.ACCEL_SHIFT, ord("6")], False)
# }}}
# {{{ canvasColourAlpha(self, f, idx)
@GuiSelectDecorator(0, "Transparent colour", "Transparent colour", None, None, False)
def canvasColourAlpha(self, f, idx):
def canvasColourAlpha_(event):
if event.GetEventType() == wx.wxEVT_TOOL:
@ -122,82 +115,60 @@ 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_
@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)
# }}}
# {{{ canvasCopy(self, event)
@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.undo(redo=True); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel);
self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo())
self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel)
# }}}
# {{{ canvasUndo(self, event)
@GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False)
def canvasUndo(self, event):
self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel);
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 = ()
# 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 datetime, io, os, stat, wx
import io, os, wx
class RoarCanvasCommandsFile():
def _import(self, f, newDirty, pathName, emptyPathName=False):
# {{{ _import(self, f, newDirty, pathName)
def _import(self, f, newDirty, pathName):
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
if not emptyPathName:
self.canvasPathName = newPathName
else:
self.canvasPathName = None
self.parentCanvas._snapshotsReset()
self.parentCanvas.update(newSize, False, newMap)
self.canvasPathName = newPathName
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
self.parentCanvas.canvas.resetCursor(); self.parentCanvas.canvas.resetUndo();
self.parentCanvas.canvas.journal.resetCursor()
self.parentCanvas.canvas.journal.resetUndo()
except FileNotFoundError as e:
rc, error, newMap, newPathName, newSize = False, str(e), None, None, None
if not rc:
@ -41,18 +41,27 @@ class RoarCanvasCommandsFile():
dialogChoice = dialog.ShowModal()
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return rc, newPathName
def _importFile(self, f, newDirty, wildcard, emptyPathName=False):
with wx.FileDialog(self.parentCanvas, "Open...", os.getcwd(), "", wildcard, wx.FD_OPEN) as dialog:
# }}}
# {{{ _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:
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); self._recentDirSave(self.lastDir);
return self._import(f, newDirty, pathName, emptyPathName=emptyPathName)
return False, None
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)
def _promptSaveChanges(self):
if self.parentCanvas.dirty:
message = "Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)")
@ -68,82 +77,34 @@ class RoarCanvasCommandsFile():
return False
else:
return 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):
# }}}
# {{{ _pushRecent(self, pathName, serialise=True)
def _pushRecent(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"].Insert(self.canvasOpenRecent.attrDict["menu"].GetMenuItemCount() - 2, menuItemId, pathName, pathName)
menuItemWindow = self.canvasOpenRecent.attrDict["menu"].Append(menuItemId, "{}".format(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:
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)
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)
# }}}
# {{{ canvasExit(self, event)
@GuiCommandDecorator("Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None)
def canvasExit(self, event):
if not self.exiting:
if self._promptSaveChanges():
self.exiting = True; self.parentFrame.Close(True);
if self._promptSaveChanges():
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:
@ -152,13 +113,14 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir);
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName);
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:
@ -167,20 +129,17 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir);
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName);
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():
@ -188,11 +147,12 @@ 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("", self.parentCanvas.canvas.map, self.parentCanvas.canvas.size)
pasteStatus, pasteResult = self.parentCanvas.canvas.exportStore.exportPastebin("253ce2f0a45140ee0a44ca99aa49260", self.parentCanvas.canvas.map, self.parentCanvas.canvas.size)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
if pasteStatus:
if not wx.TheClipboard.IsOpened():
@ -202,7 +162,8 @@ 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))
@ -212,14 +173,17 @@ 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 (*.*)|*.*", emptyPathName=True)
self._importFile(canvasImportAnsi_, True, "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*")
# }}}
# {{{ canvasImportFromClipboard(self, event)
@GuiCommandDecorator("Import from clipboard", "Import from &clipboard", None, None, None)
def canvasImportFromClipboard(self, event):
def canvasImportFromClipboard_(pathName):
@ -233,26 +197,31 @@ 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)
self._import(canvasImportFromClipboard_, True, None, emptyPathName=True)
if self._promptSaveChanges():
self._import(canvasImportFromClipboard_, True, None)
# }}}
# {{{ canvasImportSauce(self, event)
@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 (*.*)|*.*", emptyPathName=True)
self._importFile(canvasImportSauce_, True, "SAUCE files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*")
# }}}
# {{{ 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 = [100, 30]
newMap = [[[*self.parentCanvas.brushColours, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])]
newCanvasSize = list(self.parentCanvas.canvas.size)
newMap = [[[1, 1, 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):
@ -260,36 +229,17 @@ 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._recentPush(newPathName)
self._pushRecent(newPathName)
# }}}
# {{{ canvasOpenRecent(self, event, pathName=None)
@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)
if self._promptSaveChanges():
rc, newPathName = self._import(canvasImportmIRC, False, self.canvasPathName)
self._import(canvasImportmIRC, False, pathName)
# }}}
# {{{ canvasSave(self, event)
@GuiCommandDecorator("Save", "&Save", ["", wx.ART_FILE_SAVE], [wx.ACCEL_CTRL, ord("S")], None)
def canvasSave(self, event, newDirty=False):
if self.canvasPathName == None:
@ -306,7 +256,8 @@ 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:
@ -315,16 +266,24 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
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
self.canvasPathName = dialog.GetPath(); self.lastDir = os.path.dirname(self.canvasPathName);
if self.canvasSave(event, newDirty=True):
self._pushRecent(pathName)
# }}}
#
# __init__(self)
def __init__(self):
self.exiting, self.lastFiles, self.lastDir, self.snapshots = False, [], None, {}
self.imgurApiKey = ImgurApiKey.imgurApiKey if haveImgurApiKey else None
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 = ()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -6,24 +6,17 @@
from GuiFrame import GuiCommandDecorator
from RoarWindowAbout import RoarWindowAbout
from RoarWindowMelp import RoarWindowMelp
import webbrowser, wx
class RoarCanvasCommandsHelp():
@GuiCommandDecorator("About roar", "&About roar", None, None, True)
# {{{ canvasAbout(self, event)
@GuiCommandDecorator("About", "&About", None, None, True)
def canvasAbout(self, event):
RoarWindowAbout(self.parentFrame)
# }}}
@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")
#
# __init__(self)
def __init__(self):
self.menus, self.toolBars = (("&Help", self.canvasAbout,),), ()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,29 +4,63 @@
# 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 OperatorRotate import OperatorRotate
from OperatorTile import OperatorTile
from GuiFrame import GuiCommandListDecorator
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 colours", "&Invert colours", None, None, None)
@GuiCommandListDecorator(3, "Rotate", "&Rotate", None, None, None)
@GuiCommandListDecorator(4, "Tile", "&Tile", None, None, False)
@GuiCommandListDecorator(2, "Invert", "&Invert", None, None, None)
def canvasOperator(self, f, idx):
def canvasOperator_(event):
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())
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)
setattr(canvasOperator_, "attrDict", f.attrList[idx])
return canvasOperator_
# }}}
#
# __init__(self)
def __init__(self):
self.currentOperator, self.operatorState = None, None
self.menus = (
("&Operators",
self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2),
),
)
self.toolBars = ()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -6,42 +6,51 @@
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():
@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)
# {{{ 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)
def canvasTool(self, f, idx):
def canvasTool_(event):
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]
self.lastTool, self.currentTool = self.currentTool, [ToolCircle, None, ToolFill, ToolLine, ToolObject, ToolRect, ToolText][idx]
if self.currentTool != None:
self.currentTool = self.currentTool()
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)
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")
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
from ToolText import ToolText
import copy, hashlib, json, os, pdb, re, time, wx, sys
import json, wx, sys
import time
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,340 +22,209 @@ 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)
viewRect = self.parent.GetViewStart(); mapPoint = [m + n for m, n in zip((mapX, mapY), viewRect)];
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.parent.menuItemsById[self.parent.commands.canvasOperator.attrList[4]["id"]].Enable(True)
self.parent.commands.update(currentTool=self.parent.commands.currentTool, currentToolIdx=5)
self.parent.commands.update(toolName=self.parent.commands.currentTool.name)
eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, 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)
self.parent.applyTool(eventDc, True, None, None, self.parent.brushPos, False, False, False, self.parent.commands.currentTool, viewRect)
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):
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
# {{{ _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 _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)
# {{{ 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()
if eventMouse:
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, patches, patchesCursor = tool.onMouseEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown)
else:
rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]]
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)]
if tool != None:
rc, dirty = tool.onMouseEvent(self.brushColours, self.brushSize, self.canvas, self.dispatchPatchSingle, eventDc, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown, viewRect)
else:
self.dispatchPatchSingle(eventDc, True, [*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "] , viewRect)
else:
if tool != None:
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
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())
# }}}
# {{{ 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, keyModifiers,) == (wx.WXK_PAUSE, wx.MOD_SHIFT,):
pdb.set_trace()
if (keyCode == wx.WXK_PAUSE) \
and (keyModifiers == wx.MOD_SHIFT):
import pdb; pdb.set_trace()
elif keyCode in (wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP):
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)
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]
self.commands.update(cellPos=self.brushPos)
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):
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):
event.Skip()
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()
# }}}
# {{{ onEnterWindow(self, event)
def onEnterWindow(self, event):
self.lastCellState = None
# }}}
# {{{ onLeaveWindow(self, event)
def onLeaveWindow(self, event):
if False:
self.cursorHide()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, self.GetViewStart())
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 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 \
if 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, None, event.GetModifiers(), mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, self.commands.currentTool, viewRect):
elif not self.applyTool(eventDc, True, None, event.GetModifiers(), mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, self.commands.currentTool, viewRect):
event.Skip()
# }}}
# {{{ onMouseWheel(self, event)
def onMouseWheel(self, event):
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;
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
super().resize([a * b for a, b in zip(self.canvas.size, self.backend.cellSize)])
patches = []
self.backend.resize(self.canvas.size, self.backend.cellSize)
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
for numRow in range(self.canvas.size[1]):
for numCol in range(len(self.canvas.map[numRow])):
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)
self._drawPatch(eventDc, False, [numCol, numRow, *self.canvas.map[numRow][numCol]], viewRect)
else:
event.Skip()
# }}}
# {{{ onPaint(self, event)
def onPaint(self, event):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
self.backend.onPaint(self.GetClientSize(), self, viewRect)
del eventDc; self._windowEraseBackground(wx.PaintDC(self));
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect)
self.backend.drawCursorMaskWithJournal(self.canvas.journal, eventDc, viewRect)
self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart())
# }}}
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)
#
# __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)
self.SetDropTarget(self.dropTarget)
self._snapshotsReset()
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel)
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -15,61 +15,50 @@ 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)
def onClose(self, event):
if not self.canvasPanel.commands.exiting:
closeFlag = self.canvasPanel.commands._promptSaveChanges()
else:
closeFlag = True
if closeFlag:
event.Skip();
# }}}
# {{{ onMouseWheel(self, event)
def onMouseWheel(self, event):
self.canvasPanel.GetEventHandler().ProcessEvent(event)
# }}}
def onSize(self, event):
pass
def __init__(self, parent, defaultCanvasPos=(0, 75), defaultCanvasSize=(100, 30), size=(840, 640), title=""):
#
# __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=""):
super().__init__(self._getIconPathName(), size, parent, title)
self.canvas = Canvas(defaultCanvasSize)
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.canvasPanel = RoarCanvasWindow(GuiCanvasWxBackend, self.canvas, defaultCellSize, RoarCanvasCommands, self.panelSkin, self, defaultCanvasPos, defaultCellSize, defaultCanvasSize)
self.loadAccels(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)
self.assetsWindow = RoarAssetsWindow(GuiCanvasWxBackend, self)
self.addWindow(self.canvasPanel, expand=True)
self.assetsWindow = RoarAssetsWindow(GuiCanvasWxBackend, defaultCellSize, 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,19 +8,22 @@ 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>\nhttps://www.lucioillanes.de\n", 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>", 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)]
@ -36,7 +39,7 @@ class RoarWindowAbout(wx.Dialog):
self.SetTitle(title)
soundBitePathNames = glob(os.path.join("assets", "audio", "roar*.wav"))
soundBitePathName = soundBitePathNames[random.randint(0, len(soundBitePathNames) - 1)]
soundBitePathName = soundBitePathNames[random.randint(0, len(logoPathNames) - 1)]
self.soundBite = wx.adv.Sound(soundBitePathName)
if self.soundBite.IsOk():
self.soundBite.Play(wx.adv.SOUND_ASYNC)

View File

@ -1,32 +0,0 @@
#!/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,11 +11,13 @@ 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)
@ -39,7 +41,8 @@ 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):
@ -48,7 +51,8 @@ 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()
@ -81,7 +85,8 @@ 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;
@ -106,7 +111,10 @@ 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

View File

@ -1,18 +0,0 @@
#!/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,8 +7,10 @@
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,10 +5,13 @@
#
class Tool(object):
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
# {{{ 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
# }}}
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,60 +5,35 @@
#
from Tool import Tool
import wx
class ToolCircle(Tool):
name = "Circle"
TS_NONE = 0
TS_ORIGIN = 1
def _drawCircle(self, brushColours, canvas, mapPoint, originPoint, radius):
cells, patches = [], []
#
# 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
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)):
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, " "]
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);
else:
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;
dispatchFn(eventDc, True, patch, viewRect)
return True, dirty
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

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

View File

@ -11,8 +11,10 @@ class ToolLine(Tool):
TS_NONE = 0
TS_ORIGIN = 1
def _getLine(self, brushColours, brushSize, isCursor, originPoint, targetPoint):
originPoint, patches, targetPoint = originPoint.copy(), [], targetPoint.copy()
# {{{ _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()
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]
@ -22,28 +24,36 @@ 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]):
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]]
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 lineD > 0:
lineD -= pointDelta[0]; lineY += 1;
lineD += pointDelta[1]
return patches
return dirty
# }}}
# {{{ _pointDelta(self, a, b)
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]
# }}}
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
brushColours, isCursor, patches, rc = brushColours.copy(), not (mouseLeftDown or mouseRightDown), [], False
#
# 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:
@ -52,24 +62,20 @@ class ToolLine(Tool):
brushColours[1] = brushColours[0]
if self.toolState == self.TS_NONE:
if mouseLeftDown or mouseRightDown:
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, " "]]
self.toolColours, self.toolOriginPoint, self.toolState = brushColours, list(mapPoint), self.TS_ORIGIN
dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "], viewRect)
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:
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
self.toolColours, self.toolOriginPoint, self.toolState = None, None, self.TS_NONE
else:
return False, dirty
return True, dirty
# __init__(self, *args): initialisation method
def __init__(self, *args):
super().__init__(*args)
self.toolOriginPoint, self.toolState = None, self.TS_NONE
self.toolColours, self.toolOriginPoint, self.toolState = None, 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
def _dispatchSelectEvent(self, canvas, keyModifiers, mapPoint, mouseLeftDown, selectRect):
# {{{ _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, selectRect, viewRect)
def _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, selectRect, viewRect):
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()
rc, patches, patchesCursor = self.onSelectEvent(canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect)
patchesCursor = [] if patchesCursor == None else patchesCursor
patchesCursor += self._drawSelectRect(newTargetRect)
dirty = self.onSelectEvent(canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect, viewRect)
self._drawSelectRect(newTargetRect, dispatchFn, eventDc, viewRect)
self.targetRect = newTargetRect
return rc, patches, patchesCursor
def _drawSelectRect(self, rect):
patches = []
return dirty
# }}}
# {{{ _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect)
def _drawSelectRect(self, rect, dispatchFn, eventDc, viewRect):
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,21 +37,29 @@ 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]
patches += [[rectX, rectFrame[0][1], *curColours, 0, " "], [rectX, rectFrame[1][1], *curColours, 0, " "]]
dispatchFn(eventDc, True, [rectX, rectFrame[0][1], *curColours, 0, " "], viewRect)
dispatchFn(eventDc, True, [rectX, rectFrame[1][1], *curColours, 0, " "], viewRect)
for rectY in range(rectFrame[0][1], rectFrame[1][1] + 1):
curColours = [1, 1] if curColours == [0, 0] else [0, 0]
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):
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):
self.targetRect[1] = list(mapPoint)
if not mouseLeftDown:
if 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]:
@ -62,10 +70,14 @@ 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])
return True, None, self._drawSelectRect(self.targetRect)
def _mouseEventTsSelect(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown):
rc, patches, patchesCursor = False, None, None
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
if mouseLeftDown:
if (mapPoint[0] >= (self.targetRect[0][0] - 1)) \
and (mapPoint[0] <= (self.targetRect[1][0] + 1)) \
@ -73,79 +85,74 @@ class ToolObject(Tool):
and (mapPoint[1] <= (self.targetRect[1][1] + 1)):
self.lastAtPoint, self.toolState = list(mapPoint), self.TS_TARGET
else:
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)
dirty = self.onSelectEvent(canvas, (0, 0), dispatchFn, eventDc, False, keyModifiers, self.targetRect.copy(), self.targetRect, viewRect)
self._drawSelectRect(self.targetRect, dispatchFn, eventDc, viewRect)
self.objectMap, self.objectSize, self.targetRect, self.toolState = None, None, None, self.TS_NONE
else:
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
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):
if (keyModifiers == wx.MOD_CONTROL) and (self.srcRect == self.targetRect):
self.substract = True
dirty = False
if mouseLeftDown:
rc, patches, patchesCursor = self._dispatchSelectEvent(canvas, keyModifiers, mapPoint, mouseLeftDown, self.targetRect)
dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, self.targetRect, viewRect)
else:
self.toolState = self.TS_SELECT
return rc, patches, patchesCursor
return True, dirty
# }}}
# {{{ getRegion(self, canvas)
def getRegion(self, canvas):
return self.objectMap
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:
rc, patches, patchesCursor = False, None, None
return rc, patches, patchesCursor
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):
# }}}
# {{{ 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:
rc, patches, patchesCursor = self._mouseEventTsNone(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
dirty = self._mouseEventTsNone(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_SELECT:
rc, patches, patchesCursor = self._mouseEventTsSelect(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
dirty = self._mouseEventTsSelect(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_ORIGIN:
rc, patches, patchesCursor = self._mouseEventTsOrigin(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
dirty = self._mouseEventTsOrigin(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
elif self.toolState == self.TS_TARGET:
rc, patches, patchesCursor = self._mouseEventTsTarget(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown)
dirty = self._mouseEventTsTarget(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, viewRect)
else:
rc, patches, patchesCursor = False, None, None
return rc, patches, patchesCursor
def onSelectEvent(self, canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect):
patches = []
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
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]]
patches += [[rectX + disp[0], rectY + disp[1], *cellNew]]
dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellNew], viewRect)
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])):
patches += [[numCol, numRow, 1, 1, 0, " "]]
dirty = False if isCursor else True
dispatchFn(eventDc, isCursor, [numCol, numRow, 1, 1, 0, " "], viewRect)
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
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
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)
def setRegion(self, canvas, mapPoint, objectMap, objectSize, external=True):
self.external, self.toolState = external, self.TS_SELECT
if mapPoint != None:
@ -155,11 +162,13 @@ 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], (b - a for a, b in zip(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)))]
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

@ -1,23 +0,0 @@
#!/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,43 +5,32 @@
#
from Tool import Tool
import wx
class ToolRect(Tool):
name = "Rectangle"
TS_NONE = 0
TS_ORIGIN = 1
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
#
# 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);
else:
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;
dispatchFn(eventDc, True, patch, viewRect)
return True, dirty
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -5,127 +5,43 @@
#
from Tool import Tool
import re, string, wx
import 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]+$'
def _checkRtl(self, canvas, brushPos, keyChar):
rtlFlag = False
if (keyChar != None) and re.match(self.rtlRegEx, keyChar):
rtlFlag = True
#
# 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]
else:
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
rc, dirty = False, False
return rc, dirty
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):
#
# 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):
if mouseLeftDown or mouseRightDown:
brushPos[0], brushPos[1] = atPoint[0], atPoint[1]
return True, None, [[*brushPos, *brushColours, 0, "_"]]
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
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

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

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
.

13
roar.py
View File

@ -17,26 +17,21 @@ import wx
def main(*argv):
localConfDirName = getLocalConfPathName()
if not os.path.exists(localConfDirName):
os.makedirs(localConfDirName)
os.makedirs(localConfirName)
wxApp, roarClient = wx.App(False), RoarClient(None)
argv0, argv = argv[0], argv[1:]
roarClient.canvasPanel.commands._recentDirLoad(); roarClient.canvasPanel.commands._recentLoad();
roarClient.canvasPanel.commands._loadRecent()
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, dirty=False)
roarClient.canvasPanel.update(roarClient.canvasPanel.canvas.importStore.inSize, False, roarClient.canvasPanel.canvas.importStore.outMap)
roarClient.canvasPanel.commands.update(pathName=argv[0], undoLevel=-1)
roarClient.canvasPanel.commands._recentPush(argv[0])
roarClient.canvasPanel.commands.canvasTool(roarClient.canvasPanel.commands.canvasTool, 1)(None)
roarClient.canvasPanel.commands._pushRecent(argv[0])
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)