summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/asymptote/GUI/Window1.py
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/asymptote/GUI/Window1.py')
-rwxr-xr-xMaster/texmf-dist/asymptote/GUI/Window1.py837
1 files changed, 672 insertions, 165 deletions
diff --git a/Master/texmf-dist/asymptote/GUI/Window1.py b/Master/texmf-dist/asymptote/GUI/Window1.py
index 1f7b67b1fa1..134d3cd1968 100755
--- a/Master/texmf-dist/asymptote/GUI/Window1.py
+++ b/Master/texmf-dist/asymptote/GUI/Window1.py
@@ -18,6 +18,7 @@ import tempfile
import datetime
import string
import atexit
+import pickle
import xasyUtils as xu
import xasy2asy as x2a
@@ -50,7 +51,6 @@ class TransformationChanges(ActionChanges):
self.transformation = transformation
self.isLocal = isLocal
-
class ObjCreationChanges(ActionChanges):
def __init__(self, obj):
self.object = obj
@@ -60,6 +60,18 @@ class HardDeletionChanges(ActionChanges):
self.item = obj
self.objIndex = pos
+class SoftDeletionChanges(ActionChanges):
+ def __init__(self, obj, keyPos):
+ self.item = obj
+ self.keyMap = keyPos
+
+class EditBezierChanges(ActionChanges):
+ def __init__(self, obj, pos, oldPath, newPath):
+ self.item = obj
+ self.objIndex = pos
+ self.oldPath = oldPath
+ self.newPath = newPath
+
class AnchorMode:
center = 0
origin = 1
@@ -68,7 +80,7 @@ class AnchorMode:
bottomRight = 4
bottomLeft = 5
customAnchor = 6
-
+
class GridMode:
cartesian = 0
@@ -84,6 +96,14 @@ class SelectionMode:
delete = 5
setAnchor = 6
selectEdit = 7
+ openPoly = 8
+ closedPoly = 9
+ openCurve = 10
+ closedCurve = 11
+ addPoly = 12
+ addCircle = 13
+ addLabel = 14
+ addFreehand = 15
class AddObjectMode:
Circle = 0
@@ -92,14 +112,15 @@ class AddObjectMode:
class MainWindow1(Qw.QMainWindow):
defaultFrameStyle = """
- QFrame{{
+ QFrame{{
padding: 4.0;
- border-radius: 3.0;
+ border-radius: 3.0;
background: rgb({0}, {1}, {2})
}}
"""
def __init__(self):
+ self.testingActions = []
super().__init__()
self.ui = Ui_MainWindow()
global devicePixelRatio
@@ -109,12 +130,13 @@ class MainWindow1(Qw.QMainWindow):
self.settings = xo.BasicConfigs.defaultOpt
self.keyMaps = xo.BasicConfigs.keymaps
+ self.openRecent = xo.BasicConfigs.openRecent
self.raw_args = Qc.QCoreApplication.arguments()
self.args = xa.parseArgs(self.raw_args)
self.strings = xs.xasyString(self.args.language)
- self.asy2psmap = x2a.identity()
+ self.asy2psmap = x2a.yflip()
if self.settings['asyBaseLocation'] is not None:
os.environ['ASYMPTOTE_DIR'] = self.settings['asyBaseLocation']
@@ -134,7 +156,8 @@ class MainWindow1(Qw.QMainWindow):
# For initialization purposes
self.canvSize = Qc.QSize()
- self.filename = None
+ self.fileName = None
+ self.asyFileName = None
self.currDir = None
self.mainCanvas = None
self.dpi = 300
@@ -165,9 +188,9 @@ class MainWindow1(Qw.QMainWindow):
self.inMidTransformation = False
self.addMode = None
self.currentlySelectedObj = {'key': None, 'allSameKey': set(), 'selectedIndex': None, 'keyIndex': None}
- self.pendingSelectedObjList = []
+ self.pendingSelectedObjList = []
self.pendingSelectedObjIndex = -1
-
+
self.savedMousePosition = None
self.currentBoundingBox = None
self.selectionDelta = None
@@ -177,9 +200,12 @@ class MainWindow1(Qw.QMainWindow):
self.scaleFactor = 1
self.panOffset = [0, 0]
+ # Keyboard can focus outside of textboxes
+ self.setFocusPolicy(Qc.Qt.StrongFocus)
+
super().setMouseTracking(True)
# setMouseTracking(True)
-
+
self.undoRedoStack = Urs.actionStack()
self.lockX = False
@@ -211,8 +237,10 @@ class MainWindow1(Qw.QMainWindow):
self.modeButtons = {
self.ui.btnTranslate, self.ui.btnRotate, self.ui.btnScale, # self.ui.btnSelect,
- self.ui.btnPan, self.ui.btnDeleteMode, self.ui.btnAnchor,
- self.ui.btnSelectEdit
+ self.ui.btnPan, self.ui.btnDeleteMode, self.ui.btnAnchor,
+ self.ui.btnSelectEdit, self.ui.btnOpenPoly, self.ui.btnClosedPoly,
+ self.ui.btnOpenCurve, self.ui.btnClosedCurve, self.ui.btnAddPoly,
+ self.ui.btnAddCircle, self.ui.btnAddLabel, self.ui.btnAddFreehand
}
self.objButtons = {self.ui.btnCustTransform, self.ui.actionTransform, self.ui.btnSendForwards,
@@ -225,17 +253,17 @@ class MainWindow1(Qw.QMainWindow):
self.currAddOptionsWgt = None
self.currAddOptions = {
- 'options': self.settings,
+ 'options': self.settings,
'inscribed': True,
'sides': 3,
'centermode': True,
- 'fontSize': None,
+ 'fontSize': None,
'asyengine': self.asyEngine,
'fill': self.ui.btnFill.isChecked(),
'closedPath': False,
- 'useBezier': True,
+ 'useBezier': True,
'magnification': self.magnification,
- 'editBezierlockMode': xbi.Web.LockMode.angleLock,
+ 'editBezierlockMode': xbi.Web.LockMode.angleLock,
'autoRecompute': False
}
@@ -249,7 +277,7 @@ class MainWindow1(Qw.QMainWindow):
# commands switchboard
self.commandsFunc = {
- 'quit': Qc.QCoreApplication.quit,
+ 'quit': self.btnCloseFileonClick,
'undo': self.btnUndoOnClick,
'redo': self.btnRedoOnClick,
'manual': self.actionManual,
@@ -261,10 +289,10 @@ class MainWindow1(Qw.QMainWindow):
'commandPalette': self.enterCustomCommand,
'clearGuide': self.clearGuides,
'finalizeAddObj': self.finalizeAddObj,
- 'finalizeCurve': self.finalizeCurve,
- 'finalizeCurveClosed': self.finalizeCurveClosed,
+ 'finalizeCurve': self.finalizeCurve,
+ 'finalizeCurveClosed': self.finalizeCurveClosed,
'setMag': self.setMagPrompt,
- 'deleteObject': self.btnSelectiveDeleteOnClick,
+ 'deleteObject': self.btnSelectiveDeleteOnClick,
'anchorMode': self.switchToAnchorMode,
'moveUp': lambda: self.translate(0, -1),
'moveDown': lambda: self.translate(0, 1),
@@ -274,10 +302,17 @@ class MainWindow1(Qw.QMainWindow):
'scrollLeft': lambda: self.arrowButtons(-1, 0, True),
'scrollRight': lambda: self.arrowButtons(1, 0, True),
'scrollUp': lambda: self.arrowButtons(0, 1, True),
- 'scrollDown': lambda: self.arrowButtons(0, -1, True),
+ 'scrollDown': lambda: self.arrowButtons(0, -1, True),
+
+ 'zoomIn': lambda: self.arrowButtons(0, 1, False, True),
+ 'zoomOut': lambda: self.arrowButtons(0, -1, False, True),
+
+ 'open': self.btnLoadFileonClick,
+ 'save': self.actionSave,
+ 'export': self.btnExportAsymptoteOnClick,
- 'zoomIn': lambda: self.arrowButtons(0, 1, False, True),
- 'zoomOut': lambda: self.arrowButtons(0, -1, False, True)
+ 'copy': self.copyItem,
+ 'paste': self.pasteItem
}
self.hiddenKeys = set()
@@ -291,6 +326,7 @@ class MainWindow1(Qw.QMainWindow):
# from xasyoptions config file
self.loadKeyMaps()
self.setupXasyOptions()
+ self.populateOpenRecent()
self.colorDialog = Qw.QColorDialog(x2a.asyPen.convertToQColor(self._currentPen.color), self)
self.initPenInterface()
@@ -320,7 +356,7 @@ class MainWindow1(Qw.QMainWindow):
def getScrsTransform(self):
# pipeline:
# assuming origin <==> top left
- # (Pan) * (Translate) * (Flip the images) * (Zoom) * (Obj transform) * (Base Information)
+ # (Pan) * (Translate) * (Flip the images) * (Zoom) * (Obj transform) * (Base Information)
# pipeline --> let x, y be the postscript point
# p = (mx + cx + panoffset, -ny + cy + panoffset)
@@ -366,6 +402,12 @@ class MainWindow1(Qw.QMainWindow):
self.settings.load()
self.quickUpdate()
+ def openAndReloadKeymaps(self):
+ keymapsFile = self.keyMaps.settingsFileLocation()
+ subprocess.run(args=self.getExternalEditor(asypath=keymapsFile))
+ self.settings.load()
+ self.quickUpdate()
+
def setMagPrompt(self):
commandText, result = Qw.QInputDialog.getText(self, '', 'Enter magnification:')
if result:
@@ -373,6 +415,11 @@ class MainWindow1(Qw.QMainWindow):
self.currAddOptions['magnification'] = self.magnification
self.quickUpdate()
+ def setTextPrompt(self):
+ commandText, result = Qw.QInputDialog.getText(self, '', 'Enter new text:')
+ if result:
+ return commandText
+
def btnTogglePythonOnClick(self, checked):
self.terminalPythonMode = checked
@@ -381,7 +428,10 @@ class MainWindow1(Qw.QMainWindow):
def handleArguments(self):
if self.args.filename is not None:
- self.loadFile(self.args.filename)
+ if os.path.exists(self.args.filename):
+ self.actionOpen(os.path.abspath(self.args.filename))
+ else:
+ self.loadFile(self.args.filename)
else:
self.initializeEmptyFile()
@@ -417,14 +467,18 @@ class MainWindow1(Qw.QMainWindow):
self.ui.actionRedo.triggered.connect(lambda: self.execCustomCommand('redo'))
self.ui.actionTransform.triggered.connect(lambda: self.execCustomCommand('transform'))
+ self.ui.actionNewFile.triggered.connect(self.actionNewFile)
self.ui.actionOpen.triggered.connect(self.actionOpen)
+ self.ui.actionClearRecent.triggered.connect(self.actionClearRecent)
self.ui.actionSave.triggered.connect(self.actionSave)
self.ui.actionSaveAs.triggered.connect(self.actionSaveAs)
self.ui.actionManual.triggered.connect(self.actionManual)
self.ui.actionAbout.triggered.connect(self.actionAbout)
self.ui.actionSettings.triggered.connect(self.openAndReloadSettings)
+ self.ui.actionKeymaps.triggered.connect(self.openAndReloadKeymaps)
self.ui.actionEnterCommand.triggered.connect(self.enterCustomCommand)
- self.ui.actionExportAsymptote.triggered.connect(self.btnExportAsyOnClick)
+ self.ui.actionExportAsymptote.triggered.connect(self.btnExportAsymptoteOnClick)
+ self.ui.actionExportToAsy.triggered.connect(self.btnExportToAsyOnClick)
def setupXasyOptions(self):
if self.settings['debugMode']:
@@ -443,7 +497,7 @@ class MainWindow1(Qw.QMainWindow):
self.ui.btnSave.clicked.connect(self.btnSaveonClick)
self.ui.btnQuickScreenshot.clicked.connect(self.btnQuickScreenshotOnClick)
- # self.ui.btnExportAsy.clicked.connect(self.btnExportAsyOnClick)
+ # self.ui.btnExportAsy.clicked.connect(self.btnExportAsymptoteOnClick)
self.ui.btnDrawAxes.clicked.connect(self.btnDrawAxesOnClick)
# self.ui.btnAsyfy.clicked.connect(lambda: self.asyfyCanvas(True))
@@ -467,7 +521,7 @@ class MainWindow1(Qw.QMainWindow):
self.ui.btnAnchor.clicked.connect(self.btnAnchorModeOnClick)
self.ui.btnSelectColor.clicked.connect(self.btnColorSelectOnClick)
- self.ui.txtLineWidth.textEdited.connect(self.txtLineWithEdited)
+ self.ui.txtLineWidth.textEdited.connect(self.txtLineWidthEdited)
# self.ui.btnCreateCurve.clicked.connect(self.btnCreateCurveOnClick)
self.ui.btnDrawGrid.clicked.connect(self.btnDrawGridOnClick)
@@ -475,6 +529,7 @@ class MainWindow1(Qw.QMainWindow):
self.ui.btnAddCircle.clicked.connect(self.btnAddCircleOnClick)
self.ui.btnAddPoly.clicked.connect(self.btnAddPolyOnClick)
self.ui.btnAddLabel.clicked.connect(self.btnAddLabelOnClick)
+ self.ui.btnAddFreehand.clicked.connect(self.btnAddFreehandOnClick)
# self.ui.btnAddBezierInplace.clicked.connect(self.btnAddBezierInplaceOnClick)
self.ui.btnClosedCurve.clicked.connect(self.btnAddClosedCurveOnClick)
self.ui.btnOpenCurve.clicked.connect(self.btnAddOpenCurveOnClick)
@@ -489,16 +544,19 @@ class MainWindow1(Qw.QMainWindow):
self.ui.btnDeleteMode.clicked.connect(self.btnDeleteModeOnClick)
# self.ui.btnSoftDelete.clicked.connect(self.btnSoftDeleteOnClick)
self.ui.btnToggleVisible.clicked.connect(self.btnSetVisibilityOnClick)
-
+
self.ui.btnEnterCommand.clicked.connect(self.btnTerminalCommandOnClick)
self.ui.btnTogglePython.clicked.connect(self.btnTogglePythonOnClick)
self.ui.btnSelectEdit.clicked.connect(self.btnSelectEditOnClick)
def btnDeleteModeOnClick(self):
- self.currentModeStack = [SelectionMode.delete]
- self.ui.statusbar.showMessage('Delete Mode')
- self.clearSelection()
- self.updateChecks()
+ if self.currentModeStack[-1] != SelectionMode.delete:
+ self.currentModeStack = [SelectionMode.delete]
+ self.ui.statusbar.showMessage('Delete mode')
+ self.clearSelection()
+ self.updateChecks()
+ else:
+ self.btnTranslateonClick()
def btnTerminalCommandOnClick(self):
if self.terminalPythonMode:
@@ -507,18 +565,25 @@ class MainWindow1(Qw.QMainWindow):
else:
pass
# TODO: How to handle this case?
- # Like AutoCAD?
+ # Like AutoCAD?
self.ui.txtTerminalPrompt.clear()
- def btnFillOnClick(self, checked):
+ def btnFillOnClick(self, checked):
+ if self.currentModeStack == [SelectionMode.selectEdit]:
+ if isinstance(self.addMode,xbi.InteractiveBezierEditor):
+ self.addMode.swapObjFill() #Check for crashes
self.currAddOptions['fill'] = checked
self.ui.btnOpenCurve.setEnabled(not checked)
self.ui.btnOpenPoly.setEnabled(not checked)
def btnSelectEditOnClick(self):
- self.ui.statusbar.showMessage('Edit mode')
- self.currentModeStack = [SelectionMode.selectEdit]
- self.updateChecks()
+ if self.currentModeStack[-1] != SelectionMode.selectEdit:
+ self.currentModeStack = [SelectionMode.selectEdit]
+ self.ui.statusbar.showMessage('Edit mode')
+ self.clearSelection()
+ self.updateChecks()
+ else:
+ self.btnTranslateonClick()
@property
def currentPen(self):
@@ -544,7 +609,7 @@ class MainWindow1(Qw.QMainWindow):
except Exception:
pass
- self.currentModeStack[-1] = None
+ #self.currentModeStack[-1] = None
self.addMode.objectCreated.connect(self.addInPlace)
self.updateModeBtnsOnly()
@@ -557,7 +622,10 @@ class MainWindow1(Qw.QMainWindow):
def addInPlace(self, obj):
obj.asyengine = self.asyEngine
- obj.pen = self.currentPen
+ if isinstance(obj, x2a.xasyText):
+ obj.label.pen = self.currentPen
+ else:
+ obj.pen = self.currentPen
obj.onCanvas = self.xasyDrawObj
obj.setKey(str(self.globalObjectCounter))
self.globalObjectCounter = self.globalObjectCounter + 1
@@ -576,11 +644,6 @@ class MainWindow1(Qw.QMainWindow):
self.currentGuides.clear()
self.quickUpdate()
- def btnAddCircleOnClick(self):
- self.addMode = InplaceAddObj.AddCircle(self)
- self.ui.statusbar.showMessage('')
- self.updateOptionWidget()
-
LegacyHint='Click and drag to draw; right click or space bar to finalize'
Hint='Click and drag to draw; release and click in place to add node; continue dragging'
HintClose=' or c to close.'
@@ -598,49 +661,90 @@ class MainWindow1(Qw.QMainWindow):
self.ui.statusbar.showMessage(self.Hint+self.HintClose)
def btnAddBezierInplaceOnClick(self):
+ self.fileChanged = True
self.addMode = InplaceAddObj.AddBezierShape(self)
self.updateOptionWidget()
def btnAddOpenLineOnClick(self):
- self.currAddOptions['useBezier'] = False
- self.currAddOptions['closedPath'] = False
- self.drawHintOpen()
- self.btnAddBezierInplaceOnClick()
+ if self.currentModeStack[-1] != SelectionMode.openPoly:
+ self.currentModeStack = [SelectionMode.openPoly]
+ self.currAddOptions['useBezier'] = False
+ self.currAddOptions['closedPath'] = False
+ self.drawHintOpen()
+ self.btnAddBezierInplaceOnClick()
+ else:
+ self.btnTranslateonClick()
def btnAddClosedLineOnClick(self):
- self.currAddOptions['useBezier'] = False
- self.currAddOptions['closedPath'] = True
- self.drawHint()
- self.btnAddBezierInplaceOnClick()
+ if self.currentModeStack[-1] != SelectionMode.closedPoly:
+ self.currentModeStack = [SelectionMode.closedPoly]
+ self.currAddOptions['useBezier'] = False
+ self.currAddOptions['closedPath'] = True
+ self.drawHint()
+ self.btnAddBezierInplaceOnClick()
+ else:
+ self.btnTranslateonClick()
def btnAddOpenCurveOnClick(self):
- self.currAddOptions['useBezier'] = True
- self.currAddOptions['closedPath'] = False
- self.drawHintOpen()
- self.btnAddBezierInplaceOnClick()
+ if self.currentModeStack[-1] != SelectionMode.openCurve:
+ self.currentModeStack = [SelectionMode.openCurve]
+ self.currAddOptions['useBezier'] = True
+ self.currAddOptions['closedPath'] = False
+ self.drawHintOpen()
+ self.btnAddBezierInplaceOnClick()
+ else:
+ self.btnTranslateonClick()
def btnAddClosedCurveOnClick(self):
- self.currAddOptions['useBezier'] = True
- self.currAddOptions['closedPath'] = True
- self.drawHint()
- self.btnAddBezierInplaceOnClick()
+ if self.currentModeStack[-1] != SelectionMode.closedCurve:
+ self.currentModeStack = [SelectionMode.closedCurve]
+ self.currAddOptions['useBezier'] = True
+ self.currAddOptions['closedPath'] = True
+ self.drawHint()
+ self.btnAddBezierInplaceOnClick()
+ else:
+ self.btnTranslateonClick()
def btnAddPolyOnClick(self):
- self.addMode = InplaceAddObj.AddPoly(self)
- self.ui.statusbar.showMessage('')
- self.updateOptionWidget()
+ if self.currentModeStack[-1] != SelectionMode.addPoly:
+ self.currentModeStack = [SelectionMode.addPoly]
+ self.addMode = InplaceAddObj.AddPoly(self)
+ self.ui.statusbar.showMessage('Add polygon on click')
+ self.updateOptionWidget()
+ else:
+ self.btnTranslateonClick()
- def btnAddLabelOnClick(self):
- self.addMode = InplaceAddObj.AddLabel(self)
- self.ui.statusbar.showMessage('')
- self.updateOptionWidget()
+ def btnAddCircleOnClick(self):
+ if self.currentModeStack[-1] != SelectionMode.addCircle:
+ self.currentModeStack = [SelectionMode.addCircle]
+ self.addMode = InplaceAddObj.AddCircle(self)
+ self.ui.statusbar.showMessage('Add circle on click')
+ self.updateOptionWidget()
+ else:
+ self.btnTranslateonClick()
- def updateCurve(self, valid, newCurve):
- self.previewCurve = newCurve
- self.quickUpdate()
+ def btnAddLabelOnClick(self):
+ if self.currentModeStack[-1] != SelectionMode.addLabel:
+ self.currentModeStack = [SelectionMode.addLabel]
+ self.addMode = InplaceAddObj.AddLabel(self)
+ self.ui.statusbar.showMessage('Add label on click')
+ self.updateOptionWidget()
+ else:
+ self.btnTranslateonClick()
+
+ def btnAddFreehandOnClick(self):
+ if self.currentModeStack[-1] != SelectionMode.addFreehand:
+ self.currentModeStack = [SelectionMode.addFreehand]
+ self.currAddOptions['useBezier'] = False
+ self.currAddOptions['closedPath'] = False
+ self.ui.statusbar.showMessage("Draw freehand")
+ self.addMode = InplaceAddObj.AddFreehand(self)
+ self.updateOptionWidget()
+ else:
+ self.btnTranslateonClick()
def addTransformationChanges(self, objIndex, transform, isLocal=False):
- self.undoRedoStack.add(self.createAction(TransformationChanges(objIndex,
+ self.undoRedoStack.add(self.createAction(TransformationChanges(objIndex,
transform, isLocal)))
self.checkUndoRedoButtons()
@@ -665,7 +769,11 @@ class MainWindow1(Qw.QMainWindow):
parent = selectedObj.parent()
if isinstance(parent, x2a.xasyScript):
- self.hiddenKeys.add((selectedObj.key, selectedObj.keyIndex))
+ objKey=(selectedObj.key, selectedObj.keyIndex)
+ self.hiddenKeys.add(objKey)
+ self.undoRedoStack.add(self.createAction(
+ SoftDeletionChanges(selectedObj.parent(), objKey)
+ ))
self.softDeleteObj((maj, minor))
else:
index = self.fileItems.index(selectedObj.parent())
@@ -673,10 +781,10 @@ class MainWindow1(Qw.QMainWindow):
self.undoRedoStack.add(self.createAction(
HardDeletionChanges(selectedObj.parent(), index)
))
- self.checkUndoRedoButtons()
-
+
self.fileItems.remove(selectedObj.parent())
+ self.checkUndoRedoButtons()
self.fileChanged = True
self.clearSelection()
self.asyfyCanvas()
@@ -709,8 +817,16 @@ class MainWindow1(Qw.QMainWindow):
def btnUndoOnClick(self):
- self.undoRedoStack.undo()
- self.checkUndoRedoButtons()
+ if self.currentlySelectedObj['selectedIndex'] is not None:
+ # avoid deleting currently selected object
+ maj, minor = self.currentlySelectedObj['selectedIndex']
+ selectedObj = self.drawObjects[maj][minor]
+ if selectedObj != self.drawObjects[-1][0]:
+ self.undoRedoStack.undo()
+ self.checkUndoRedoButtons()
+ else:
+ self.undoRedoStack.undo()
+ self.checkUndoRedoButtons()
def btnRedoOnClick(self):
self.undoRedoStack.redo()
@@ -731,6 +847,12 @@ class MainWindow1(Qw.QMainWindow):
self.fileItems.pop()
elif isinstance(change, HardDeletionChanges):
self.fileItems.insert(change.objIndex, change.item)
+ elif isinstance(change, SoftDeletionChanges):
+ key, keyIndex = change.keyMap
+ self.hiddenKeys.remove((key, keyIndex))
+ change.item.transfKeymap[key][keyIndex].deleted = False
+ elif isinstance(change, EditBezierChanges):
+ self.fileItems[change.objIndex].path = change.oldPath
self.asyfyCanvas()
def handleRedoChanges(self, change):
@@ -742,6 +864,12 @@ class MainWindow1(Qw.QMainWindow):
self.fileItems.append(change.object)
elif isinstance(change, HardDeletionChanges):
self.fileItems.remove(change.item)
+ elif isinstance(change, SoftDeletionChanges):
+ key, keyIndex = change.keyMap
+ self.hiddenKeys.add((key, keyIndex))
+ change.item.transfKeymap[key][keyIndex].deleted = True
+ elif isinstance(change, EditBezierChanges):
+ self.fileItems[change.objIndex].path = change.newPath
self.asyfyCanvas()
# is this a "pythonic" way?
@@ -765,11 +893,26 @@ class MainWindow1(Qw.QMainWindow):
if result:
self.execCustomCommand(commandText)
- def addItemFromPath(self, path):
- newItem = x2a.xasyShape(path, self.asyEngine, pen=self.currentPen)
+ def addXasyShapeFromPath(self, path, pen = None, transform = x2a.identity(), key = None):
+ if not pen:
+ pen = self.currentPen
+ else:
+ pen = x2a.asyPen(self.asyEngine, color = pen['color'], width = pen['width'], pen_options = pen['options'])
+
+ newItem = x2a.xasyShape(path, self.asyEngine, pen = pen, transform = transform)
+ newItem.setKey(key)
self.fileItems.append(newItem)
- self.fileChanged = True
- self.asyfyCanvas()
+
+ def addXasyTextFromData(self, text, location, pen, transform, key, align, fontSize):
+ if not pen:
+ pen = self.currentPen
+ else:
+ pen = x2a.asyPen(self.asyEngine, color = pen['color'], width = pen['width'], pen_options = pen['options'])
+ newItem = x2a.xasyText(text, location, self.asyEngine, pen, transform, key, align, fontSize)
+ newItem.setKey(key)
+ newItem.onCanvas = self.xasyDrawObj
+ self.fileItems.append(newItem)
+
def actionManual(self):
asyManualURL = 'https://asymptote.sourceforge.io/asymptote.pdf'
@@ -778,11 +921,35 @@ class MainWindow1(Qw.QMainWindow):
def actionAbout(self):
Qw.QMessageBox.about(self,"xasy","This is xasy "+xasyVersion.xasyVersion+"; a graphical front end to the Asymptote vector graphics language: https://asymptote.sourceforge.io/")
- def btnExportAsyOnClick(self):
+ def actionExport(self, pathToFile):
+ asyFile = io.open(os.path.realpath(pathToFile), 'w')
+ xf.saveFile(asyFile, self.fileItems, self.asy2psmap)
+ asyFile.close()
+ self.ui.statusbar.showMessage(f"Exported to '{pathToFile}' as Asymptote File.")
+
+ def btnExportToAsyOnClick(self):
+ if self.fileName:
+ pathToFile = os.path.splitext(self.fileName)[0]+'.asy'
+ else:
+ self.btnExportAsymptoteOnClick()
+ return
+ if os.path.isfile(pathToFile):
+ reply = Qw.QMessageBox.question(self, 'Message',
+ f'"{os.path.split(pathToFile)[1]}" already exists. Do you want to overwrite it?',
+ Qw.QMessageBox.Yes, Qw.QMessageBox.No)
+ if reply == Qw.QMessageBox.No:
+ return
+ self.actionExport(pathToFile)
+
+ def btnExportAsymptoteOnClick(self):
diag = Qw.QFileDialog(self)
diag.setAcceptMode(Qw.QFileDialog.AcceptSave)
formatId = {
+ 'asy': {
+ 'name': 'Asymptote Files',
+ 'ext': ['*.asy']
+ },
'pdf': {
'name': 'PDF Files',
'ext': ['*.pdf']
@@ -805,14 +972,14 @@ class MainWindow1(Qw.QMainWindow):
}
}
- formats = ['pdf', 'svg', 'eps', 'png', '*']
+ formats = ['asy', 'pdf', 'svg', 'eps', 'png', '*']
formatText = ';;'.join('{0:s} ({1:s})'.format(formatId[form]['name'], ' '.join(formatId[form]['ext']))
for form in formats)
if self.currDir is not None:
diag.setDirectory(self.currDir)
- rawFile = os.path.splitext(os.path.basename(self.filename))[0] + '.pdf'
+ rawFile = os.path.splitext(os.path.basename(self.fileName))[0] + '.asy'
diag.selectFile(rawFile)
diag.setNameFilter(formatText)
@@ -823,25 +990,107 @@ class MainWindow1(Qw.QMainWindow):
return
finalFiles = diag.selectedFiles()
-
- with io.StringIO() as finalCode:
- xf.saveFile(finalCode, self.fileItems, self.asy2psmap)
- finalString = finalCode.getvalue()
+ finalString = xf.xasy2asyCode(self.fileItems, self.asy2psmap)
for file in finalFiles:
ext = os.path.splitext(file)
if len(ext) < 2:
- ext = 'pdf'
+ ext = 'asy'
else:
ext = ext[1][1:]
+ if ext == 'asy':
+ pathToFile = os.path.splitext(file)[0]+'.'+ext
+ asyFile = io.open(os.path.realpath(pathToFile), 'w')
+ xf.saveFile(asyFile, self.fileItems, self.asy2psmap)
+ asyFile.close()
+ else:
+ with subprocess.Popen(args=[self.asyPath, '-f{0}'.format(ext), '-o{0}'.format(file), '-'], encoding='utf-8',
+ stdin=subprocess.PIPE) as asy:
+
+ asy.stdin.write(finalString)
+ asy.stdin.close()
+ asy.wait(timeout=35)
+
+ def actionExportXasy(self, file):
+ xasyObjects, asyItems = xf.xasyToDict(self.fileName, self.fileItems, self.asy2psmap)
- with subprocess.Popen(args=[self.asyPath, '-f{0}'.format(ext), '-o{0}'.format(file), '-'], encoding='utf-8',
- stdin=subprocess.PIPE) as asy:
- print('test:', finalString)
- asy.stdin.write(finalString)
- asy.stdin.close()
- asy.wait(timeout=35)
+ if asyItems:
+ # Save imported items into the twin asy file
+ asyScriptItems = [item['item'] for item in asyItems if item['type'] == 'xasyScript']
+
+ prefix = os.path.splitext(self.fileName)[0]
+ asyFilePath = prefix + '.asy'
+
+ saveAsyFile = io.open(asyFilePath, 'w')
+ xf.saveFile(saveAsyFile, asyScriptItems, self.asy2psmap)
+ saveAsyFile.close()
+ self.updateScript()
+
+ openFile = open(file, 'wb')
+ pickle.dump(xasyObjects, openFile)
+ openFile.close()
+
+ def actionLoadXasy(self, file):
+ self.erase()
+ self.ui.statusbar.showMessage('Load {0}'.format(file)) # TODO: This doesn't show on the UI
+ self.fileName = file
+ self.currDir = os.path.dirname(self.fileName)
+
+ input_file = open(file, 'rb')
+ xasyObjects = pickle.load(input_file)
+ input_file.close()
+
+ prefix = os.path.splitext(self.fileName)[0]
+ asyFilePath = prefix + '.asy'
+ rawText = None
+ existsAsy = False
+
+ if os.path.isfile(asyFilePath):
+ asyFile = io.open(asyFilePath, 'r')
+ rawText = asyFile.read()
+ asyFile.close()
+ rawText, transfDict = xf.extractTransformsFromFile(rawText)
+ obj = x2a.xasyScript(canvas=self.xasyDrawObj, engine=self.asyEngine, transfKeyMap=transfDict)
+ obj.setScript(rawText)
+ self.fileItems.append(obj)
+ existsAsy = True
+
+ self.asyfyCanvas(force=True)
+
+ for item in xasyObjects['objects']:
+ key=item['transfKey']
+ if(key) in obj.transfKeymap.keys():
+ continue
+ obj.maxKey=max(obj.maxKey,int(key))
+ if item['type'] == 'xasyScript':
+ print("Uh oh, there should not be any asy objects loaded")
+
+ elif item['type'] == 'xasyText':
+ self.addXasyTextFromData( text = item['text'],
+ location = item['location'], pen = None,
+ transform = x2a.asyTransform(item['transform']), key = item['transfKey'],
+ align = item['align'], fontSize = item['fontSize']
+ )
+
+ elif item['type'] == 'xasyShape':
+ nodeSet = item['nodes']
+ linkSet = item['links']
+ path = x2a.asyPath(self.asyEngine)
+ path.initFromNodeList(nodeSet, linkSet)
+ self.addXasyShapeFromPath(path, pen = item['pen'], transform = x2a.asyTransform(item['transform']), key = item['transfKey'])
+ else:
+ print("ERROR")
+
+ self.asy2psmap = x2a.asyTransform(xasyObjects['asy2psmap'])
+ self.globalObjectCounter = obj.maxKey+1
+
+ self.asyfyCanvas()
+
+ if existsAsy:
+ self.ui.statusbar.showMessage(f"Corresponding Asymptote File '{os.path.basename(asyFilePath)}' found. Loaded both files.")
+ else:
+ self.ui.statusbar.showMessage("No Asymptote file found. Loaded exclusively GUI objects.")
def loadKeyMaps(self):
"""Inverts the mapping of the key
@@ -865,28 +1114,147 @@ class MainWindow1(Qw.QMainWindow):
def erase(self):
self.fileItems.clear()
+ self.hiddenKeys.clear()
+ self.undoRedoStack.clear()
+ self.checkUndoRedoButtons()
self.fileChanged = False
- def actionOpen(self):
+ #We include this function to keep the general program flow consistent
+ def closeEvent(self, event):
+ if self.actionClose() == Qw.QMessageBox.Cancel:
+ event.ignore()
+
+ def actionNewFile(self):
if self.fileChanged:
- save="Save current file?"
- reply=Qw.QMessageBox.question(self,'Message',save,Qw.QMessageBox.Yes,
- Qw.QMessageBox.No)
+ reply = self.saveDialog()
if reply == Qw.QMessageBox.Yes:
self.actionSave()
+ elif reply == Qw.QMessageBox.Cancel:
+ return
+ self.erase()
+ self.asyfyCanvas(force=True)
+ self.fileName = None
+ self.updateTitle()
+
+
+ def actionOpen(self, fileName = None):
+ if self.fileChanged:
+ reply = self.saveDialog()
+ if reply == Qw.QMessageBox.Yes:
+ self.actionSave()
+ elif reply == Qw.QMessageBox.Cancel:
+ return
- filename = Qw.QFileDialog.getOpenFileName(self, 'Open Asymptote File','', '*.asy')
- if filename[0]:
- self.loadFile(filename[0])
+ if fileName:
+ # Opening via open recent or cmd args
+ _, file_extension = os.path.splitext(fileName)
+ if file_extension == '.xasy':
+ self.actionLoadXasy(fileName)
+ else:
+ self.loadFile(fileName)
+ self.populateOpenRecent(fileName)
+ else:
+ filename = Qw.QFileDialog.getOpenFileName(self, 'Open Xasy/Asymptote File','', '(*.xasy *.asy)')
+ if filename[0]:
+ _, file_extension = os.path.splitext(filename[0])
+ if file_extension == '.xasy':
+ self.actionLoadXasy(filename[0])
+ else:
+ self.loadFile(filename[0])
+
+ self.populateOpenRecent(filename[0].strip())
+
+ def actionClearRecent(self):
+ self.ui.menuOpenRecent.clear()
+ self.openRecent.clear()
+ self.ui.menuOpenRecent.addAction("Clear", self.actionClearRecent)
+
+ def populateOpenRecent(self, recentOpenedFile = None):
+ self.ui.menuOpenRecent.clear()
+ if recentOpenedFile:
+ self.openRecent.insert(recentOpenedFile)
+ for count, path in enumerate(self.openRecent.pathList):
+ if count > 8:
+ break
+ action = Qw.QAction(path, self, triggered = lambda state, path = path: self.actionOpen(fileName = path))
+ self.ui.menuOpenRecent.addAction(action)
+ self.ui.menuOpenRecent.addSeparator()
+ self.ui.menuOpenRecent.addAction("Clear", self.actionClearRecent)
+
+ def saveDialog(self) -> bool:
+ save = "Save current file?"
+ replyBox = Qw.QMessageBox()
+ replyBox.setText("Save current file?")
+ replyBox.setWindowTitle("Message")
+ replyBox.setStandardButtons(Qw.QMessageBox.Yes | Qw.QMessageBox.No | Qw.QMessageBox.Cancel)
+ reply = replyBox.exec()
+
+ return reply
+
+ def actionClose(self):
+ if self.fileChanged:
+ reply = self.saveDialog()
+ if reply == Qw.QMessageBox.Yes:
+ self.actionSave()
+ Qc.QCoreApplication.quit()
+ elif reply == Qw.QMessageBox.No:
+ Qc.QCoreApplication.quit()
+ else:
+ return reply
+ else:
+ Qc.QCoreApplication.quit()
def actionSave(self):
- if self.filename is None:
+ if self.fileName is None:
self.actionSaveAs()
+
else:
- saveFile = io.open(self.filename, 'w')
- xf.saveFile(saveFile, self.fileItems, self.asy2psmap)
- saveFile.close()
+ _, file_extension = os.path.splitext(self.fileName)
+ if file_extension == ".asy":
+ if self.existsXasy():
+ warning = "Choose save format. Note that objects saved in asy format cannot be edited graphically."
+ replyBox = Qw.QMessageBox()
+ replyBox.setWindowTitle('Warning')
+ replyBox.setText(warning)
+ replyBox.addButton("Save as .xasy", replyBox.NoRole)
+ replyBox.addButton("Save as .asy", replyBox.YesRole)
+ replyBox.addButton(Qw.QMessageBox.Cancel)
+ reply = replyBox.exec()
+ if reply == 1:
+ saveFile = io.open(self.fileName, 'w')
+ xf.saveFile(saveFile, self.fileItems, self.asy2psmap)
+ saveFile.close()
+ self.ui.statusbar.showMessage('File saved as {}'.format(self.fileName))
+ self.fileChanged = False
+ elif reply == 0:
+ prefix = os.path.splitext(self.fileName)[0]
+ xasyFilePath = prefix + '.xasy'
+ if os.path.isfile(xasyFilePath):
+ warning = f'"{os.path.basename(xasyFilePath)}" already exists. Do you want to overwrite it?'
+ reply = Qw.QMessageBox.question(self, "Same File", warning, Qw.QMessageBox.No, Qw.QMessageBox.Yes)
+ if reply == Qw.QMessageBox.No:
+ return
+
+ self.actionExportXasy(xasyFilePath)
+ self.fileName = xasyFilePath
+ self.ui.statusbar.showMessage('File saved as {}'.format(self.fileName))
+ self.fileChanged = False
+ else:
+ return
+
+ else:
+ saveFile = io.open(self.fileName, 'w')
+ xf.saveFile(saveFile, self.fileItems, self.asy2psmap)
+ saveFile.close()
+ self.fileChanged = False
+ elif file_extension == ".xasy":
+ self.actionExportXasy(self.fileName)
+ self.ui.statusbar.showMessage('File saved as {}'.format(self.fileName))
+ self.fileChanged = False
+ else:
+ print("ERROR: file extension not supported")
self.updateScript()
+ self.updateTitle()
def updateScript(self):
for item in self.fileItems:
@@ -895,15 +1263,30 @@ class MainWindow1(Qw.QMainWindow):
item.setScript(item.updatedCode)
item.updatedCode = None
+ def existsXasy(self):
+ for item in self.fileItems:
+ if not isinstance(item, x2a.xasyScript):
+ return True
+ return False
+
def actionSaveAs(self):
- saveLocation = Qw.QFileDialog.getSaveFileName(self, 'Save File')[0]
+ initSave = os.path.splitext(str(self.fileName))[0]+'.xasy'
+ saveLocation = Qw.QFileDialog.getSaveFileName(self, 'Save File', initSave, "Xasy File (*.xasy)")[0]
if saveLocation:
- saveFile = io.open(saveLocation, 'w')
- xf.saveFile(saveFile, self.fileItems, self.asy2psmap)
- saveFile.close()
- self.filename = saveLocation
+ _, file_extension = os.path.splitext(saveLocation)
+ if not file_extension:
+ saveLocation += '.xasy'
+ self.actionExportXasy(saveLocation)
+ elif file_extension == ".xasy":
+ self.actionExportXasy(saveLocation)
+ else:
+ print("ERROR: file extension not supported")
+ self.fileName = saveLocation
self.updateScript()
-
+ self.fileChanged = False
+ self.updateTitle()
+ self.populateOpenRecent(saveLocation)
+
def btnQuickScreenshotOnClick(self):
saveLocation = Qw.QFileDialog.getSaveFileName(self, 'Save Screenshot','')
@@ -913,6 +1296,9 @@ class MainWindow1(Qw.QMainWindow):
def btnLoadFileonClick(self):
self.actionOpen()
+ def btnCloseFileonClick(self):
+ self.actionClose()
+
def btnSaveonClick(self):
self.actionSave()
@@ -933,7 +1319,7 @@ class MainWindow1(Qw.QMainWindow):
self._currentPen.setColorFromQColor(self.colorDialog.selectedColor())
self.updateFrameDispColor()
- def txtLineWithEdited(self, text):
+ def txtLineWidthEdited(self, text):
new_val = xu.tryParse(text, float)
if new_val is not None:
if new_val > 0:
@@ -985,12 +1371,12 @@ class MainWindow1(Qw.QMainWindow):
def mouseMoveEvent(self, mouseEvent: Qg.QMouseEvent): # TODO: Actually refine grid snapping...
if not self.ui.imgLabel.underMouse() and not self.mouseDown:
- return
+ return
self.updateMouseCoordLabel()
asyPos, canvasPos = self.getAsyCoordinates()
- # add mode
+ # add mode
if self.addMode is not None:
if self.addMode.active:
self.addMode.mouseMove(asyPos, mouseEvent)
@@ -1016,7 +1402,7 @@ class MainWindow1(Qw.QMainWindow):
self.quickUpdate()
return
- # otherwise, in transformation
+ # otherwise, in transformation
if self.inMidTransformation:
if self.currentModeStack[-1] == SelectionMode.translate:
newPos = canvasPos - self.savedMousePosition
@@ -1080,11 +1466,11 @@ class MainWindow1(Qw.QMainWindow):
self.pendingSelectedObjList.clear()
self.pendingSelectedObjIndex = -1
self.quickUpdate()
- return
+ return
def mouseReleaseEvent(self, mouseEvent):
- assert isinstance(mouseEvent, Qg.QMouseEvent)
+ assert isinstance(mouseEvent, Qg.QMouseEvent)
if not self.mouseDown:
return
@@ -1124,8 +1510,7 @@ class MainWindow1(Qw.QMainWindow):
keyModifiers = keyModifiers | defaultModifiers
if keyModifiers & int(Qc.Qt.ControlModifier):
oldMag = self.magnification
-
- factor=0.5/devicePixelRatio;
+ factor = 0.5/devicePixelRatio
cx, cy = self.canvSize.width()*factor, self.canvSize.height()*factor
centerPoint = Qc.QPointF(cx, cy) * self.getScrsTransform().inverted()[0]
@@ -1203,7 +1588,7 @@ class MainWindow1(Qw.QMainWindow):
if self.addMode is not None:
if self.addMode.active and isinstance(self.addMode, InplaceAddObj.AddBezierShape):
bezierException = True
-
+
if not self.ui.imgLabel.underMouse() and not bezierException:
return
@@ -1252,7 +1637,15 @@ class MainWindow1(Qw.QMainWindow):
self.addMode = None
self.deleteAddOptions()
- def editFinalized(self):
+ def editAccepted(self, obj, objIndex):
+ self.undoRedoStack.add(self.createAction(
+ EditBezierChanges(obj, objIndex,
+ self.addMode.asyPathBackup,
+ self.addMode.asyPath
+ )
+ ))
+ self.checkUndoRedoButtons()
+
self.addMode.forceFinalize()
self.removeAddMode()
self.fileChanged = True
@@ -1260,7 +1653,10 @@ class MainWindow1(Qw.QMainWindow):
def editRejected(self):
self.addMode.resetObj()
- self.editFinalized()
+ self.addMode.forceFinalize()
+ self.removeAddMode()
+ self.fileChanged = True
+ self.quickUpdate()
def setupSelectEdit(self):
"""For Select-Edit mode. For now, if the object selected is a bezier curve, opens up a bezier editor"""
@@ -1270,12 +1666,20 @@ class MainWindow1(Qw.QMainWindow):
# bezier path
self.addMode = xbi.InteractiveBezierEditor(self, obj, self.currAddOptions)
self.addMode.objectUpdated.connect(self.objectUpdated)
- self.addMode.editAccepted.connect(self.editFinalized)
+ self.addMode.editAccepted.connect(lambda: self.editAccepted(obj, maj))
self.addMode.editRejected.connect(self.editRejected)
self.updateOptionWidget()
self.currentModeStack[-1] = SelectionMode.selectEdit
self.fileChanged = True
+ elif isinstance(obj, x2a.xasyText):
+ newText = self.setTextPrompt()
+ if newText:
+ self.drawObjects.remove(obj.generateDrawObjects(False))
+ obj.label.setText(newText)
+ self.drawObjects.append(obj.generateDrawObjects(True))
+ self.fileChanged = True
else:
+ self.ui.statusbar.showMessage('Warning: Selected object cannot be edited')
self.clearSelection()
self.quickUpdate()
@@ -1359,7 +1763,7 @@ class MainWindow1(Qw.QMainWindow):
# but it's much more work...
newCenter = self.magnification * newCenter
self.panOffset = [-newCenter.x(), newCenter.y()]
-
+
self.quickUpdate()
def selectObject(self):
@@ -1410,7 +1814,7 @@ class MainWindow1(Qw.QMainWindow):
def getWindowCoordinates(self):
# assert self.ui.imgLabel.underMouse()
return self.mapFromGlobal(Qg.QCursor.pos())
-
+
def refreshCanvas(self):
if self.mainCanvas.isActive():
self.mainCanvas.end()
@@ -1430,16 +1834,20 @@ class MainWindow1(Qw.QMainWindow):
self.coordLabel.setText('{0:.2f}, {1:.2f} '.format(nx, ny))
def quickUpdate(self):
+ # TODO: Some documentation here would be nice since this is one of the
+ # main functions that gets called everywhere.
self.updateMouseCoordLabel()
self.refreshCanvas()
- self.preDraw(self.mainCanvas)
+ self.preDraw(self.mainCanvas) # coordinates/background
self.quickDraw()
self.mainCanvas.end()
self.postDraw()
self.updateScreen()
+ self.updateTitle()
+
def quickDraw(self):
assert self.isReady()
dpi = self.magnification * self.dpi
@@ -1466,7 +1874,7 @@ class MainWindow1(Qw.QMainWindow):
if self.pendingSelectedObjList:
maj, minor = self.pendingSelectedObjList[self.pendingSelectedObjIndex]
self.drawObjects[maj][minor].draw(canvas=self.mainCanvas, dpi=dpi)
- # and apply the preview too...
+ # and apply the preview too...
elif activeItem is not None:
if self.useGlobalCoords:
activeItem.draw(self.newTransform, canvas=self.mainCanvas, dpi=dpi)
@@ -1474,6 +1882,17 @@ class MainWindow1(Qw.QMainWindow):
activeItem.draw(self.newTransform, applyReverse=True, canvas=self.mainCanvas, dpi=dpi)
activeItem = None
+ def updateTitle(self):
+ # TODO: Undo redo doesn't update appropriately. Have to find a fix for this.
+ title = ''
+ if self.fileName:
+ title += os.path.basename(self.fileName)
+ else:
+ title += "[Not Saved]"
+ if self.fileChanged:
+ title += ' *'
+ self.setWindowTitle(title)
+
def updateScreen(self):
self.finalPixmap = Qg.QPixmap(self.canvSize)
self.finalPixmap.setDevicePixelRatio(devicePixelRatio)
@@ -1502,6 +1921,7 @@ class MainWindow1(Qw.QMainWindow):
for x in np.arange(0, 2 * x_range + 1, majorGrid): # have to do
# this in two stages...
preCanvas.setPen(minorGridCol)
+ self.makePenCosmetic(preCanvas)
for xMinor in range(1, minorGridCount + 1):
xCoord = x + ((xMinor / (minorGridCount + 1)) * majorGrid)
preCanvas.drawLine(Qc.QLine(xCoord, -9999, xCoord, 9999))
@@ -1509,17 +1929,20 @@ class MainWindow1(Qw.QMainWindow):
for y in np.arange(0, 2 * y_range + 1, majorGrid):
preCanvas.setPen(minorGridCol)
+ self.makePenCosmetic(preCanvas)
for yMinor in range(1, minorGridCount + 1):
yCoord = y + ((yMinor / (minorGridCount + 1)) * majorGrid)
preCanvas.drawLine(Qc.QLine(-9999, yCoord, 9999, yCoord))
preCanvas.drawLine(Qc.QLine(-9999, -yCoord, 9999, -yCoord))
preCanvas.setPen(majorGridCol)
+ self.makePenCosmetic(preCanvas)
preCanvas.drawLine(Qc.QLine(-9999, y, 9999, y))
preCanvas.drawLine(Qc.QLine(-9999, -y, 9999, -y))
for x in np.arange(0, 2 * x_range + 1, majorGrid):
preCanvas.setPen(majorGridCol)
+ self.makePenCosmetic(preCanvas)
preCanvas.drawLine(Qc.QLine(x, -9999, x, 9999))
preCanvas.drawLine(Qc.QLine(-x, -9999, -x, 9999))
@@ -1568,6 +1991,7 @@ class MainWindow1(Qw.QMainWindow):
if self.drawAxes:
preCanvas.setPen(Qc.Qt.gray)
+ self.makePenCosmetic(preCanvas)
preCanvas.drawLine(Qc.QLine(-9999, 0, 9999, 0))
preCanvas.drawLine(Qc.QLine(0, -9999, 0, 9999))
@@ -1590,13 +2014,14 @@ class MainWindow1(Qw.QMainWindow):
painter.setPen(self.currentPen.toQPen())
painter.drawPath(self.addMode.getPreview())
self.addMode.postDrawPreview(painter)
-
+
def drawTransformPreview(self, painter):
if self.currentBoundingBox is not None and self.currentlySelectedObj['selectedIndex'] is not None:
painter.save()
maj, minor = self.currentlySelectedObj['selectedIndex']
selObj = self.drawObjects[maj][minor]
+ self.makePenCosmetic(painter)
if not self.useGlobalCoords:
painter.save()
painter.setTransform(
@@ -1621,13 +2046,14 @@ class MainWindow1(Qw.QMainWindow):
with Qg.QPainter(self.postCanvasPixmap) as postCanvas:
postCanvas.setRenderHints(self.mainCanvas.renderHints())
postCanvas.setTransform(self.getScrsTransform())
+ # self.makePenCosmetic(postCanvas)
self.drawTransformPreview(postCanvas)
if self.pendingSelectedObjList:
maj, minor = self.pendingSelectedObjList[self.pendingSelectedObjIndex]
postCanvas.drawRect(self.drawObjects[maj][minor].boundingBox)
-
+
self.drawAddModePreview(postCanvas)
if self.customAnchor is not None and self.anchorMode == AnchorMode.customAnchor:
@@ -1657,11 +2083,29 @@ class MainWindow1(Qw.QMainWindow):
activeBtn = self.ui.btnDeleteMode
elif self.currentModeStack[-1] == SelectionMode.selectEdit:
activeBtn = self.ui.btnSelectEdit
+ elif self.currentModeStack[-1] == SelectionMode.openPoly:
+ activeBtn = self.ui.btnOpenPoly
+ elif self.currentModeStack[-1] == SelectionMode.closedPoly:
+ activeBtn = self.ui.btnClosedPoly
+ elif self.currentModeStack[-1] == SelectionMode.openCurve:
+ activeBtn = self.ui.btnOpenCurve
+ elif self.currentModeStack[-1] == SelectionMode.closedCurve:
+ activeBtn = self.ui.btnClosedCurve
+ elif self.currentModeStack[-1] == SelectionMode.addPoly:
+ activeBtn = self.ui.btnAddPoly
+ elif self.currentModeStack[-1] == SelectionMode.addCircle:
+ activeBtn = self.ui.btnAddCircle
+ elif self.currentModeStack[-1] == SelectionMode.addLabel:
+ activeBtn = self.ui.btnAddLabel
+ elif self.currentModeStack[-1] == SelectionMode.addFreehand:
+ activeBtn = self.ui.btnAddFreehand
else:
activeBtn = None
-
+
disableFill = isinstance(self.addMode, InplaceAddObj.AddBezierShape) and not self.currAddOptions['closedPath']
+ if isinstance(self.addMode, xbi.InteractiveBezierEditor):
+ disableFill = disableFill or not (self.addMode.obj.path.nodeSet[-1] == "cycle")
self.ui.btnFill.setEnabled(not disableFill)
if disableFill and self.ui.btnFill.isEnabled():
self.ui.btnFill.setChecked(not disableFill)
@@ -1670,22 +2114,36 @@ class MainWindow1(Qw.QMainWindow):
for button in self.modeButtons:
button.setChecked(button is activeBtn)
+ if activeBtn in [self.ui.btnDeleteMode,self.ui.btnSelectEdit]:
+ self.ui.btnAlignX.setEnabled(False)
+ self.ui.btnAlignY.setEnabled(False)
+ else:
+ self.ui.btnAlignX.setEnabled(True)
+ self.ui.btnAlignY.setEnabled(True)
+
+
def updateChecks(self):
self.removeAddMode()
self.updateModeBtnsOnly()
self.quickUpdate()
def btnAlignXOnClick(self, checked):
- self.lockY = checked
- if self.lockX:
- self.lockX = False
- self.ui.btnAlignY.setChecked(False)
+ if self.currentModeStack[0] in [SelectionMode.selectEdit,SelectionMode.delete]:
+ self.ui.btnAlignX.setChecked(False)
+ else:
+ self.lockY = checked
+ if self.lockX:
+ self.lockX = False
+ self.ui.btnAlignY.setChecked(False)
def btnAlignYOnClick(self, checked):
- self.lockX = checked
- if self.lockY:
- self.lockY = False
- self.ui.btnAlignX.setChecked(False)
+ if self.currentModeStack[0] in [SelectionMode.selectEdit,SelectionMode.delete]:
+ self.ui.btnAlignY.setChecked(False)
+ else:
+ self.lockX = checked
+ if self.lockY:
+ self.lockY = False
+ self.ui.btnAlignX.setChecked(False)
def btnAnchorModeOnClick(self):
if self.currentModeStack[-1] != SelectionMode.setAnchor:
@@ -1704,22 +2162,31 @@ class MainWindow1(Qw.QMainWindow):
self.updateChecks()
def btnRotateOnClick(self):
- self.currentModeStack = [SelectionMode.rotate]
- self.ui.statusbar.showMessage('Rotate mode')
- self.clearSelection()
- self.updateChecks()
+ if self.currentModeStack[-1] != SelectionMode.rotate:
+ self.currentModeStack = [SelectionMode.rotate]
+ self.ui.statusbar.showMessage('Rotate mode')
+ self.clearSelection()
+ self.updateChecks()
+ else:
+ self.btnTranslateonClick()
def btnScaleOnClick(self):
- self.currentModeStack = [SelectionMode.scale]
- self.ui.statusbar.showMessage('Scale mode')
- self.clearSelection()
- self.updateChecks()
+ if self.currentModeStack[-1] != SelectionMode.scale:
+ self.currentModeStack = [SelectionMode.scale]
+ self.ui.statusbar.showMessage('Scale mode')
+ self.clearSelection()
+ self.updateChecks()
+ else:
+ self.btnTranslateonClick()
def btnPanOnClick(self):
- self.currentModeStack = [SelectionMode.pan]
- self.ui.statusbar.showMessage('Pan mode')
- self.clearSelection()
- self.updateChecks()
+ if self.currentModeStack[-1] != SelectionMode.pan:
+ self.currentModeStack = [SelectionMode.pan]
+ self.ui.statusbar.showMessage('Pan mode')
+ self.clearSelection()
+ self.updateChecks()
+ else:
+ self.btnTranslateonClick()
def btnWorldCoordsOnClick(self, checked):
self.useGlobalCoords = checked
@@ -1754,24 +2221,26 @@ class MainWindow1(Qw.QMainWindow):
self.quickUpdate()
def btnLoadEditorOnClick(self):
+ pathToFile = os.path.splitext(self.fileName)[0]+'.asy'
if self.fileChanged:
save = "Save current file?"
reply = Qw.QMessageBox.question(self, 'Message', save, Qw.QMessageBox.Yes,
Qw.QMessageBox.No)
if reply == Qw.QMessageBox.Yes:
- self.actionSave()
-
- subprocess.Popen(args=self.getExternalEditor(asypath=self.filename));
+ self.actionExport(pathToFile)
+
+ subprocess.run(args=self.getExternalEditor(asypath=pathToFile));
+ self.loadFile(pathToFile)
def btnAddCodeOnClick(self):
header = """
-// xasy object created at $time
+// xasy object created at $time
// Object Number: $uid
-// This header is automatically generated by xasy.
+// This header is automatically generated by xasy.
// Your code here
"""
header = string.Template(header).substitute(time=str(datetime.datetime.now()), uid=str(self.globalObjectCounter))
-
+
with tempfile.TemporaryDirectory() as tmpdir:
newPath = os.path.join(tmpdir, 'tmpcode.asy')
f = io.open(newPath, 'w')
@@ -1791,6 +2260,7 @@ class MainWindow1(Qw.QMainWindow):
self.asyfyCanvas()
self.globalObjectCounter = self.globalObjectCounter + 1
+
def softDeleteObj(self, objKey):
maj, minor = objKey
drawObj = self.drawObjects[maj][minor]
@@ -1869,7 +2339,7 @@ class MainWindow1(Qw.QMainWindow):
s = editor.split()
rawExternalEditor = s[0]
rawExtEditorArgs = s[1:]+["$asypath"]
-
+
execEditor = [rawExternalEditor]
for arg in rawExtEditorArgs:
@@ -1881,34 +2351,37 @@ class MainWindow1(Qw.QMainWindow):
def loadFile(self, name):
filename = os.path.abspath(name)
if not os.path.isfile(filename):
- filename = filename + '.asy'
+ parts = os.path.splitext(filename)
+ if parts[1] == '':
+ filename = parts[0] + '.asy'
if not os.path.isfile(filename):
self.ui.statusbar.showMessage('File {0} not found'.format(filename))
return
self.ui.statusbar.showMessage('Load {0}'.format(filename))
- self.filename = filename
- self.currDir = os.path.dirname(self.filename)
+ self.fileName = filename
+ self.asyFileName = filename
+ self.currDir = os.path.dirname(self.fileName)
self.erase()
- f = open(self.filename, 'rt')
+ f = open(self.fileName, 'rt')
try:
rawFileStr = f.read()
except IOError:
Qw.QMessageBox.critical(self, self.strings.fileOpenFailed, self.strings.fileOpenFailedText)
else:
- rawText, transfDict, maxKey = xf.extractTransformsFromFile(rawFileStr)
+ rawText, transfDict = xf.extractTransformsFromFile(rawFileStr)
item = x2a.xasyScript(canvas=self.xasyDrawObj, engine=self.asyEngine, transfKeyMap=transfDict)
item.setScript(rawText)
self.fileItems.append(item)
- self.asyfyCanvas(True)
+ self.asyfyCanvas(force=True)
- maxKey2 = item.getMaxKeyCounter()
+ self.globalObjectCounter = item.maxKey+1
self.asy2psmap = item.asy2psmap
- self.globalObjectCounter = max(maxKey + 1, maxKey2)
+
finally:
f.close()
@@ -1916,3 +2389,37 @@ class MainWindow1(Qw.QMainWindow):
self.itemCount = 0
for item in self.fileItems:
self.drawObjects.append(item.generateDrawObjects(forceUpdate))
+
+ def makePenCosmetic(self, painter):
+ localPen = painter.pen()
+ localPen.setCosmetic(True)
+ painter.setPen(localPen)
+
+ def copyItem(self):
+ self.selectOnHover()
+ if self.currentlySelectedObj['selectedIndex'] is not None:
+ maj, minor = self.currentlySelectedObj['selectedIndex']
+ if isinstance(self.fileItems[maj],x2a.xasyShape) or isinstance(self.fileItems[maj],x2a.xasyText):
+ self.copiedObject = self.fileItems[maj].copy()
+ else:
+ self.ui.statusbar.showMessage('Copying not supported with current item type')
+ else:
+ self.ui.statusbar.showMessage('No object selected to copy')
+ self.copiedObject = None
+ self.clearSelection()
+
+ def pasteItem(self):
+ if hasattr(self, 'copiedObject') and not self.copiedObject is None:
+ self.copiedObject = self.copiedObject.copy()
+ self.addInPlace(self.copiedObject)
+ mousePos = self.getWindowCoordinates() - self.copiedObject.path.toQPainterPath().boundingRect().center() - (Qc.QPointF(self.canvSize.width(), self.canvSize.height()) + Qc.QPointF(62, 201))/2 #I don't really know what that last constant is? Is it the size of the framing?
+ newTransform = Qg.QTransform.fromTranslate(mousePos.x(), mousePos.y())
+ self.currentlySelectedObj['selectedIndex'] = (self.globalObjectCounter - 1,0)
+ self.currentlySelectedObj['key'], self.currentlySelectedObj['allSameKey'] = self.selectObjectSet()
+ newTransform = x2a.asyTransform.fromQTransform(newTransform)
+ objKey = self.currentlySelectedObj['selectedIndex']
+ self.addTransformationChanges(objKey, newTransform, not self.useGlobalCoords)
+ self.transformObject(objKey, newTransform, not self.useGlobalCoords)
+ self.quickUpdate()
+ else:
+ self.ui.statusbar.showMessage('No object to paste')