using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Windows.Forms; using System.Xml; using System.Xml.Serialization; using tom; namespace VPScriptEditor { public partial class VPScriptEditor { private Pointer[] inputPtr, outputPtr; private PointerAnnotation[] annotations; private ITextDocument inDoc, outDoc; private int currPointerNb, nbUndefinedPtr, nextUntranslatedPtr, nextUnsurePtr; private string outputFileName; private FontFile vpFont; private Bitmap conersBmp; public VPScriptEditor() { InitializeComponent(); StreamReader dfile = System.IO.File.OpenText(System.Windows.Forms.Application.StartupPath + @"\database\VP-database.xml"); vpFont = new FontFile(dfile, Color.Black); dfile.Close(); Stream imgStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("VPScriptEditor.corners.png"); if (imgStream == null) throw new Exception("Can't open embedded image"); conersBmp = new Bitmap(imgStream); inDoc = getDocument(richEditInputPtr); outDoc = getDocument(richEditOutputPtr); updateTitle(); } [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)] private extern static IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, out IntPtr lParam); private const int WM_USER = 0x400, EM_GETOLEINTERFACE = WM_USER + 60; private ITextDocument getDocument(RichTextBox rtb) { IntPtr iRichEditOle = IntPtr.Zero; if (SendMessage(rtb.Handle, EM_GETOLEINTERFACE, 0, out iRichEditOle) == IntPtr.Zero) throw new Exception("Can't access IRichEditOle object from RichTextBox"); return (ITextDocument)System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown(iRichEditOle, typeof(tom.ITextDocument)); } private Pointer[] parseXML(String content) { Match match = Regex.Match(content.Replace("\r\n", "\n"), "^\\n*((?:.|\\n)*?)(?:\\n\\n)?\\n*$"); if (!match.Success) throw new Exception("Bad script format!"); MatchCollection matches = Regex.Matches(match.Groups[1].Value, "\\n(|)\\n((?:.|\\n)*?)(?=(?:\\n\\n|$))"); if (matches.Count == 0) throw new Exception("Void script!"); Pointer[] pointers = new Pointer[matches.Count]; for (int i = 0; i < matches.Count; i++) { int nb = int.Parse(matches[i].Groups[1].Value); if (nb <= 0 || nb > matches.Count) throw new Exception("Pointer no " + nb + " out of range!"); if (pointers[nb - 1] != null) throw new Exception("Duplicate pointer no " + nb); pointers[nb - 1] = new Pointer(matches[i].Groups[2].Value, matches[i].Groups[5].Value.Length > 0 ? WindowType.Normal : (matches[i].Groups[4].Value.Length > 0 ? WindowType.Fixed : WindowType.None), matches[i].Groups[5].Value, matches[i].Groups[6].Value, matches[i].Groups[7].Value, matches[i].Groups[8].Value, matches[i].Groups[9].Value); } return pointers; } private string getAnnotationFileName(string filename) { return Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".annotation.xml"; } private void OpenMenuItem_Click(System.Object sender, System.EventArgs e) { StreamReader sr = null; try { OpenFileDialog ofd = new OpenFileDialog(); ofd.Title = "Select *original* script file"; ofd.Filter = "Valkyrie Profile script files (*.xml)|*.xml|" + "All files|*.*"; if (ofd.ShowDialog() != DialogResult.OK) return; String origFileName = ofd.FileName, outputFileName; sr = new StreamReader(ofd.OpenFile()); Pointer[] inputPtr = parseXML(sr.ReadToEnd()); sr.Close(); ofd.Title = "Select *translated* script file"; while (true) if (ofd.ShowDialog() != DialogResult.OK) return; else if ((outputFileName = ofd.FileName) == origFileName) MessageBox.Show("Select a different file than the original script!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); else break; sr = new StreamReader(ofd.OpenFile()); Pointer[] outputPtr = parseXML(sr.ReadToEnd()); sr.Close(); if (outputPtr.Length != inputPtr.Length) throw new Exception("The two scripts don't contain same number of pointers"); String annotationFileName = getAnnotationFileName(outputFileName); PointerAnnotation[] annotations; if (System.IO.File.Exists(annotationFileName)) { XmlSerializer serializer = new XmlSerializer(typeof(PointerAnnotation[])); sr = new StreamReader(annotationFileName); annotations = (PointerAnnotation[])serializer.Deserialize(sr); } else { MessageBox.Show("No annotation file found for " + Path.GetFileName(outputFileName) + "\nA new one will be created on next save", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); annotations = new PointerAnnotation[inputPtr.Length]; for (int i = 0; i < inputPtr.Length; i++) annotations[i] = new PointerAnnotation(); } this.inputPtr = inputPtr; this.outputPtr = outputPtr; this.outputFileName = outputFileName; this.annotations = annotations; nbUndefinedPtr = 0; foreach (PointerAnnotation pa in annotations) if (pa.state == PointerState.Undefined) nbUndefinedPtr++; toolStripStatusLabel1.Text = Path.GetFileName(outputFileName) + " successfully loaded"; } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } finally { if (sr != null) sr.Close(); } initInterface(); } private void saveCurrentPointer() { outputPtr[currPointerNb].setVars(inputPtr[currPointerNb].rooms, inputPtr[currPointerNb].type, txtbxOutputX.Text, txtbxOutputY.Text, txtbxOutputWidth.Text, txtbxOutputHeight.Text, richEditOutputPtr.Text.Replace("\r\n", "\n")); annotations[currPointerNb].state = chkDone.Checked ? PointerState.Done : (chkUnsure.Checked ? PointerState.Unsure : PointerState.Undefined); } private bool saveOutput(string fileName) { saveCurrentPointer(); StreamWriter sw = null; try { StringBuilder str = new StringBuilder("\n\n"); for (int i = 0; i < outputPtr.Length; i++) { Pointer p = outputPtr[i]; str.Append("\n"); if (p.type == WindowType.None) str.Append(""); else if (p.type == WindowType.Fixed) str.Append(""); else str.Append(""); str.Append("\n"); str.Append(p.content.Replace("\r\n", "\n")); str.Append("\n\n"); } str.Append("\n"); sw = new StreamWriter(fileName); sw.Write(str); sw.Close(); XmlSerializer serializer = new XmlSerializer(typeof(PointerAnnotation[])); sw = new StreamWriter(getAnnotationFileName(fileName)); serializer.Serialize(sw, annotations); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } finally { if (sw != null) sw.Close(); } toolStripStatusLabel1.Text = "Last save on " + DateTime.Now.ToString(); return true; } private void updateInputPtr() { Pointer p = inputPtr[currPointerNb]; txtbxRoom.Text = p.rooms; txtbxInputX.Text = p.x; txtbxInputY.Text = p.y; txtbxInputWidth.Text = p.width; txtbxInputHeight.Text = p.height; richEditInputPtr.Text = p.content; } private void updateOutputPtr() { Pointer p = outputPtr[currPointerNb]; txtbxOutputX.Text = p.x; txtbxOutputY.Text = p.y; txtbxOutputWidth.Text = p.width; txtbxOutputHeight.Text = p.height; richEditOutputPtr.Text = p.content; txtbxOutputX.Enabled = txtbxOutputY.Enabled = txtbxOutputWidth.Enabled = txtbxOutputHeight.Enabled = bttnResize.Enabled = p.type == WindowType.Normal; PointerState ps = annotations[currPointerNb].state; chkDone.CheckedChanged -= this.chkbx_CheckedChanged; chkDone.Checked = ps == PointerState.Done; chkDone.CheckedChanged += this.chkbx_CheckedChanged; chkUnsure.CheckedChanged -= this.chkbx_CheckedChanged; chkUnsure.Checked = ps == PointerState.Unsure; chkUnsure.CheckedChanged += this.chkbx_CheckedChanged; } private void updateCommentBttn() { bttnComment.Text = annotations[currPointerNb].comment == null ? "Add comment" : "View/Edit comment"; } private void updateInterface() { nextUntranslatedPtr = searchPointer(PointerState.Undefined); nextUnsurePtr = searchPointer(PointerState.Unsure); bttnNext.Enabled = currPointerNb < inputPtr.Length - 1; bttnPrevious.Enabled = currPointerNb > 0; bttnNextUntranslated.Enabled = nextUntranslatedPtr != -1; bttnNextUnsure.Enabled = nextUnsurePtr != -1; txtbxPointerNumber.Text = Convert.ToString(currPointerNb + 1); updateInputPtr(); updateOutputPtr(); updateCommentBttn(); pnlInput.Invalidate(); pnlOutput.Invalidate(); } private void updateTitle() { this.Text = AboutBox.AssemblyProduct + (outputFileName != null ? " - " + Path.GetFileName(outputFileName) : ""); } private void updateStatusProgress() { lblStatusProgress.Text = (inputPtr.Length - nbUndefinedPtr) + "/" + inputPtr.Length; } private void initInterface() { currPointerNb = 0; txtbxPointerNumber.Enabled = bttnReset.Enabled = SaveMenuItem.Enabled = SaveAsMenuItem.Enabled = chkDone.Enabled = chkUnsure.Enabled = bttnComment.Enabled = richEditOutputPtr.Enabled = true; updateInterface(); updateTitle(); updateStatusProgress(); ActiveControl = richEditOutputPtr; } private int searchPointer(PointerState ps) { for (int i = 1; i < annotations.Length; i++) if (annotations[(currPointerNb + i) % annotations.Length].state == ps) return (currPointerNb + i) % annotations.Length; return -1; } private void gotoPointer(int nb) { saveCurrentPointer(); currPointerNb = nb; updateInterface(); } private void txtbxPointerNumber_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Return) { e.Handled = true; int nb; if (int.TryParse(txtbxPointerNumber.Text, out nb) && nb > 0 && nb <= inputPtr.Length) gotoPointer(nb - 1); else { MessageBox.Show("Pointer out of range. Please enter a number between 1 and " + inputPtr.Length + ".", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); txtbxPointerNumber.Text = System.Convert.ToString(currPointerNb + 1); } } } private void bttnPrevious_Click(System.Object sender, System.EventArgs e) { gotoPointer(currPointerNb - 1); } private void bttnNext_Click(System.Object sender, System.EventArgs e) { gotoPointer(currPointerNb + 1); } private void bttnNextUntranslated_Click(object sender, EventArgs e) { gotoPointer(nextUntranslatedPtr); } private void bttnNextUnsure_Click(object sender, EventArgs e) { gotoPointer(nextUnsurePtr); } private void bttnReset_Click(System.Object sender, System.EventArgs e) { outputPtr[currPointerNb].copyFrom(inputPtr[currPointerNb]); chkDone.Checked = chkUnsure.Checked = false; updateOutputPtr(); } private void ExitMenuItem_Click(System.Object sender, System.EventArgs e) { Application.Exit(); } private void SaveMenuItem_Click(System.Object sender, System.EventArgs e) { saveOutput(outputFileName); } private void SaveAsMenuItem_Click(object sender, EventArgs e) { SaveFileDialog sfd = new SaveFileDialog(); sfd.Title = "Save as"; sfd.Filter = "Valkyrie Profile script files (*.xml)|*.xml|" + "All files|*.*"; if (sfd.ShowDialog() != DialogResult.OK) return; if (saveOutput(sfd.FileName)) { outputFileName = sfd.FileName; updateTitle(); } } private void colorizeRichEdit(ITextDocument itd) { itd.Freeze(); itd.Undo((int)tomConstants.tomSuspend); ITextRange r = itd.Range(0, 0); r.MoveEnd((int)tomConstants.tomCharacter, r.StoryLength); r.Font.ForeColor = 0; MatchCollection matches = Regex.Matches(r.Text, "<(.*?)/>"); for (int i = 0; i < matches.Count; i++) { r.Start = matches[i].Groups[0].Index; r.End = r.Start + matches[i].Groups[0].Length; r.Font.ForeColor = 0xFF; } itd.Undo((int)tomConstants.tomResume); itd.Unfreeze(); } private void parseScriptLines(string[] input, int a, int b) { for (int i = a; i < b; i++) input[i] = Regex.Replace(Regex.Replace(input[i], "(.+?)", new MatchEvaluator(delegate(Match m) { return new String(m.Groups[2].Value[0], int.Parse(m.Groups[1].Value)); })), "<.+?/>", ""); } private static bool isNewTag(String s) { return s == ""; } private Bitmap drawPointer(RichTextBox rtb, string xStr, string yStr, string wStr, string hStr) { String[] text = Regex.Split(rtb.Text, "\n"); int currLineNb = Regex.Matches(rtb.Text.Substring(0, rtb.SelectionStart), "\\n").Count; int begLineNb = Array.FindLastIndex(text, currLineNb, isNewTag) + 1, endLineNb = Array.FindIndex(text, currLineNb + 1, isNewTag); if (endLineNb < 0) endLineNb = text.Length; parseScriptLines(text, begLineNb, endLineNb); Bitmap bmp = new Bitmap(320, 240, PixelFormat.Format32bppArgb); Graphics gBmp = Graphics.FromImage(bmp); gBmp.CompositingMode = CompositingMode.SourceOver; gBmp.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height); int x_base, y_curr, h, w; if (!int.TryParse(xStr, out x_base) || !int.TryParse(yStr, out y_curr)) x_base = y_curr = 10; if (int.TryParse(wStr, out w) && int.TryParse(hStr, out h)) { SolidBrush innerBrush = new SolidBrush(Color.FromArgb(248, 216, 96)), outerBrush = new SolidBrush(Color.FromArgb(88, 64, 8)); gBmp.FillRectangle(outerBrush, x_base - 8, y_curr, 1, h); gBmp.FillRectangle(innerBrush, x_base - 7, y_curr, 2, h); gBmp.FillRectangle(outerBrush, x_base - 2, y_curr - 7, w + 5, 1); gBmp.FillRectangle(innerBrush, x_base - 2, y_curr - 6, w + 5, 2); gBmp.FillRectangle(outerBrush, x_base + w + 9, y_curr, 1, h); gBmp.FillRectangle(innerBrush, x_base + w + 7, y_curr, 2, h); gBmp.FillRectangle(outerBrush, x_base - 2, y_curr + h + 6, w + 5, 1); gBmp.FillRectangle(innerBrush, x_base - 2, y_curr + h + 4, w + 5, 2); gBmp.DrawImage(conersBmp, x_base - 8, y_curr - 7, new Rectangle(0, 0, 7, 7), GraphicsUnit.Pixel); gBmp.DrawImage(conersBmp, x_base - 8, y_curr + h, new Rectangle(0, 7, 7, 7), GraphicsUnit.Pixel); gBmp.DrawImage(conersBmp, x_base + w + 3, y_curr - 7, new Rectangle(7, 0, 7, 7), GraphicsUnit.Pixel); gBmp.DrawImage(conersBmp, x_base + w + 3, y_curr + h, new Rectangle(7, 7, 7, 7), GraphicsUnit.Pixel); } int x_curr = x_base; for (int j = begLineNb; j < endLineNb; j++) { foreach (char ch in text[j]) if (vpFont.containsSymbol(ch)) { gBmp.DrawImage(vpFont.getBitmap(ch), x_curr, y_curr); x_curr += vpFont.getSymbolWidth(ch); } x_curr = x_base; y_curr += 14; } return bmp; } private void pnlInput_Paint(object sender, PaintEventArgs e) { e.Graphics.DrawImage(drawPointer(richEditInputPtr, txtbxInputX.Text, txtbxInputY.Text, txtbxInputWidth.Text, txtbxInputHeight.Text), 0, 0); } private void pnlOutput_Paint(object sender, PaintEventArgs e) { e.Graphics.DrawImage(drawPointer(richEditOutputPtr, txtbxOutputX.Text, txtbxOutputY.Text, txtbxOutputWidth.Text, txtbxOutputHeight.Text), 0, 0); } private void richEdit_SelectionChanged(object sender, EventArgs e) { ((RichTextBox)sender == richEditInputPtr ? pnlInput : pnlOutput).Invalidate(); } private void richEditInputPtr_TextChanged(object sender, EventArgs e) { colorizeRichEdit(inDoc); } private void richEditOutputPtr_SelectionChanged(object sender, EventArgs e) { pnlOutput.Invalidate(); } private void richEditOutputPtr_TextChanged(object sender, EventArgs e) { colorizeRichEdit(outDoc); // simple trick to circumvent a stupid vertical scroll bug richEditOutputPtr.SelectionStart = richEditOutputPtr.SelectionStart; } private void richEditOutputPtr_KeyDown(object sender, KeyEventArgs e) { if ((e.Shift && e.KeyCode == Keys.Insert) || (e.Control && e.KeyCode == Keys.V)) { richEditOutputPtr.Paste(DataFormats.GetFormat("Text")); e.Handled = true; } else if ((e.Modifiers == Keys.None || e.Modifiers == Keys.Control) && e.KeyCode == Keys.Delete) pnlOutput.Invalidate(); } private void richEditOutputPtr_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar >= ' ' && e.KeyChar != (char)Keys.F16 && !vpFont.containsSymbol(e.KeyChar)) { e.Handled = true; System.Media.SystemSounds.Beep.Play(); } } private void bttnResize_Click(System.Object sender, System.EventArgs e) { String[] text = Regex.Split(richEditOutputPtr.Text, "\n"); int w = 0, h = 0, curr_h = 0; for (int j = 0; j < text.Length; j++) if (isNewTag(text[j])) { h = Math.Max(curr_h, h); curr_h = 0; } else { int curr_w = 0; parseScriptLines(text, j, j + 1); foreach (char ch in text[j]) if (vpFont.containsSymbol(ch)) curr_w += vpFont.getSymbolWidth(ch); w = Math.Max(curr_w, w); curr_h += 14; } h = Math.Max(curr_h, h); txtbxOutputWidth.Text = System.Convert.ToString(w); txtbxOutputHeight.Text = System.Convert.ToString(h); pnlOutput.Invalidate(); } private void chkbx_CheckedChanged(object sender, EventArgs e) { CheckBox chk = (CheckBox)sender, chk2 = chk == chkDone ? chkUnsure : chkDone; if (chk.Checked && chk2.Checked) { nbUndefinedPtr++; chk2.Checked = false; } else { nbUndefinedPtr += (!chkDone.Checked && !chkUnsure.Checked ? 1 : -1); updateStatusProgress(); } } private void bttnComment_Click(object sender, EventArgs e) { CommentForm cf = new CommentForm(currPointerNb, annotations[currPointerNb].comment); cf.ShowDialog(); annotations[currPointerNb].comment = cf.Comment; updateCommentBttn(); } private void txtbxOutput_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Return) { e.Handled = true; pnlOutput.Invalidate(); } } private void wordWarpToolStripMenuItem_CheckedChanged(object sender, EventArgs e) { richEditInputPtr.WordWrap = richEditOutputPtr.WordWrap = ((ToolStripMenuItem)sender).Checked; inDoc = getDocument(richEditInputPtr); outDoc = getDocument(richEditOutputPtr); } private void aboutToolStripMenuItem1_Click(object sender, EventArgs e) { new AboutBox().ShowDialog(); } } }