def cleanWord(self, word): word = util.splitToWords(util.lower(util.toInputStr(word))) if len(word) == 0: return "" return word[0]
def OnMisc(self, event=None): if (self.tsIndex == -1) or self.block: return ts = self.titles.pages[self.pageIndex][self.tsIndex] ts.items = [ util.toInputStr(s) for s in misc.fromGUI(self.textEntry.GetValue()).split("\n") ] self.stringsLb.SetString(self.tsIndex, "--".join(ts.items)) ts.x = util.str2float(self.xEntry.GetValue(), 0.0) ts.y = util.str2float(self.yEntry.GetValue(), 0.0) ts.setAlignment( self.alignCombo.GetClientData(self.alignCombo.GetSelection())) self.xEntry.Enable(not ts.isCentered) ts.size = util.getSpinValue(self.sizeEntry) ts.font = self.fontCombo.GetClientData(self.fontCombo.GetSelection()) ts.isBold = self.boldCb.GetValue() ts.isItalic = self.italicCb.GetValue() ts.isUnderlined = self.underlinedCb.GetValue() self.previewCtrl.Refresh()
def OnReplace(self, event): if not self.sc.word: return sp = self.ctrl.sp u = undo.SinglePara(sp, undo.CMD_MISC, self.sc.line) word = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = sp.lines sp.gotoPos(self.sc.line, self.sc.col) ls[self.sc.line].text = util.replace( ls[self.sc.line].text, word, self.sc.col, len(self.sc.word)) sp.rewrapPara(sp.getParaFirstIndexFromLine(self.sc.line)) # rewrapping a paragraph can have moved the cursor, so get the new # location of it, and then advance past the just-changed word self.sc.line = sp.line self.sc.col = sp.column + len(word) sp.clearMark() sp.markChanged() u.setAfter(sp) sp.addUndo(u) self.gotoNext(False)
def OnMisc(self, event = None): if (self.tsIndex == -1) or self.block: return ts = self.titles.pages[self.pageIndex][self.tsIndex] ts.items = [util.toInputStr(s) for s in misc.fromGUI(self.textEntry.GetValue()).split("\n")] self.stringsLb.SetString(self.tsIndex, "--".join(ts.items)) ts.x = util.str2float(self.xEntry.GetValue(), 0.0) ts.y = util.str2float(self.yEntry.GetValue(), 0.0) ts.setAlignment(self.alignCombo.GetClientData(self.alignCombo.GetSelection())) self.xEntry.Enable(not ts.isCentered) ts.size = util.getSpinValue(self.sizeEntry) ts.font = self.fontCombo.GetClientData(self.fontCombo.GetSelection()) ts.isBold = self.boldCb.GetValue() ts.isItalic = self.italicCb.GetValue() ts.isUnderlined = self.underlinedCb.GetValue() self.previewCtrl.Refresh()
def refresh(self): for t in self.types.itervalues(): tmp = [] for v in t.items: v = util.upper(util.toInputStr(v)).strip() if len(v) > 0: tmp.append(v) t.items = tmp
def OnReplace(self, event=None, autoFind=False): if self.searchLine == -1: return False value = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines sp = self.ctrl.sp u = undo.SinglePara(sp, undo.CMD_MISC, self.searchLine) ls[self.searchLine].text = util.replace( ls[self.searchLine].text, value, self.searchColumn, self.searchWidth) sp.rewrapPara(sp.getParaFirstIndexFromLine(self.searchLine)) self.searchLine = -1 diff = len(value) - self.searchWidth if not self.dirUp: sp.column += self.searchWidth + diff else: sp.column -= 1 if sp.column < 0: sp.line -= 1 if sp.line < 0: sp.line = 0 sp.column = 0 self.searchLine = 0 self.searchColumn = 0 self.searchWidth = 0 else: sp.column = len(ls[sp.line].text) sp.clearMark() sp.markChanged() u.setAfter(sp) sp.addUndo(u) self.OnFind(autoFind=autoFind) return True
def OnReplace(self, event=None, autoFind=False): if self.searchLine == -1: return False value = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines sp = self.ctrl.sp u = undo.SinglePara(sp, undo.CMD_MISC, self.searchLine) ls[self.searchLine].text = util.replace(ls[self.searchLine].text, value, self.searchColumn, self.searchWidth) sp.rewrapPara(sp.getParaFirstIndexFromLine(self.searchLine)) self.searchLine = -1 diff = len(value) - self.searchWidth if not self.dirUp: sp.column += self.searchWidth + diff else: sp.column -= 1 if sp.column < 0: sp.line -= 1 if sp.line < 0: sp.line = 0 sp.column = 0 self.searchLine = 0 self.searchColumn = 0 self.searchWidth = 0 else: sp.column = len(ls[sp.line].text) sp.clearMark() sp.markChanged() u.setAfter(sp) sp.addUndo(u) self.OnFind(autoFind=autoFind) return True
def OnMisc(self, event=None): t = self.autoCompletion.getType(self.lt) t.enabled = bool(self.enabledCb.IsChecked()) self.itemsEntry.Enable(t.enabled) # this is cut&pasted from autocompletion.AutoCompletion.refresh, # but I don't want to call that since it does all types, this does # just the changed one. tmp = [] for v in misc.fromGUI(self.itemsEntry.GetValue()).split("\n"): v = util.toInputStr(v).strip() if len(v) > 0: tmp.append(v) t.items = tmp
def OnMisc(self, event = None): t = self.autoCompletion.getType(self.lt) t.enabled = bool(self.enabledCb.IsChecked()) self.itemsEntry.Enable(t.enabled) # this is cut&pasted from autocompletion.AutoCompletion.refresh, # but I don't want to call that since it does all types, this does # just the changed one. tmp = [] for v in misc.fromGUI(self.itemsEntry.GetValue()).split("\n"): v = util.toInputStr(v).strip() if len(v) > 0: tmp.append(v) t.items = tmp
def OnMisc(self, event = None): self.headers.emptyLinesAfter = util.getSpinValue(self.elinesEntry) if (self.hdrIndex == -1) or self.block: return h = self.headers.hdrs[self.hdrIndex] h.text = util.toInputStr(misc.fromGUI(self.textEntry.GetValue())) self.stringsLb.SetString(self.hdrIndex, h.text) h.xoff = util.getSpinValue(self.xoffEntry) h.line = util.getSpinValue(self.lineEntry) h.align = self.alignCombo.GetClientData(self.alignCombo.GetSelection()) h.isBold = self.boldCb.GetValue() h.isItalic = self.italicCb.GetValue() h.isUnderlined = self.underlinedCb.GetValue()
def OnReplace(self, event): if not self.sc.word: return word = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines ls[self.sc.line].text = util.replace(ls[self.sc.line].text, word, self.sc.col, len(self.sc.word)) self.ctrl.searchLine = -1 diff = len(word) - len(self.sc.word) self.sc.col += len(self.sc.word) + diff self.didReplaces = True self.ctrl.sp.markChanged() self.gotoNext(False)
def OnMisc(self, event=None): self.headers.emptyLinesAfter = util.getSpinValue(self.elinesEntry) if (self.hdrIndex == -1) or self.block: return h = self.headers.hdrs[self.hdrIndex] h.text = util.toInputStr(misc.fromGUI(self.textEntry.GetValue())) self.stringsLb.SetString(self.hdrIndex, h.text) h.xoff = util.getSpinValue(self.xoffEntry) h.line = util.getSpinValue(self.lineEntry) h.align = self.alignCombo.GetClientData(self.alignCombo.GetSelection()) h.isBold = self.boldCb.GetValue() h.isItalic = self.italicCb.GetValue() h.isUnderlined = self.underlinedCb.GetValue()
def OnReplace(self, event): if not self.sc.word: return word = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines ls[self.sc.line].text = util.replace( ls[self.sc.line].text, word, self.sc.col, len(self.sc.word)) self.ctrl.searchLine = -1 diff = len(word) - len(self.sc.word) self.sc.col += len(self.sc.word) + diff self.didReplaces = True self.ctrl.sp.markChanged() self.gotoNext(False)
def OnReplace(self, event = None, autoFind = False): if self.ctrl.searchLine != -1: value = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines ls[self.ctrl.searchLine].text = util.replace( ls[self.ctrl.searchLine].text, value, self.ctrl.searchColumn, self.ctrl.searchWidth) self.ctrl.searchLine = -1 diff = len(value) - self.ctrl.searchWidth if not self.dirUp: self.ctrl.sp.column += self.ctrl.searchWidth + diff else: self.ctrl.sp.column -= 1 if self.ctrl.sp.column < 0: self.ctrl.sp.line -= 1 if self.ctrl.sp.line < 0: self.ctrl.sp.line = 0 self.ctrl.sp.column = 0 self.ctrl.searchLine = 0 self.ctrl.searchColumn = 0 self.ctrl.searchWidth = 0 else: self.ctrl.sp.column = len(ls[self.ctrl.sp.line].text) if diff != 0: self.didReplaces = True self.ctrl.sp.markChanged() self.OnFind(autoFind = autoFind) return True else: return False
def OnReplace(self, event=None, autoFind=False): if self.ctrl.searchLine != -1: value = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue())) ls = self.ctrl.sp.lines ls[self.ctrl.searchLine].text = util.replace( ls[self.ctrl.searchLine].text, value, self.ctrl.searchColumn, self.ctrl.searchWidth) self.ctrl.searchLine = -1 diff = len(value) - self.ctrl.searchWidth if not self.dirUp: self.ctrl.sp.column += self.ctrl.searchWidth + diff else: self.ctrl.sp.column -= 1 if self.ctrl.sp.column < 0: self.ctrl.sp.line -= 1 if self.ctrl.sp.line < 0: self.ctrl.sp.line = 0 self.ctrl.sp.column = 0 self.ctrl.searchLine = 0 self.ctrl.searchColumn = 0 self.ctrl.searchWidth = 0 else: self.ctrl.sp.column = len(ls[self.ctrl.sp.line].text) if diff != 0: self.didReplaces = True self.ctrl.sp.markChanged() self.OnFind(autoFind=autoFind) return True else: return False
def recalc(self, doAll=True): for it in self.cvars.numeric.itervalues(): util.clampObj(self, it.name, it.minVal, it.maxVal) for el in self.types.itervalues(): for it in el.cvars.numeric.itervalues(): util.clampObj(el, it.name, it.minVal, it.maxVal) for it in self.cvars.stringLatin1.itervalues(): setattr(self, it.name, util.toInputStr(getattr(self, it.name))) for pf in self.pdfFonts.itervalues(): pf.refresh() # make sure usable space on the page isn't too small if doAll and (self.marginTop + self.marginBottom) >= (self.paperHeight - 100.0): self.marginTop = 0.0 self.marginBottom = 0.0 h = self.paperHeight - self.marginTop - self.marginBottom # how many lines on a page self.linesOnPage = int(h / util.getTextHeight(self.fontSize))
def recalc(self, doAll=True): for it in self.cvars.numeric.itervalues(): util.clampObj(self, it.name, it.minVal, it.maxVal) for el in self.types.itervalues(): for it in el.cvars.numeric.itervalues(): util.clampObj(el, it.name, it.minVal, it.maxVal) for it in self.cvars.stringLatin1.itervalues(): setattr(self, it.name, util.toInputStr(getattr(self, it.name))) for pf in self.pdfFonts.itervalues(): pf.refresh() # make sure usable space on the page isn't too small if doAll and (self.marginTop + self.marginBottom) >= \ (self.paperHeight - 100.0): self.marginTop = 0.0 self.marginBottom = 0.0 h = self.paperHeight - self.marginTop - self.marginBottom # how many lines on a page self.linesOnPage = int(h / util.getTextHeight(self.fontSize))
def importFDX(fileName, frame): elemMap = { "Action" : screenplay.ACTION, "Character" : screenplay.CHARACTER, "Dialogue" : screenplay.DIALOGUE, "Parenthetical" : screenplay.PAREN, "Scene Heading" : screenplay.SCENE, "Shot" : screenplay.SHOT, "Transition" : screenplay.TRANSITION, } # the 5 MB limit is arbitrary, we just want to avoid getting a # MemoryError exception for /dev/zero etc. data = util.loadFile(fileName, frame, 5000000) if data == None: return None if len(data) == 0: wx.MessageBox("File is empty.", "Error", wx.OK, frame) return None try: root = etree.XML(data) lines = [] for para in root.xpath("Content//Paragraph"): et = para.get("Type") # "General" has embedded Dual Dialogue paragraphs inside it; # nothing to do for the General element itself. if et == "General": continue # all unknown linetypes are converted to Action lt = elemMap.get(et, screenplay.ACTION) s = u"" for text in para.xpath("Text"): # text.text is None for paragraphs with no text, and += # blows up trying to add a string object and None, so # guard against that if text.text: s += text.text # FD uses some fancy unicode apostrophe, replace it with a # normal one s = s.replace(u"\u2019", "'") s = util.toInputStr(util.toLatin1(s)) lines.append(screenplay.Line(screenplay.LB_LAST, lt, s)) if len(lines) == 0: wx.MessageBox("The file contains no importable lines", "Error", wx.OK, frame) return None return lines except etree.XMLSyntaxError, e: wx.MessageBox("Error parsing file: %s" %e, "Error", wx.OK, frame) return None
def importFountain(fileName, frame): # regular expressions for fountain markdown. # https://github.com/vilcans/screenplain/blob/master/screenplain/richstring.py ire = re.compile( # one star r'\*' # anything but a space, then text r'([^\s].*?)' # finishing with one star r'\*' # must not be followed by star r'(?!\*)' ) bre = re.compile( # two stars r'\*\*' # must not be followed by space r'(?=\S)' # inside text r'(.+?[*_]*)' # finishing with two stars r'(?<=\S)\*\*' ) ure = re.compile( # underline r'_' # must not be followed by space r'(?=\S)' # inside text r'([^_]+)' # finishing with underline r'(?<=\S)_' ) boneyard_re = re.compile('/\\*.*?\\*/', flags=re.DOTALL) # random magicstring used to escape literal star '\*' literalstar = "Aq7RR" # returns s with markdown formatting removed. def unmarkdown(s): s = s.replace("\\*", literalstar) for style in (bre, ire, ure): s = style.sub(r'\1', s) return s.replace(literalstar, "*") data = util.loadFile(fileName, frame, 1000000) if data == None: return None if len(data) == 0: wx.MessageBox("File is empty.", "Error", wx.OK, frame) return None inf = [] inf.append(misc.CheckBoxItem("Import titles as action lines.")) inf.append(misc.CheckBoxItem("Remove unsupported formatting markup.")) inf.append(misc.CheckBoxItem("Import section/synopsis as notes.")) dlg = misc.CheckBoxDlg(frame, "Fountain import options", inf, "Import options:", False) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return None importTitles = inf[0].selected removeMarkdown = inf[1].selected importSectSyn = inf[2].selected # pre-process data - fix newlines, remove boneyard. data = data.decode("utf-8") data = util.fixNL(data) data = boneyard_re.sub('', data) prelines = data.split("\n") lines = [] tabWidth = 4 lns = [] sceneStartsList = ("INT", "EXT", "EST", "INT./EXT", "INT/EXT", "I/E", "I./E") TWOSPACE = " " skipone = False # First check if title lines are present: c = 0 while c < len(prelines): if prelines[c] != "": c = c+1 else: break # prelines[0:i] are the first bunch of lines, that could be titles. # Our check for title is simple: # - the line does not start with 'fade' # - the first line has a single ':' if c > 0: l = util.toInputStr(prelines[0].expandtabs(tabWidth).lstrip().lower()) if not l.startswith("fade") and l.count(":") == 1: # these are title lines. Now do what the user requested. if importTitles: # add TWOSPACE to all the title lines. for i in xrange(c): prelines[i] += TWOSPACE else: #remove these lines prelines = prelines[c+1:] for l in prelines: if l != TWOSPACE: lines.append(util.toInputStr(l.expandtabs(tabWidth))) else: lines.append(TWOSPACE) linesLen = len(lines) def isPrevEmpty(): if lns and lns[-1].text == "": return True return False def isPrevType(ltype): return (lns and lns[-1].lt == ltype) # looks ahead to check if next line is not empty def isNextEmpty(i): return (i+1 < len(lines) and lines[i+1] == "") def getPrevType(): if lns: return lns[-1].lt else: return screenplay.ACTION def isParen(s): return (s.startswith('(') and s.endswith(')')) def isScene(s): if s.endswith(TWOSPACE): return False if s.startswith(".") and not s.startswith(".."): return True tmp = s.upper() if (re.match(r'^(INT|EXT|EST)[ .]', tmp) or re.match(r'^(INT\.?/EXT\.?)[ .]', tmp) or re.match(r'^I/E[ .]', tmp)): return True return False def isTransition(s): return ((s.isupper() and s.endswith("TO:")) or (s.startswith(">") and not s.endswith("<"))) def isCentered(s): return s.startswith(">") and s.endswith("<") def isPageBreak(s): return s.startswith('===') and s.lstrip('=') == '' def isNote(s): return s.startswith("[[") and s.endswith("]]") def isSection(s): return s.startswith("#") def isSynopsis(s): return s.startswith("=") and not s.startswith("==") # first pass - identify linetypes for i in range(linesLen): if skipone: skipone = False continue s = lines[i] sl = s.lstrip() # mark as ACTION by default. line = screenplay.Line(screenplay.LB_FORCED, screenplay.ACTION, s) # Start testing lines for element type. Go in order: # Scene Character, Paren, Dialog, Transition, Note. if s == "" or isCentered(s) or isPageBreak(s): # do nothing - import as action. pass elif s == TWOSPACE: line.lt = getPrevType() elif isScene(s): line.lt = screenplay.SCENE if sl.startswith('.'): line.text = sl[1:] else: line.text = sl elif isTransition(sl) and isPrevEmpty() and isNextEmpty(i): line.lt = screenplay.TRANSITION if line.text.startswith('>'): line.text = sl[1:].lstrip() elif s.isupper() and isPrevEmpty() and not isNextEmpty(i): line.lt = screenplay.CHARACTER if s.endswith(TWOSPACE): line.lt = screenplay.ACTION elif isParen(sl) and (isPrevType(screenplay.CHARACTER) or isPrevType(screenplay.DIALOGUE)): line.lt = screenplay.PAREN elif (isPrevType(screenplay.CHARACTER) or isPrevType(screenplay.DIALOGUE) or isPrevType(screenplay.PAREN)): line.lt = screenplay.DIALOGUE elif isNote(sl): line.lt = screenplay.NOTE line.text = sl.strip('[]') elif isSection(s) or isSynopsis(s): if not importSectSyn: if isNextEmpty(i): skipone = True continue line.lt = screenplay.NOTE line.text = sl.lstrip('=#') if line.text == TWOSPACE: pass elif line.lt != screenplay.ACTION: line.text = line.text.lstrip() else: tmp = line.text.rstrip() # we don't support center align, so simply add required indent. if isCentered(tmp): tmp = tmp[1:-1].strip() width = frame.panel.ctrl.sp.cfg.getType(screenplay.ACTION).width if len(tmp) < width: tmp = ' ' * ((width - len(tmp)) // 2) + tmp line.text = tmp if removeMarkdown: line.text = unmarkdown(line.text) if line.lt == screenplay.CHARACTER and line.text.endswith('^'): line.text = line.text[:-1] lns.append(line) ret = [] # second pass helper functions. def isLastLBForced(): return ret and ret[-1].lb == screenplay.LB_FORCED def makeLastLBLast(): if ret: ret[-1].lb = screenplay.LB_LAST def isRetPrevType(t): return ret and ret[-1].lt == t # second pass - remove unneeded empty lines, and fix the linebreaks. for ln in lns: if ln.text == '': if isLastLBForced(): makeLastLBLast() else: ret.append(ln) elif not isRetPrevType(ln.lt): makeLastLBLast() ret.append(ln) else: ret.append(ln) makeLastLBLast() return ret
def importTextFile(fileName, frame): # the 1 MB limit is arbitrary, we just want to avoid getting a # MemoryError exception for /dev/zero etc. data = util.loadFile(fileName, frame, 1000000) if data == None: return None if len(data) == 0: wx.MessageBox("File is empty.", "Error", wx.OK, frame) return None data = util.fixNL(data) lines = data.split("\n") tabWidth = 4 # key = indent level, value = Indent indDict = {} for i in range(len(lines)): s = util.toInputStr(lines[i].rstrip().expandtabs(tabWidth)) # don't count empty lines towards indentation statistics if s.strip() == "": lines[i] = "" continue cnt = util.countInitial(s, " ") ind = indDict.get(cnt) if not ind: ind = Indent(cnt) indDict[cnt] = ind tmp = s.upper() if util.multiFind(tmp, ["EXT.", "INT."]): ind.sceneStart += 1 if util.multiFind(tmp, ["CUT TO:", "DISSOLVE TO:"]): ind.trans += 1 if re.match(r"^ +\(.*\)$", tmp): ind.paren += 1 ind.lines.append(s.lstrip()) lines[i] = s if len(indDict) == 0: wx.MessageBox("File contains only empty lines.", "Error", wx.OK, frame) return None # scene/action indent setType(SCENE_ACTION, indDict, lambda v: v.sceneStart) # indent with most lines is dialogue in non-pure-action scripts setType(screenplay.DIALOGUE, indDict, lambda v: len(v.lines)) # remaining indent with lines is character most likely setType(screenplay.CHARACTER, indDict, lambda v: len(v.lines)) # transitions setType(screenplay.TRANSITION, indDict, lambda v: v.trans) # parentheticals setType(screenplay.PAREN, indDict, lambda v: v.paren) # some text files have this type of parens: # # JOE # (smiling and # hopping along) # # this handles them. parenIndent = findIndent(indDict, lambda v: v.lt == screenplay.PAREN) if parenIndent != -1: paren2Indent = findIndent(indDict, lambda v, var: (v.lt == -1) and (v.indent == var), parenIndent + 1) if paren2Indent != -1: indDict[paren2Indent].lt = screenplay.PAREN # set line type to ACTION for any indents not recognized for v in indDict.itervalues(): if v.lt == -1: v.lt = screenplay.ACTION dlg = ImportDlg(frame, indDict.values()) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return None dlg.Destroy() ret = [] for i in range(len(lines)): s = lines[i] cnt = util.countInitial(s, " ") s = s.lstrip() sUp = s.upper() if s: lt = indDict[cnt].lt if lt == IGNORE: continue if lt == SCENE_ACTION: if s.startswith("EXT.") or s.startswith("INT."): lt = screenplay.SCENE else: lt = screenplay.ACTION if ret and (ret[-1].lt != lt): ret[-1].lb = screenplay.LB_LAST if lt == screenplay.CHARACTER: if sUp.endswith("(CONT'D)"): s = sUp[:-8].rstrip() elif lt == screenplay.PAREN: if s == "(continuing)": s = "" if s: line = screenplay.Line(screenplay.LB_SPACE, lt, s) ret.append(line) elif ret: ret[-1].lb = screenplay.LB_LAST if len(ret) == 0: ret.append(screenplay.Line(screenplay.LB_LAST, screenplay.ACTION)) # make sure the last line ends an element ret[-1].lb = screenplay.LB_LAST return ret
def importTextFile(fileName, frame): # the 1 MB limit is arbitrary, we just want to avoid getting a # MemoryError exception for /dev/zero etc. data = util.loadFile(fileName, frame, 1000000) if data == None: return None if len(data) == 0: wx.MessageBox("File is empty.", "Error", wx.OK, frame) return None data = util.fixNL(data) lines = data.split("\n") tabWidth = 4 # key = indent level, value = Indent indDict = {} for i in range(len(lines)): s = util.toInputStr(lines[i].rstrip().expandtabs(tabWidth)) # don't count empty lines towards indentation statistics if s.strip() == "": lines[i] = "" continue cnt = util.countInitial(s, " ") ind = indDict.get(cnt) if not ind: ind = Indent(cnt) indDict[cnt] = ind tmp = s.upper() if util.multiFind(tmp, ["EXT.", "INT."]): ind.sceneStart += 1 if util.multiFind(tmp, ["CUT TO:", "DISSOLVE TO:"]): ind.trans += 1 if re.match(r"^ +\(.*\)$", tmp): ind.paren += 1 ind.lines.append(s.lstrip()) lines[i] = s if len(indDict) == 0: wx.MessageBox("File contains only empty lines.", "Error", wx.OK, frame) return None # scene/action indent setType(SCENE_ACTION, indDict, lambda v: v.sceneStart) # indent with most lines is dialogue in non-pure-action scripts setType(screenplay.DIALOGUE, indDict, lambda v: len(v.lines)) # remaining indent with lines is character most likely setType(screenplay.CHARACTER, indDict, lambda v: len(v.lines)) # transitions setType(screenplay.TRANSITION, indDict, lambda v: v.trans) # parentheticals setType(screenplay.PAREN, indDict, lambda v: v.paren) # some text files have this type of parens: # # JOE # (smiling and # hopping along) # # this handles them. parenIndent = findIndent(indDict, lambda v: v.lt == screenplay.PAREN) if parenIndent != -1: paren2Indent = findIndent( indDict, lambda v, var: (v.lt == -1) and (v.indent == var), parenIndent + 1) if paren2Indent != -1: indDict[paren2Indent].lt = screenplay.PAREN # set line type to ACTION for any indents not recognized for v in indDict.itervalues(): if v.lt == -1: v.lt = screenplay.ACTION dlg = ImportDlg(frame, indDict.values()) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return None dlg.Destroy() ret = [] for i in range(len(lines)): s = lines[i] cnt = util.countInitial(s, " ") s = s.lstrip() sUp = s.upper() if s: lt = indDict[cnt].lt if lt == IGNORE: continue if lt == SCENE_ACTION: if s.startswith("EXT.") or s.startswith("INT."): lt = screenplay.SCENE else: lt = screenplay.ACTION if ret and (ret[-1].lt != lt): ret[-1].lb = screenplay.LB_LAST if lt == screenplay.CHARACTER: if sUp.endswith("(CONT'D)"): s = sUp[:-8].rstrip() elif lt == screenplay.PAREN: if s == "(continuing)": s = "" if s: line = screenplay.Line(screenplay.LB_SPACE, lt, s) ret.append(line) elif ret: ret[-1].lb = screenplay.LB_LAST if len(ret) == 0: ret.append(screenplay.Line(screenplay.LB_LAST, screenplay.ACTION)) # make sure the last line ends an element ret[-1].lb = screenplay.LB_LAST return ret
def importFountain(fileName, frame): # regular expressions for fountain markdown. # https://github.com/vilcans/screenplain/blob/master/screenplain/richstring.py ire = re.compile( # one star r'\*' # anything but a space, then text r'([^\s].*?)' # finishing with one star r'\*' # must not be followed by star r'(?!\*)') bre = re.compile( # two stars r'\*\*' # must not be followed by space r'(?=\S)' # inside text r'(.+?[*_]*)' # finishing with two stars r'(?<=\S)\*\*') ure = re.compile( # underline r'_' # must not be followed by space r'(?=\S)' # inside text r'([^_]+)' # finishing with underline r'(?<=\S)_') boneyard_re = re.compile('/\\*.*?\\*/', flags=re.DOTALL) # random magicstring used to escape literal star '\*' literalstar = "Aq7RR" # returns s with markdown formatting removed. def unmarkdown(s): s = s.replace("\\*", literalstar) for style in (bre, ire, ure): s = style.sub(r'\1', s) return s.replace(literalstar, "*") data = util.loadFile(fileName, frame, 1000000) if data == None: return None if len(data) == 0: wx.MessageBox("File is empty.", "Error", wx.OK, frame) return None inf = [] inf.append(misc.CheckBoxItem("Import titles as action lines.")) inf.append(misc.CheckBoxItem("Remove unsupported formatting markup.")) inf.append(misc.CheckBoxItem("Import section/synopsis as notes.")) dlg = misc.CheckBoxDlg(frame, "Fountain import options", inf, "Import options:", False) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return None importTitles = inf[0].selected removeMarkdown = inf[1].selected importSectSyn = inf[2].selected # pre-process data - fix newlines, remove boneyard. data = util.fixNL(data) data = boneyard_re.sub('', data) prelines = data.split("\n") for i in xrange(len(prelines)): try: util.toLatin1(prelines[i]) except: prelines[i] = util.cleanInput( u"" + prelines[i].decode('UTF-8', "ignore")) lines = [] tabWidth = 4 lns = [] sceneStartsList = ("INT", "EXT", "EST", "INT./EXT", "INT/EXT", "I/E", "I./E") TWOSPACE = " " skipone = False # First check if title lines are present: c = 0 while c < len(prelines): if prelines[c] != "": c = c + 1 else: break # prelines[0:i] are the first bunch of lines, that could be titles. # Our check for title is simple: # - the line does not start with 'fade' # - the first line has a single ':' if c > 0: l = util.toInputStr(prelines[0].expandtabs(tabWidth).lstrip().lower()) if not l.startswith("fade") and l.count(":") == 1: # these are title lines. Now do what the user requested. if importTitles: # add TWOSPACE to all the title lines. for i in xrange(c): prelines[i] += TWOSPACE else: #remove these lines prelines = prelines[c + 1:] for l in prelines: if l != TWOSPACE: lines.append(util.toInputStr(l.expandtabs(tabWidth))) else: lines.append(TWOSPACE) linesLen = len(lines) def isPrevEmpty(): if lns and lns[-1].text == "": return True return False def isPrevType(ltype): return (lns and lns[-1].lt == ltype) # looks ahead to check if next line is not empty def isNextEmpty(i): return (i + 1 < len(lines) and lines[i + 1] == "") def getPrevType(): if lns: return lns[-1].lt else: return screenplay.ACTION def isParen(s): return (s.startswith('(') and s.endswith(')')) def isScene(s): if s.endswith(TWOSPACE): return False if s.startswith(".") and not s.startswith(".."): return True tmp = s.upper() if (re.match(r'^(INT|EXT|EST)[ .]', tmp) or re.match(r'^(INT\.?/EXT\.?)[ .]', tmp) or re.match(r'^I/E[ .]', tmp)): return True return False def isTransition(s): return ((s.isupper() and s.endswith("TO:")) or (s.startswith(">") and not s.endswith("<"))) def isCentered(s): return s.startswith(">") and s.endswith("<") def isPageBreak(s): return s.startswith('===') and s.lstrip('=') == '' def isNote(s): return s.startswith("[[") and s.endswith("]]") def isSection(s): return s.startswith("#") def isSynopsis(s): return s.startswith("=") and not s.startswith("==") # first pass - identify linetypes for i in range(linesLen): if skipone: skipone = False continue s = lines[i] sl = s.lstrip() # mark as ACTION by default. line = screenplay.Line(screenplay.LB_FORCED, screenplay.ACTION, s) # Start testing lines for element type. Go in order: # Scene Character, Paren, Dialog, Transition, Note. if s == "" or isCentered(s) or isPageBreak(s): # do nothing - import as action. pass elif s == TWOSPACE: line.lt = getPrevType() elif isScene(s): line.lt = screenplay.SCENE if sl.startswith('.'): line.text = sl[1:] else: line.text = sl elif isTransition(sl) and isPrevEmpty() and isNextEmpty(i): line.lt = screenplay.TRANSITION if line.text.startswith('>'): line.text = sl[1:].lstrip() elif s.isupper() and isPrevEmpty() and not isNextEmpty(i): line.lt = screenplay.CHARACTER if s.endswith(TWOSPACE): line.lt = screenplay.ACTION elif isParen(sl) and (isPrevType(screenplay.CHARACTER) or isPrevType(screenplay.DIALOGUE)): line.lt = screenplay.PAREN elif (isPrevType(screenplay.CHARACTER) or isPrevType(screenplay.DIALOGUE) or isPrevType(screenplay.PAREN)): line.lt = screenplay.DIALOGUE elif isNote(sl): line.lt = screenplay.NOTE line.text = sl.strip('[]') elif isSection(s) or isSynopsis(s): if not importSectSyn: if isNextEmpty(i): skipone = True continue line.lt = screenplay.NOTE line.text = sl.lstrip('=#') if line.text == TWOSPACE: pass elif line.lt != screenplay.ACTION: line.text = line.text.lstrip() else: tmp = line.text.rstrip() # we don't support center align, so simply add required indent. if isCentered(tmp): tmp = tmp[1:-1].strip() width = frame.panel.ctrl.sp.cfg.getType( screenplay.ACTION).width if len(tmp) < width: tmp = ' ' * ((width - len(tmp)) // 2) + tmp line.text = tmp if removeMarkdown: line.text = unmarkdown(line.text) if line.lt == screenplay.CHARACTER and line.text.endswith('^'): line.text = line.text[:-1] lns.append(line) ret = [] # second pass helper functions. def isLastLBForced(): return ret and ret[-1].lb == screenplay.LB_FORCED def makeLastLBLast(): if ret: ret[-1].lb = screenplay.LB_LAST def isRetPrevType(t): return ret and ret[-1].lt == t # second pass - remove unneeded empty lines, and fix the linebreaks. for ln in lns: if ln.text == '': if isLastLBForced(): makeLastLBLast() else: ret.append(ln) elif not isRetPrevType(ln.lt): makeLastLBLast() ret.append(ln) else: ret.append(ln) makeLastLBLast() return ret