Пример #1
0
class App(wx.App):
    """This bootstraps our application and keeps track of preferences, etc."""

    NAME = 'Twine'
    VERSION = '1.3.6 (running on Python %s.%s)' % (
        sys.version_info[0], sys.version_info[1]
    )  #Named attributes not available in Python 2.6
    RECENT_FILES = 5

    def __init__(self, redirect=False):
        """Initializes the application."""
        wx.App.__init__(self, redirect=redirect)
        locale.setlocale(locale.LC_ALL, '')
        self.stories = []
        self.loadPrefs()

        # try to load our app icon under win32
        # if it doesn't work, we continue anyway

        self.icon = wx.EmptyIcon()

        if sys.platform == 'win32':
            try:
                self.icon = wx.Icon('icons' + os.sep + 'app.ico',
                                    wx.BITMAP_TYPE_ICO)
            except:
                pass

        # restore save location

        try:
            os.chdir(self.config.Read('savePath'))
        except:
            os.chdir(os.path.expanduser('~'))

        if not self.openOnStartup():
            if self.config.HasEntry('LastFile') \
            and os.path.exists(self.config.Read('LastFile')):
                self.open(self.config.Read('LastFile'))
            else:
                self.newStory()

    def newStory(self, event=None):
        """Opens a new, blank story."""
        s = StoryFrame(parent=None, app=self)
        self.stories.append(s)
        s.Show(True)

    def removeStory(self, story, byMenu=False):
        """Removes a story from our collection. Should be called when it closes."""
        try:
            self.stories.remove(story)
            if byMenu:
                counter = 0
                for s in self.stories:
                    if isinstance(s, StoryFrame):
                        counter = counter + 1
                if counter == 0:
                    self.newStory()

        except ValueError:
            None

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

        if dialog.ShowModal() == wx.ID_OK:
            opened = True
            self.config.Write('savePath', os.getcwd())
            self.addRecentFile(dialog.GetPath())
            self.open(dialog.GetPath())

        dialog.Destroy()

    def openRecent(self, story, index):
        """Opens a recently-opened file."""
        filename = story.recentFiles.GetHistoryFile(index)
        if not os.path.exists(filename):
            self.removeRecentFile(story, index)
        else:
            self.open(filename)
            self.addRecentFile(filename)

    def open(self, path):
        """Opens a specific story file."""
        try:
            openedFile = open(path, 'r')
            newStory = StoryFrame(None,
                                  app=self,
                                  state=pickle.load(openedFile))
            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
            # and it's pristine (e.g. no changes ever made to it),
            # then we close it after opening the file successfully

            if (len(self.stories) == 2) and (self.stories[0].pristine):
                self.stories[0].Destroy()

        except:
            self.displayError('opening your story')

    def openOnStartup(self):
        """
        Opens any files that were passed via argv[1:]. Returns
        whether anything was opened.
        """
        if len(sys.argv) is 1:
            return False

        for file in sys.argv[1:]:
            self.open(file)

        return True

    def exit(self, event=None):
        """Closes all open stories, implicitly quitting."""
        # need to make a copy of our stories list since
        # stories removing themselves will alter the list midstream
        for s in list(self.stories):
            if isinstance(s, StoryFrame):
                s.Close()

    def showPrefs(self, event=None):
        """Shows the preferences dialog."""
        if (not hasattr(self, 'prefFrame')):
            self.prefFrame = PreferenceFrame(self)
        else:
            try:
                self.prefFrame.Raise()
            except wx._core.PyDeadObjectError:
                # user closed the frame, so we need to recreate it
                delattr(self, 'prefFrame')
                self.showPrefs(event)

    def addRecentFile(self, path):
        """Adds a path to the recent files history and updates the menus."""
        for s in self.stories:
            if isinstance(s, StoryFrame):
                s.recentFiles.AddFileToHistory(path)
                s.recentFiles.Save(self.config)

    def removeRecentFile(self, story, index):
        """Remove all missing files from the recent files history and update the menus."""
        def removeRecentFile_do(story, index, showdialog=True):
            filename = story.recentFiles.GetHistoryFile(index)
            story.recentFiles.RemoveFileFromHistory(index)
            story.recentFiles.Save(self.config)
            if showdialog:
                text = 'The file ' + filename + ' no longer exists.\n' + \
                       'This file has been removed from the Recent Files list.'
                dlg = wx.MessageDialog(None, text, 'Information',
                                       wx.OK | wx.ICON_INFORMATION)
                dlg.ShowModal()
                dlg.Destroy()
                return True
            else:
                return False

        showdialog = True
        for s in self.stories:
            if s != story and isinstance(s, StoryFrame):
                removeRecentFile_do(s, index, showdialog)
                showdialog = False
        removeRecentFile_do(story, index, showdialog)

    def verifyRecentFiles(self, story):
        done = False
        while done == False:
            for index in range(story.recentFiles.GetCount()):
                if not os.path.exists(story.recentFiles.GetHistoryFile(index)):
                    self.removeRecentFile(story, index)
                    done = False
                    break
            else:
                done = True

    def about(self, event=None):
        """Shows the about dialog."""
        info = wx.AboutDialogInfo()
        info.SetName(self.NAME)
        info.SetVersion(self.VERSION)
        info.SetDescription(
            '\nA tool for creating interactive stories\nwritten by Chris Klimas\n\nhttp://gimcrackd.com/etc/src/'
        )
        info.SetCopyright(
            'The Twee compiler and associated JavaScript files in this application are released under the GNU Public License.\n\nThe files in the targets directory are derivative works of Jeremy Ruston\'s TiddlyWiki project and are used under the terms of its license.'
        )
        wx.AboutBox(info)

    def storyFormatHelp(self, event=None):
        """Opens the online manual to the section on story formats."""
        wx.LaunchDefaultBrowser(
            'http://gimcrackd.com/etc/doc/#simple,storyformats')

    def openDocs(self, event=None):
        """Opens the online manual."""
        wx.LaunchDefaultBrowser('http://gimcrackd.com/etc/doc/')

    def openGroup(self, event=None):
        """Opens the Google group."""
        wx.LaunchDefaultBrowser('http://groups.google.com/group/tweecode/')

    def reportBug(self, event=None):
        """Opens the online bug report form."""
        wx.LaunchDefaultBrowser('http://code.google.com/p/twee/issues/entry')

    def loadPrefs(self):
        """Loads user preferences into self.config, setting up defaults if none are set."""
        self.config = wx.Config('Twine')

        monoFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT)

        if not self.config.HasEntry('savePath'):
            self.config.Write('savePath', os.path.expanduser('~'))
        if not self.config.HasEntry('fsTextColor'):
            self.config.Write('fsTextColor', '#afcdff')
        if not self.config.HasEntry('fsBgColor'):
            self.config.Write('fsBgColor', '#100088')
        if not self.config.HasEntry('fsFontFace'):
            self.config.Write('fsFontFace', metrics.face('mono'))
        if not self.config.HasEntry('fsFontSize'):
            self.config.WriteInt('fsFontSize', metrics.size('fsEditorBody'))
        if not self.config.HasEntry('fsLineHeight'):
            self.config.WriteInt('fsLineHeight', 120)
        if not self.config.HasEntry('windowedFontFace'):
            self.config.Write('windowedFontFace', metrics.face('mono'))
        if not self.config.HasEntry('windowedFontSize'):
            self.config.WriteInt('windowedFontSize',
                                 metrics.size('editorBody'))
        if not self.config.HasEntry('storyFrameToolbar'):
            self.config.WriteBool('storyFrameToolbar', True)
        if not self.config.HasEntry('storyPanelSnap'):
            self.config.WriteBool('storyPanelSnap', False)
        if not self.config.HasEntry('fastStoryPanel'):
            self.config.WriteBool('fastStoryPanel', False)

    def applyPrefs(self):
        """Asks all of our stories to update themselves based on a preference change."""
        map(lambda s: s.applyPrefs(), self.stories)

    def displayError(self, activity):
        """
        Displays an error dialog with diagnostic info. Call with what you were doing
        when the error occurred (e.g. 'saving your story', 'building your story'.)
        """
        exception = sys.exc_info()
        text = 'An error occurred while ' + activity + ' ('
        text += str(exception[1]) + ').'
        error = wx.MessageDialog(None, text, 'Error', wx.OK | wx.ICON_ERROR)
        error.ShowModal()

    def getPath(self):
        """Returns the path to the executing script or application."""
        scriptPath = os.path.realpath(sys.path[0])

        # OS X py2app'd apps will direct us right into the app bundle

        scriptPath = re.sub('[^/]+.app/Contents/Resources', '', scriptPath)

        # Windows py2exe'd apps add an extraneous library.zip at the end

        scriptPath = scriptPath.replace('\\library.zip', '')
        return scriptPath

    NAME = 'Twine'
    VERSION = '1.3.6'
    RECENT_FILES = 10
Пример #2
0
class App(wx.App):
    """This bootstraps our application and keeps track of preferences, etc."""

    NAME = 'Twine'
    VERSION = '%s (running on %s %s)' % (versionString, platform.system(), platform.release()) #Named attributes not available in Python 2.6
    RECENT_FILES = 10

    def __init__(self, redirect = False):
        """Initializes the application."""
        wx.App.__init__(self, redirect = redirect)
        locale.setlocale(locale.LC_ALL, '')
        self.stories = []
        self.loadPrefs()
        self.determinePaths()
        self.loadTargetHeaders()

        # try to load our app icon
        # if it doesn't work, we continue anyway

        self.icon = wx.EmptyIcon()

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


        # restore save location

        try:
            os.chdir(self.config.Read('savePath'))
        except:
            os.chdir(os.path.expanduser('~'))

        if not self.openOnStartup():
            if self.config.HasEntry('LastFile') \
            and os.path.exists(self.config.Read('LastFile')):
                self.open(self.config.Read('LastFile'))
            else:
                self.newStory()

    def newStory(self, event = None):
        """Opens a new, blank story."""
        s = StoryFrame(parent = None, app = self)
        self.stories.append(s)
        s.Show(True)

    def removeStory(self, story, byMenu = False):
        """Removes a story from our collection. Should be called when it closes."""
        try:
            self.stories.remove(story)
            if byMenu:
                counter = 0
                for s in self.stories:
                    if isinstance(s, StoryFrame):
                        counter = counter + 1
                if counter == 0:
                    self.newStory()

        except ValueError:
            None

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

        if dialog.ShowModal() == wx.ID_OK:
            opened = True
            self.config.Write('savePath', os.getcwd())
            self.addRecentFile(dialog.GetPath())
            self.open(dialog.GetPath())

        dialog.Destroy()

    def openRecent(self, story, index):
        """Opens a recently-opened file."""
        filename = story.recentFiles.GetHistoryFile(index)
        if not os.path.exists(filename):
            self.removeRecentFile(story, index)
        else:
            self.open(filename)
            self.addRecentFile(filename)

    def MacOpenFile(self, path):
        """OS X support"""
        self.open(path)

    def open(self, path):
        """Opens a specific story file."""
        try:
            openedFile = open(path, 'r')
            newStory = StoryFrame(None, app = self, state = pickle.load(openedFile))
            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
            # and it's pristine (e.g. no changes ever made to it),
            # then we close it after opening the file successfully

            if (len(self.stories) == 2) and (self.stories[0].pristine):
                self.stories[0].Destroy()

        except:
            self.displayError('opening your story')

    def openOnStartup(self):
        """
        Opens any files that were passed via argv[1:]. Returns
        whether anything was opened.
        """
        if len(sys.argv) is 1:
            return False

        for file in sys.argv[1:]:
            self.open(file)

        return True

    def exit(self, event = None):
        """Closes all open stories, implicitly quitting."""
        # need to make a copy of our stories list since
        # stories removing themselves will alter the list midstream
        for s in list(self.stories):
            if isinstance(s, StoryFrame):
                s.Close()

    def showPrefs(self, event = None):
        """Shows the preferences dialog."""
        if (not hasattr(self, 'prefFrame')):
            self.prefFrame = PreferenceFrame(self)
        else:
            try:
                self.prefFrame.Raise()
            except wx._core.PyDeadObjectError:
                # user closed the frame, so we need to recreate it
                delattr(self, 'prefFrame')
                self.showPrefs(event)

    def addRecentFile(self, path):
        """Adds a path to the recent files history and updates the menus."""
        for s in self.stories:
            if isinstance(s, StoryFrame):
                s.recentFiles.AddFileToHistory(path)
                s.recentFiles.Save(self.config)

    def removeRecentFile(self, story, index):
        """Remove all missing files from the recent files history and update the menus."""

        def removeRecentFile_do(story, index, showdialog = True):
            filename = story.recentFiles.GetHistoryFile(index)
            story.recentFiles.RemoveFileFromHistory(index)
            story.recentFiles.Save(self.config)
            if showdialog:
                text = 'The file ' + filename + ' no longer exists.\n' + \
                       'This file has been removed from the Recent Files list.'
                dlg = wx.MessageDialog(None, text, 'Information', wx.OK | wx.ICON_INFORMATION)
                dlg.ShowModal()
                dlg.Destroy()
                return True
            else:
                return False
        showdialog = True
        for s in self.stories:
            if s != story and isinstance(s, StoryFrame):
                removeRecentFile_do(s, index, showdialog)
                showdialog = False
        removeRecentFile_do(story, index, showdialog)

    def verifyRecentFiles(self, story):
        done = False
        while done == False:
            for index in range(story.recentFiles.GetCount()):
                if not os.path.exists(story.recentFiles.GetHistoryFile(index)):
                    self.removeRecentFile(story, index)
                    done = False
                    break
            else:
                done = True

    def about(self, event = None):
        """Shows the about dialog."""
        info = wx.AboutDialogInfo()
        info.SetName(self.NAME)
        info.SetVersion(self.VERSION)
        info.SetIcon(self.icon)
        info.SetWebSite('http://twinery.org/')
        info.SetDescription('An open-source tool for telling interactive stories\nwritten by Chris Klimas')
        info.SetDevelopers(['Leon Arnott','Emmanuel Turner','Henry Soule','Misty De Meo','Phillip Sutton','Thomas M. Edwards','Maarten ter Huurne','and others.'])

        info.SetLicense('The Twine development application and its Python source code is free software: you can redistribute it and/or modify'
                          + ' it under the terms of the GNU General Public License as published by the Free Software'
                          + ' Foundation, either version 3 of the License, or (at your option) any later version.'
                          + ' See the GNU General Public License for more details.\n\nThe Javascript game engine in compiled game files is a derivative work of Jeremy Ruston\'s TiddlyWiki project,'
                          + ' and is used under the terms of the MIT license.')
        wx.AboutBox(info)

    def storyFormatHelp(self, event = None):
        """Opens the online manual to the section on story formats."""
        wx.LaunchDefaultBrowser('http://twinery.org/wiki/story_format')

    def openForum(self, event = None):
        """Opens the forum."""
        wx.LaunchDefaultBrowser('http://twinery.org/forum/')

    def openDocs(self, event = None):
        """Opens the online manual."""
        wx.LaunchDefaultBrowser('http://twinery.org/wiki/')

    def openGitHub(self, event = None):
        """Opens the GitHub page."""
        wx.LaunchDefaultBrowser('https://github.com/tweecode/twine')

    def loadPrefs(self):
        """Loads user preferences into self.config, setting up defaults if none are set."""
        sc = self.config = wx.Config('Twine')

        monoFont = wx.SystemSettings.GetFont(wx.SYS_ANSI_FIXED_FONT)

        for k,v in {
            'savePath' : os.path.expanduser('~'),
            'fsTextColor' : '#afcdff',
            'fsBgColor' : '#100088',
            'fsFontFace' : metrics.face('mono'),
            'fsFontSize' : metrics.size('fsEditorBody'),
            'fsLineHeight' : 120,
            'windowedFontFace' : metrics.face('mono'),
            'monospaceFontFace' : metrics.face('mono2'),
            'windowedFontSize' : metrics.size('editorBody'),
            'monospaceFontSize' : metrics.size('editorBody'),
            'flatDesign' : False,
            'storyFrameToolbar' : True,
            'storyPanelSnap' : False,
            'fastStoryPanel' : False,
            'imageArrows' : True,
            'displayArrows' : True,
            'createPassagePrompt' : True,
            'importImagePrompt' : True,
            'passageWarnings' : True
        }.iteritems():
            if not sc.HasEntry(k):
                if type(v) == str:
                    sc.Write(k,v)
                elif type(v) == int:
                    sc.WriteInt(k,v)
                elif type(v) == bool:
                    sc.WriteBool(k,v)

    def applyPrefs(self):
        """Asks all of our stories to update themselves based on a preference change."""
        map(lambda s: s.applyPrefs(), self.stories)

    def displayError(self, activity,stacktrace = True):
        """
        Displays an error dialog with diagnostic info. Call with what you were doing
        when the error occurred (e.g. 'saving your story', 'building your story'.)
        """
        text = 'An error occurred while ' + activity + '.\n\n'
        if stacktrace:
            text += ''.join(traceback.format_exc(5))
        else:
            text += '(' + str(sys.exc_info()[1]) + ').'
        error = wx.MessageDialog(None, text, 'Error', wx.OK | wx.ICON_ERROR)
        error.ShowModal()

    def MacReopenApp(self):
        """OS X support"""
        self.GetTopWindow().Raise()

    def determinePaths(self):
        """Determine the paths to relevant files used by application"""
        scriptPath = os.path.dirname(os.path.realpath(sys.argv[0]))
        if sys.platform == 'win32':
            # Windows py2exe'd apps add an extraneous library.zip at the end
            scriptPath = re.sub('\\\\\w*.zip', '', scriptPath)
        elif sys.platform == "darwin":
            scriptPath = re.sub('MacOS\/.*', '', scriptPath)

        scriptPath += os.sep
        self.iconsPath = scriptPath + 'icons' + os.sep
        self.builtinTargetsPath = scriptPath + 'targets' + os.sep

        if sys.platform == "darwin":
            self.externalTargetsPath = re.sub('[^/]+.app/.*', 'targets' + os.sep, self.builtinTargetsPath)
            if not os.path.isdir(self.externalTargetsPath):
                self.externalTargetsPath = ''
        else:
            self.externalTargetsPath = ''

    def loadTargetHeaders(self):
        """Load the target headers and populate the self.headers dictionary"""
        self.headers = {}
        # Get paths to built-in targets
        paths = [(t, self.builtinTargetsPath + t + os.sep) for t in os.listdir(self.builtinTargetsPath)]
        if self.externalTargetsPath:
            # Get paths to external targets
            paths += [(t, self.externalTargetsPath + t + os.sep) for t in os.listdir(self.externalTargetsPath)]
        # Look in subdirectories only for the header file
        for path in paths:
            try:
                if not os.path.isfile(path[1]) and os.access(path[1] + 'header.html', os.R_OK):
                    header = Header.factory(*path, builtinPath = self.builtinTargetsPath)
                    self.headers[header.id] = header
            except:
                pass
Пример #3
0
class App(wx.App):
    """This bootstraps our application and keeps track of preferences, etc."""

    NAME = 'Twine'
    VERSION = '%s (running on %s %s)' % (
        versionString, platform.system(), platform.release()
    )  #Named attributes not available in Python 2.6
    RECENT_FILES = 10

    def __init__(self, redirect=False):
        """Initializes the application."""
        wx.App.__init__(self, redirect=redirect)
        locale.setlocale(locale.LC_ALL, '')
        self.stories = []
        self.hiddenwindows = []
        self.loadPrefs()
        self.determinePaths()
        self.loadTargetHeaders()
        self.SetExitOnFrameDelete(True)  # default

        if not len(self.headers):
            self.displayError(
                'starting up: there are no story formats available!\n\n' +
                'The "targets" directory could have been removed or emptied.\n\nYou may have to reinstall Twine',
                False)
            sys.exit()

        # try to load our app icon
        # if it doesn't work, we continue anyway
        self.icon = wx.Icon()
        try:
            self.icon = wx.Icon(self.iconsPath + 'app.ico', wx.BITMAP_TYPE_ICO)
        except:
            pass

        # don't restore save location
        # try:
        #     os.chdir(self.config.Read('savePath'))
        # except:
        #     os.chdir(os.path.expanduser('~'))

        # if not self.openOnStartup():
        #     if self.config.HasEntry('LastFile') \
        #     and os.path.exists(self.config.Read('LastFile')):
        #         self.open(self.config.Read('LastFile'))
        #     else:
        #         self.newStory()

        # always open new blank file
        self.newStory()

    def newHiddenWindow(self, display=False):
        frame = StoryFrame(parent=None, app=self)
        self.hiddenwindows.append(frame)
        frame.SetPosition((-1000, -1000))
        frame.SetSize(1, 1)
        frame.Bind(wx.EVT_CLOSE, lambda e: None)  # empty event handlers
        frame.Bind(wx.EVT_MENU, lambda e: None, id=wx.ID_CLOSE)
        frame.Bind(wx.EVT_MENU,
                   frame.app.newStory,
                   id=StoryFrame.STORY_NEW_PASSAGE)  # override ctrl-N behavior
        # frame.Bind(wx.EVT_MENU, lambda e: frame.app.exit(), id=wx.ID_EXIT)
        frame.Show(display)  # but off screen and small

    def newStory(self, event=None):
        """Opens a new, blank story."""
        s = StoryFrame(parent=None, app=self)
        self.stories.append(s)
        s.Show(True)

    def removeStory(self, story, byMenu=False):
        """Removes a story from our collection. Should be called when it closes."""
        try:
            counter = 0
            for s in self.stories:
                if isinstance(s, StoryFrame):
                    counter = counter + 1
                if counter > 1:  # 2+ stories left
                    # can safely remove
                    self.stories.remove(s)
                    return (True, None)
                else:  # last story, removing it would be bad
                    shouldcreate = True
                    if self.hiddenwindows:  # not empty list
                        h = self.hiddenwindows[0]
                    else:
                        self.newHiddenWindow()
                        h = self.hiddenwindows[0]
                    h.Show(True)
                    self.stories.remove(s)
                    return (True, h)

        except ValueError:
            return (False, None)  # used to be pass

    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", \
                               wx.FD_OPEN | wx.FD_CHANGE_DIR)

        if dialog.ShowModal() == wx.ID_OK:
            self.config.Write('savePath', os.getcwd())
            # self.addRecentFile(dialog.GetPath())
            self.open(dialog.GetPath())

        dialog.Destroy()

    # def openRecent(self, story, index):
    #     """Opens a recently-opened file."""
    #     filename = story.recentFiles.GetHistoryFile(index)
    #     if not os.path.exists(filename):
    #         self.removeRecentFile(story, index)
    #     else:
    #         self.open(filename)
    #         self.addRecentFile(filename)

    def MacOpenFile(self, path):
        """OS X support"""
        self.open(path)

    def open(self, path):
        """Opens a specific story file."""
        openedFile = None
        try:
            with open(path, 'rb') as openedFile:
                openedFile.seek(0)
                st = pickle.load(openedFile)
                newStory = StoryFrame(None, app=self, state=st)
                newStory.saveDestination = path
                self.stories.append(newStory)
                newStory.Show(True)
                # self.addRecentFile(path)
                self.config.Write('LastFile', path)

            # weird special case:
            # if we only had one story opened before
            # and it's pristine (e.g. no changes ever made to it),
            # then we close it after opening the file successfully

            if (len(self.stories) == 2) and (self.stories[0].pristine):
                self.stories[0].Destroy()

        except:
            # self.displayError('opening your story')
            # weird error...
            pass

    def openOnStartup(self):
        """
        Opens any files that were passed via argv[1:]. Returns
        whether anything was opened.
        """
        if len(sys.argv) is 1:
            return False

        for file in sys.argv[1:]:
            self.open(file)

        return True

    def exit(self, event=None):
        """Closes all open stories and quits the application."""
        # need to make a copy of our stories list since
        # stories removing themselves will alter the list midstream
        for s in list(self.stories):
            if isinstance(s, StoryFrame):
                try:
                    s.Close()
                except RuntimeError:
                    pass  # already closed, i guess?
        for h in list(self.hiddenwindows):
            h.Destroy()

    def showPrefs(self, event=None):
        """Shows the preferences dialog."""
        if not hasattr(self, 'prefFrame'):
            self.prefFrame = PreferenceFrame(self)
        else:
            try:
                self.prefFrame.Raise()
            except RuntimeError:
                # user closed the frame, so we need to recreate it
                delattr(self, 'prefFrame')
                self.showPrefs(event)

    # def addRecentFile(self, path):
    #     """Adds a path to the recent files history and updates the menus."""
    #     for s in self.stories:
    #         if isinstance(s, StoryFrame):
    #             for i in range(s.recentFiles.GetCount()): # necessary?
    #                 if str(path).strip() == s.recentFiles.GetHistoryFile(i).strip():
    #                     s.recentFiles.RemoveFileFromHistory(i)
    #             s.recentFiles.AddFileToHistory(str(path))
    #             s.recentFiles.Save(self.config)

    # def removeRecentFile(self, story, index):
    #     """Remove all missing files from the recent files history and update the menus."""

    #     def removeRecentFile_do(story, index):
    #         filename = story.recentFiles.GetHistoryFile(index)
    #         story.recentFiles.RemoveFileFromHistory(index)
    #         story.recentFiles.Save(self.config)
    #         # silence error
    #         # if showdialog:
    #         #     text = 'The file ' + filename + ' no longer exists.\n' + \
    #         #            'This file has been removed from the Recent Files list.'
    #         #     dlg = wx.MessageDialog(None, text, 'Information', wx.OK | wx.ICON_INFORMATION)
    #         #     dlg.ShowModal()
    #         #     dlg.Destroy()
    #         #     return True
    #         # else:
    #         #     return False

    #     # showdialog = True
    #     for s in self.stories:
    #         if s != story and isinstance(s, StoryFrame):
    #             removeRecentFile_do(s, index)
    #             # showdialog = False
    #     removeRecentFile_do(story, index)

    # def verifyRecentFiles(self, story):
    #     done = False
    #     while done == False:
    #         for index in range(story.recentFiles.GetCount()):
    #             if not os.path.exists(story.recentFiles.GetHistoryFile(index)):
    #                 self.removeRecentFile(story, index)
    #                 done = False
    #                 break
    #         else:
    #             done = True

    def about(self, event=None):
        """Shows the about dialog."""
        info = wx.adv.AboutDialogInfo()
        info.SetName(self.NAME)
        info.SetVersion(self.VERSION)
        info.SetIcon(self.icon)
        info.SetWebSite('http://twinery.org/')
        info.SetDescription(
            'An open-source tool for telling interactive stories\nwritten by Chris Klimas'
        )
        info.SetDevelopers([
            'Leon Arnott', 'Emmanuel Turner', 'Henry Soule', 'Misty De Meo',
            'Phillip Sutton', 'Thomas M. Edwards', 'Maarten ter Huurne',
            'and others.'
        ])

        info.SetLicense(
            'The Twine development application and its Python source code is free software:'
            ' you can redistribute it and/or modify it under the terms of the GNU General Public License'
            ' as published by the Free Software Foundation, either version 3 of the License,'
            ' or (at your option) any later version. See the GNU General Public License for more details.'
            '\n\n'
            'The Javascript game engine in compiled game files is a derivative work of Jeremy Ruston\'s'
            ' TiddlyWiki project, and is used under the terms of the MIT license.'
        )
        wx.adv.AboutBox(info)

    def storyFormatHelp(self, event=None):
        """Opens the online manual to the section on story formats."""
        wx.LaunchDefaultBrowser('https://twinery.org/wiki/story_format')

    def openForum(self, event=None):
        """Opens the forum."""
        wx.LaunchDefaultBrowser('https://twinery.org/forum/')

    def openQA(self, event=None):
        """Opens the Question & Answer site."""
        wx.LaunchDefaultBrowser('https://twinery.org/questions/')

    def openDocs(self, event=None):
        """Opens the online manual."""
        wx.LaunchDefaultBrowser('https://twinery.org/wiki/')

    def openGitHub(self, event=None):
        """Opens the GitHub page."""
        wx.LaunchDefaultBrowser('https://github.com/frozenpandaman/twine')

    def loadPrefs(self):
        """Loads user preferences into self.config, setting up defaults if none are set."""
        sc = self.config = wx.Config('Twine')

        for k, v in {
                'savePath': os.path.expanduser('~'),
                'fsTextColor': '#afcdff',
                'fsBgColor': '#100088',
                'fsFontFace': metrics.face('mono'),
                'fsFontSize': metrics.size('fsEditorBody'),
                'fsLineHeight': 120,
                'windowedFontFace': metrics.face('mono'),
                'monospaceFontFace': metrics.face('mono2'),
                'windowedFontSize': metrics.size('editorBody'),
                'monospaceFontSize': metrics.size('editorBody'),
                'flatDesign': True,
                'storyFrameToolbar': True,
                'storyPanelSnap': False,
                'storyPanelOverlap': False,
                'fastStoryPanel': False,
                'imageArrows': True,
                'displayArrows': True,
                'createPassagePrompt': True,
                'importImagePrompt': True,
                'passageWarnings': True
        }.iteritems():
            if not sc.HasEntry(k):
                if type(v) == str:
                    sc.Write(k, v)
                elif type(v) == int:
                    sc.WriteInt(k, v)
                elif type(v) == bool:
                    sc.WriteBool(k, v)

    def applyPrefs(self):
        """Asks all of our stories to update themselves based on a preference change."""
        for story in self.stories:
            story.applyPrefs()

    def displayError(self, activity, stacktrace=True):
        """
        Displays an error dialog with diagnostic info. Call with what you were doing
        when the error occurred (e.g. 'saving your story', 'building your story'.)
        """
        text = 'An error occurred while ' + activity + '.\n\n'
        if stacktrace:
            text += ''.join(traceback.format_exc(5))
        else:
            text += '(' + str(sys.exc_info()[1]) + ').'
        error = wx.MessageDialog(None, text, 'Error', wx.OK | wx.ICON_ERROR)
        error.ShowModal()

    def MacReopenApp(self):
        """OS X support"""
        if len(self.stories) == 0:
            self.newStory()
            self.stories[0].Raise()
        else:
            if len(self.stories) == 1:
                topwindow = self.GetTopWindow()
                if len(self.hiddenwindows) != 0:
                    if topwindow == self.hiddenwindows[0]:
                        topwindow = self.stories[0]
                    topwindow.Raise()

    def determinePaths(self):
        """Determine the paths to relevant files used by application"""
        scriptPath = os.path.dirname(os.path.realpath(sys.argv[0]))
        if sys.platform == "darwin":
            scriptPath = re.sub('MacOS\/.*', '', scriptPath)
        # elif sys.platform == 'win32':
        #     # Windows py2exe'd apps add an extraneous library.zip at the end
        #     scriptPath = re.sub('\\\\\w*.zip', '', scriptPath)

        scriptPath += os.sep
        self.iconsPath = scriptPath + 'icons' + os.sep
        self.toolbarIconsPath = scriptPath + 'icons' + os.sep + 'small' + os.sep
        self.builtinTargetsPath = scriptPath + 'targets' + os.sep

        if sys.platform == "darwin":
            self.externalTargetsPath = re.sub('[^/]+.app/.*',
                                              'targets' + os.sep,
                                              self.builtinTargetsPath)
            if not os.path.isdir(self.externalTargetsPath):
                self.externalTargetsPath = ''
        else:
            self.externalTargetsPath = ''

    def loadTargetHeaders(self):
        """Load the target headers and populate the self.headers dictionary"""
        self.headers = {}
        # Get paths to built-in targets
        if not os.path.isdir(self.builtinTargetsPath):
            return
        paths = [(t, self.builtinTargetsPath + t + os.sep)
                 for t in os.listdir(self.builtinTargetsPath)]
        if self.externalTargetsPath:
            # Get paths to external targets
            paths += [(t, self.externalTargetsPath + t + os.sep)
                      for t in os.listdir(self.externalTargetsPath)]
        # Look in subdirectories only for the header file
        for path in paths:
            try:
                if not os.path.isfile(path[1]) and os.access(
                        path[1] + 'header.html', os.R_OK):
                    header = Header.factory(
                        *path, builtinPath=self.builtinTargetsPath)
                    self.headers[header.id] = header
            except:
                pass