{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.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2019-09-08 18:00:11 +02:00
parent d410ef9321
commit 6da84c05c5
3 changed files with 100 additions and 116 deletions

View File

@ -23,15 +23,15 @@ class Canvas():
self.journal.pushCursor(patchDelta)
else:
if commitUndo:
if not self.dirty:
self.journal.pushDeltas([], []); self.dirty = True;
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
self.journal.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch)
# }}}
# {{{ resize(self, newSize, commitUndo=True): XXX
def resize(self, newSize, commitUndo=True):
if newSize != self.size:
self.dirty = False
self.dirtyJournal = False
if self.map == None:
self.map, oldSize = [], [0, 0]
else:
@ -40,35 +40,43 @@ class Canvas():
self.journal.resetCursor()
if commitUndo:
undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize]
if not self.dirty:
self.journal.pushDeltas([], []); self.dirty = True;
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
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]):
if not self.dirty:
self.journal.pushDeltas([], []); self.dirty = True;
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
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([[1, 1, 0, " "]] * deltaSize[0])
for numNewCol in range(oldSize[0], newSize[0]):
self.dispatchPatch(False, [numNewCol, numRow, 1, 1, 0, " "], commitUndo)
if commitUndo:
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
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]):
if not self.dirty:
self.journal.pushDeltas([], []); self.dirty = True;
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
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([[[1, 1, 0, " "]] * newSize[0]])
for numNewCol in range(newSize[0]):
self.dispatchPatch(False, [numNewCol, numNewRow, 1, 1, 0, " "], commitUndo)
if commitUndo:
if not self.dirtyJournal:
self.journal.pushDeltas([], []); self.dirtyJournal = True;
self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None)
self.dispatchPatch(False, [numNewCol, numNewRow, 1, 1, 0, " "], False)
self.size = newSize
return True
else:
@ -87,7 +95,7 @@ class Canvas():
#
# __init__(self, size): initialisation method
def __init__(self, size):
self.dirty, self.dirtyCursor, self.map, self.size = False, False, None, size
self.dirtyJournal, self.dirtyCursor, self.map, self.size = False, False, None, size
self.exportStore, self.importStore, self.journal = CanvasExportStore(), CanvasImportStore(), CanvasJournal()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -52,14 +52,6 @@ def GuiCanvasSelectDecorator(idx, caption, label, icon, accel, initialState):
class GuiCanvasInterface():
"""XXX"""
# {{{ _dialogSaveChanges(self): XXX
def _dialogSaveChanges(self):
with wx.MessageDialog(self.parentCanvas, \
"Do you want to save changes to {}?".format(self.canvasPathName), \
"", wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_QUESTION|wx.YES_NO) as dialog:
dialogChoice = dialog.ShowModal()
return dialogChoice
# }}}
# {{{ _initColourBitmaps(self): XXX
def _initColourBitmaps(self):
for numColour in range(len(self.canvasColour.attrList)):
@ -84,6 +76,24 @@ class GuiCanvasInterface():
toolBitmapDc.DrawRectangle(8, 8, 16, 16)
self.canvasColourAlpha.attrList[0]["icon"] = ["", None, toolBitmap]
# }}}
# {{{ _promptSaveChanges(self): XXX
def _promptSaveChanges(self):
if self.parentCanvas.dirty:
with wx.MessageDialog(self.parentCanvas, \
"Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)"), \
"", wx.CANCEL|wx.CANCEL_DEFAULT|wx.ICON_QUESTION|wx.YES_NO) as dialog:
dialogChoice = dialog.ShowModal()
if dialogChoice == wx.ID_CANCEL:
return False
elif dialogChoice == wx.ID_NO:
return True
elif dialogChoice == wx.ID_YES:
return self.canvasSaveAs(None) if self.canvasPathName == None else self.canvasSave(None)
else:
return False
else:
return True
# }}}
# {{{ canvasAbout(self, event): XXX
@GuiCanvasCommandDecorator("About", "&About", None, None, True)
@ -158,62 +168,43 @@ class GuiCanvasInterface():
# {{{ canvasExit(self, event): XXX
@GuiCanvasCommandDecorator("Exit", "E&xit", None, [wx.ACCEL_CTRL, ord("X")], None)
def canvasExit(self, event):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
self.parentFrame.Close(True)
if self._promptSaveChanges():
self.parentFrame.Close(True)
# }}}
# {{{ canvasNew(self, event, newCanvasSize=None): XXX
@GuiCanvasCommandDecorator("New", "&New", ["", wx.ART_NEW], [wx.ACCEL_CTRL, ord("N")], None)
def canvasNew(self, event, newCanvasSize=None):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
if newCanvasSize == None:
newCanvasSize = list(self.parentCanvas.defaultCanvasSize)
newMap = [[[1, 1, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])]
self.parentCanvas.update(newCanvasSize, False, newMap)
self.canvasPathName = None
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(pathName="", undoLevel=-1)
if self._promptSaveChanges():
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
if newCanvasSize == None:
newCanvasSize = list(self.parentCanvas.defaultCanvasSize)
newMap = [[[1, 1, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])]
self.parentCanvas.dirty = False
self.parentCanvas.update(newCanvasSize, False, newMap)
self.canvasPathName = None
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
# }}}
# {{{ canvasOpen(self, event): XXX
@GuiCanvasCommandDecorator("Open", "&Open", ["", wx.ART_FILE_OPEN], [wx.ACCEL_CTRL, ord("O")], None)
def canvasOpen(self, event):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog:
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
self.canvasPathName = dialog.GetPath()
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importTextFile(self.canvasPathName)
if rc:
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(pathName=self.canvasPathName, undoLevel=-1)
return True
else:
print("error: {}".format(error), file=sys.stderr)
if self._promptSaveChanges():
with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog:
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
self.canvasPathName = dialog.GetPath()
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importTextFile(self.canvasPathName)
if rc:
self.parentCanvas.dirty = False
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
return True
else:
print("error: {}".format(error), file=sys.stderr)
return False
# }}}
# {{{ canvasPaste(self, event): XXX
@GuiCanvasCommandDecorator("Paste", "&Paste", ["", wx.ART_PASTE], None, False)
@ -231,14 +222,15 @@ class GuiCanvasInterface():
def canvasSave(self, event):
if self.canvasPathName == None:
if self.canvasSaveAs(event) == False:
return
return False
try:
with open(self.canvasPathName, "w", encoding="utf-8") as outFile:
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
self.parentCanvas.canvas.exportStore.exportTextFile( \
self.parentCanvas.canvas.map, self.parentCanvas.canvas.size, outFile)
self.parentCanvas.canvas.exportStore.exportTextFile(self.parentCanvas.canvas.map, self.parentCanvas.canvas.size, outFile)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True
self.parentCanvas.dirty = False
self.update(dirty=self.parentCanvas.dirty)
return True
except IOError as error:
return False
# }}}
@ -420,26 +412,19 @@ class GuiCanvasInterface():
# {{{ canvasImportAnsi(self, event): XXX
@GuiCanvasCommandDecorator("Import ANSI...", "Import ANSI...", None, None, None)
def canvasImportAnsi(self, event):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "ANSI files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog:
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
elif self._promptSaveChanges():
self.canvasPathName = dialog.GetPath()
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importAnsiFile(self.canvasPathName)
if rc:
self.parentCanvas.dirty = True
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.canvasPathName = "(Imported)"
self.canvasPathName = None
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(pathName="(Imported)", undoLevel=-1)
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
return True
else:
print("error: {}".format(error), file=sys.stderr)
@ -449,27 +434,21 @@ class GuiCanvasInterface():
@GuiCanvasCommandDecorator("Import from clipboard", "&Import from clipboard", None, None, None)
def canvasImportFromClipboard(self, event):
rc = False
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) \
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) \
and wx.TheClipboard.Open():
inBuffer = wx.TextDataObject()
if wx.TheClipboard.GetData(inBuffer):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importTextBuffer(io.StringIO(inBuffer.GetText()))
if rc:
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.canvasPathName = "(Clipboard)"
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(pathName="(Clipboard)", undoLevel=-1)
else:
print("error: {}".format(error), file=sys.stderr)
if self._promptSaveChanges():
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importTextBuffer(io.StringIO(inBuffer.GetText()))
if rc:
self.parentCanvas.dirty = True
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.canvasPathName = None
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
else:
print("error: {}".format(error), file=sys.stderr)
wx.TheClipboard.Close()
if not rc:
with wx.MessageDialog(self.parentCanvas, "Clipboard does not contain text data and/or cannot be opened", "", wx.ICON_QUESTION | wx.OK | wx.OK_DEFAULT) as dialog:
@ -478,26 +457,19 @@ class GuiCanvasInterface():
# {{{ canvasImportSauce(self, event): XXX
@GuiCanvasCommandDecorator("Import SAUCE...", "Import SAUCE...", None, None, None)
def canvasImportSauce(self, event):
if self.canvasPathName != None:
saveChanges = self._dialogSaveChanges()
if saveChanges == wx.ID_CANCEL:
return
elif saveChanges == wx.ID_NO:
pass
elif saveChanges == wx.ID_YES:
self.canvasSave(event)
with wx.FileDialog(self.parentCanvas, "Open", os.getcwd(), "", "SAUCE files (*.ans;*.txt)|*.ans;*.txt|All Files (*.*)|*.*", wx.FD_OPEN) as dialog:
if dialog.ShowModal() == wx.ID_CANCEL:
return False
else:
elif self._promptSaveChanges():
self.canvasPathName = dialog.GetPath()
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
rc, error = self.parentCanvas.canvas.importStore.importSauceFile(self.canvasPathName)
if rc:
self.parentCanvas.dirty = True
self.parentCanvas.update(self.parentCanvas.canvas.importStore.inSize, False, self.parentCanvas.canvas.importStore.outMap)
self.canvasPathName = "(Imported)"
self.canvasPathName = None
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
self.update(pathName="(Imported)", undoLevel=-1)
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
return True
else:
print("error: {}".format(error), file=sys.stderr)
@ -519,7 +491,7 @@ class GuiCanvasInterface():
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"] != "":
if self.lastPanelState["pathName"] != None:
basePathName = os.path.basename(self.lastPanelState["pathName"])
textItems.append("Current file: {}".format(basePathName))
self.parentFrame.SetTitle("{} - roar".format(basePathName))
@ -527,6 +499,9 @@ class GuiCanvasInterface():
self.parentFrame.SetTitle("roar")
if "toolName" in self.lastPanelState:
textItems.append("Current tool: {}".format(self.lastPanelState["toolName"]))
if "dirty" in self.lastPanelState \
and self.lastPanelState["dirty"]:
textItems.append("*")
self.parentFrame.statusBar.SetStatusText(" | ".join(textItems))
if "undoLevel" in self.lastPanelState:
if self.lastPanelState["undoLevel"] >= 0:

View File

@ -80,7 +80,7 @@ class GuiCanvasPanel(wx.Panel):
# }}}
# {{{ onPanelInput(self, event): XXX
def onPanelInput(self, event):
self.canvas.dirty, self.canvas.dirtyCursor = False, False
self.canvas.dirtyJournal, self.canvas.dirtyCursor = False, False
eventDc, eventType, tool = self.backend.getDeviceContext(self), event.GetEventType(), self.interface.currentTool
if eventType == wx.wxEVT_CHAR:
mapPoint = self.brushPos
@ -97,8 +97,9 @@ class GuiCanvasPanel(wx.Panel):
event, mapPoint, self.brushColours, self.brushSize, \
event.Dragging(), event.LeftIsDown(), event.RightIsDown(), \
self.dispatchPatch, eventDc)
if self.canvas.dirty:
self.interface.update(cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel)
if self.canvas.dirtyJournal:
self.dirty = True
self.interface.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel)
if eventType == wx.wxEVT_MOTION:
self.interface.update(cellPos=mapPoint)
# }}}
@ -119,7 +120,7 @@ class GuiCanvasPanel(wx.Panel):
self.backend, self.interface = backend(defaultCanvasSize, defaultCellSize), interface(self, parentFrame)
self.brushColours, self.brushPos, self.brushSize = [4, 1], [0, 0], [1, 1]
self.canvas, self.canvasPos, self.defaultCanvasPos, self.defaultCanvasSize, self.defaultCellSize = canvas, defaultCanvasPos, defaultCanvasPos, defaultCanvasSize, defaultCellSize
self.parentFrame = parentFrame
self.dirty, self.parentFrame = False, parentFrame
self.Bind(wx.EVT_CLOSE, self.onPanelClose)
self.Bind(wx.EVT_ENTER_WINDOW, self.onPanelEnterWindow)