Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added JSON Save and Load #166

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
29 changes: 23 additions & 6 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env python2

import sys, os, locale, re, pickle, wx, platform, traceback
import sys, os, locale, re, simplejson, pickle, jsonpickle, wx, platform, traceback, time
import metrics
from tiddlywiki import TiddlyWiki
from tiddlywiki import Tiddler
from header import Header
from storyframe import StoryFrame
from prefframe import PreferenceFrame
Expand Down Expand Up @@ -33,12 +35,13 @@ def __init__(self, redirect = False):

self.icon = wx.EmptyIcon()

jsonpickle.set_encoder_options('simplejson', sort_keys=True, indent=4)

try:
self.icon = wx.Icon(self.iconsPath + 'app.ico', wx.BITMAP_TYPE_ICO)
except:
pass


# restore save location

try:
Expand Down Expand Up @@ -74,9 +77,10 @@ def removeStory(self, story, byMenu = False):
except ValueError:
pass

def openDialog(self, event = None):
def openDialog(self, event=None):
"""Opens a story file of the user's choice."""
dialog = wx.FileDialog(None, 'Open Story', os.getcwd(), "", "Twine Story (*.tws)|*.tws", \
fileExtension = "Twine Story (*.tws)|*.tws|JSON Twine Story (*.twjs)|*.twjs"
dialog = wx.FileDialog(None, 'Open Story', os.getcwd(), "", fileExtension, \
wx.FD_OPEN | wx.FD_CHANGE_DIR)

if dialog.ShowModal() == wx.ID_OK:
Expand All @@ -102,14 +106,27 @@ def MacOpenFile(self, path):
def open(self, path):
"""Opens a specific story file."""
try:
usejson = True
if path[len(path)-3:] == "tws":
usejson = False

openedFile = open(path, 'r')
newStory = StoryFrame(None, app = self, state = pickle.load(openedFile))
fileData = openedFile.read()
openedFile.close()

if usejson:
state = jsonpickle.decode(fileData)
else:
state = pickle.loads(fileData)

newStory = StoryFrame(None, app = self, state = state)

newStory.saveDestination = path

self.stories.append(newStory)
newStory.Show(True)
self.addRecentFile(path)
self.config.Write('LastFile', path)
openedFile.close()

# weird special case:
# if we only had one story opened before
Expand Down
68 changes: 55 additions & 13 deletions storyframe.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import sys, re, os, urllib, urlparse, pickle, wx, codecs, tempfile, images, version
import sys, re, os, urllib, urlparse, simplejson, pickle, jsonpickle, wx, codecs, tempfile, images, version
from wx.lib import imagebrowser
from tiddlywiki import TiddlyWiki
from tiddlywiki import Tiddler
import time
from storypanel import StoryPanel
from passagewidget import PassageWidget
from statisticsdialog import StatisticsDialog
from storysearchframes import StoryFindFrame, StoryReplaceFrame
from storymetadataframe import StoryMetadataFrame
from utils import isURL


class StoryFrame(wx.Frame):
"""
A StoryFrame displays an entire story. Its main feature is an
Expand Down Expand Up @@ -90,6 +91,9 @@ def __init__(self, parent, app, state=None, refreshIncludes=True):
fileMenu.Append(wx.ID_SAVEAS, 'S&ave Story As...\tCtrl-Shift-S')
self.Bind(wx.EVT_MENU, self.saveAs, id=wx.ID_SAVEAS)

fileMenu.Append(StoryFrame.SAVEAS_JSON, 'S&ave Story As (JSON)...')
self.Bind(wx.EVT_MENU, self.saveAsJSON, id=StoryFrame.SAVEAS_JSON)

fileMenu.Append(wx.ID_REVERT_TO_SAVED, '&Revert to Saved')
self.Bind(wx.EVT_MENU, self.revert, id=wx.ID_REVERT_TO_SAVED)

Expand Down Expand Up @@ -486,7 +490,7 @@ def checkCloseDo(self, event, byMenu):
elif result == wx.ID_NO:
self.dirty = False
else:
self.save(None)
self.save()
if self.dirty:
event.Veto()
return
Expand All @@ -509,24 +513,45 @@ def checkCloseDo(self, event, byMenu):
event.Skip()
self.Destroy()

def saveAs(self, event=None):
def exportJSON(self, event=None):
self.saveAsMain(True,event)

def saveAs(self, event=None):
self.saveAsMain(False,event)

def saveAsJSON(self, event=None):
self.saveAsMain(True,event)

def saveAsMain(self, usejson=False, event=None):
"""Asks the user to choose a file to save state to, then passes off control to save()."""

extensionStr = "Twine Story (*.tws)|*.tws|Twine Story without private content [copy] (*.tws)|*.tws"
if usejson:
extensionStr = "JSON Twine Story (*.twjs)|*.twjs|JSON Twine Story without private content [copy] (*.twjs)|*.twjs"

dialog = wx.FileDialog(self, 'Save Story As', os.getcwd(), "", \
"Twine Story (*.tws)|*.tws|Twine Story without private content [copy] (*.tws)|*.tws", \
extensionStr, \
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR)

if dialog.ShowModal() == wx.ID_OK:
if dialog.GetFilterIndex() == 0:
self.saveDestination = dialog.GetPath()
self.app.config.Write('savePath', os.getcwd())
self.app.addRecentFile(self.saveDestination)
self.save(None)
self.save(event)
elif dialog.GetFilterIndex() == 1:
npsavedestination = dialog.GetPath()
try:
if usejson:
fileData = jsonpickle.encode(self.serialize_noprivate(npsavedestination))
fileData = self.storyPanel.stripTimeFields( fileData )
else:
fileData = pickle.dumps(self.serialize_noprivate(npsavedestination))

dest = open(npsavedestination, 'wb')
pickle.dump(self.serialize_noprivate(npsavedestination), dest)
dest.write(fileData)
dest.close()

self.app.addRecentFile(npsavedestination)
except:
self.app.displayError('saving your story')
Expand Down Expand Up @@ -819,13 +844,23 @@ def createInfoPassage(self, event):

def save(self, event=None):
if self.saveDestination == '':
self.saveAs()
self.saveAsMain(False,event)
return

try:
dest = open(self.saveDestination, 'wb')
pickle.dump(self.serialize(), dest)
usejson = True
if self.saveDestination[len(self.saveDestination)-3:] == "tws":
usejson = False

if usejson:
fileData = jsonpickle.encode(self.serialize())
fileData = self.storyPanel.stripTimeFields( fileData )
else:
fileData = pickle.dumps(self.serialize())

dest = open(self.saveDestination, 'wb')
dest.write(fileData)
dest.close()

self.setDirty(False)
self.app.config.Write('LastFile', self.saveDestination)
except:
Expand Down Expand Up @@ -945,7 +980,7 @@ def getLocalDir(self):
dir = os.getcwd()
return dir

def readIncludes(self, lines, callback, silent=False):
def readIncludes(self, lines, callback, usejson=True, silent=False):
"""
Examines all of the source files included via StoryIncludes, and performs a callback on each passage found.

Expand All @@ -970,9 +1005,14 @@ def readIncludes(self, lines, callback, silent=False):
openedFile = open(os.path.join(twinedocdir, line), 'r')

if extension == '.tws':
s = StoryFrame(None, app=self.app, state=pickle.load(openedFile), refreshIncludes=False)
fileData = openedFile.read()
openedFile.close()

if usejson:
s = StoryFrame(None, app=self.app, state=jsonpickle.decode(fileData), refreshIncludes=False)
else:
s = StoryFrame(None, app=self.app, state=pickle.loads(fileData), refreshIncludes=False)

for widget in s.storyPanel.widgetDict.itervalues():
if excludetags.isdisjoint(widget.passage.tags):
callback(widget.passage)
Expand Down Expand Up @@ -1277,6 +1317,8 @@ def getHeader(self):
FILE_EXPORT_SOURCE = 103
FILE_IMPORT_HTML = 104

SAVEAS_JSON = 1105

EDIT_FIND_NEXT = 201

VIEW_SNAP = 301
Expand Down
23 changes: 21 additions & 2 deletions storypanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,21 @@
import sys, wx, re, pickle
import geometry
from tiddlywiki import TiddlyWiki
import time
from passagewidget import PassageWidget

# r + s1 + r + s2 + r + ... + sn + r
def unfoldPattern(s,r):
pat = ''
e = '[]{}.?*^+-'
for c in s:
pat += r
if c in e:
pat += '\\'
pat += c
pat += r
return pat

class StoryPanel(wx.ScrolledWindow):
"""
A StoryPanel is a container for PassageWidgets. It translates
Expand Down Expand Up @@ -45,7 +58,8 @@ def __init__(self, parent, app, id = wx.ID_ANY, state = None):
self.tooltipplace = None
self.tooltipobj = None
self.textDragSource = None

#self.timeFields = re.compile(',?[^\{\}\[\],]*"(created|modified)": (' + unfoldPattern('{,[{},{[{[,,,,,,,,]},{}]}]}', '[^\{\}\[\],]*') + '|\{[^\{\}]+\})')
self.timeFields = re.compile('[^\{\}\[\],]*"(created|modified)": (' + unfoldPattern('{[{},{[{[,,,,,,,,]},{}]}],}', '[^\{\}\[\],]*') + '|\{[^\{\}]+\}),?')
if state:
self.scale = state['scale']
for widget in state['widgets']:
Expand Down Expand Up @@ -83,6 +97,9 @@ def __init__(self, parent, app, id = wx.ID_ANY, state = None):
self.Bind(wx.EVT_LEAVE_WINDOW, self.handleHoverStop)
self.Bind(wx.EVT_MOTION, self.handleHover)

def stripTimeFields( self, s ):
return self.timeFields.sub('',s)

def newWidget(self, title = None, text = '', tags = (), pos = None, quietly = False, logicals = False):
"""Adds a new widget to the container."""

Expand Down Expand Up @@ -154,6 +171,7 @@ def copyWidgets(self):
if widget.selected: data.append(widget.serialize())

clipData = wx.CustomDataObject(wx.CustomDataFormat(StoryPanel.CLIPBOARD_FORMAT))

clipData.SetData(pickle.dumps(data, 1))

if wx.TheClipboard.Open():
Expand All @@ -176,7 +194,8 @@ def pasteWidgets(self, pos = (0,0), logicals = False):
wx.TheClipboard.Close()

if gotData:
data = pickle.loads(clipData.GetData())

data = pickle.loads(clipData.GetData())

self.eachWidget(lambda w: w.setSelected(False, False))

Expand Down
7 changes: 5 additions & 2 deletions tiddlywiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ class Tiddler: # pylint: disable=old-style-class
Note: Converting this to a new-style class breaks pickling of new TWS files on old Twine releases.
"""

def __init__(self, source, type = 'twee', obfuscatekey = ""):
def __init__(self, source = "", type = 'twee', obfuscatekey = ""):
# cache of passage names linked from this one
self.links = []
self.displays = []
Expand All @@ -345,9 +345,12 @@ def __getstate__(self):
'modified': now,
'title': self.title,
'tags': self.tags,
'text': self.text,
'text': self.text
}

def __setstate__(self,d):
self.__dict__ = d

def __repr__(self):
return "<Tiddler '" + self.title + "'>"

Expand Down