diff --git a/bin/xp3tools/HistoryLayer.tjs b/bin/xp3tools/HistoryLayer.tjs new file mode 100644 index 0000000..c276f25 --- /dev/null +++ b/bin/xp3tools/HistoryLayer.tjs @@ -0,0 +1,1020 @@ +// HistoryLayer.tjs - メッセージ履歴レイヤ +// Copyright (C)2001-2002, W.Dee 改変・配布は自由です + +class LButtonLayer extends ButtonLayer + // parent に onClick イベントを送るようにしたボタンレイヤ +{ + function LButtonLayer(window, parent) + { + super.ButtonLayer(window, parent); + focusable = false; + } + + function finalize() + { + super.finalize(...); + } + + function onClick() + { + super.onClick(...); + } + + function onMouseUp(x, y, button, shift) + { + if(enabled && button == mbLeft) + parent.onButtonClick(this); + super.onMouseUp(...); + } + +} + +class HistoryLayer extends Layer +{ + var prevPageButton = void; + var nextPageButton = void; + var closeButton = void; + + var antialiased = true; // アンチエイリアス文字描画を行うか + var verticalView = false; // 縦書きかどうか + var everypage = false; // ページ毎の履歴表示を行なうか + var autoReturn = true; // 自動的に改行するかどうか + var maxLines = 2000; // 最大保持行数 + var data = []; // 行データ ( リングバッファ ) + var lineStart = []; // 行表示開始位置 ( リングバッファ ) + var actionInfo = []; // 履歴クリック情報 ( リングバッファ ) + var dataStart = 0; // データの開始位置 + var dataLines = 0; // データ中に含まれる行数 < maxLines -1 + var dataPos = 0; // データ書き込み位置 + + // ページ単位での閲覧機能のコードは kiyobee 氏から頂きました。 + // この場を借りてお礼申し上げます。 + + // "ページ毎"の時は、data, lineStart, actionInfo を2次元に使っている。 + var maxPages = 100; // 最大ページ数 + var dataPages = 0; // データ中の有効なページ数 + var dataPage = 0;// 現在書き込んでいるページ + + var marginL = 12; + var marginR = 12; + var marginT = 12; + var marginB = 12; + var fontName = "MS P明朝"; + var fontBold = false; + var fontHeight = 24; + var lineHeight = 26; + var relinePos_org = 0; // 改行位置 + var limitPos_org = 0; // 画面の端っこ位置 + var relinePos = 0; // 改行位置 + var limitPos = 0; // 画面の端っこ位置 + var indentPos = 0; // インデント位置 + var repageLine = 0; // 改ページ行数 + + var historyColor = 0xffffff; // 履歴文字色 + + var controlHeight = 20; + + var dispStart = 0; + var dispLines = 0; + var canScroll = false; + + var currentLine = ""; + + var currentAction = void; + var currentActionExp = void; + var currentActionID = 1; + + var lastHighlightedActionID = 0; + + var lastWheelTick; // 最後にホイールを操作した tick count + + // 禁則文字 + var wwFollowing = "%),:;]}。」゙゚。,、.:;゛゜ヽヾゝ" + "ゞ々’”)〕]}〉》」』】°′″℃¢%‰"; // 行頭禁則文字 + var wwFollowingWeak="!.?、・ァィゥェォャュョッー・?!ーぁぃぅぇぉっゃゅょゎァィ" + "ゥェォッャュョヮヵヶ"; // 行頭(弱)禁則文字 + var wwLeading="\\$([{「‘“(〔[{〈《「『【¥$£"; // 行末禁則文字 + + wwFollowing += wwFollowingWeak; + + function HistoryLayer(win, par) + { + super.Layer(...); + (HistoryLayer_config incontextof this)(); // configuration + (HistoryLayer_config_override incontextof this)() + if typeof global.HistoryLayer_config_override != "undefined"; + + name = "メッセージ履歴レイヤ"; + + setImageSize(parent.width, parent.height); + setSizeToImageSize(); + hitType = htMask; + hitThreshold = 1; + + font.height = fontHeight; + font.bold = fontBold; + if(verticalView) + { + font.angle = 2700; + font.face = '@' + fontName; + } + else + { + font.angle = 0; + font.face = fontName; + } + + focusable = true; + + cursor = window.cursorDefault; + + clear(); + } + + function finalize() + { + invalidate prevPageButton if prevPageButton !== void; + invalidate nextPageButton if nextPageButton !== void; + invalidate closeButton if closeButton !== void; + + super.finalize(...); + } + + function clear() + { + // 内容のクリア + lineStart = []; + actionInfo = []; + dataStart = 0; + dataLines = 0; + dataPos = 0; + dataPages = 0; + dataPage = 0; + + if(everypage) + { + data[dataPage] = []; + lineStart[dataPage] = []; + actionInfo[dataPage] = []; + } + else + dataLines = 1; + + + currentLine = ""; + + currentAction = void; + currentActionExp = void; + currentActionID = 1; + + lastHighlightedActionID = 0; + + calcRelinePos(); + } + + function calcRelinePos() + { + if(verticalView) + { + relinePos = relinePos_org = height - marginT - marginB - controlHeight; + limitPos = limitPos_org = height - marginT - controlHeight; + repageLine = (width - marginL - marginR) \ lineHeight; + } + else + { + relinePos = relinePos_org = width - marginL - marginR; + // 改ページの基準となる行数を計算 + limitPos = limitPos_org = width - marginL; + repageLine = (height - marginT - marginB - controlHeight) \ lineHeight; + } + } + + function setOptions(elm) + { + // オプションを設定 + if(elm.autoreturn !== void) + autoReturn = +elm.autoreturn; + } + + function makeButtons() + { + if(prevPageButton !== void) return; // すでに作成されている + + prevPageButton = new LButtonLayer(window, this); + nextPageButton = new LButtonLayer(window, this); + closeButton = new LButtonLayer(window, this); + + if(verticalView) + { + nextPageButton.left = 0; + nextPageButton.top = 0; + nextPageButton.width = (width-controlHeight) \ 2; + nextPageButton.height = controlHeight-2; + nextPageButton.caption = "≪ 次ページ "; + nextPageButton.color = 0x808080; + nextPageButton.visible = true; + + prevPageButton.left = nextPageButton.width; + prevPageButton.top = 0; + prevPageButton.width = nextPageButton.width; + prevPageButton.height = controlHeight-2; + prevPageButton.caption = " 前ページ ≫"; + prevPageButton.color = 0x808080; + prevPageButton.visible = true; + } + else + { + prevPageButton.left = 0; + prevPageButton.top = 0; + prevPageButton.width = (width-controlHeight) \ 2; + prevPageButton.height = controlHeight-2; + prevPageButton.caption = "≪ 前ページ "; + prevPageButton.color = 0x808080; + prevPageButton.visible = true; + + nextPageButton.left = prevPageButton.width; + nextPageButton.top = 0; + nextPageButton.width = prevPageButton.width; + nextPageButton.height = controlHeight-2; + nextPageButton.caption = " 次ページ ≫"; + nextPageButton.color = 0x808080; + nextPageButton.visible = true; + } + + closeButton.left = width-controlHeight; + closeButton.top = 0; + closeButton.width = controlHeight; + closeButton.height = controlHeight-2; + closeButton.caption = "×"; + closeButton.captionColor= 0xffffff; + closeButton.color = 0x707090; + closeButton.visible = true; + closeButton.hint = "メッセージ履歴を閉じる"; + + } + + property lastLine + { + getter + { + if(everypage) + return data[dataPage][dataPos]; + else + return data[dataPos]; + } + + setter(line) + { + if(everypage) + data[dataPage][dataPos] = line; + else + data[dataPos] = line; + } + } + + property lastAction + { + getter + { + if(everypage) + return actionInfo[dataPage][dataPos]; + else + return actionInfo[dataPos]; + } + + setter(n) + { + if(everypage) + actionInfo[dataPage][dataPos] = n; + else + actionInfo[dataPos] = n; + } + } + + function getLine(n) + { + // n 番目の行を得る + n += dataStart; + if(n >= maxLines) n -= maxLines; + return data[n]; + } + + function getPage(n) + { + // n 番目のページを得る + n += dataStart; + if(n >= maxPages) n -= maxPages; + return data[n]; + } + + function getLineStart(n) + { + // n 番目の行の表示開始位置を得る + n += dataStart; + if(n >= maxLines) n -= maxLines; + return lineStart[n]; + } + + function getLineStart2(n, m) + { + // n ページ目の、m 行目の表示開始位置を得る + n += dataStart; + if(n >= maxPages) n -= maxPages; + return lineStart[n][m]; + } + + function getActionInfo(n) + { + // n 番目のアクション情報を得る + n += dataStart; + if(n >= maxLines) n -= maxLines; + return actionInfo[n]; + } + + function getActionInfo2(n, m) + { + // n ページ目の、m 行目のアクション情報を得る + n += dataStart; + if(n >= maxPages) n -= maxPages; + return actionInfo[n][m]; + } + + function endAction() + { + if(currentAction !== void) + { + // 現在のアクションがすでにある場合 + var ca = currentAction; + var last = ca[ca.count - 1]; + last.end = font.getTextWidth(currentLine); + } + } + + function setNewAction(action) + { + // アクションを新規に設定する + if(action == "") action = void; + if(action === void) return; + endAction(); + currentActionExp = action; + if(currentAction == void) currentAction = []; + var last = currentAction[currentAction.count] = %[]; + last.start = font.getTextWidth(currentLine); + last.action = action; + last.id = ++currentActionID; + } + + function continueAction() + { + if(currentActionExp === void) return; + if(currentAction == void) currentAction = []; + var last = currentAction[currentAction.count] = %[]; + last.start = font.getTextWidth(currentLine); + last.action = currentActionExp; + last.id = currentActionID; + } + + function clearAction() + { + endAction(); + currentActionExp = void; + } + + function store(ch) + { + if(!autoReturn) + { + // 自動改行を行わない場合 + currentLine += ch; + } + else + { + // 自動改行を行う場合 + var len; + if((len = font.getTextWidth(currentLine += ch)) >= limitPos) // originally relinePos insani + { + var curlen = currentLine.length; + var lastch = curlen >= 1 ? currentLine[curlen - 1] : ''; + +// insani +/* if(((lastch=='' || wwLeading.indexOf(lastch)==-1) && + wwFollowing.indexOf(ch)==-1) || + (lastch!='' && wwFollowingWeak.indexOf(lastch)!=-1 && + wwFollowingWeak.indexOf(ch)!=-1) || len > limitPos) +*/ +// insani + { + // 最後に描画したのが行末禁則文字でない場合 + // しかもこれから描画するのが行頭禁則文字でない + // 場合 + // または弱禁則文字が連続していない場合 + // はたまたこれから描画するのが強禁則文字ではなくて、 + // 確実に 右端を越える場合 + // ( この場合は余白は考えない ) + currentLine= + currentLine.substring(0, currentLine.length - ch.length); // 追加した文字を取り除く + reline(); + currentLine = ch; + } + } + } + } + +// insani + function processWrap(ch) + { + var len; + if((len = font.getTextWidth(currentLine + ch)) >= relinePos) + { + reline(); + } + } +// insani + + function repage() + { + // 改ページ + if(!everypage) return; + if(dataPos ==0) return; // 何もデータが入ってない場合は改ページしない + + endAction(); + lastLine = currentLine; + lastAction = currentAction; + + dataPage++; + if(dataPage >= maxPages) dataPage = 0; + dataPos = 0; + data[dataPage] = []; + lineStart[dataPage] = []; + lineStart[dataPage][dataPos] = indentPos; + actionInfo[dataPage] = []; + actionInfo[dataPage][dataPos] = currentAction; + if(dataPage == dataStart) dataStart++; + if(dataStart >= maxPages) dataStart = 0; + if(dataPages < maxPages-1) dataPages++; + + currentAction = void; + currentLine = ''; + continueAction(); + } + + function reline() + { + // 改行 + + if(everypage) + { + if(dataPos + 1 >= repageLine) + { + // 改ページすべき行数になったとき + repage(); + } + else + { + endAction(); + lastLine = currentLine; + lastAction = currentAction; + + dataPos++; + lineStart[dataPage][dataPos] = indentPos; + limitPos = limitPos_org - indentPos; + relinePos = relinePos_org - indentPos; + + currentAction = void; + currentLine = ''; + continueAction(); + } + } + else + { + endAction(); + lastLine = currentLine; + lastAction = currentAction; + + dataPos++; + if(dataPos >= maxLines) dataPos=0; + data[dataPos] = void; + lineStart[dataPos] = indentPos; + limitPos = limitPos_org - indentPos; + relinePos = relinePos_org - indentPos; + if(dataPos == dataStart) dataStart++; + if(dataStart >= maxLines) dataStart = 0; + if(dataLines < maxLines) dataLines++; + + currentAction = void; + currentLine = ''; + continueAction(); + } + + } + + function beginIndent() + { + // 現在位置にインデントを設定 + indentPos = font.getTextWidth(currentLine); + } + + function endIndent() + { + // インデントを解除 + indentPos = 0; + } + + function clearBack(n) + { + // 背景を塗りつぶす + if(n === void) + { + face = dfBoth; + fillRect(0, 0, width, height, 0xc8000000); + } + else + { + face = dfBoth; + if(verticalView) + fillRect(width - marginR - (n+1)*lineHeight, controlHeight, lineHeight, + height - controlHeight, 0xc8000000); + else + fillRect(0, n*lineHeight + controlHeight + marginT, width, lineHeight, 0xc8000000); + } + } + + function dispInit() + { + // 全部再描画と初期設定 + makeButtons(); // ボタンを作成 + + lastLine = currentLine; + endAction(); + lastAction = currentAction; + + antialiased = window.chDefaultAntialiased; + clearBack(); + + if(everypage) + { + if(dataPages>0) + { + canScroll = true; + dispStart = dataPages - 1; + } + else + { + canScroll = false; + dispStart = 0; + } + drawPage(); + } + else + { + if(verticalView) + dispLines = (width - marginR - marginL) \ lineHeight; + else + dispLines = (height - marginT - marginB - controlHeight) \ lineHeight; + + if(dataLines <= dispLines) + { + // 表示可能範囲内に収まる + canScroll = false; + dispStart = 0; + var i; + for(i= 0; i < dataLines; i++) + drawLine(i); + } + else + { + // 表示可能範囲内に収まらない + canScroll = true; + dispStart = dataLines - dispLines; + var i; + for(i = 0; i < dispLines; i++) + drawLine(i); + } + } + + updateButtonState(); + visible = true; + setMode(); + focus(); + lastWheelTick = 0; + + cursor = window.cursorDefault; + } + + function dispUninit() + { + // window から呼ばれる + removeMode(); + visible = false; + } + + function drawLine(n) + { + // 表示行 n を描画する + var line = everypage?getPage(dispStart)[n]:getLine(n + dispStart); + if(everypage && line=="") return; + var linestart = everypage?getLineStart2(dispStart, n):getLineStart(n + dispStart); + if(verticalView) + { + var x = width - marginR - n*lineHeight; + drawText(x, marginT + controlHeight + linestart, line, historyColor, 255, antialiased); + } + else + { + var y = n*lineHeight + controlHeight + marginT; + drawText(marginL + linestart, y, line, historyColor, 255, antialiased); + } + } + + function drawPage() + { + var page = getPage(dispStart); + var i; + if(verticalView) + { + var x = width - marginR; + for(i = 0; i < repageLine; i++) + { + if(page[i]!="") + drawText(x, marginT + controlHeight + getLineStart2(dispStart, i), + page[i], historyColor, 255, antialiased); + x -= lineHeight; + } + } + else + { + var y = controlHeight + marginT; + for(i = 0; i < repageLine; i++) + { + if(page[i]!="") + drawText(marginL + getLineStart2(dispStart, i), y, page[i], + historyColor, 255, antialiased); + y += lineHeight; + } + } + } + + function getActionInfoFromPos(x, y) + { + // x,y 位置のアクション ID を得る + var line; + if(verticalView) + line = -(x - width + marginR) \ lineHeight; + else + line = (y - controlHeight - marginT) \ lineHeight; + if(line < 0) return void; + if(!everypage && dataLines <= dispLines && line >= dataLines) return void; // はみ出ている + var ai; + if(everypage) + { + ai = getActionInfo2(dispStart, line); + } + else + { + line += dispStart; + ai = getActionInfo(line); + } + if(ai === void) return void; // 情報がない + var p = verticalView ? (y - marginT - controlHeight) : (x - marginL); + p -= everypage ? getLineStart2(dispStart, line) : getLineStart(line); + for(var i = ai.count - 1; i >= 0; i--) + { + var info = ai[i]; + if(info.end !== void && info.start < p && p <= info.end) return info; + } + return void; + } + + function highlightAction(id) + { + // 画面上にある ID で示された ID をすべて ハイライトする + lastHighlightedActionID = id; + if(id == 0) return; + var max = everypage ? repageLine : ((dataLines <= dispLines) ? dataLines : dispLines); + for(var i = 0; i < max; i++) + { + var ai = everypage?getActionInfo2(dispStart, i):getActionInfo(i + dispStart); + if(ai === void) continue; + for(var ii = ai.count - 1; ii >= 0; ii--) + { + var info = ai[ii]; + if(info.end !== void && info.id == id) + { + var linestart = everypage?getLineStart2(dispStart, i):getLineStart(i + dispStart); + if(verticalView) + { + var x = width - marginR - (i-1)*lineHeight - 1; + fillRect(x - lineHeight, info.start + marginT + controlHeight + linestart, + 1, info.end - info.start, 0xff000000 | historyColor); + } + else + { + var y = i*lineHeight + controlHeight + marginT; + fillRect(marginL + linestart + info.start, y + lineHeight - 1, + info.end - info.start, 1, 0xff000000 | historyColor); + } + } + } + } + } + + function clearActionHighlights() + { + // 画面上にある lastHighlightedActionID で示されたハイライト表示を + // すべて消す + if(lastHighlightedActionID == 0) return; + var max = everypage ? repageLine : ((dataLines <= dispLines) ? dataLines : dispLines); + for(var i = 0; i < max; i++) + { + var ai = everypage?getActionInfo2(dispStart, i):getActionInfo(i + dispStart); + if(ai === void) continue; + for(var ii = ai.count - 1; ii >= 0; ii--) + { + var info = ai[ii]; + if(info.end !== void && info.id == lastHighlightedActionID) + { + clearBack(i); + drawLine(i); // 行を描画しなおす + } + } + } + lastHighlightedActionID = 0; + cursor = window.cursorDefault; + } + + function updateButtonState() + { + if(!canScroll) + { + prevPageButton.enabled = canScroll; + prevPageButton.captionColor = canScroll?0xff8080:0x808080; + nextPageButton.enabled = canScroll; + nextPageButton.captionColor = canScroll?0xff8080:0x808080; + return; + } + if(dispStart==0) + { + prevPageButton.enabled = false; + prevPageButton.captionColor = 0x808080; + } + else + { + prevPageButton.enabled = true; + prevPageButton.captionColor = 0xff8080; + } + if( (everypage && dispStart >= dataPages-1) || + (!everypage && dispStart >= dataLines-dispLines)) + { + nextPageButton.enabled = false; + nextPageButton.captionColor = 0x808080; + } + else + { + nextPageButton.enabled = true; + nextPageButton.captionColor = 0xff8080; + } + } + + function prevPage() + { + // 前ページに移動 + if(!canScroll) return; + clearActionHighlights(); + if(everypage) + { + if(dispStart<1) return; + dispStart--; + clearBack(); + drawPage(); + } + else + { + clearBack(); + if(dispStart >= dispLines) + dispStart -= dispLines; + else + dispStart = 0; + var i; + for(i = 0 ; i < dispLines; i++) + drawLine(i); + } + updateButtonState(); + } + + function nextPage() + { + // 次ページに移動 + if(!canScroll) return; + clearActionHighlights(); + if(everypage) + { + if(dispStart>=dataPages-1) return; + dispStart++; + clearBack(); + drawPage(); + } + else + { + clearBack(); + if(dispStart < dataLines - dispLines) + dispStart += dispLines; + if(dispStart > dataLines - dispLines) + dispStart = dataLines - dispLines; + var i; + for(i = 0 ; i < dispLines; i++) + drawLine(i); + } + updateButtonState(); + } + + function scrollUp() + { + if(dispStart < dataLines - dispLines) + { + clearActionHighlights(); + if(verticalView) + copyRect(width - marginR - lineHeight *(dispLines - 1), controlHeight, this, + width - marginR - lineHeight *(dispLines), controlHeight, + lineHeight * (dispLines-1), height - controlHeight); + else + copyRect(0, controlHeight + marginT, this, + 0, controlHeight + lineHeight + marginT, width, lineHeight*(dispLines-1)); + clearBack(dispLines - 1); + dispStart++; + drawLine(dispLines - 1); + updateButtonState(); + } + } + + function scrollDown() + { + if(dispStart!=0) + { + clearActionHighlights(); + if(verticalView) + copyRect(width - marginR - lineHeight *(dispLines), controlHeight, this, + width - marginR - lineHeight *(dispLines-1), + controlHeight, lineHeight * (dispLines-1), height - controlHeight); + else + copyRect(0, controlHeight + lineHeight + marginT, this, + 0, controlHeight + marginT, width, lineHeight*(dispLines-1)); + clearBack(0); + dispStart--; + drawLine(0); + updateButtonState(); + } + } + + function hide() + { + window.hideHistory(); + } + + function onButtonClick(sender) + { + if(sender == prevPageButton) + prevPage(); + else if(sender == nextPageButton) + nextPage(); + else if(sender == closeButton) + hide(); + } + + function onMouseDown(x, y, button) + { + if(button == mbRight) hide(); + else if(button == mbLeft) + { + var n = getActionInfoFromPos(x,y); + if(n !== void) + { + n.action!; + } + } + super.onMouseDown(...); + } + + function onMouseMove(x, y, shift) + { + var n = getActionInfoFromPos(x,y); + n = (n === void) ? 0 : n.id; + if(lastHighlightedActionID != n) + { + clearActionHighlights(); + highlightAction(n); + lastHighlightedActionID = n; + if(n) cursor = window.cursorPointed; + } + super.onMouseMove(...); + } + + function onMouseLeave() + { + clearActionHighlights(); + super.onMouseLeave(...); + } + + function onKeyPress(key) + { + super.onKeyPress(...); + } + + function onKeyDown(key) + { + window.hideMouseCursor(); + if(canScroll) + { + if(verticalView) + { + if(key == VK_DOWN) + nextPage(); + else if(key == VK_UP) + prevPage(); + else if(key == VK_LEFT || key == VK_PRIOR) + { + if(everypage) + prevPage(); + else + scrollUp(); + } + else if(key == VK_RIGHT || key == VK_NEXT) + { + if(everypage) + nextPage(); + else + scrollDown(); + } + } + else + { + if(key == VK_DOWN) + { + if(everypage) + nextPage(); + else + scrollUp(); + } + else if(key == VK_UP) + { + if(everypage) + prevPage(); + else + scrollDown(); + } + else if(key == VK_LEFT || key == VK_PRIOR) + prevPage(); + else if(key == VK_RIGHT || key == VK_NEXT) + nextPage(); + } + } + if(key == VK_ESCAPE || key == VK_RETURN || key == VK_SPACE) + { + hide(); + } + } + + function windowMouseWheel(shift, delta, x, y) + { + // ウィンドウのホイール操作メッセージがここに流される + var currenttick = System.getTickCount(); + delta = delta \ 120; + if(delta > 0 ) + { + // 奥 + while(delta--) + { + if(everypage) + prevPage(); + else + scrollDown(); + } + } + else if(delta < 0 ) + { + // 手前 + if(currenttick - lastWheelTick > 150 && + ((everypage && dispStart >= dataPages-1) || + (!everypage && dispStart >= dataLines - dispLines))) + { + /* くるくる回しているうちにいきなり履歴が閉じたりしないような仕掛け */ + // 既に最終部分を表示している + hide(); + } + else + { + delta = -delta; + while(delta--) + { + if(everypage) + nextPage(); + else + scrollUp(); + } + } + } + lastWheelTick = currenttick; + } +} + + + diff --git a/bin/xp3tools/MSVCR71.dll b/bin/xp3tools/MSVCR71.dll new file mode 100644 index 0000000..9d9e028 Binary files /dev/null and b/bin/xp3tools/MSVCR71.dll differ diff --git a/bin/xp3tools/MainWindow.tjs b/bin/xp3tools/MainWindow.tjs new file mode 100644 index 0000000..f49e9b3 --- /dev/null +++ b/bin/xp3tools/MainWindow.tjs @@ -0,0 +1,5203 @@ +//;# MainWindow.tjs - KAG メインウィンドウ +//;# Copyright (C)2001-2002, W.Dee 改変・配布は自由です +//;<<'END_OF_TJS_SCRIPT'; + +// このスクリプトは有効な perl5 スクリプトもある + +class KAGWindow extends Window +{ + // KAG のウィンドウ クラス + // KAG の動作のメインな部分はここに記述してある + + // 以下のうち、/*C*/ のつく変数は、末端の perl スクリプトによって + // 自動的に栞にコピーされるコードが生成される変数 + + var scWidth = 640; // 画面横幅 + var scHeight = 480; // 画面縦幅 + + var aboutWidth = 320; // 「このソフトについて」ウィンドウの横幅 + var aboutHeight = 200; // 同縦幅 + + var isFirstProcess = true; // 一番最初の process の呼び出しかどうか + + var freeSaveDataMode = false; // 栞をメニューバーなどで管理せずに、個別のファイルとして管理する + var saveThumbnail = false; // サムネイルを保存するかどうか + + var snapshotLayer = void; // 画像のスナップショットを一時的に保存するレイヤ + var snapshotLockCount = 0; // ロックカウント + + var lastSaveDataNameGlobal = ""; // 最後に保存したフリーセーブモードでのファイル名 + /*C*/var lastSaveDataName = ""; // 最後に保存したフリーセーブモードでのファイル名 + + var saveDataLocation = "savedata"; // セーブデータ保存場所 + + var saveDataID = "00000000-0000-0000-0000-000000000000"; // セーブデータの ID + + var readOnlyMode = false; // 読み込み専用モード(データをディスクに書かない) + var dataName = "data"; // セーブデータ名 + var saveDataMode = ""; // データ保存モード( "c" で暗号化 ) + + var recordHistoryOfStore = 0; // 通過履歴を記録するかどうか + // 0 = 自動的には記録しない 1 = 保存可能なラベルごと + // 2 = 選択肢 ( @s タグ ) ごと + var maxHistoryOfStore = 5; // 通過記録の最大数 + var historyOfStore = []; // 通過履歴データ + var nextRecordHistory = false; + // 次の保存可能なラベル通過時に現在の情報を保存するかどうか + + var stablePosibility = false; + // 栞を保存可能なラベル間で stable になる可能性があるかどうか + + var fullScreened = false; // 現在フルスクリーンかどうか + + var isMain = true; // これがメインウィンドウかどうか + + var askOnClose = true; // 終了時に終了するかをユーザに聞くかどうか + + var helpFile = ""; // 「ヘルプ > 目次」で開くファイル + + var quakeTimer; // quake 用のタイマ + var defaultQuakeTimeInChUnit = false; + /*C*/var quaking = false; // 揺れているか + /*C*/var quakeEndTick = 0; // 揺れを終了させる tick + /*C*/var quakeHorzMax = 0; // 横振幅 + /*C*/var quakeVertMax = 0; // 縦振幅 + /*C*/var quakePhase = 0; + + var chDefaultAntialiased; // 文字にアンチエイリアスをかけるかどうか + var chDefaultFace; // 文字のデフォルトのフォント + + var initialMessageLayerVisible = true; + + var historyLayer; // メッセージ履歴レイヤ + /*C*/var historyWriteEnabled = true; // メッセージレイヤに文字を出力するか + /*C*/var historyEnabled = true; // メッセージ履歴レイヤを表示可能か + var historyShowing = false; // メッセージ履歴レイヤを表示中か + var lastHistoryHiddenTick = 0; // 最後に履歴レイヤが非表示になったときの tick + + /*C*/var numCharacterLayers = 0; // 前景レイヤの数 + /*C*/var numMessageLayers = 1; // メッセージレイヤの数 + var fore = %[]; // 表レイヤ + var back = %[]; // 裏レイヤ + + var scPositionX = %[]; // 立ち絵の中心座標(X) + + var tempLayer = void; // 一時的なレイヤ + + var lineBreak; // 行待ち用アニメーションレイヤ + var pageBreak; // ページ待ち用グリフのアニメーションレイヤ + var clickWaiting = false; // クリック待ちかどうか + + var mainConductor; // メインのコンダクタ + var extraConductor; // 予備のコンダクタ + var conductor; // 現在のコンダクタ + var usingExtraConductor = false; // 予備のコンダクタを使用中かどうか + var onExtraConductorReturn; // extraConductor から通常のコンダクタに戻るときによぶ関数 + + var tagHandlers; // タグのハンドラ群辞書配列 + + var current; // 現在操作中のメッセージレイヤ + /*C*/var currentNum; // 現在操作中のメッセージレイヤ番号 + /*C*/var currentPage; // 現在操作中のメッセージレイヤのページ(表0/裏1) + /*C*/var currentWithBack = false; // 裏画面にも文字を描画するかどうか + + var bgm; // BGM オブジェクト + + var numSEBuffers = 1; // 効果音バッファの数 + var se = []; // 効果音バッファオブジェクト + + var movie; // ムービーオブジェクト + + var transCount; // 現在進行中のトランジションの数 + var moveCount; // 現在進行中の自動移動の数 + + var chSpeeds = %[ + fast: 10, // 「高速」文字表示スピード + normal: 30, // 「普通」文字表示スピード + slow: 50, // 「遅い」文字表示スピード + ]; + + var userChSpeed = 30; // ユーザの選んだ文字表示スピード + var userCh2ndSpeed = -1; // ユーザの選んだ 既読部分の文字表示スピード + var chNonStopToPageBreak = false; // ページ末まで一気に読み進むか ( l タグを無視するか ) + var ch2ndNonStopToPageBreak = false; // 既読の部分でページ末まで一気に読み進むか + /*C*/var chUserMode = true; // 文字表示速度は現在ユーザの選んだものか + /*C*/var chSpeed = 30; // 現在の文字表示スピード + /*C*/var actualChSpeed = chSpeed; // 実際の文字表示スピード + + /*C*/var beforeNoWaitActualChSpeed; // nowait に入る前の actualChSpeed + /*C*/var beforeNoWaitChUserMode; // nowait に入る前の chUserMode + + /*C*/var clickSkipEnabled = true; // クリックスキップが有効か + /*C*/var nextSkipEnabled = true; // 次の選択肢(/未読)まで進むが有効か + var skipMode = 0; // スキップのモード + // 0=スキップなし, 1=クリック待ち記号まで, 2=改ページ待ち記号まで, 3=次の停止まで + // 4=早送り + var autoMode = false; // 自動読みすすみモードかどうか + var autoModePageWait = 350; // 自動読みすすみモード時の改ページ時のウェイト + var autoModeLineWait = 50; // 自動読みすすみモード時の行クリック待ち時のウェイト + // 上の二つは、ノーウェイトにしたい場合は 0 ではなくて -4 を指定すること + + var skipKeyRepressed = false; // return あるいは space キー ( f キーなどではない ) + // が押されると true になる ( スキップ解除時に false + + var autoModePageWaits = %[ + fast: 400, // 自動読みすすみモードの改ページ時 ウェイト「短い」 + faster: 700, // 自動読みすすみモードの改ページ時 ウェイト「やや短い」 + medium: 1000, // 自動読みすすみモードの改ページ時 ウェイト「普通」 + slower: 1300, // 自動読みすすみモードの改ページ時 ウェイト「やや遅い」 + slow: 2000, // 自動読みすすみモードの改ページ時 ウェイト「遅い」 + ]; + + /*C*/var canCancelSkipByClick = true; // クリックによりスキップ状態のキャンセルができるか + + /*C*/var autoWCEnabled = false; // 自動ウェイトが有効かどうか + /*C*/var autoWCChars = ""; // 自動ウェイトをかける文字 + var autoWCWaits = []; // 自動ウェイトのウェイト + + var timeOrigin; // resetWait で設定された時間原点 + var lastWaitTime; // wait mode=until で実際に待った時間 + + var stableHandlers = []; // システムが安定(クリック待ち/停止)したときに呼ばれる + var runHandlers = []; // システムが走り始めたときに呼ばれる + var inStable = true; // 安定しているとき(走行中でないとき) true + var inSleep = false; // s タグで停止しているとき true + + var updateBeforeCh = 0; // 文字を描画する前にいったん画面描画に入るかどうかのカウント + + var messageLayerHiding = false; // ユーザによりメッセージレイヤが隠されているか + + /*C*/var rightClickEnabled = true; // 右クリックが有効か + /*C*/var rightClickCall = false; // 右クリックで特定のルーチンを呼ぶか + /*C*/var rightClickJump = false; // 右クリックかで特定のラベルにジャンプするか + /*C*/var rightClickTarget = ""; // 右クリックでの呼び出し先 + /*C*/var rightClickStorage = ""; // 右クリックでの呼び出し先 + /*C*/var rightClickName = "default"; // 右クリックのメニュー表示名 + /*C*/var rightClickCurrentMenuName = ""; // 右クリックのメニューに現在設定されている名前 + var rightClickDefaultName = ""; // 右クリックのデフォルトのメニュー表示名 + + /*C*/var lastClickGlyphVisible; // extraConductor を使用する直前にクリック待ち記号が可視だったかどうか + var lastClickGlyphMessagePage; + // extraConductor を使用する直前のクリック待ち記号の表示されているメッセージレイヤのページ + var lastClickGlyphMessageNum; // 〃 番号 + var lastClickGlyphWhich; // 〃 "page" か "line" か + var inSleepBeforeExtraConductor; // extraConductor を使用する直前が inSleep だったか + + // 通常のマウスカーソル + /*C*/var cursorDefault = crArrow; // 通常のマウスカーソル + /*C*/var cursorPointed = crHandPoint; // ボタン、リンク等をポイントした時のマウスカーソル + /*C*/var cursorWaitingClick = crArrow; // クリック待ちのマウスカーソル + /*C*/var cursorDraggable = crSizeAll; // ドラッグ可能な場所用のマウスカーソル + + /*C*/var startAnchorEnabled = false; // 「最初に戻る」が使用可能かどうか + + /*C*/var storeEnabled = true; // ユーザが「栞をはさむ」メニューにアクセス可能かどうか + /*C*/var restoreEnabled = true; // ユーザが「栞をたどる」メニューにアクセス可能かどうか + var storeLabelPassed = false; // 保存可能なラベルを通過したかどうか + /*C*/var currentLabel = ""; // 現在のラベル + /*C*/var currentPageName = ""; // 現在のページ名 + var currentRecordName = ""; // 現在の記録名 ( trail_ストレージ_ラベル ) + var autoRecordPageShowing = false; // ラベル通過記録をするか + + var numBookMarks = 10; // メニューに用意する栞のサブメニュー項目の数 + var showBookMarkDate = false; // メニューに栞をはさんだ日付時刻を表示するか + + var bookMarkNames = []; // メニューに設定されている栞の名前 + var bookMarkDates = []; // 栞の日付 + var bookMarkProtectedStates = []; // 栞が保護されているかの情報 + + var showFixedPitchOnlyInFontSelector = false; // フォント選択で固定ピットフォントのみを表示するか + + var flags = %[]; // フラグ(ユーザ) + var pflags = %[]; // 「保存可能なラベル」を通過した時点でのフラグ(ユーザ) + var pcflags = %[]; // 〃 (コア) + var sflags = %[]; // システム変数領域(ユーザ) + var scflags = %[]; // システム変数領域(コア) + var tflags = %[]; // 一時フラグ + + var tempBookMarks = []; // 一時的に保存した栞 + + var clickCount = 0; // 左クリックカウント + var lastMouseDownX; // 最後にクリックされた X 座標 + var lastMouseDownY; // 最後にクリックされた Y 座標 + + var mouseKeyEnabledCount = 0; // マウスキーが有効かどうか + + var kagPlugins = []; // KAG プラグイン + + var keyDownHook = []; // キーが押されたときに呼び出される物 + var leftClickHook = []; // 右クリックされたときに呼び出される物 + var rightClickHook = []; // 左クリックされたときに呼び出される物 + + //------------------------------------------------------ コンストラクタ -- + + function KAGWindow(ismain = true, width = 0, height = 0) + { + // コンストラクタ + // 引数 : ismain : メインウィンドウとして作成されるのかどうか + super.Window(); // 親クラスのコンストラクタを呼ぶ + + // コンフィギュレーション + isMain = ismain; + if(ismain) + { + (KAGWindow_config incontextof this)(); + (KAGWindow_config_override incontextof this)() + if typeof global.KAGWindow_config_override != "undefined"; + } + + userChSpeed = chSpeed = actualChSpeed = chSpeeds.normal; + autoModePageWait = autoModePageWaits.medium; + + askOnClose = false if !ismain; + + // saveDataLocation がフルパスでないようならば System.exePath を + // 付け加える + if(saveDataLocation.indexOf(":") == -1) + saveDataLocation = System.exePath + saveDataLocation; + + // メニューアイテムの作成 + if(ismain) (KAGWindow_createMenus incontextof this)(); + if(typeof this.rightClickMenuItem != "undefined") + rightClickDefaultName = rightClickCurrentMenuName = rightClickMenuItem.caption; + + if(typeof this.autoModeMediumMenuItem != "undefined") + autoModeMediumMenuItem.checked = true; + if(typeof this.windowedMenuItem != "undefined") + windowedMenuItem.checked = true; + if(typeof this.chNormalMenuItem != "undefined") + chNormalMenuItem.checked = true; + if(typeof this.ch2ndNoChangeMenuItem != "undefined") + ch2ndNoChangeMenuItem.checked = true; + + if(ismain) (Menu_visible_config incontextof this)(); + + createBookMarkSubMenus(); + + // ウィンドウ外見の調整 + if(ismain) + { + borderStyle = bsSingle; + innerSunken = true; + } + else + { + borderStyle = bsDialog; + innerSunken = false; + } + showScrollBars = false; + if(ismain) caption = System.title; + + // システムタイトルをキャプションと同じに + if(ismain) System.title = caption; + + // ウィンドウサイズの調整 + if(width != 0 && height != 0) + { + // 与えられたサイズを適用 + scWidth = width; + scHeight = height; + } + setInnerSize(scWidth, scHeight); + + // quake 用タイマの作成 + quakeTimer = new Timer(onQuakeTimerInterval, ''); + add(quakeTimer); + quakeTimer.interval = 50; + + // 背景レイヤの作成 + fore.messages = []; + back.messages = []; + fore.layers = []; + back.layers = []; + fore.base = new BaseLayer(this, null, "表-背景"); + add(fore.base); + fore.base.setImageSize(scWidth, scHeight); + fore.base.setSizeToImageSize(); + back.base = new BaseLayer(this, fore.base, "裏-背景"); + add(back.base); + back.base.setImageSize(scWidth, scHeight); + back.base.setSizeToImageSize(); + fore.base.setCompLayer(back.base); + back.base.setCompLayer(fore.base); + fore.base.freeImage(); + back.base.freeImage(); + + fore.base.setDefaultCursor(cursorDefault); + back.base.setDefaultCursor(cursorDefault); + + // メッセージ履歴レイヤの作成 + historyLayer = new HistoryLayer(this, fore.base); + add(historyLayer); + + // 前景レイヤの作成 + allocateCharacterLayers(numCharacterLayers); + + // メッセージレイヤの作成 + allocateMessageLayers(numMessageLayers, false); + current = fore.messages[0]; + currentNum = 0; + currentPage = 0; + currentWithBack = false; + if(initialMessageLayerVisible) + { + fore.messages[0].visible = true; + back.messages[0].visible = true; + } + + chDefaultAntialiased = fore.messages[0].defaultAntialiased; + // 文字にアンチエイリアスをかけるかどうか + chDefaultFace = fore.messages[0].userFace; + // 文字のデフォルトのフォント + + if(typeof this.chAntialiasMenuItem != "undefined") + chAntialiasMenuItem.checked = chDefaultAntialiased; + + // 行待ち/ページ待ちアニメーションレイヤの作成 + lineBreak = new ClickGlyphLayer(this, fore.base); + add(lineBreak); + lineBreak.name = "行クリック待ち記号"; + pageBreak = new ClickGlyphLayer(this, fore.base); + add(pageBreak); + pageBreak.name = "ページ末クリック待ち記号"; + + // タグハンドラ/コンダクタを作成 + tagHandlers = getHandlers(); + mainConductor = new Conductor(this, tagHandlers); + add(mainConductor); + conductor = mainConductor; + extraConductor = new Conductor(this, tagHandlers); + add(extraConductor); + + // BGM オブジェクトを作成 + bgm = new BGM(this); + add(bgm); + + // 効果音オブジェクトを作成 + for(var i = 0; i < numSEBuffers; i++) + add(se[i] = new SESoundBuffer(this, i)); + + // ムービーオブジェクトを作成 + if(ismain) + { + movie = new Movie(this); + add(movie); + } + + // デフォルトのハンドラを追加 + stableHandlers.add(defaultStableHandler); + runHandlers.add(defaultRunHandler); + + // システム変数の読み込み + if(ismain) loadSystemVariables(); + + // システム変数を反映 + if(ismain) + { + setSystemStateFromSystemVariables(); + setBookMarkMenuCaptions(); + } + + // メッセージレイヤのクリア + clearMessageLayers(false); + + // ウィンドウ位置の調節 + if(this.width + this.left > System.desktopLeft + System.desktopWidth) + left = ((System.desktopWidth - this.width) >>1) + System.desktopLeft; + if(this.height + this.top > System.desktopTop + System.desktopHeight) + top = ((System.desktopHeight - this.height) >>1) + System.desktopTop; + + // ウィンドウを表示 + if(ismain) visible = true; + + // 画面サイズがウィンドウサイズよりも小さい場合は + // フルスクリーンにしてみる + if(ismain) + { + if(System.screenWidth <= scWidth && System.screenHeight <= scHeight) + onFullScreenMenuItemClick(this); + } + + // 前回起動時にフルスクリーンだった場合はフルスクリーンにしてみる + if(ismain) + { + if(scflags.fullScreen !== void && +scflags.fullScreen) + onFullScreenMenuItemClick(this); + } + + // いったんシステム変数を書き出す + if(ismain) saveSystemVariables(); + } + + //------------------------------------------------------------- finalize -- + + function finalize() + { + // finalize メソッド + + // プラグインの無効化 + for(var i = 0; i < kagPlugins.count; i++) invalidate kagPlugins[i]; + + // 前景、メッセージレイヤを無効化 + for(var i = 0; i< fore.layers.count; i++) invalidate fore.layers[i]; + for(var i = 0; i< back.layers.count; i++) invalidate back.layers[i]; + for(var i = 0; i< fore.messages.count; i++) invalidate fore.messages[i]; + for(var i = 0; i< back.messages.count; i++) invalidate back.messages[i]; + + // snapshotLayer を無効化 + invalidate snapshotLayer if snapshotLayer !== void; + + // tempLayer を無効化 + invalidate tempLayer if tempLayer !== void; + + // スーパークラスの finalize を呼ぶ + super.finalize(...); + } + + //-------------------------------------------------- onCloseQuery/close -- + + function onCloseQuery() + { + saveSystemVariables(); + if(!askOnClose) { super.onCloseQuery(true); return; } + super.onCloseQuery(askYesNo("終了しますか?")); + } + + function close() + { + // ウィンドウを閉じる + saveSystemVariables(); + super.close(...); + if(!this isvalid) + { + // 無効化されている ; (ウィンドウが閉じられた) + // この時点ですべてのオブジェクトは無効化されているので + global.System.exit(); + } + } + + function shutdown() + { + // ウィンドウを閉じるが、終了確認を行わない + var askOnClose_save = askOnClose; + askOnClose = false; + close(); + askOnClose = askOnClose_save; + } + + //------------------------------------------------------ プラグイン処理 -- + + function forEachEventHook(method, func, arg) + { + // すべてのプラグインオブジェクトの method にたいして + // func を実行する + // func の引数には各要素と arg が渡される + if(kagPlugins.count) + { + var array = []; + array.assign(kagPlugins); // いったんコピーし、コピーした物に対して実行する + var arraycount = array.count; + for(var i =0; i>1), top + ((height - win.height)>>1)); + win.process('about.ks' ,,, true); // about.ks を immediate で表示 + win.showModal(); // モード付きで表示 + invalidate win; + } + + function onReloadScenarioMenuItemClick(sender) + { + saveBookMark(1000, false); + loadBookMark(1000); + } + + function onShowConsoleMenuItemClick(sender) + { + Debug.console.visible = true; + } + + function onShowContollerMenuItemClick(sender) + { + Debug.controller.visible = true; + } + + function internalSetMenuAccessibleAll(menu, state) + { + // autoEnable が true のすべてのメニュー項目の accessible に値 state を + // 設定する + if(typeof menu.autoEnable != "undefined" && menu.autoEnable) + menu.accessible = state; + if(typeof menu.stopRecur == "undefined" || !menu.stopRecur) + { + var children = menu.children; + for(var i = children.count -1; i >= 0; i--) + internalSetMenuAccessibleAll(children[i], state); // 再帰 + } + } + + function canStore() + { + return storeEnabled && storeLabelPassed; + } + + function canRestore() + { + return restoreEnabled; + } + + function setMenuAccessibleAll() + { + // メニュー項目の使用可/不可を設定する + + // autoEnable が true のすべてのメニュー項目の accessible の + // 値を設定する + var notmodal = !historyLayer.visible && !messageLayerHiding; + var state = inStable && notmodal; + internalSetMenuAccessibleAll(menu, state); + + // その他のメニューの使用可/不可 + if(typeof this.skipToNextStopMenuItem != "undefined") + skipToNextStopMenuItem.enabled = state && !inSleep && nextSkipEnabled; + + if(typeof this.rightClickMenuItem != "undefined") + rightClickMenuItem.enabled = inStable && !historyLayer.visible; + + if(typeof this.showHistoryMenuItem != "undefined") + showHistoryMenuItem.enabled = inStable && !messageLayerHiding && + historyEnabled; + + if(typeof this.autoModeMenuItem != "undefined") + autoModeMenuItem.enabled = notmodal; + + if(typeof this.goBackMenuItem != "undefined") + goBackMenuItem.enabled = state && isHistoryOfStoreAlive(); + + if(typeof this.goToStartMenuItem != "undefined") + goToStartMenuItem.enabled = state && startAnchorEnabled; + + if(typeof this.storeMenu != "undefined") + { + var st = state && canStore(); + var children = storeMenu.children; + if(freeSaveDataMode) storeMenu.enabled = st; + for(var i = children.count - 1; i >= 0; i--) + { + var obj = children[i]; + obj.enabled = obj.orgEnabled && st; + } + } + + if(typeof this.restoreMenu != "undefined") + { + var st = state && canRestore(); + var children = restoreMenu.children; + if(freeSaveDataMode) restoreMenu.enabled = st; + for(var i = children.count - 1; i >= 0; i--) + { + var obj = children[i]; + obj.enabled = obj.orgEnabled && st; + } + } + } + + //----------------------------------------------- マウスキーを有効にする -- + + function enableMouseKey() + { + // マウスキーを有効にする + if(mouseKeyEnabledCount == 0) useMouseKey = true; + mouseKeyEnabledCount++; // 参照カウンタ方式 + } + + function disableMouseKey() + { + // マウスキーを無効にする + mouseKeyEnabledCount --; + if(mouseKeyEnabledCount == 0) useMouseKey = false; + } + + //----------------------------------------------------- システム変数関連 -- + + function loadSystemVariables() + { + // システム変数の読み込み + try + { + var fn = saveDataLocation + "/" + dataName + + "sc.ksd"; + if(Storages.isExistentStorage(fn)) + { + scflags = Scripts.evalStorage(fn); + scflags = %[] if scflags === void; + } + else + { + scflags = %[]; + } + + var fn = saveDataLocation + "/" + dataName + + "su.ksd"; + if(Storages.isExistentStorage(fn)) + { + sflags = Scripts.evalStorage(fn); + scflags = %[] if scflags === void; + } + else + { + sflags = %[]; + } + } + catch(e) + { + throw new Exception("システム変数データを読み込めないか、" + "あるいはシステム変数データが壊れています(" + e.message + ")"); + } + } + + function setSystemStateFromSystemVariables() + { + // システム変数に基づいてシステムを設定 + // (フルスクリーン関連をのぞく) + if(scflags.autoModePageWait !== void) + { + if(typeof this.autoModeWaitMenu !== "undefined") + { + var children = autoModeWaitMenu.children; + for(var i = children.count-1; i >= 0; i--) + { + var item = children[i]; + if(typeof item.wait !== "undefined" && item.wait == scflags.autoModePageWait) + { + item.checked = true; + break; + } + } + } + } + + if(scflags.userChSpeed !== void) + { + if(typeof this.chSpeedMenu !== "undefined") + { + var children = chSpeedMenu.children; + for(var i = children.count-1; i >= 0; i--) + { + var item = children[i]; + if(typeof item.speed !== "undefined" && item.speed == scflags.userChSpeed) + { + item.checked = true; + break; + } + } + } + } + + if(scflags.userCh2ndSpeed !== void) + { + if(typeof this.chSpeedMenu !== "undefined") + { + var children = ch2ndSpeedMenu.children; + for(var i = children.count-1; i >= 0; i--) + { + var item = children[i]; + if(typeof item.speed !== "undefined" && item.speed == scflags.userCh2ndSpeed) + { + item.checked = true; + break; + } + } + } + } + + lastSaveDataNameGlobal = scflags.lastSaveDataNameGlobal if scflags.lastSaveDataNameGlobal !== void; + + bookMarkNames = scflags.bookMarkNames if scflags.bookMarkNames !== void; + bookMarkDates = scflags.bookMarkDates if scflags.bookMarkDates !== void; + bookMarkProtectedStates = scflags.bookMarkProtectedStates if scflags.bookMarkProtectedStates !== void; + + autoModePageWait = scflags.autoModePageWait if scflags.autoModePageWait !== void; + userChSpeed = scflags.userChSpeed if scflags.userChSpeed !== void; + userCh2ndSpeed = scflags.userCh2ndSpeed if scflags.userCh2ndSpeed !== void; + + setUserSpeed(); + + chNonStopToPageBreak = scflags.chNonStopToPageBreak if scflags.chNonStopToPageBreak !== void; + if(typeof this.chNonStopToPageBreakItem != "undefined") + chNonStopToPageBreakItem.checked = chNonStopToPageBreak; + + ch2ndNonStopToPageBreak = scflags.ch2ndNonStopToPageBreak if scflags.ch2ndNonStopToPageBreak !== void; + if(typeof this.ch2ndNonStopToPageBreakItem != "undefined") + ch2ndNonStopToPageBreakItem.checked = ch2ndNonStopToPageBreak; + + chDefaultAntialiased = scflags.chDefaultAntialiased if scflags.chDefaultAntialiased !== void; + if(typeof this.chAntialiasMenuItem != "undefined") + chAntialiasMenuItem.checked = chDefaultAntialiased; + chDefaultFace = scflags.chDefaultFace if scflags.chDefaultFace !== void; + + autoMode = scflags.autoMode if scflags.autoMode !== void; + if(typeof this.autoModeMenuItem != "undefined") + autoModeMenuItem.checked = autoMode; + + setMessageLayerUserFont(); + + bgm.restoreSystemState(scflags); + + for(var i = 0; i pflags + (Dictionary.assignStruct incontextof pflags)(flags); + + internalStoreFlags(pcflags); + } + + function internalRestoreFlags(f, clear = true, elm = void) + { + // f から情報を読み出す + // clear が true ならばメッセージレイヤをクリアする + // se, bgm がそれぞれ true ならばその情報も反映させる + // backlay が true の場合は、表画面にロードすべきものを裏画面にロードする + + // KAGWindow に関するもの + // ここの [start_restore_vars] から [end_restore_vars] で囲まれた部分は + // (略) + // [start_restore_vars] + lastSaveDataName = f.lastSaveDataName if f.lastSaveDataName !== void; + quaking = f.quaking if f.quaking !== void; + quakeEndTick = f.quakeEndTick if f.quakeEndTick !== void; + quakeHorzMax = f.quakeHorzMax if f.quakeHorzMax !== void; + quakeVertMax = f.quakeVertMax if f.quakeVertMax !== void; + quakePhase = f.quakePhase if f.quakePhase !== void; + historyWriteEnabled = f.historyWriteEnabled if f.historyWriteEnabled !== void; + historyEnabled = f.historyEnabled if f.historyEnabled !== void; + numCharacterLayers = f.numCharacterLayers if f.numCharacterLayers !== void; + numMessageLayers = f.numMessageLayers if f.numMessageLayers !== void; + currentNum = f.currentNum if f.currentNum !== void; + currentPage = f.currentPage if f.currentPage !== void; + currentWithBack = f.currentWithBack if f.currentWithBack !== void; + chUserMode = f.chUserMode if f.chUserMode !== void; + chSpeed = f.chSpeed if f.chSpeed !== void; + actualChSpeed = f.actualChSpeed if f.actualChSpeed !== void; + beforeNoWaitActualChSpeed = f.beforeNoWaitActualChSpeed if f.beforeNoWaitActualChSpeed !== void; + beforeNoWaitChUserMode = f.beforeNoWaitChUserMode if f.beforeNoWaitChUserMode !== void; + clickSkipEnabled = f.clickSkipEnabled if f.clickSkipEnabled !== void; + nextSkipEnabled = f.nextSkipEnabled if f.nextSkipEnabled !== void; + canCancelSkipByClick = f.canCancelSkipByClick if f.canCancelSkipByClick !== void; + autoWCEnabled = f.autoWCEnabled if f.autoWCEnabled !== void; + autoWCChars = f.autoWCChars if f.autoWCChars !== void; + rightClickEnabled = f.rightClickEnabled if f.rightClickEnabled !== void; + rightClickCall = f.rightClickCall if f.rightClickCall !== void; + rightClickJump = f.rightClickJump if f.rightClickJump !== void; + rightClickTarget = f.rightClickTarget if f.rightClickTarget !== void; + rightClickStorage = f.rightClickStorage if f.rightClickStorage !== void; + rightClickName = f.rightClickName if f.rightClickName !== void; + rightClickCurrentMenuName = f.rightClickCurrentMenuName if f.rightClickCurrentMenuName !== void; + lastClickGlyphVisible = f.lastClickGlyphVisible if f.lastClickGlyphVisible !== void; + cursorDefault = f.cursorDefault if f.cursorDefault !== void; + cursorPointed = f.cursorPointed if f.cursorPointed !== void; + cursorWaitingClick = f.cursorWaitingClick if f.cursorWaitingClick !== void; + cursorDraggable = f.cursorDraggable if f.cursorDraggable !== void; + startAnchorEnabled = f.startAnchorEnabled if f.startAnchorEnabled !== void; + storeEnabled = f.storeEnabled if f.storeEnabled !== void; + restoreEnabled = f.restoreEnabled if f.restoreEnabled !== void; + currentLabel = f.currentLabel if f.currentLabel !== void; + currentPageName = f.currentPageName if f.currentPageName !== void; + // [end_restore_vars] + + // perl スクリプトによって自動的に処理されないもの、いくつか。 + + // 自動ウェイト + autoWCWaits.assign(f.autoWCWaits) if f.autoWCWaits !== void; + + // 背景レイヤ + var backlay = elm != void && elm.backlay != void && +elm.backlay; + if(backlay) + { + back.base.restore(f.foreBaseLayer); + } + else + { + fore.base.restore(f.foreBaseLayer); + back.base.restore(f.backBaseLayer); + } + + // メッセージレイヤ + allocateMessageLayers(numMessageLayers); + if(backlay) + { + for(var i = 0; i < numMessageLayers; i++) + { + back.messages[i].restore(f.foreMessageLayers[i]); + } + } + else + { + for(var i = 0; i < numMessageLayers; i++) + { + fore.messages[i].restore(f.foreMessageLayers[i]); + back.messages[i].restore(f.backMessageLayers[i]); + } + } + + if(clear) + { + for(var i = 0; i < numMessageLayers; i++) + { + fore.messages[i].clear(); + back.messages[i].clear(); + } + if(historyWriteEnabled) + { + if(historyLayer.everypage) + historyLayer.repage(); + else + historyLayer.reline(), historyLayer.reline(); + } + historyLayer.clearAction(); + } + + // 前景レイヤ + allocateCharacterLayers(numCharacterLayers); + if(backlay) + { + for(var i = 0; i < numCharacterLayers; i++) + { + back.layers[i].restore(f.foreCharacterLayers[i]); + } + } + else + { + for(var i = 0; i < numCharacterLayers; i++) + { + fore.layers[i].restore(f.foreCharacterLayers[i]); + back.layers[i].restore(f.backCharacterLayers[i]); + } + } + + // quake 関連 + restoreQuake(); + + // bgm + if(elm === void || elm.bgm === void || +elm.bgm) + { + bgm.restore(f.bgm); + } + + // 効果音 + if(elm === void || elm.se === void || +elm.se) + { + for(var i = 0; i flags + (Dictionary.assignStruct incontextof flags)(pflags); + + // 読み込み + internalRestoreFlags(pcflags); + + // 栞管理関連 + storeLabelPassed = true; + nextRecordHistory = false; + stablePosibility = false; + + // コンダクタ + currentRecordName = ""; + mainConductor.restore(pcflags.mainConductor); + extraConductor.clear(); + setConductorToMain(); + + // メニュー関連 + setMenuAccessibleAll(); + + // 実行開始 + processGo(); + } + + function clearVariables() + { + // ゲーム変数のクリア + (Dictionary.clear incontextof flags)(); + } + + //--------------------------------------------------------- 通過記録管理 -- + + function pushHistoryOfStore() + { + // 通過記録を行う + // pflags, pcflags に情報を格納した後に呼ぶこと + + if(nextRecordHistory) + { + if(stablePosibility) + { + // stablePosibility が false の場合は、 + // そこのラベルで通過記録を行っても + // そこよりも前に戻るすべがないので通過記録をしない + + // 辞書配列を作成 + var dic = %[]; + + // user と core を記録 + dic.user = %[]; + (Dictionary.assignStruct incontextof dic.user)(pflags); + dic.core = %[]; + (Dictionary.assignStruct incontextof dic.core)(pcflags); + + // dic を historyOfStore の先頭に挿入 + historyOfStore.insert(0, dic); + + // はみ出た分を削除 + if(historyOfStore.count > maxHistoryOfStore) + historyOfStore.count = maxHistoryOfStore; + } + + nextRecordHistory = false; + } + } + + function setToRecordHistory() + { + // 次の「保存可能なラベル」通過時に + // 通過記録を行うように設定する + // ( ただし、そのときに記録されるのは、現在の状態 ) + nextRecordHistory = true; + } + + function isHistoryOfStoreAlive() + { + // 通過記録が利用可能かどうかを返す + return historyOfStore.count; + } + + function goBackHistory(ask = true) + { + // 通過記録をたどり、戻る + + if(!isHistoryOfStoreAlive()) + return false; + + var result; + if(ask) + { + var prompt = "「"+ historyOfStore[0].core.currentPageName + "」まで戻りますか?"; + result = askYesNo(prompt); + } + else + { + result = true; + } + + if(result) + { + // user と core を pflags, pcflags に戻す + (Dictionary.assignStruct incontextof pflags)(historyOfStore[0].user); + (Dictionary.assignStruct incontextof pcflags)(historyOfStore[0].core); + + // 記録の先頭を削除する + historyOfStore.erase(0); + + // データを元に、栞をたどる動作をする + restoreFlags(); + + return true; + } + return false; + } + + //--------------------------------------------------------------- 栞管理 -- + + function createBookMarkSubMenus() + { + // 「栞をたどる」「栞をはさむ」以下にサブメニュー項目を追加 + if(freeSaveDataMode) return; // フリーセーブモードではなにもしない + if(typeof this.storeMenu !== "undefined" && storeMenu.visible) + { + for(var i = 0; i= 0; i--) + { + if(bookMarkDates[i] != '') // 空文字列の場合は栞がないということ + { + // 栞が存在する + var caption; + if(showBookMarkDate) caption = bookMarkDates[i] + " "; + caption += bookMarkNames[i]; + var item = children[i]; + item.caption = caption; + item.enabled = false; + item.orgEnabled = !bookMarkProtectedStates[i]; + } + else + { + // 栞が存在しない + var item = children[i]; + item.caption = "(未設定)"; + item.enabled = false; + item.orgEnabled = !bookMarkProtectedStates[i]; + } + } + } + + // 栞をたどる + if(typeof this.restoreMenu !== "undefined") + { + var children = restoreMenu.children; + for(var i = children.count - 1; i >= 0; i--) + { + if(bookMarkDates[i] != '') // 空文字列の場合は栞がないということ + { + // 栞が存在する + var caption; + if(showBookMarkDate) caption = bookMarkDates[i] + " "; + caption += bookMarkNames[i]; + var item = restoreMenu.children[i]; + item.caption = caption; + item.enabled = false; + item.orgEnabled = true; + } + else + { + var item = restoreMenu.children[i]; + item.caption = "(未設定)"; + item.enabled = false; + item.orgEnabled = false; + } + } + } + setMenuAccessibleAll(); + } + + function setBookMarkProtectetState(num, s) + { + // n 番の栞の保護フラグを設定する + // s = true ならば栞に書き込み保護をする + bookMarkProtectedStates[num] = s; + setBookMarkMenuCaptions(); + } + + function onBookMarkStore(sender) + { + // 栞をはさむメニューが選択された +// if(!sender.parent.accessEnabled) return; + saveBookMarkWithAsk(sender.bmNum); + } + + function onBookMarkRestore(sender) + { + // 栞をたどるメニューが選択された +// if(!sender.parent.accessEnabled) return; + loadBookMarkWithAsk(sender.bmNum); + } + + function getBookMarkPageName(num) + { + // 栞番号 num のブックマーク名を得る + if(bookMarkDates[num] != '') // 空文字列の場合は栞がないということ + return bookMarkNames[num]; + return "(未設定)"; + } + + function getBookMarkDate(num) + { + // 栞番号 num の日付を得る + return bookMarkDates[num]; + } + + function getBookMarkFileNameAtNum(num) + { + if(num >= 999) // 999 番以降は特殊なデータに用いるので + return saveDataLocation + "/" + dataName + num + ".ksd"; + else + return saveDataLocation + "/" + dataName + num + (saveThumbnail?".bmp":".kdt"); + } + + function lockSnapshot() + { + // スナップショットをロックする + // 初めてスナップショットがロックされた時点での画面を保存する + if(snapshotLockCount == 0) + { + if(snapshotLayer === void) + snapshotLayer = new Layer(this, primaryLayer); + snapshotLayer.setImageSize(scWidth, scHeight); + snapshotLayer.face = dfBoth; + snapshotLayer.piledCopy(0, 0, kag.fore.base, 0, 0, scWidth, scHeight); + } + snapshotLockCount ++; + } + + function unlockSnapshot() + { + // スナップショットのロックを解除する + if(snapshotLockCount == 0) + throw new Exception("snapshotLockCount がアンダーフローしました"); + snapshotLockCount --; + if(snapshotLockCount == 0) + { + if(snapshotLayer !== void) + invalidate snapshotLayer, snapshotLayer = void; + } + } + + function calcThumbnailSize() + { + // サムネイルのサイズを計算する + // 横幅は 133 に + var ratio = scHeight / scWidth; + var w = 133; + var h = (int)(w * ratio); + + // サムネイル用ビットマップのサイズを計算 + // サムネイル用画像は 256 色 BMP + var size = + ((((w - 1) >> 2) + 1) << 2) * h + + 1024 + 54; + return %[width : w, height : h, size : size]; + } + + function freeSnapshot() + { + // スナップショットを強制的に破棄し、snapshotLockCount を 0 に設定する + snapshotLockCount = 0; + if(snapshotLayer !== void) + invalidate snapshotLayer, snapshotLayer = void; + } + + function saveBookMarkToFile(fn, savehist = true) + { + // ファイル fn に栞を保存する + if(readOnlyMode) return false; + pcflags.storeTime = (new Date()).getTime(); // 日付を保存 + + // セーブデータをまとめる + var data = %[]; + data.id = saveDataID; + data.core = pcflags; + data.user = pflags; + if(savehist) data.history = historyOfStore; + + if(saveThumbnail) + { + // サムネイルを保存 + lockSnapshot(); + try + { + // サムネイルのサイズまで縮小 + var size = calcThumbnailSize(); + var tmp = new Layer(this, primaryLayer); + try + { + tmp.setImageSize(size.width, size.height); + tmp.face = dfBoth; + tmp.stretchCopy(0, 0, size.width, size.height, snapshotLayer, + 0, 0, snapshotLayer.imageWidth, snapshotLayer.imageHeight, stLinear); + /* + // サムネイル画像をセピア調にして保存する場合はコメントアウトを解除 + tmp.doGrayScale(); + tmp.adjustGamma( + 1.3, 0, 255, // R gamma, floor, ceil + 1.0, 0, 255, // G gamma, floor, ceil + 0.8, 0, 255); // B gamma, floor, ceil + */ + try + { + // サムネイルを保存 + tmp.saveLayerImage(fn, "bmp8"); + + // データを保存 + var mode = saveDataMode; + mode += "o" + size.size; // モード文字列に 書き込みオフセットを指定 + (Dictionary.saveStruct incontextof data)(fn, mode); + } + catch(e) + { + invalidate tmp; + unlockSnapshot(); + System.inform("ファイルに保存できません (ファイルを開けないか、" + "書き込み禁止です)"); + return false; + } + } + catch(e) + { + invalidate tmp; + throw e; + } + invalidate tmp; + } + catch(e) + { + unlockSnapshot(); + throw e; + } + unlockSnapshot(); + } + else + { + // 通常のファイルに保存 + try + { + (Dictionary.saveStruct incontextof data)(fn, saveDataMode); + } + catch(e) + { + System.inform("ファイルに保存できません (ファイルを開けないか、" + "書き込み禁止です)"); + return false; + } + } + + return true; + } + + function saveBookMark(num, savehist = true) + { + // 栞番号 num に栞を保存する + if(readOnlyMode) return false; + if(bookMarkProtectedStates[num]) return false; + + var ret = saveBookMarkToFile(getBookMarkFileNameAtNum(num), savehist); + if(ret) + { + // メニュー / bookMarkNames / bookMarkDates を更新 + getBookMarkInfoFromData(pcflags, num); + } + return ret; + } + + function getBookMarkInfoFromData(dic, num) + { + // 辞書配列 dic から栞のページ名と日付を読み出し、 + // bookMarkDates[num] や bookMarkNames[num] に設定する + if(num < numBookMarks) + { + bookMarkNames[num] = dic.currentPageName; + var date = new Date(); + date.setTime(dic.storeTime); + date = "%04d/%02d/%02d %02d:%02d".sprintf( + date.getYear(), date.getMonth() + 1, date.getDate(), + date.getHours(), date.getMinutes() ); + bookMarkDates[num] = date; + setBookMarkMenuCaptions(); + saveSystemVariables(); + } + } + + function loadBookMarkFromFile(fn, loaduser = true) + { + // ファイル fn から栞を読み込む + // loaduser が false の時は user を読み込まない + try + { + if(!Storages.isExistentStorage(fn)) return false; //ファイルがない + + var data; + + var modestr; + + if(saveThumbnail) + { + // 指定オフセットからデータを読み込む + modestr += "o" + calcThumbnailSize().size; + } + + data = Scripts.evalStorage(fn, modestr); + + if(data.id != saveDataID) + { + System.inform("他のシステムのデータを読み込もうとしました", "エラー"); + return false; + } + + pcflags = data.core; + pcflags = %[] if pcflags === void; + if(loaduser) + { + pflags = data.user; + pflags = %[] if pflags === void; + } + else + { + (Dictionary.assignStruct incontextof pflags)(flags); + } + historyOfStore = data.history; + historyOfStore = [] if historyOfStore === void; + } + catch(e) + { + System.inform("栞を読み込めないか、栞が" + "壊れているか、あるいは他の形式の栞データ" + "です(" + e.message + ")", "エラー"); + return false; + } + + restoreFlags(); + return true; + } + + function loadBookMark(num, loaduser = true) + { + // 栞番号 num からデータを読み出す + return loadBookMarkFromFile(getBookMarkFileNameAtNum(num), loaduser); + } + + function saveBookMarkWithAsk(num) + { + // 栞番号 num に栞を設定する + // そのとき、設定するかどうかをたずねる + if(readOnlyMode) return false; + if(bookMarkProtectedStates[num]) return false; + var prompt = "栞 "; + if(num < numBookMarks) prompt += (num + 1); + if(bookMarkDates[num] != "") // bookMarkDates が空文字の場合は栞は存在しない + prompt += "「" + bookMarkNames[num] + "」"; + prompt += "に「"+ pcflags.currentPageName + "」をはさみますか?"; + var result = askYesNo(prompt); + if(result) return saveBookMark(num); + return false; + } + + function loadBookMarkWithAsk(num) + { + // 栞番号 num から栞を読み出す + // そのとき、読み出すかどうかをたずねる + if(num < numBookMarks && bookMarkDates[num] == "") // bookMarkDates が空文字の場合は栞は存在しない + return false; + var prompt = "栞 "; + if(num < numBookMarks) prompt += (num + 1); + prompt += "「"+ bookMarkNames[num] + "」をたどりますか?"; + var result = askYesNo(prompt); + if(result) return loadBookMark(num); + return false; + } + + function saveBookMarkToFileWithAsk() + { + // 任意のファイルに栞を保存する + // currentPageName をファイル名として適合するように + // 変形する + var invalid = "\\/:,;*?\"<>!."; + var valid = "¥/:,;*?”<>!."; + + var initialname = saveDataLocation + "/"; + var through = false; + var orgname = currentPageName; + + if(lastSaveDataNameGlobal != "") + { + try + { + initialname = Storages.extractStoragePath(lastSaveDataNameGlobal); + } + catch(e) + { + initialname = saveDataLocation + "/"; + } + } + + if(orgname == "") + { + // 栞の見出しがないので + if(lastSaveDataName == "") + orgname = System.title; // System.title を変わりに使う + else + initialname = lastSaveDataName, through = true; + } + + if(!through) + { + var length = orgname.length; + for(var i = 0; i < length; i++) + { + var ch = orgname[i]; + var ind = invalid.indexOf(ch); + if(ind != -1) + initialname += valid[ind]; + else if(#ch >= 32) + initialname += ch; + } + } + + // 保存するファイル名を得る + var selectdata = %[ + title:"栞をはさむ", + filter: [saveThumbnail ? + "サムネイル画像付き栞データ(*.bmp)|*.bmp" : + "栞データ(*.kdt)|*.kdt"], + filterIndex : 1, + name : initialname, + initialDir : "", + defaultExt : saveThumbnail?"bmp":"kdt", + save : true, + ]; + if(Storages.selectFile(selectdata)) + { + // 保存 + saveBookMarkToFile(lastSaveDataName = lastSaveDataNameGlobal = selectdata.name); + lastSaveDataName = Storages.chopStorageExt(lastSaveDataName); + } + } + + function loadBookMarkFromFileWithAsk() + { + // 任意のファイルから栞を読み込む + var initialdir = ""; + if(lastSaveDataNameGlobal == "") + initialdir = saveDataLocation + "/"; + + var selectdata = %[ + title:"栞をたどる", + filter: [saveThumbnail ? + "サムネイル画像付き栞データ(*.bmp)|*.bmp" : + "栞データ(*.kdt)|*.kdt"], + filterIndex : 1, + name : lastSaveDataNameGlobal, + initialDir : initialdir, + defaultExt : saveThumbnail?"bmp":"kdt", + save : false, + ]; + if(Storages.selectFile(selectdata)) + { + loadBookMarkFromFile(lastSaveDataName = lastSaveDataNameGlobal = selectdata.name); + lastSaveDataName = Storages.chopStorageExt(lastSaveDataName); + } + } + + + function copyBookMark(from, to) + { + // 栞番号 from から栞番号 to に栞をコピーする + if(readOnlyMode) return false; + if(bookMarkProtectedStates[to]) return; + + var fn = getBookMarkFileNameAtNum(from); + + if(!Storages.isExistentStorage(fn)) return; //ファイルがない + + var data = Scripts.evalStorage(fn); + + fn = getBookMarkFileNameAtNum(to); + + (Dictionary.saveStruct incontextof data)(fn, saveDataMode); + getBookMarkInfoFromData(data.core, to); + } + + function eraseBookMark(num) + { + // 栞を消す + // num < numBookMarks の時にしか動作しないようになったので注意 + if(num < numBookMarks) + { + if(!bookMarkProtectedStates[num]) + { + bookMarkDates[num] = ""; + setBookMarkMenuCaptions(); + } + } + } + + function tempDisableStore(elm) + { + // 栞を一時的に保存不可能にする + storeEnabled = true; + if(elm.store === void) + storeLabelPassed = false; + else + storeLabelPassed = !(+elm.store); + if(elm.restore == void) + restoreEnabled = true; + else + restoreEnabled = !(+elm.restore); + setMenuAccessibleAll(); + } + + function setStoreEnabled(enabled) + { + // 栞メニューの有効/無効の設定 + storeEnabled = enabled; + restoreEnabled = enabled; + setMenuAccessibleAll(); + } + + function setStartAnchorEnabled(enabled) + { + // 「最初に戻る」の有効/無効の設定 + startAnchorEnabled = enabled; + if(enabled) saveBookMark(999, false); // 999 番に保存 + setMenuAccessibleAll(); + } + + function goToStart() + { + // 最初に戻る + if(!startAnchorEnabled) return; + loadBookMark(999, false); // 栞を読み込む + } + + function goToStartWithAsk() + { + // 最初に戻る(確認あり) + var result = askYesNo("最初に戻ります。よろしいですか ?"); + if(result) goToStart(); + } + + function tempSave(num) + { + // tempBookMarks[num] に現在の状態を保存する + tempBookMarks[num] = %[]; + internalStoreFlags(tempBookMarks[num]); + } + + function tempLoad(num, elm) + { + // tempBookMarks[num] から状態を読み込む + internalRestoreFlags(tempBookMarks[num], false, elm); + } + + function restoreBookMark(num, ask = true) + { + // KAG 2.x 互換用 + if(ask) + return loadBookMarkWithAsk(num); + else + return loadBookMark(num); + } + + function storeBookMark(num, ask = true) + { + // KAG 2.x 互換用 + if(ask) + return saveBookMarkWithAsk(num); + else + return saveBookMark(num); + } + + //------------------------------------------------- 未読/既読/ラベル記録 -- + + function setRecordLabel(storage, label) + { + // 現在のラベルを設定する + if(autoRecordPageShowing) + { + if(label != '') + { + if(label[0] == '*') label = label.substring(1); + if(label[1] == '-') return; // ローカルラベル + } + currentRecordName = 'trail_' + Storages.chopStorageExt( + Storages.extractStorageName(storage)) + '_' + label; + } + } + + function incRecordLabel(count) + { + // sflags[currentRecordName]++ + if(autoRecordPageShowing) + { + if(currentRecordName != "") + { + if(count) + { + if(sflags[currentRecordName] === void) + sflags[currentRecordName] = 0; + sflags[currentRecordName]++; + } + currentRecordName = ""; + } + } + } + + //------------------------------------------- システム全体に関係するもの -- + + function setTitle(title) + { + // タイトルを設定 + if(isMain) System.title = title; + caption = title; + } + + function setCursor(elm) + { + // マウスカーソルの設定 + var conv = function(variable, value) + { + if(value !== void) + { + if(!(typeof value == "String" && + (value.indexOf('.cur')!=-1 || value.indexOf('.ani')!=-1) )) + value = +value; + this[variable] = value; + } + } incontextof this; + + conv('cursorDefault', elm['default']); + conv('cursorPointed', elm.pointed); + conv('cursorWaitingClick', elm.click); + conv('cursorDraggable', elm.draggable); + fore.base.setDefaultCursor(cursorDefault); + back.base.setDefaultCursor(cursorDefault); + } + + //---------------------------------------------------- トリガ管理(TJS用) -- + + function waitTrigger(elm) + { + // elm.name で示されたトリガを待つ + if((elm.canskip !== void && +elm.canskip) && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ動作中 + if(elm.onskip !== void) elm.onclick!; + return 0; // すぐに返る + } + conductor.wait(%[ + click : function(arg) + { + if(arg !== void) arg!; + } incontextof this, + click_arg : elm.onskip, + elm.name => function + { + } incontextof this + ]); + } + else + { + conductor.wait(%[ + elm.name => function + { + } incontextof this + ]); + } + return -2; + } + + function trigger(name) + { + // name で示したトリガを発動する + conductor.trigger(name); + } + + //------------------------------------------------------- メッセージ履歴 -- + + function showHistory() + { + // メッセージ履歴レイヤを表示する + historyLayer.parent = fore.base; // メッセージ履歴レイヤの親も再設定 + historyLayer.absolute = 2000000; + historyLayer.dispInit(); + historyShowing = true; + if(typeof this.showHistoryMenuItem != "undefined") + showHistoryMenuItem.checked = true; + setMenuAccessibleAll(); + } + + function hideHistory() + { + // メッセージ履歴レイヤを非表示にする + historyLayer.dispUninit(); + historyShowing = false; + if(typeof this.showHistoryMenuItem != "undefined") + showHistoryMenuItem.checked = false; + setMenuAccessibleAll(); + lastHistoryHiddenTick = System.getTickCount(); + conductor.trigger('history'); // 'history' を送る + } + + function setHistoryOptions(elm) + { + // elm からメッセージ履歴レイヤのオプションを設定する + historyWriteEnabled = +elm.output if elm.output !== void; + historyEnabled = +elm.enabled if elm.enabled !== void; + if(elm.enabled !== void && !historyEnabled) + historyLayer.clearAction(); + historyLayer.setOptions(elm); // その他のオプション + setMenuAccessibleAll(); + } + + function showHistoryByScenario(elm) + { + // メッセージ履歴をシナリオ中から表示させる + showHistory(); + conductor.wait(%[ // コンダクタを待ちに + history : function + { + // やることなし + } incontextof this + ]); + return -2; // break + } + + //-------------------------------------------------------------- process -- + + function process(file, label, countpage = true, immediate = false) + { + // 指定ファイル、指定ラベルから実行を開始する + if(!usingExtraConductor) incRecordLabel(countpage); + setUserSpeed(); + + if(file != '') + { + // ファイルを読み込み + conductor.loadScenario(file); + } + + if(label != '') + { + // ラベルに移動する + conductor.goToLabel(label); + } + + if(isFirstProcess) + { + storeFlags(); // 一番最初の状態をストア + isFirstProcess = false; + } + + + dm("処理を開始します"); + inSleep = false; + notifyRun(); + conductor.run(immediate); // 実行開始 + } + + function processGo() + { + // コンダクタを現在位置から実行開始させる + dm("処理を開始します"); + inSleep = false; + notifyRun(); + conductor.run(false); // 実行開始 + } + + function processCall(file, label) + { + // 指定ファイル、指定ラベルを呼ぶ + // incRecordLabel(); は呼ばないので注意 + + if(file != '') + { + // ファイルを読み込み + conductor.loadScenario(file); + } + + inSleep = false; + notifyRun(); + conductor.callLabel(label); // 実行開始 + dm("処理を開始します"); + conductor.run(); + } + + //------------------------------------------------- コンダクタのイベント -- + + function onConductorScenarioLoad(name) + { + // コンダクタが新しいシナリオファイルを読み込む前によばれる。 + // name は読み込もうとしたシナリオファイル。 + // 戻り値に文字列を返すと、それをシナリオとして + // ファイルの変わりに使うようになるので、ここにフィルタを書くこ + // とができる。 + // true を返すと通常のシナリオファイル読み込みとなる。 + return true; + } + + + function onConductorScenarioLoaded(name) + { + // コンダクタが新しいシナリオファイルを読み込んだ +// if(!usingExtraConductor) incRecordLabel(true); + return true; + } + + function onConductorLabel(label, page) + { + // コンダクタがラベルを通過した + if(!usingExtraConductor) + { + incRecordLabel(true); + setRecordLabel(conductor.curStorage, label); + } + setUserSpeed(); + if(!usingExtraConductor) + { + if(!getCurrentRead() && skipMode != 4) + cancelSkip(); // 未読なのでスキップを停止 + currentLabel = label; + } + if(page !== void && page !== '') + { + if(page[0] == '&') page = (page.substring(1))!; + currentPageName = page; + } + if(page !== void) + { + pushHistoryOfStore(); + stablePosibility = false; + dm(conductor.curStorage + " : ラベル/ページ : " + label + "/" + currentPageName); + if(usingExtraConductor) throw new Exception("右クリックサブルーチン内/extraCondutor" + "サブルーチン内では保存可能なラベルを記述できません"); + storeFlags(), storeLabelPassed = true, setMenuAccessibleAll(); + if(recordHistoryOfStore == 1) // 1 : 保存可能なラベルごと + setToRecordHistory(); + } + return true; + } + + function onConductorJump(elm) + { + // コンダクタで jump タグを処理するとき + if(!usingExtraConductor) incRecordLabel(elm.countpage === void || +elm.countpage); + return true; + } + + function onConductorCall(elm) + { + // コンダクタが call タグを処理するとき + if(!usingExtraConductor) incRecordLabel(elm.countpage !== void && +elm.countpage); + return true; + } + + function onConductorReturn(elm) + { + // コンダクタが return タグを処理するとき + if(!usingExtraConductor) incRecordLabel(elm.countpage === void || +elm.countpage); + if(conductor === extraConductor) + { + // extraConductor サブルーチン用のコンダクタから呼ばれている + if(conductor.callStackDepth == 1) + { + // つまり、最終の return が実行されたと言うこと + dm("extraConductor サブルーチンから戻ります ..."); + var run; + if(elm.storage !== void || elm.target !== void) run = true; else run = false; + returnExtraConductor(run); + if(elm.storage !== void) conductor.loadScenario(elm.storage); + if(elm.target !== void) conductor.goToLabel(elm.target); + setRecordLabel(conductor.curStorage, currentLabel = conductor.curLabel); + if(run) + { + notifyRun(); + conductor.run(); + } + return false; // return は実行しない + } + } + return true; + } + + function onConductorAfterReturn() + { + // コンダクタが return タグを実行した後 + if(!usingExtraConductor) + { + setRecordLabel(conductor.curStorage, currentLabel = conductor.curLabel); + } + setUserSpeed(); + if(!usingExtraConductor) + { + if(!getCurrentRead() && skipMode != 4) + cancelSkip(); // 未読なのでスキップを停止 + } + } + + + function onConductorScript(script, scriptname, lineofs) + { + // iscript タグ + try + { + Scripts.exec(script, scriptname, lineofs); + } + catch(e) + { + throw new Exception(scriptname + " の 行 " + lineofs + " から始まる" + " iscript ブロックでエラーが発生しました。" + "\n( 詳細はコンソールを参照してください )\n" + e.message); + } + return true; + } + + function onConductorUnknownTag(tagname, elm) + { + // 不明なタグがあった場合 + dm("タグ/マクロ \"" + tagname + "\" は存在しません"); + return 0; // この戻り値は、各タグハンドラが返す物とおなじ + } + + //----------------------------------------------------------- stable/run -- + + function notifyStable() + { + // システムが安定(クリック待ち/停止)したときに、ハンドラを呼ぶ + if(!inStable) + { + inStable = true; + var handlers = stableHandlers; + for(var i = handlers.count-1; i>=0; i--) + handlers[i](); + + // stableHook + forEachEventHook('onStableStateChanged', + function(handler, f) { handler(f.stable); } incontextof this, + %[stable:true]); + } + } + + function notifyRun() + { + // システムが走り始めたときに、ハンドラを呼ぶ + if(inStable) + { + inStable = false; + var handlers = runHandlers; + for(var i = handlers.count-1; i>=0; i--) + handlers[i](); + + // runHook + forEachEventHook('onStableStateChanged', + function(handler, f) { handler(f.stable); } incontextof this, + %[stable:false]); + + if(autoMode) hideMouseCursor(); + } + } + + function defaultStableHandler() + { + // デフォルトの stable ハンドラ + setMenuAccessibleAll(); + } + + function defaultRunHandler() + { + // デフォルトの run ハンドラ + hideHistory(); + hideClickGlyphs(); + showMessageLayerByUser(); + setMenuAccessibleAll(); + } + + //----------------------------------------------------------- 文字列入力 -- + + var inputTemp; + function inputString(elm) + { + // 文字列を入力する + var name = elm.name; + var initial = name!; + var res = System.inputString(elm.title, elm.prompt, initial); + if(res !== void) + { + // name に res を代入する + inputTemp = res; + ("(" + name + ") = kag.inputTemp")!; + } + } + + //-------------------------------------------------- extraConductor 処理 -- + + function callExtraConductor(storage, label, onreturn) + { + // extraConductor を使ってサブルーチンを呼び出す + onExtraConductorReturn = onreturn; + inSleepBeforeExtraConductor = inSleep; // inSleep 保存 + storeMessageLayerSelProcessLock(); // メッセージレイヤの storeSelProcessLock を呼ぶ + conductor = extraConductor; // コンダクタを切り替える + (Dictionary.assign incontextof extraConductor.macros)(mainConductor.macros); + // マクロはコピー + usingExtraConductor = true; + if(storage == '') + { + // ストレージ指定がないので現在のストレージを読み込ませる + storage = mainConductor.curStorage; + } + + // 呼び出す + conductor.clearCallStack(); + processCall(storage, label); + } + + function returnExtraConductor(run) + { + // extraConductor のサブルーチンから戻る + // run が true の場合は 待機状態の復帰は行わない + + conductor.sleep(); // 停止 + conductor.interrupt(); + // interrupt は コンダクタのイベント内でコンダクタの実行を + // 停止させるためのメソッド + conductor = mainConductor; // コンダクタを切り替え + (Dictionary.assign incontextof mainConductor.macros)(extraConductor.macros); + // マクロはコピー + usingExtraConductor = false; + if(!run) + { restoreClickGlyphState(); // クリック待ち記号の復帰 + inSleep = inSleepBeforeExtraConductor; // inSleep 復帰 + notifyStable(); + } + restoreMessageLayerSelProcessLock(); // メッセージレイヤの restoreSelProcessLock を呼ぶ + setMenuAccessibleAll(); + cancelSkip(); + + if(onExtraConductorReturn !== void) onExtraConductorReturn(); + } + + //------------------------------------------------------- 右クリック処理 -- + + function setRightClickOptions(elm) + { + // 右クリックのオプションを設定する + rightClickEnabled = +elm.enabled if elm.enabled !== void; + if(elm.call !== void) + { + rightClickCall = +elm.call; + if(rightClickCall) rightClickJump = false; + } + if(elm.jump !== void) + { + rightClickJump = +elm.jump; + if(rightClickJump) rightClickCall = false; + } + rightClickTarget = elm.target if elm.target !== void; + rightClickStorage = elm.storage if elm.storage !== void; + if(elm.name !== void) + { + if(typeof this.rightClickMenuItem != "undefined") + { + rightClickName = elm.name; + if(rightClickName == "default") + rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName; + else + rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickName; + } + } + } + + function callRightClickSubRoutine() + { + // 右クリックサブルーチンを呼ぶ + if(typeof this.rightClickMenuItem != "undefined") + { + rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName; + } + + callExtraConductor(rightClickStorage, rightClickTarget, restoreFromRightClick); + + lockMessageLayerSelProcess(); // 選択肢ロック + } + + function restoreFromRightClick() + { + // 右クリックサブルーチンから抜けるときに呼ばれる + if(typeof this.rightClickMenuItem != "undefined") + { + if(rightClickName == "default") + rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickDefaultName; + else + rightClickMenuItem.caption = rightClickCurrentMenuName = rightClickName; + } + } + + function setConductorToMain() + { + // restore の時に呼ばれ、コンダクタを main に切り替える + if(usingExtraConductor) + { + extraConductor.sleep(); + extraConductor.interrupt(); + conductor= mainConductor; + usingExtraConductor = false; + } + } + + function jumpToRightClickTarget() + { + process(rightClickStorage, rightClickTarget); + } + + function onPrimaryRightClick() + { + // プライマリレイヤで右クリックされたときに呼ばれる + if(!callHook(rightClickHook)) + { + if(System.getKeyState(VK_LBUTTON)) + { + enterAutoMode(); + return; + } + if(!rightClickEnabled) return; + if(inStable) + { + if(rightClickJump) + jumpToRightClickTarget(); + else if(rightClickCall && conductor == mainConductor) + callRightClickSubRoutine(); + else + switchMessageLayerHiddenByUser(); + } + setMenuAccessibleAll(); + } + } + + //------------------------------------------------------- 前景レイヤ処理 -- + + function allocateCharacterLayers(num) + { + // 前景レイヤ数を num に設定する + if(fore.layers.count > num) + { + // レイヤが減る + for(var i = num; i num) + { + // レイヤが減る + for(var i = num; i= 0; i--) messages[i].clear(); + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].clear(); + if(resetcurrent) + { + currentNum = 0; + currentPage = 0; + current = fore.messages[0]; + currentWithBack = false; + } + } + + function lockMessageLayerSelProcess() + { + // すべてのメッセージレイヤに 選択とprocessを禁止させる + var messages; + messages = fore.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].setSelProcessLock(true); + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].setSelProcessLock(true); + } + + function unlockMessageLayerSelProcess() + { + // すべてのメッセージレイヤの選択を許可する + var messages; + messages = fore.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].setSelProcessLock(false); + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].setSelProcessLock(false); + } + + function setMessageLayerUserFont() + { + // すべてのメッセージレイヤの defaultAntialiased と + // userFace を設定する + var messages; + messages = fore.messages; + for(var i = messages.count-1; i >= 0; i--) + messages[i].defaultAntialiased = chDefaultAntialiased, + messages[i].userFace = chDefaultFace; + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) + messages[i].defaultAntialiased = chDefaultAntialiased, + messages[i].userFace = chDefaultFace; + } + + function storeMessageLayerSelProcessLock() + { + // すべてのメッセージレイヤの storeSelProcessLock を呼び出す + var messages; + messages = fore.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].storeSelProcessLock(); + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].storeSelProcessLock(); + } + + function restoreMessageLayerSelProcessLock() + { + // すべてのメッセージレイヤの restoreSelProcessLock を呼び出す + var messages; + messages = fore.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].restoreSelProcessLock(); + messages = back.messages; + for(var i = messages.count-1; i >= 0; i--) messages[i].restoreSelProcessLock(); + } + + function setMessageLayerHiddenState(b) + { + var layers; + layers = fore.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b); + layers = fore.layers; + for(var i = layers.count-1; i >= 0; i--) layers[i].setHiddenStateByUser(b); + + // プラグインを呼ぶ + forEachEventHook('onMessageHiddenStateChanged', + function(handler, f) { handler(f.hidden); } incontextof this, + %[hidden:b]); + } + + function hideMessageLayerByUser() + { + // メッセージレイヤを一時的に隠す + if(messageLayerHiding) return; + setMessageLayerHiddenState(true); + if(typeof this.rightClickMenuItem !== "undefined") + rightClickMenuItem.checked = true; + messageLayerHiding = true; + fore.base.cursor = cursorWaitingClick; + setMenuAccessibleAll(); + } + + function showMessageLayerByUser() + { + // 一時的に隠されていたメッセージレイヤを元に戻す + if(!messageLayerHiding) return; + setMessageLayerHiddenState(false); + if(typeof this.rightClickMenuItem !== "undefined") + rightClickMenuItem.checked = false; + messageLayerHiding = false; + conductor.trigger('message'); // 'message' を送る + if(clickWaiting) + fore.base.cursor = cursorWaitingClick; + else + fore.base.cursor = cursorDefault; + setMenuAccessibleAll(); + } + + function switchMessageLayerHiddenByUser() + { + // メッセージレイヤの非表示/表示を切り替える + if(messageLayerHiding) showMessageLayerByUser(); else hideMessageLayerByUser(); + } + + function hideMessageLayerByScenario(elm) + { + // シナリオからメッセージを一時的に隠す + hideMessageLayerByUser(); + conductor.wait(%[ // コンダクタを待ちに + message : function + { + // やることなし + } incontextof this + ]); + return -2; // break + } + + function selectFont() + { + // フォントを選択する + fore.base.font.face = chDefaultFace; + fore.base.font.height = -20; + var flags = fsfSameCharSet | fsfNoVertical | fsfTrueTypeOnly | fsfUseFontFace; + if(showFixedPitchOnlyInFontSelector) flags |= fsfFixedPitch; + if(fore.base.font.doUserSelect(flags, "フォントの選択", + "フォントを選択してください", "ABCDEFGHIあいうえお亜胃宇絵御")) + { + chDefaultFace = fore.base.font.face; + setMessageLayerUserFont(); + } + } + + function mapPrerenderedFont(storage) + { + // レンダリング済みフォントを現在の操作対象のレイヤに選択 + // されているフォントにマッピングする + current.lineLayer.font.mapPrerenderedFont(storage); + } + + //------------------------------------------------- レイヤを正しい順序に -- + + function reorderLayers() + { + // レイヤを正しい順序に並び替える + var index = 1000; + for(var i = 0; iレイヤオブジェクト -- + + function getLayerFromElm(elm, prefix = '') + { + // elm に指定されている page と layer 属性から、該当する + // オブジェクトを返す + // prefix には、属性名の前につけるプレフィクスを指定する + var base; + if(elm[prefix + 'page'] == 'back') base = back; else base = fore; + var layer = elm[prefix + 'layer']; + if(layer == 'base') return base.base; // 背景 + if(layer[0] == 'm') + { + // message? ( ? = 数値 ) + // ここではあまり厳密にエラーチェックはしない + if(layer == 'message') return base.messages[currentNum]; + return base.messages[+layer.substr(7)]; + } + return base.layers[+layer]; + } + + function getLayerPageFromElm(elm, backlay) + { + // getLayerFromElm と似ているが、page 属性まではみない。 + // backlay が true のときは裏、false の時は表のレイヤを返す。 + // elm.layer が void の時は背景レイヤを帰す + var base = backlay?back:fore; + var layer = elm.layer; + if(layer === void || layer == 'base') return base.base; // 背景 + if(layer[0] == 'm') + { + if(layer == 'message') return base.messages[currentNum]; + return base.messages[+layer.substr(7)]; + } + return base.layers[+layer]; + } + + function getMessageLayerPageFromElm(elm) + { + // elm から該当する表/裏画面のメッセージレイヤ配列を返す + if(elm.page == 'back') return 1; else return 0; + } + + function getMessageLayerNumberFromElm(elm) + { + // elm の layer 属性の示すメッセージレイヤ番号を返す + var layer = elm.layer; + if(layer === void || layer == 'message') return currentNum; + return +layer.substr(7); + } + + function getMessageLayerObjectFromElm(elm) + { + // elm の layer 属性の示すメッセージレイヤを返す + var page = elm.page; + var layer = elm.layer; + if(page === void && layer === void) return current; + var base; + if(page == 'back') base = back; else base = fore; + if(layer === void || layer == 'message') return base.messages[currentNum]; + return base.messages[+layer.substr(7)]; + } + + function getMessageLayerObjectFromPageAndNumber(page, num) + { + return (page?back:fore).messages[num]; + } + + //----------------------------------------------------- レイヤコピー関連 -- + + function backupLayer(elm, toback) + { + // レイヤの表←→裏間のコピーを行う + // toback = true の場合は表→裏、false の場合は裏→表 + if(elm.layer !== void) + { + // レイヤ指定がある + getLayerPageFromElm(elm, toback).assignComp(); // 対のレイヤの内容をコピー + } + else + { + // レイヤ指定が無いので全部のレイヤをコピー + var base = toback ? back:fore; + base.base.assignComp(); + var layers = base.layers, messages = base.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].assignComp(); + for(var i = messages.count-1; i >= 0; i--) messages[i].assignComp(); + + forEachEventHook('onCopyLayer', + function(handler, f) { handler(f.toback); } incontextof this, + %[toback:toback]); + } + } + + function copyLayer(elm) + { + // elm に従って同種のレイヤ間のコピーを行う + var src = getLayerFromElm(elm, 'src'); + var dest = getLayerFromElm(elm, 'dest'); + dest.assign(src); + } + + //--------------------------------------------------- アニメーション関連 -- + + function onAnimationStopped(name, segment) + { + // アニメーションが停止した + conductor.trigger('anim:' + name + ':' + segment); + } + + function waitAnimation(elm) + { + // アニメーションの停止をまつ + var layer = getLayerFromElm(elm); + var seg = +elm.seg; + if(!layer.canWaitAnimStop(seg)) return 0; // アニメーションの停止を待つ + conductor.wait(%[ + 'anim:' + layer.name + ':' + seg => function + { + } incontextof this + ]); + return -2; + } + + //--------------------------------------------------- トランジション関連 -- + + function onLayerTransitionCompleted(layer, dest, src) + { + // レイヤでトランジションが終了したときに呼ばれる + conductor.trigger('trans'); // 'trans' を送る + } + + function waitTransition(elm) + { + // トランジションを待つ + if(transCount == 0) return 0; // トランジションを待てない + if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ動作中 + stopAllTransitions(); + return 0; // トランジションを停止させてすぐに返る + } + conductor.wait(%[ + click : function + { + updateBeforeCh = 1; + stopAllTransitions(); // すべてのトランジションは停止 + } incontextof this, + trans : function + { + updateBeforeCh = 1; + } incontextof this + ]); + } + else + { + conductor.wait(%[ + trans : function + { + updateBeforeCh = 1; + } incontextof this + ]); + } + return -2; + } + + function stopAllTransitions() + { + // すべてのトランジションを停止させる + var layers, messages; + fore.base.stopTransition(); + layers = fore.layers, messages = fore.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].stopTransition(); + for(var i = messages.count-1; i >= 0; i--) messages[i].stopTransition(); + back.base.stopTransition(); + layers = back.layers, messages = back.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].stopTransition(); + for(var i = messages.count-1; i >= 0; i--) messages[i].stopTransition(); + transCount = 0; // 一応 + } + + function callExchangeInfo() + { + // すべての背景レイヤをのぞく表レイヤに対して + // exchangeInfo を呼ぶ + var layers = fore.layers, messages = fore.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].exchangeInfo(); + for(var i = messages.count-1; i >= 0; i--) messages[i].exchangeInfo(); + } + + function callAssignTransSrc() + { + // すべての背景レイヤをのぞく表レイヤに対して + // assignTransSrc を呼ぶ + var layers = fore.layers, messages = fore.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].assignTransSrc(); + for(var i = messages.count-1; i >= 0; i--) messages[i].assignTransSrc(); + forEachEventHook('onCopyLayer', + function(handler, f) { handler(f.toback); } incontextof this, + %[toback:false]); + } + + function exchangeForeBack() + { + // レイヤの裏と表を取り替える + var tmp = fore; + fore = back; + back = tmp; + current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し + forEachEventHook('onExchangeForeBack', + function(handler, f) { handler(); } incontextof this); + } + + function swapBaseLayer() + { + // 背景レイヤのみを取り替える + var tmp = fore.base; + fore.base = back.base; + back.base = tmp; + current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し + } + + function swapCharacterLayer(id) + { + // 前景レイヤの表と裏を取り替える + var fl = fore.layers, bl = back.layers; + var tmp = fl[id]; + fl[id] = bl[id]; + bl[id] = tmp; + } + + function swapMessageLayer(id) + { + // メッセージレイヤの表と裏を取り替える + var fl = fore.messages, bl = back.messages; + var tmp = fl[id]; + fl[id] = bl[id]; + bl[id] = tmp; + current = (currentPage?back:fore).messages[currentNum]; // current は設定し直し + } + + //--------------------------------------------------------- 自動移動関連 -- + + function onLayerMoveStop() + { + // レイヤの自動移動が終了した + conductor.trigger('move'); + } + + function waitMove(elm) + { + // 自動移動を待つ + if(moveCount == 0) return 0; // 自動移動を待てない + if((elm.canskip === void || +elm.canskip) && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ動作中 + stopAllMoves(); + return 0; // 自動移動を停止させてすぐに返る + } + conductor.wait(%[ + click : function + { + updateBeforeCh = 1; + stopAllMoves(); // すべてのトランジションは停止 + } incontextof this, + move : function + { + updateBeforeCh = 1; + } incontextof this + ]); + } + else + { + conductor.wait(%[ + move : function + { + updateBeforeCh = 1; + } incontextof this + ]); + } + return -2; + } + + function stopAllMoves() + { + // すべての自動移動を停止させる + var layers, messages; + fore.base.stopMove(); + back.base.stopMove(); + layers = fore.layers, messages = fore.messages; + for(var i = layers.count-1; i >= 0; i--) layers[i].stopMove(); + for(var i = messages.count-1; i >= 0; i--) messages[i].stopMove(); + moveCount = 0; // 一応 + } + + //------------------------------------------------ ディレイ/スキップ関連 -- + + function setDelay(elm) + { + // delay タグの処理 + var speed = elm.speed; + if(speed == 'nowait') + { + chSpeed = 0; + chUserMode = false; + } + else if(speed == 'user') + { + chUserMode = true; + setUserSpeed(); + } + else + { + chSpeed = +speed; + chUserMode = false; + } + if(!skipMode) actualChSpeed = chSpeed; + } + + function getCurrentRead() + { + // 現在のシナリオ部分が既読かどうかを判定する + return autoRecordPageShowing && currentRecordName != "" && + +sflags[currentRecordName] || !autoRecordPageShowing; + } + + function setUserSpeed() + { + // ユーザの選択した文字表示スピードを設定 + // この関数を読んだ時点ですでに userChSpeed には + // あたらしい値が設定されているとみなす。 + // あるいは、ラベルごとに、その区域が既読か未読かで + // 表示スピードを変える目的で呼ばれる + if(chUserMode) + { + if(getCurrentRead()) + chSpeed = userCh2ndSpeed==-1?userChSpeed:userCh2ndSpeed; // 既読 + else + chSpeed = userChSpeed; // 未読 + } + if(!skipMode) actualChSpeed = chSpeed; + } + + function skipToClick() + { + // クリック待ち記号までスキップ + skipMode = 1; + actualChSpeed = 0; + } + + function skipToPage() + { + // 改ページ待ち記号までスキップ + skipMode = 2; + actualChSpeed = 0; + } + + function skipToStop() + { + // 次の停止までスキップ + onPrimaryClick(); // クリックの動作をエミュレートする + skipMode = 3; + actualChSpeed = 0; + } + + function skipToStop2() + { + // 次の停止までスキップ(早送りモード) + onPrimaryClick(); + skipMode = 4; + actualChSpeed = 0; + } + + function cancelSkip() + { + // スキップ動作をキャンセル + skipMode = 0; + skipKeyRepressed = false; + actualChSpeed = chSpeed; + } + + function enterNoWait() + { + // nowait タグの処理 + beforeNoWaitActualChSpeed = actualChSpeed; + beforeNoWaitChUserMode = chUserMode; + actualChSpeed = 0; + } + + function leaveNoWait() + { + // endnowait タグの処理 + actualChSpeed = beforeNoWaitActualChSpeed; + chUserMode = beforeNoWaitChUserMode; + } + + function setAutoWait(elm) + { + // 自動ウェイトを設定する + autoWCEnabled = +elm.enabled if elm.enabled !== void; + autoWCChars = elm.ch if elm.ch !== void; + autoWCWaits = [].split(",", elm.time) if elm.time !== void; + } + + function cancelAutoMode() + { + // 自動読みすすみモードのキャンセル + autoMode = false; + if(typeof this.autoModeMenuItem !== "undefined") + autoModeMenuItem.checked = false; + } + + function enterAutoMode() + { + // 自動読みすすみモードに入る + if(typeof this.autoModeMenuItem !== "undefined") + autoModeMenuItem.checked = true; + if(inStable) + onPrimaryClick(); + autoMode = true; + } + + //--------------------------------------------------------- ウェイト関連 -- + + function resetWait() + { + // 時間原点のリセット + timeOrigin = System.getTickCount(); + } + + function waitTime(waittime, canskip) + { + // waittime 分待つ + if(waittime == 0) return 0; + if(canskip) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + return 0; // スキップ中の場合はなにもせずに返る + } + conductor.waitWithTimeOut(%[ + click : function + { + // やることなし + } incontextof this, + + timeout : function + { + // やることなし + } incontextof this + ], waittime); + } + else + { + // スキップできない場合 + conductor.waitWithTimeOut(%[ + timeout : function + { + // やることなし + } incontextof this + ], waittime); + } + return -2; // break + + } + + function doWait(elm) + { + // wait タグの処理 + var waittime; + if(elm.mode == 'until') + { + // until モード + waittime = timeOrigin + +elm.time - System.getTickCount(); + if(waittime < 0) { lastWaitTime = 0; return 0; } // すでに時間が経過している + lastWaitTime = waittime; + if(waittime < 6) return 0; // あまりに待ち時間が短いので待たない + } + else + { + waittime = +elm.time; + } + return waitTime(waittime, (elm.canskip === void || +elm.canskip) && clickSkipEnabled); + } + + function doWaitCh(elm) + { + // +elm.time のカウント分、待つ + var t = elm.time; + return waitTime(actualChSpeed * (t === void ? 1 : +t), + (elm.canskip === void || +elm.canskip) && clickSkipEnabled); + } + + //------------------------------------------------------------ quake関連 -- + + function doQuake(elm) + { + // elm に従って quake を開始 + if(elm.time !== void) + { + if(defaultQuakeTimeInChUnit) + { + if(elm.timemode == 'ms') + quakeEndTick = System.getTickCount() + +elm.time; + else + quakeEndTick = System.getTickCount() + +elm.time * chSpeed; + } + else + { + if(elm.timemode == 'delay') + quakeEndTick = System.getTickCount() + +elm.time * chSpeed; + else + quakeEndTick = System.getTickCount() + +elm.time; + } + } + else + { + quakeEndTick = -1; + } + + if(elm.hmax !== void) quakeHorzMax = +elm.hmax; else quakeHorzMax = 10; + if(elm.vmax !== void) quakeVertMax = +elm.vmax; else quakeVertMax = 10; + + quakeTimer.enabled = true; + quaking = true; + } + + function restoreQuake() + { + // restore から呼ばれ、栞を保存したときに揺れていた場合は揺らす + if(quaking && quakeEndTick == -1) + quakeTimer.enabled =true; + } + + function stopQuake() + { + // 揺れを停止 + setLayerPos(0, 0); + quakeTimer.enabled = false; + quaking = false; + conductor.trigger('quake'); + } + + function onQuakeTimerInterval() + { + // quakeTimer により呼ばれる + if(quakeEndTick != -1 && System.getTickCount() > quakeEndTick) { stopQuake(); return; } + if(historyShowing) + { + // メッセージ履歴レイヤ表示中はさすがに揺れていられない + setLayerPos(0, 0); + return; + } + var x, y; + if(quakeHorzMax == quakeVertMax) + { + // だいたい同じ + x = int(Math.random() * quakeHorzMax - quakeHorzMax); + y = int(Math.random() * quakeVertMax - quakeVertMax); + } + else if(quakeHorzMax < quakeVertMax) + { + // 縦揺れ + x = int(Math.random() * quakeHorzMax - quakeHorzMax); + y = int((quakePhase ? Math.random() : -Math.random()) * quakeVertMax); + } + else + { + // 横揺れ + x = int((quakePhase ? Math.random() : -Math.random()) * quakeHorzMax); + y = int(Math.random() * quakeVertMax - quakeVertMax); + } + quakePhase = !quakePhase; + setLayerPos(x, y); + } + + function waitQuake(elm) + { + // 揺れが終了するまでまつ + if(!quaking || quakeEndTick == -1) return 0; // 揺れていなければ待たない + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + stopQuake(); + return 0; // スキップ中の場合は揺れを停止させて返る + } + conductor.wait(%[ + click : function + { + stopQuake(); // 揺れは停止する + } incontextof this, + + quake : function + { + // やることなし + } incontextof this + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + quake : function + { + // やることなし + } incontextof this + ]); + } + return -2; + } + + //------------------------------------------------------------- クリック -- + + function onPrimaryClick() + { + // プライマリレイヤで「クリックの動作」がなにもフィルタリングされなかった + // とき、プライマリレイヤから呼ばれる。 + clickCount ++; + if(!callHook(leftClickHook)) + { + if(messageLayerHiding) + { + showMessageLayerByUser(); // メッセージレイヤを表示する + } + else + { + var st = conductor.status; + var runst = conductor.mRun; + var stopst = conductor.mStop; + + if(st != stopst && autoMode) + { + // 自動読みすすみの場合 + cancelAutoMode(); + } + else if(st != stopst && canCancelSkipByClick && skipMode && skipMode != 4) + { + // クリックによるスキップの解除が可能 + cancelSkip(); + } + else + { + // この時点でフィルタリングされないメッセージは待ち状態のクリアなので + // conductor に 'click' を送り解除を伝える。 + + if(!conductor.trigger('click')) // 待ち状態でない場合は単に無視される + { + // ハンドラが見つからないなど、処理されなかった場合 + if(st == runst && clickSkipEnabled && skipMode == 0) + { + // クリックによるスキップが可能 + skipToClick(); + } + } + } + } + } + } + + function onPrimaryClickByKey() + { + // キーが押されたときプライマリレイヤをクリックしたのと + // 同じ動作をするが、さらに一時的にマウスカーソルを隠す + onPrimaryClick(); + hideMouseCursor(); + } + + function waitClick(elm) + { + // クリックを待つ + conductor.wait(%[ + click : function + { + } incontextof this]); + return -2; + } + + function onMouseDown(x, y) + { + lastMouseDownX = x; + lastMouseDownY = y; + super.onMouseDown(...); + } + + //------------------------------------------------------- キーボード操作 -- + + function processKeys(key, shift) + { + if(checkProceedingKey(key, shift)) return; + + if(key == #'F') + { + // 次の選択肢/未読まで進む + skipToNextStopByKey(); + return; + } + + if(key == #'B') + { + // 前に戻る + goBackByKey(); + return; + } + + if(key == #'A') + { + // 自動的に読み勧める + switchAutoModeByKey(); + return; + } + + if(freeSaveDataMode) + { + if(key == #'S') + { + // 栞をはさむ + if(typeof this.storeMenu != "undefined" && storeMenu.enabled) + storeMenu.click(); + return; + } + + if(key == #'L') + { + // 栞をたどる + if(typeof this.restoreMenu != "undefined" && restoreMenu.enabled) + restoreMenu.click(); + return; + } + } + + if(key == #'R' || (key == VK_UP && (shift & ssShift))) + { + // メッセージ履歴を表示 + showHistoryByKey(); + return; + } + + if(key == VK_ESCAPE) + { + // メッセージを消す + if(typeof this.rightClickMenuItem != "undefined" && + rightClickMenuItem.enabled) + { + rightClickMenuItem.click(); // クリックをエミュレート + return; + } + } + } + + function preProcessKeys(key, shift) + { + return callHook(keyDownHook, key, shift); + } + + function internalOnKeyDown(key, shift) + { + if(!preProcessKeys(key, shift)) processKeys(key, shift); + } + + function checkProceedingKey(key, shift) + { + // key が読みすすみのキー ( スペースキーかReturnキー ) の場合は + // キーを処理し、true を返す。そうでなければ false を返す + if(key == VK_RETURN || key == VK_SPACE) + { + if((shift & ssRepeat) && clickSkipEnabled && + conductor.status == conductor.mRun) + { + // キーリピート + if(skipMode != 4 && skipKeyRepressed) + skipToStop2(); // まだskipMode 4に入っていない場合は早送りモードに入る + // skipKeyRepressed をチェックするのは + // 連続してキーリピートが発生しているときに + // cancelSkip 後にスキップに突入するのを防ぐため + } + else + { + skipKeyRepressed = true; + onPrimaryClickByKey(); + } + return true; + } + + return false; + } + + function skipKeyPressing() + { + // VK_RETURN あるいは VK_SPACE が押されているかどうか + return System.getKeyState(VK_RETURN) || System.getKeyState(VK_SPACE); + } + + function goBackByKey() + { + if(typeof this.goBackMenuItem != "undefined" && + goBackMenuItem.enabled) + goBackMenuItem.click(); // クリックをエミュレート + } + + function skipToNextStopByKey() + { + if(typeof this.skipToNextStopMenuItem != "undefined" && + skipToNextStopMenuItem.enabled) + skipToNextStopMenuItem.click(); // クリックをエミュレート + } + + function showHistoryByKey() + { + if(typeof this.showHistoryMenuItem != "undefined" && + showHistoryMenuItem.enabled) + showHistoryMenuItem.click(); // クリックをエミュレート + } + + function switchAutoModeByKey() + { + if(typeof this.autoModeMenuItem != "undefined" && + autoModeMenuItem.enabled) + autoModeMenuItem.click(); // クリックをエミュレート + } + + function onKeyDown(key, shift) + { + if(focusedLayer === null) + internalOnKeyDown(key, shift); + super.onKeyDown(...); + } + + function onMouseWheel(shift, delta, x, y) + { + // ホイールが回転した + super.onMouseWheel(...); + if(!historyLayer.visible) + { + if(delta > 0) + showHistoryByKey(); // メッセージ履歴を表示 + else if(System.getTickCount() - lastHistoryHiddenTick > 150) + onPrimaryClick(); // クリックをエミュレート + // ↑ tick を比較しているのは、メッセージ履歴を隠す操作とホイールを + // 手前に回す操作が連続した場合に勝手に読み進むのをある程度防ぐ仕掛け + } + else + { + // メッセージ履歴にイベントを垂れ流す + historyLayer.windowMouseWheel(shift, delta, x, y); + } + } + + //------------------------------------------------- クリック待ち記号処理 -- + + function hideClickGlyphs() + { + // クリック待ち記号を非表示に + lineBreak.visible = false; + pageBreak.visible = false; + if(conductor == mainConductor) + { + // クリック待ち記号の状態を記録 + lastClickGlyphVisible = false; + } + } + + function storeClickGlyphState(which) + { + // クリック待ち記号の情報を一時的に待避 + // このデータは右クリックサブルーチンやextraConductorサブルーチンから戻るときに参照する + if(conductor == mainConductor) + { + lastClickGlyphVisible = true; + lastClickGlyphMessagePage = currentPage; + lastClickGlyphMessageNum = currentNum; + lastClickGlyphWhich = which; + } + } + + function restoreClickGlyphState() + { + // lastClickGlyph *** に一時的に待避したクリック待ち記号の情報 + // に基づいてクリック待ち記号を設定する + if(lastClickGlyphVisible) + { + var layer = getMessageLayerObjectFromPageAndNumber + (lastClickGlyphMessagePage, lastClickGlyphMessageNum); + if(layer !== void) + { + switch(lastClickGlyphWhich) + { + case 'line': + layer.showLineBreakGlyph(lineBreak); + break; + case 'page': + layer.showPageBreakGlyph(pageBreak); + break; + } + } + } + } + + function canIgnoreL() + { + // L タグを無視できるかどうか + return chNonStopToPageBreak || (getCurrentRead() && ch2ndNonStopToPageBreak); + } + + function showLineBreak(elm) + { + // 現在のメッセージレイヤに行待ち記号を表示する + stablePosibility = true; + if(canIgnoreL()) + { + // l タグの無視 + if(elm.canskip === void || !+elm.canskip) + return (skipMode==3 || skipMode==4) ? 0 : -4; + } + if(autoMode) + { + // 自動読みすすみの場合 + return autoModeLineWait; + } + if(skipMode == 1) cancelSkip(); + if(skipMode == 4 && !skipKeyPressing()) cancelSkip(); + if(skipMode == 4) return -4; + if(skipMode) return 0; // スキップ中 + + current.showLineBreakGlyph(lineBreak); + storeClickGlyphState("line"); + + if(!current.nodeVisible) + { + dm("警告 : 非表示になっている" + + (currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum + + "で行クリック待ちになりました"); + } + + // conductor を 'click' まで待ち状態に + conductor.wait(%[ + click : function + { + clickWaiting = false; + fore.base.cursor = cursorDefault; + notifyRun(); + } incontextof this + ]); + clickWaiting = true; + fore.base.cursor = cursorWaitingClick; + notifyStable(); + return -2; + } + + function showPageBreak(elm) + { + // 現在のメッセージレイヤにページ待ち記号を表示する + stablePosibility = true; + if(skipMode == 1 || skipMode == 2) cancelSkip(); + if(skipMode == 4 && !skipKeyPressing()) cancelSkip(); + if(skipMode) return -4; // いったんイベントを処理 + if(autoMode) + { + // 自動読みすすみの場合 + return autoModePageWait; + } + + current.showPageBreakGlyph(pageBreak); + storeClickGlyphState("page"); + + if(!current.nodeVisible) + { + dm("警告 : 非表示になっている" + + (currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum + + "でページクリック待ちになりました"); + } + + // conductor を 'click' まで待ち状態に + conductor.wait(%[ + click : function + { + clickWaiting = false; + fore.base.cursor = cursorDefault; + notifyRun(); + } incontextof this + ]); + clickWaiting = true; + fore.base.cursor = cursorWaitingClick; + notifyStable(); + return -2; + } + + function showPageBreakAndClear() + { + // メッセージレイヤが最終行まで達して自動改ページがされるときに + // 呼ばれる。現在のメッセージレイヤにページ待ち記号を表示し、 + // 実行再開時には MessageLayer.clear2 を呼ぶ + stablePosibility = true; + if(skipMode == 1 || skipMode == 2) cancelSkip(); + if(skipMode == 4 && !skipKeyPressing()) cancelSkip(); + var lasttagname = conductor.lastTagName; + if(!autoMode && ((!canIgnoreL() && lasttagname == 'l') || lasttagname == 'p')) + { current.clear2(); return -5; }// いったんイベントを処理(タグは後回し) + if(skipMode) { current.clear2(); return -5; }// いったんイベントを処理(タグは後回し) + + if(!current.nodeVisible) + { + dm("警告 : 非表示になっている" + + (currentPage ? "裏" : "表") + "メッセージレイヤ" + currentNum + + "で自動改ページクリック待ちになりました"); + } + + if(autoMode) + { + conductor.waitWithTimeOut(%[ // タイムアウト付きウェイト + click : function + { + current.clear2(); // clear2 を呼ぶ + cancelAutoMode(); + } incontextof this, + timeout : function + { + current.clear2(); // clear2 を呼ぶ + } incontextof this + ], autoModePageWait <= 0 ? 1 : autoModePageWait); + return -3; + } + else + { + current.showPageBreakGlyph(pageBreak); + storeClickGlyphState("page"); + + // conductor を 'click' まで待ち状態に + conductor.wait(%[ + click : function + { + clickWaiting = false; + fore.base.cursor = cursorDefault; + current.clear2(); // clear2 を呼ぶ + notifyRun(); + } incontextof this + ]); + clickWaiting = true; + fore.base.cursor = cursorWaitingClick; + notifyStable(); + return -3; + } + } + + //------------------------------------------------------------- BGM 処理 -- + + function onBGMFadeCompleted() + { + // BGM のフェードが完了した + conductor.trigger('bgmfade'); + } + + function onBGMStop() + { + // BGM が停止した + conductor.trigger('bgmstop'); + } + + function waitBGMFade(elm) + { + // BGM のフェード終了を待つ + if(!bgm.inFading) return 0; // フェード中でなければ待たない + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + bgm.stopFade(); + return 0; // スキップ中の場合はフェードを停止させて返る + } + conductor.wait(%[ + click : function + { + bgm.stopFade(); // フェーディングは停止する + } incontextof this, + + bgmfade : function + { + // やることなし + } incontextof this + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + bgmfade : function + { + // やることなし + } incontextof this + ]); + } + return -2; + } + + function waitBGMStop(elm) + { + // BGM の再生終了を待つ + if(!bgm.canWaitStop) return 0; // BGM 再生終了を待てなければそのまま戻る + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + bgm.stop(); + return 0; // スキップ中の場合は再生を停止させて返る + } + conductor.wait(%[ + click : function + { + bgm.stop(); // 再生を終了する + } incontextof this, + + bgmstop : function + { + // やることなし + } incontextof this + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + bgmstop : function + { + // やることなし + } incontextof this + ]); + } + return -2; + } + + //----------------------------------------------------------- 効果音処理 -- + + function onSESoundBufferFadeCompleted(id) + { + // 効果音のフェードが終了した + conductor.trigger('sefade' + id); + } + + function onSESoundBufferStop(id) + { + // 効果音の再生が終了した + conductor.trigger('sestop' + id); + } + + function waitSEFade(elm) + { + var id = +elm.buf; + var buf = se[id]; + if(!buf.inFading) return 0; // フェード中でなければそのまま戻る + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + buf.stopFade(); + return 0; // スキップ中の場合はフェードを停止させて返る + } + conductor.wait(%[ + click : function (id) + { + se[id].stopFade(); // フェードを終了する + } incontextof this, + + click_arg : id, // ハンドラへの引数 + + 'sefade'+id => + function (id) + { + // やることなし + } incontextof this, + + 'sefade'+id+'_arg' => id // ハンドラへの引数 + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + 'sefade'+id => + function (id) + { + // やることなし + } incontextof this, + + 'sefade'+id+'_arg' => id // ハンドラへの引数 + ]); + } + return -2; + } + + function waitSEStop(elm) + { + var id = +elm.buf; + var buf = se[id]; + if(!buf.canWaitStop()) return 0; // 終了を待てなければそのまま返る + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + buf.stop(); + return 0; // スキップ中の場合は再生を停止させて返る + } + conductor.wait(%[ + click : function (id) + { + se[id].stop(); // 再生を終了する + } incontextof this, + + 'click_arg' => id, // ハンドラへの引数 + + 'sestop'+id => + function (id) + { + // やることなし + } incontextof this, + + 'sestop'+id+'_arg' => id // ハンドラへの引数 + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + 'sestop'+id => + function (id) + { + // やることなし + } incontextof this, + + 'sestop'+id+'_arg' => id // ハンドラへの引数 + ]); + } + return -2; + } + + //--------------------------------------------------------- ムービー関連 -- + + function onMovieStop() + { + // ムービーの再生が終了した + conductor.trigger('moviestop'); + } + + function waitMovieStop(elm) + { + // ムービーの再生終了を待つ + if(!movie.canWaitStop) return 0; // ムービー再生終了を待てなければそのまま戻る + if(elm.canskip !== void && +elm.canskip && clickSkipEnabled) + { + // スキップできる場合 + if(skipMode) + { + // スキップ中の場合 + movie.stop(); + return 0; // スキップ中の場合は再生を停止させて返る + } + conductor.wait(%[ + click : function + { + movie.stop(); // 再生を終了する + } incontextof this, + + moviestop : function + { + // やることなし + } incontextof this + ]); + } + else + { + // スキップできない場合 + conductor.wait(%[ + moviestop : function + { + // やることなし + } incontextof this + ]); + } + return -2; + } + + //------------------------------------------------------- タグハンドラ群 -- + + function getHandlers() + { + return %[ // 辞書配列オブジェクト + + /* + タグハンドラ群は、名前とそれに対応する関数のペアを列挙するもので、 + 関数名 : function(elm) + { + // 関数の中身 + } incontextof this, + の書式を用いる。ただし、関数名が予約語の場合は、「関数名 : 」ではなく + 「"関数名" => 」を用いる。 + incontextof this は、関数が正しく このクラスの + オブジェクトのコンテキスト上で動くようにするために必要。 + */ + + //--------------------------------------- タグハンドラ群(メッセージ操作) -- + + ch : function(elm) + { + // 文字表示 + var acs = actualChSpeed; + if(updateBeforeCh) + { + if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; } + } + var text = elm.text; + if(currentWithBack) current.comp.processCh(text); + if(current.processCh(text)) + { + return showPageBreakAndClear(); + } + if(historyWriteEnabled) historyLayer.store(text); + if(autoWCEnabled) + { + // 自動ウェイト + var ind; + if((ind = autoWCChars.indexOf(text)) != -1) + { + return int(acs * autoWCWaits[ind]); + } + } + return acs; + } incontextof this, + +//insani + wrap : function(elm) + { + var text = elm.text; + if(currentWithBack) current.comp.processWrap(text); + if(historyWriteEnabled) historyLayer.processWrap(text); + if(current.processWrap(text)) + { + return showPageBreakAndClear(); + } + return 0; + } incontextof this, +//insani + + graph : function(elm) + { + // グラフィックを文字として表示 + var acs = actualChSpeed; + if(updateBeforeCh) + { + if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; } + } + if(currentWithBack) current.comp.processGraph(elm); + if(current.processGraph(elm)) + { + return showPageBreakAndClear(); + } + if(historyWriteEnabled && elm.alt !== void) historyLayer.store(elm.alt); + return acs; + } incontextof this, + + hch : function(elm) + { + // 縦中横 + var acs = actualChSpeed; + if(updateBeforeCh) + { + if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; } + } + var text = elm.text; + var expand = elm.expand !== void && +elm.expand; + if(currentWithBack) current.comp.putHorizonCh(text, expand); + if(current.putHorizonCh(text, expand)) + { + return showPageBreakAndClear(); + } + if(historyWriteEnabled) historyLayer.store(text); + return acs; + } incontextof this, + + r : function(elm) + { + // 改行 + if(historyWriteEnabled) historyLayer.reline(); + if(currentWithBack) current.comp.processReturn(); + if(current.processReturn()) + { + var ret = showPageBreakAndClear(); + // 改行はpendingしない + if(ret == -5) + ret = -4; + else if(ret == -3) + ret = -2; + return ret; + } + return actualChSpeed; + } incontextof this, + + ruby : function(elm) + { + // 次の文字に対するルビ設定 + if(currentWithBack) current.comp.setRuby(elm.text); + current.setRuby(elm.text); + return 0; + } incontextof this, + + font : function(elm) + { + // フォント設定 + if(currentWithBack) current.comp.setFont(elm); + current.setFont(elm); + return 0; + } incontextof this, + + deffont : function(elm) + { + // デフォルトのフォント設定 + if(currentWithBack) current.comp.setDefaultFont(elm); + current.setDefaultFont(elm); + return 0; + } incontextof this, + + resetfont : function(elm) + { + // フォントのリセット + if(currentWithBack) current.comp.resetFont(); + current.resetFont(); + return 0; + } incontextof this, + + style : function(elm) + { + // スタイル設定 + if(currentWithBack) current.comp.setStyle(elm); + current.setStyle(elm); + return 0; + } incontextof this, + + defstyle : function(elm) + { + // デフォルトのスタイル設定 + if(currentWithBack) current.comp.setDefaultStyle(elm); + current.setDefaultStyle(elm); + return 0; + } incontextof this, + + resetstyle : function(elm) + { + // スタイルのリセット + if(currentWithBack) current.comp.resetStyle(); + current.resetStyle(); + return 0; + } incontextof this, + + link : function(elm) + { + // ハイパーリンクの開始 + if(currentWithBack) current.comp.beginHyperLink(elm); + current.beginHyperLink(elm); + return 0; + } incontextof this, + + endlink : function(elm) + { + // ハイパーリンクの終了 + if(currentWithBack) current.comp.endHyperLink(elm); + current.endHyperLink(elm); + return 0; + } incontextof this, + + button : function(elm) + { + // グラフィカルボタン + if(currentWithBack) current.comp.addButton(elm); + current.addButton(elm); + return 0; + } incontextof this, + + edit : function(elm) + { + // 単一行編集 + if(currentWithBack) current.comp.addEdit(elm); + current.addEdit(elm); + return 0; + } incontextof this, + + checkbox : function(elm) + { + // 単一行編集 + if(currentWithBack) current.comp.addCheckBox(elm); + current.addCheckBox(elm); + return 0; + } incontextof this, + + commit : function(elm) + { + // フォーム要素のコミット + current.commit(); + return 0; + } incontextof this, + + l : function(elm) + { + // 行クリック待ち + return showLineBreak(elm); + } incontextof this, + + p : function(elm) + { + // ページクリック待ち + if(historyWriteEnabled) historyLayer.reline(); + return showPageBreak(elm); + } incontextof this, + + current : function(elm) + { + // 操作対象のメッセージレイヤの指定 + setCurrentMessageLayer(elm); + return 0; + } incontextof this, + + position : function(elm) + { + // メッセージレイヤの位置、属性を設定 + getMessageLayerObjectFromElm(elm).setPosition(elm); + return 0; + } incontextof this, + + ct : function(elm) + { + // メッセージレイヤのリセット(すべてのメッセージレイヤのクリアと + // current のリセット) + if(historyWriteEnabled) historyLayer.repage(); + clearMessageLayers(true); + return 0; + } incontextof this, + + cm : function(elm) + { + // メッセージレイヤのリセットを行うが、ct のように + // current のリセットは行わないもの + if(historyWriteEnabled) historyLayer.repage(); + clearMessageLayers(false); + return 0; + } incontextof this, + + er : function(elm) + { + // 現在のメッセージレイヤのクリア + if(historyWriteEnabled) historyLayer.repage(); + if(currentWithBack) current.comp.clear(); + current.clear(); + return 0; + } incontextof this, + + indent : function(elm) + { + // インデントの設定 + if(currentWithBack) current.comp.setIndent(); + current.setIndent(); + if(historyWriteEnabled) historyLayer.beginIndent(); + return 0; + } incontextof this, + + endindent : function(elm) + { + // インデントの解除 + if(currentWithBack) current.comp.resetIndent(); + current.resetIndent(); + if(historyWriteEnabled) historyLayer.endIndent(); + return 0; + } incontextof this, + + delay : function(elm) + { + // 文字表示速度の指定 + setDelay(elm); + return 0; + } incontextof this, + + nowait : function(elm) + { + // 一時的にノーウェイトで実行 + enterNoWait(); + return 0; + } incontextof this, + + endnowait : function(elm) + { + // nowait の解除 + leaveNoWait(); + return 0; + } incontextof this, + + locate : function(elm) + { + // 文字表示位置を指定 + if(currentWithBack) current.comp.locate(elm.x, elm.y); + current.locate(elm.x, elm.y); + return 0; + } incontextof this, + + glyph : function(elm) + { + // クリック待ち記号を指定 + current.setGlyph(elm); + return 0; + } incontextof this, + + locklink : function(elm) + { + // リンクのロック + lockMessageLayerSelProcess(); + return 0; + } incontextof this, + + unlocklink : function(elm) + { + // リンクのアンロック + unlockMessageLayerSelProcess(); + return 0; + } incontextof this, + + //----------------------------------------- タグハンドラ群(システム操作) -- + + loadplugin : function(elm) + { + // プラグインの読み込み + Plugins.link(elm.module); + dm("プラグインを読み込みました : " + elm.module); + return 0; + } incontextof this, + + title : function(elm) + { + // タイトルの設定 + setTitle(elm.name); + return 0; + } incontextof this, + + s : function(elm) + { + // 実行停止 + stablePosibility = true; + cancelSkip(); + if(!usingExtraConductor) incRecordLabel(true); + inSleep = true; + if(recordHistoryOfStore == 2) // 2 : 選択肢 ( @s タグ ) ごと + setToRecordHistory(); + notifyStable(); + return -1; + } incontextof this, + + clickskip : function(elm) + { + // クリックスキップの設定 + clickSkipEnabled = +elm.enabled; + return 0; + } incontextof this, + + nextskip : function(elm) + { + // 次の選択肢(/未読)まで進むの設定 + nextSkipEnabled = +elm.enabled; + return 0; + } incontextof this, + + cancelskip : function(elm) + { + // スキップの解除 + cancelSkip(); + return 0; + } incontextof this, + + cancelautomode : function(elm) + { + // 「自動的に読み進む」の解除 + cancelAutoMode(); + return 0; + } incontextof this, + + resetwait : function(elm) + { + // 時間原点の設定 + resetWait(); + return 0; + } incontextof this, + + wait : function(elm) + { + // ウェイト + return doWait(elm); + } incontextof this, + + wc : function(elm) + { + // 指定文字分のウェイト + return doWaitCh(elm); + } incontextof this, + + waitclick : function(elm) + { + // クリックを待つ + return waitClick(elm); + } incontextof this, + + rclick : function(elm) + { + // 右クリックの動作設定 + setRightClickOptions(elm); + return 0; + } incontextof this, + + history : function(elm) + { + // メッセージ履歴レイヤの設定 + setHistoryOptions(elm); + return 0; + } incontextof this, + + showhistory : function(elm) + { + // メッセージ履歴レイヤの表示 + return showHistoryByScenario(elm); + } incontextof this, + + hr : function(elm) + { + // メッセージ履歴レイヤに改行を出力 + if(historyWriteEnabled) + { + if(elm.repage !== void && +elm.repage) + historyLayer.repage(); + else + historyLayer.reline(); + } + return 0; + } incontextof this, + + hact : function(elm) + { + // メッセージ履歴にアクションを設定 + if(historyWriteEnabled) + historyLayer.setNewAction(elm.exp); + return 0; + } incontextof this, + + endhact : function(elm) + { + // メッセージ履歴のアクションをクリア + if(historyWriteEnabled) + historyLayer.clearAction(); + return 0; + } incontextof this, + + hidemessage : function(elm) + { + // メッセージを一時的に隠す + return hideMessageLayerByScenario(elm); + } incontextof this, + + quake : function(elm) + { + // 揺れ + doQuake(elm); + return 0; + } incontextof this, + + stopquake : function(elm) + { + // 揺れの停止 + stopQuake(); + return 0; + } incontextof this, + + wq : function(elm) + { + // 揺れの停止を待つ + return waitQuake(elm); + } incontextof this, + + autowc : function(elm) + { + // 自動ウェイト + setAutoWait(elm); + return 0; + } incontextof this, + + cursor : function(elm) + { + // マウスカーソルの変更 + setCursor(elm); + return 0; + } incontextof this, + + close : function(elm) + { + // ウィンドウを閉じる + close(); + return 0; + } incontextof this, + + copybookmark : function(elm) + { + // 栞をコピー + copyBookMark(+elm.from, +elm.to); + return 0; + } incontextof this, + + erasebookmark : function(elm) + { + // 栞を削除 + eraseBookMark(+elm.place); + return 0; + } incontextof this, + + disablestore : function(elm) + { + // 栞を一時的に使用不可に + tempDisableStore(elm); + return 0; + } incontextof this, + + store : function(elm) + { + // 栞の使用不可・使用可を設定する + setStoreEnabled(+elm.enabled); + return 0; + } incontextof this, + + load : function(elm) + { + // 栞の読み込み + if(elm.ask !== void && +elm.ask) + loadBookMarkWithAsk(+elm.place); + else + loadBookMark(+elm.place); + return -4; + } incontextof this, + + save : function(elm) + { + // 栞の読み込み + if(elm.ask !== void && +elm.ask) + saveBookMarkWithAsk(+elm.place); + else + saveBookMark(+elm.place); + return -4; + } incontextof this, + + startanchor : function(elm) + { + // 「最初に戻る」の使用不可・使用可を設定する + setStartAnchorEnabled(elm.enabled === void || +elm.enabled); + return 0; + } incontextof this, + + gotostart : function(elm) + { + // 「最初に戻る」 + if(elm.ask !== void && +elm.ask) + goToStartWithAsk(); + else + goToStart(); + return -4; + } incontextof this, + + goback : function(elm) + { + // 通過記録を戻る + if(elm.ask !== void && +elm.ask) + goBackHistory(true); + else + goBackHistory(false); + return -4; + } incontextof this, + + record : function(elm) + { + // 通過記録をする + setToRecordHistory(); + return 0; + } incontextof this, + + tempsave : function(elm) + { + // 状態のメモリへの保存 + tempSave(+elm.place); + return 0; + } incontextof this, + + tempload : function(elm) + { + // 状態のメモリへの保存 + tempLoad(+elm.place, elm); + //elm.se === void || +elm.se, elm.bgm === void || +elm.bgm, + //elm.backlay !== void && +elm.backlay); + return 0; + } incontextof this, + + mappfont : function(elm) + { + // レンダリング済みフォントを現在のフォントにマッピング + mapPrerenderedFont(elm.storage); + return 0; + } incontextof this, + + locksnapshot : function(elm) + { + // 画面のスナップショットをロックする + lockSnapshot(); + return 0; + } incontextof this, + + unlocksnapshot : function(elm) + { + // 画面のスナップショットのロックを解除する + unlockSnapshot(); + return 0; + } incontextof this, + + //------------------------------------------- タグハンドラ群(レイヤ操作) -- + + image : function(elm) + { + // 画像読み込み + updateBeforeCh = 1; + var start = System.getTickCount(); + getLayerFromElm(elm).loadImages(elm); + dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました"); + return 0; + } incontextof this, + + img : function(elm) + { + // 画像読み込み(imageとおなじ) + updateBeforeCh = 1; + var start = System.getTickCount(); + getLayerFromElm(elm).loadImages(elm); + dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました"); + return 0; + } incontextof this, + + pimage : function(elm) + { + // 部分追加画像読み込み + getLayerFromElm(elm).loadPartialImage(elm); + return 0; + } incontextof this, + + freeimage : function(elm) + { + // 画像のクリア + updateBeforeCh = 1; + getLayerFromElm(elm).freeImage(elm); + return 0; + } incontextof this, + + animstart : function(elm) + { + // アニメーションの開始 + updateBeforeCh = 1; + getLayerFromElm(elm).startAnim(elm); + return 0; + } incontextof this, + + animstop : function(elm) + { + // アニメーションの停止 + updateBeforeCh = 1; + getLayerFromElm(elm).stopAnim(+elm.seg); + return 0; + } incontextof this, + + wa : function(elm) + { + // アニメーションの停止待ち + return waitAnimation(elm); + } incontextof this, + + mapimage : function(elm) + { + // クリッカブルマップの領域画像を読み込む + getLayerFromElm(elm).loadProvinceImage(elm.storage); + return 0; + } incontextof this, + + mapaction : function(elm) + { + // クリッカブルマップの領域アクション定義を読み込む + getLayerFromElm(elm).loadProvinceActions(elm.storage); + return 0; + } incontextof this, + + mapdisable : function(elm) + { + // クリッカブルマップを無効にする + getLayerFromElm(elm).clearProvinceActions(); + return 0; + } incontextof this, + + backlay : function(elm) + { + // レイヤを裏画面にコピー + updateBeforeCh = 1; + backupLayer(elm, true); + return 0; + } incontextof this, + + forelay : function(elm) + { + // レイヤを表画面にコピー + updateBeforeCh = 1; + backupLayer(elm, false); + return 0; + } incontextof this, + + copylay : function(elm) + { + // 同種のレイヤ同士のコピー + updateBeforeCh = 1; + copyLayer(elm); + return 0; + } incontextof this, + + layopt : function(elm) + { + // レイヤのオプションを設定 + updateBeforeCh = 1; + getLayerFromElm(elm).setOptions(elm); + return 0; + } incontextof this, + + trans : function(elm) + { + // トランジションの開始 + getLayerPageFromElm(elm, false).beginTransition(elm); + return 0; + } incontextof this, + + wt : function(elm) + { + // トランジションを待つ + return waitTransition(elm); + } incontextof this, + + stoptrans : function(elm) + { + // トランジションを停止する + stopAllTransitions(); + return 0; + } incontextof this, + + move : function(elm) + { + // 自動移動の開始 + getLayerFromElm(elm).beginMove(elm); + return 0; + } incontextof this, + + wm : function(elm) + { + // 自動移動を待つ + return waitMove(elm); + } incontextof this, + + stopmove : function(elm) + { + // 自動移動を停止する + stopAllMoves(); + return 0; + } incontextof this, + + laycount : function(elm) + { + updateBeforeCh = 1; + allocateCharacterLayers(+elm.layers) if elm.layers !== void; + allocateMessageLayers(+elm.messages) if elm.messages !== void; + return 0; + } incontextof this, + + //------------------------------ タグハンドラ群(効果音・BGM・ビデオ操作) -- + + playbgm : function(elm) + { + // BGM の演奏 + bgm.play(elm); + return 0; + } incontextof this, + + stopbgm : function(elm) + { + // BGM の停止 + bgm.stop(); + return 0; + } incontextof this, + + pausebgm : function(elm) + { + // BGM の一時停止 + bgm.pause(); + return 0; + } incontextof this, + + resumebgm : function(elm) + { + // BGM の再開 + bgm.resume(); + return 0; + } incontextof this, + + fadeinbgm : function(elm) + { + // BGM のフェードイン + bgm.fadeIn(elm); + return 0; + } incontextof this, + + fadeoutbgm : function(elm) + { + // BGM のフェードアウト + bgm.fadeOut(elm); + return 0; + } incontextof this, + + fadebgm : function(elm) + { + // BGM の指定音量までのフェード + bgm.fade(elm); + return 0; + } incontextof this, + + xchgbgm : function(elm) + { + // BGM の入れ替え/クロスフェード + bgm.exchange(elm); + return 0; + } incontextof this, + + bgmopt : function(elm) + { + // BGM のオプション設定 + bgm.setOptions(elm); + return 0; + } incontextof this, + + wb : function(elm) + { + // BGM のフェード終了待ち + return waitBGMFade(elm); + } incontextof this, + + wl : function(elm) + { + // BGM の再生終了待ち + return waitBGMStop(elm); + } incontextof this, + + playse : function(elm) + { + // 効果音の再生 + se[+elm.buf].play(elm); + return 0; + } incontextof this, + + stopse : function(elm) + { + // 効果音の停止 + se[+elm.buf].stop(); + return 0; + } incontextof this, + + fadeinse : function(elm) + { + // 効果音のフェードイン再生 + se[+elm.buf].fadeIn(elm); + return 0; + } incontextof this, + + fadeoutse : function(elm) + { + // 効果音のフェードアウト + se[+elm.buf].fadeOut(elm); + return 0; + } incontextof this, + + fadese : function(elm) + { + // 効果音のフェード + se[+elm.buf].fade(elm); + return 0; + } incontextof this, + + seopt : function(elm) + { + // 効果音のフェード + se[+elm.buf].setOptions(elm); + return 0; + } incontextof this, + + wf : function(elm) + { + // 効果音のフェード終了待ち + return waitSEFade(elm); + } incontextof this, + + ws : function(elm) + { + // 効果音の再生終了待ち + return waitSEStop(elm); + } incontextof this, + + video : function(elm) + { + // ムービーのオプションを設定する + movie.setOptions(elm); + return 0; + } incontextof this, + + playvideo : function(elm) + { + // ムービーを再生する + movie.play(elm.storage); + return 0; + } incontextof this, + + stopvideo : function(elm) + { + // ムービーを停止する + movie.stop(); + return 0; + } incontextof this, + + openvideo : function(elm) + { + // ムービー再生の準備をする + movie.open(elm.storage); + return 0; + } incontextof this, + + wv : function(elm) + { + // ムービーの再生終了を待つ + return waitMovieStop(elm); + } incontextof this, + + //--------------------------------------- タグハンドラ群(変数・TJS 操作) -- + + eval : function(elm) + { + // 式の評価 + elm.exp!; + return 0; + } incontextof this, + + trace : function(elm) + { + // 式のトレース表示 + var exp = elm.exp; + var result = exp!; + dm("▼[trace] expression=\"" + exp + "\" type of result=" + typeof result + + " result=" + result); + return 0; + } incontextof this, + + input : function(elm) + { + // 文字列の入力 + inputString(elm); + return 0; + } incontextof this, + + clearsysvar : function(elm) + { + // システム変数のクリア + clearSystemVariables(); + return 0; + } incontextof this, + + clearvar : function(elm) + { + // ゲーム変数のクリア + clearVariables(); + return 0; + } incontextof this, + + waittrig : function(elm) + { + // トリガを待つ + return waitTrigger(elm); + } incontextof this, + + //----------------------------------------------- タグハンドラ群の終わり -- + + interrupt : function(elm) { return -2; } incontextof this ]; + } +} + + + +// TJS スクリプトはここで終わり +" +END_OF_TJS_SCRIPT +# "; /* + +# assign でコピーすべき変数の再生成を行う perl スクリプト + +open FH, "MainWindow.tjs" or die; +undef($/); +$content = ; + +$list_store = ''; +$list_restore = ''; +while($content =~ /\/\*C\*\/var\s+(\w+)/gs) +{ + $list_store .= "\t\tf.$1 = $1;\n"; + $list_restore .= "\t\t$1 = f.$1 if f.$1 !== void;\n"; +} + +$content =~ +s/\t\t\/\/ \[start_store_vars\]\n.*?\t\t\/\/ \[end_store_vars\]/\t\t\/\/ \[start_store_vars\]\n$list_store\t\t\/\/ \[end_store_vars\]/s; +$content =~ +s/\t\t\/\/ \[start_restore_vars\]\n.*?\t\t\/\/ \[end_restore_vars\]/\t\t\/\/ \[start_restore_vars\]\n$list_restore\t\t\/\/ \[end_restore_vars\]/s; + +open FH, ">MainWindow.tjs" or die; +print FH $content; + + +# */ + diff --git a/bin/xp3tools/MessageLayer.tjs b/bin/xp3tools/MessageLayer.tjs new file mode 100644 index 0000000..b2f4f4c --- /dev/null +++ b/bin/xp3tools/MessageLayer.tjs @@ -0,0 +1,2812 @@ +//;# MessageLayer.tjs - メッセージレイヤ +//;# Copyright (C)2001-2002, W.Dee 改変・配布は自由です +//;<<'END_OF_TJS_SCRIPT'; + +// このスクリプトは有効な perl5 スクリプトでもあって、 +// assign/store/restore で実際にコピーする変数のリストを更新するために perl を用い、 +// perl MessgeLayer.tjs +// で更新を行う +// ( 処理の内容としては TJS2 でも十分書けるんだけど、あいにく +// perl のような実行環境が TJS2 にはないというか作ってない ) + +class LinkButtonLayer extends ButtonLayer +{ + // グラフィカルボタンとして動作するためのレイヤ + var linkNum; // リンク番号 + var onenter; // マウスが入ってきたときに実行するもの + var onleave; // マウスが出ていったときに実行するもの + var _eventTransparent = false; + + function LinkButtonLayer(win, par) + { + // コンストラクタ + super.ButtonLayer(...); + focusable = false; // フォーカスは受け取らない + hint = ""; + } + + function finalize() + { + super.finalize(...); + } + + function onClick() + { + super.onClick(...); + } + + function onMouseUp(x, y, button, shift) + { + if(enabled && button == mbLeft) parent.onButtonClick(linkNum); + super.onMouseUp(...); + } + + function onMouseEnter() + { + if(onenter !== void) onenter!; + super.onMouseEnter(...); + } + + function onMouseLeave() + { + if(onleave !== void) onleave!; + super.onMouseLeave(...); + } + + function assign(src) + { + super.assign(src); + linkNum = src.linkNum; + onenter = src.onenter; + onleave = src.onleave; + hint = src.hint; + eventTransparent = src.eventTransparent; + } + + property eventTransparent + { + // イベントを透過するかどうか + setter(x) + { + if(_eventTransparent != x) + { + _eventTransparent = x; + hitThreshold = x ? 256: 0; + } + } + getter() + { + return _eventTransparent; + } + } +} + +class LinkCheckBoxLayer extends CheckBoxLayer +{ + // メッセージレイヤに「リンク」として管理されるための + // チェックボックス + var linkNum; // リンク番号 + var exp; // 式 + var vertical; // 縦書きモード + + function LinkCheckBoxLayer(win, par) + { + // コンストラクタ + super.CheckBoxLayer(...); + joinFocusChain = false; // フォーカスチェーンには参加しない + hint = ""; + } + + function finalize() + { + super.finalize(...); + } + + function assign(src) + { + super.assign(src); + linkNum = src.linkNum; + vertical = src.vertical; + hint = src.hint; + exp = src.exp; + } + + function onKeyDown(key, shift, process) + { + // 縦書きの時は右と左を入れ替える + if(vertical) + { + if(key == VK_LEFT) key = VK_RIGHT; + else if(key == VK_RIGHT) key = VK_LEFT; + } + super.onKeyDown(key, shift, process); + } + + function onSearchPrevFocusable(layer) + { + super.onSearchPrevFocusable(parent.findPrevFocusable(this, layer)); + } + + function onSearchNextFocusable(layer) + { + super.onSearchNextFocusable(parent.findNextFocusable(this, layer)); + } + + function onFocus(prevfocused, direction) + { + parent.keyLink = linkNum; + super.onFocus(...); + } + + function commit() + { + kag.inputTemp = checked; + ("(" + exp + ") = kag.inputTemp")!; + } +} + + +class LinkEditLayer extends EditLayer +{ + // メッセージレイヤに「リンク」として管理されるための + // 単一行編集レイヤ + var linkNum; // リンク番号 + var exp; // 式 + + function LinkEditLayer(win, par) + { + // コンストラクタ + super.EditLayer(...); + joinFocusChain = false; // フォーカスチェーンには参加しない + hint = ""; + } + + function finalize() + { + super.finalize(...); + } + + function assign(src) + { + super.assign(src); + linkNum = src.linkNum; + exp = src.exp; + } + + function onKeyDown(key, shift, process) + { + // 縦書きの時は右と左を入れ替える + if(Edit_vertical) + { + if(key == VK_LEFT) key = VK_RIGHT; + else if(key == VK_RIGHT) key = VK_LEFT; + } + super.onKeyDown(key, shift, process); + } + + function onSearchPrevFocusable(layer) + { + super.onSearchPrevFocusable(parent.findPrevFocusable(this, layer)); + } + + function onSearchNextFocusable(layer) + { + super.onSearchNextFocusable(parent.findNextFocusable(this, layer)); + } + + function onFocus(prevfocused, direction) + { + parent.keyLink = linkNum; + super.onFocus(...); + } + + function commit() + { + kag.inputTemp = text; + ("(" + exp + ") = kag.inputTemp")!; + } +} + +class MessageLayer extends KAGLayer +{ + var wwFollowing = "%),:;]}。」゙゚。,、.:;゛゜ヽヾゝ" + "ゞ々’”)〕]}〉》」』】°′″℃¢%‰"; // 行頭禁則文字 + var wwFollowingWeak="!.?、・ァィゥェォャュョッー・?!ーぁぃぅぇぉっゃゅょゎァィ" + "ゥェォッャュョヮヵヶ"; // 行頭(弱)禁則文字 + var wwLeading="\\$([{「‘“(〔[{〈《「『【¥$£"; // 行末禁則文字 + + wwFollowing += wwFollowingWeak; + + var id; // 識別子 + var comp; // 対応する表/裏メッセージレイヤ + + + // 以下、/*C*/ の記号のついたものは、assign のときに自動的にコピーされるもの。 + // /*S*/ の記号のついたものは、store/resto の時に自動的にコピーされるもの。 + // これらの変数名をいじったり、変数を削除したり、追加する場合はいったんまた + // perl スクリプトとしてこのスクリプトを実行する必要があります。 + // ( 末端の perl スクリプトで処理 ) + + /*CS*/var frameGraphic = ""; // フレーム画像ファイル名 + /*CS*/var frameKey = clNone; // フレーム画像キー + /*CS*/var frameColor = 0x000000; // フレームの色 + /*CS*/var frameOpacity = 128; // フレームの不透明度 + /*CS*/var marginL = 8; // 左マージン + /*CS*/var marginT = 8; // 上マージン + /*CS*/var marginR = 8; // 右マージン + /*CS*/var marginB = 8; // 下マージン + /*CS*/var marginRCh = 1; // 右端(縦書きの場合は下端)に確保する禁則処理用余白 // originally 2 insani + /*C*/var x; + /*C*/var y; // 現在の表示位置 + /*C*/var relinexpos; // 改行すべき最終右(縦書きの場合は下)位置 + /*C*/var isLastLine; // ページ最終行を描画中 + /*C*/var indentxpos; // インデント左(縦書きの場合は上)位置 + var links = []; // リンク + /*C*/var linkFilled; // リンク領域画像を塗りつぶしたかどうか + /*C*/var numLinks = 0; // レイヤ内のリンクの数 + + var lastLink = -1; // 最後に選択したリンク番号 + var keyLink = -1; // キーボードで選択したリンク番号 + var inLink = -1; // リンクを描画中か + var highlightLayer; // リンクを強調表示するためのレイヤ + /*C*/var selProcessLock = false; // process 後にリンクが操作されるのを防ぐためのフラグ + /*C*/var storedSelProcessLock = false; // storeSelProcessLock 時点での状態 + + /*CS*/var defaultLinkColor = 0x0080ff; // デフォルトのリンク色 + /*CS*/var defaultLinkOpacity = 64; // デフォルトのリンクの不透明度 + + /*CS*/var defaultFontSize = 24; // デフォルトのフォント高さ + /*C*/var fontSize; // フォント高さ + /*C*/var _fontSize; // 仮フォント高さ + /*CS*/var defaultLineSize = 0; // デフォルトのライン高さ + /*C*/var reserveLineSize = 0; // '予約' ライン高さ + /*C*/var lineSize; // ライン高さ + /*CS*/var defaultRubySize = 10; // デフォルトのルビの高さ + /*C*/var rubySize; // ルビ高さ + /*C*/var _rubySize; // 仮ルビ高さ + /*CS*/var defaultRubyOffset = -2; // デフォルトのルビのオフセット + /*C*/var rubyOffset; // ルビのオフセット + /*C*/var _rubyOffset; // 仮ルビオフセット + + /*CS*/var defaultLineSpacing = 6; // デフォルトの行間 + /*C*/var lineSpacing; // 行間 + /*CS*/var defaultPitch = 0; // デフォルトの字間 + /*C*/var pitch; // 字間 + /*CS*/var defaultShadow = true; // デフォルトで影をつけるか + /*C*/var shadow; // 影をつけるか + /*CS*/var defaultEdge = false; // デフォルトで縁取りをするか + /*C*/var edge; // 縁取りをするか + /*CS*/var defaultShadowColor = 0x000000; // デフォルトの影の色 + /*C*/var shadowColor; // 影の色 + /*CS*/var defaultEdgeColor = 0x0080ff; // デフォルトの縁取りの色 + /*C*/var edgeColor; // 縁取りの色 + /*CS*/var defaultBold = true; // デフォルトでボールドで描画するか + /*C*/var bold; // ボールドで描画するか + /*CS*/var defaultFace = "user"; // デフォルトのフォント + /*C*/var userFace = "MS P明朝"; // ユーザの選んだフォント + /*C*/var face; // フォント + /*CS*/var defaultChColor = 0xffffff; // デフォルトの文字色 + /*C*/var chColor; // 文字色 + /*C*/var defaultAntialiased = true; // デフォルトでアンチエイリアスを掛けるか + /*C*/var antialiased; // アンチエイリアスを掛けるか + /*CS*/var vertical = false; // 縦書きモードの時に true + /*C*/var currentRuby = ""; // 次の文字に対するルビ + /*C*/var lastDrawnCh = ""; // 最後に描画した文字 + + /*CS*/var edgeExtent = 1; // 袋文字のふとさ + /*CS*/var edgeEmphasis = 512; // 袋文字の強調度 + + /*C*/var sizeChanged = false; // フォントサイズが変更されると true + + /*C*/var nextClearFlag = false; // 下端に達したとき、次の文字表示でレイヤをクリアするかどうか + + var lineLayer; // 行描画用のレイヤ + /*C*/var lineLayerBase; // ベースライン(横書き:下線位置/縦書き:中央位置) + /*C*/var lineLayerPos; // lineLayer 中の文字表示位置 + /*C*/var lineLayerLength; // lineLayer 中の文字幅 + /*C*/var lineLayerOriginX; // 表示オフセットX + /*C*/var lineLayerOriginY; // 表示オフセットY + var lineLayerLinks = []; // lineLayer が管理しているリンク + + /*C*/var align=-1; // -1=左/上そろえ 0=中央揃え 1=右/下そろえ + /*CS*/var defaultAutoReturn = true; // デフォルトで自動改行を行うかどうか + /*C*/var autoReturn = true; // 自動改行・改ページ処理を行うか + + /*CS*/var lineBreakGlyph = "linebreak"; // 行待ち記号名 + /*CS*/var lineBreakGlyphKey = clNone; // 行待ち記号のカラーキー + /*CS*/var pageBreakGlyph = "pagebreak"; // ページ待ち記号名 + /*CS*/var pageBreakGlyphKey = clNone; // ページ待ち記号のカラーキー + /*CS*/var glyphFixedPosition = false; // 記号を固定箇所に表示するか + /*CS*/var glyphFixedLeft = 0; // その位置 + /*CS*/var glyphFixedTop = 0; + + /*CS*/var draggable = false; // メッセージレイヤをドラッグ可能か + var dragging = false; // ドラッグ中か + var dragOriginX, dragOriginY; // ドラッグ中、つかんでいる座標 + + /*C*/var selClickLock = false; // 連打による誤操作を防ぐためのフラグ + /*C*/var lastMouseX; + /*C*/var lastMouseY; // リンクを表示したときの最後のマウスカーソルの位置 + + var ml, mt, mw, mh; // 初期レイヤサイズ(config用) + + var invisibleByUser = false; // ユーザにより一時的に不可視 + var visibleBeforeUserInvisible = false; + + var lastMouseDownX; // 最後にマウスがクリックされた X 座標 + var lastMouseDownY; // 最後にマウスがクリックされた Y 座標 + + // リンクタイプ + var ltNormal = 1; + var ltButton = 2; + var ltEdit = 3; + var ltCheckBox = 4; + + function MessageLayer(owner, parent, name, id, do_config) + { + // MessageLayer コンストラクタ + // owner : オーナー KAG Window + // parent : 親レイヤ + // name 名前 + // id : 識別子 + // left, top, width, height : 初期位置 + // do_config : コンフィギュレーションを行うか + super.KAGLayer(...); + + this.id = id; + this.name = name; + + imageModified = true; + + // コンフィギュレーション + if(do_config) + { + (MessageLayer_config incontextof this)(); + (MessageLayer_config_override incontextof this)() + if typeof global.MessageLayer_config_override != "undefined"; + + // 初期サイズは mw mh に入っているので + setPos(ml, mt); + setImageSize(mw, mh); + setSize(mw, mh); + } + else + { + // config を行わない場合 + // サイズはデフォルトで決定する + setPos(0, 0); + setImageSize(parent.width, parent.height); + setSize(parent.width, parent.height); + } + + // config 用一時変数の消去 + delete ml; delete mt; delete mw; delete mh; + + // 当たり判定初期化 + hitType = htMask; + hitThreshold = 0; // マウスメッセージは全域不透過 + + // 行描画用の浮遊レイヤを確保 + lineLayer = new global.KAGLayer(window, this); + lineLayer.hitType = htMask; + lineLayer.hitThreshold = 256; // マウスメッセージは全域透過 + lineLayer.name = "行描画用浮遊メッセージレイヤ"; + + // リンクをハイライト表示するためのレイヤ + highlightLayer = new global.KAGLayer(window, this); + highlightLayer.hitType = htProvince; + // 領域画像で当たり判定を行う + } + + function finalize() + { + // invalidateLinkObjects(); // リンクに結びつけられたオブジェクトの無効化 + invalidateLinkObjects(); + invalidate highlightLayer; + invalidate lineLayer; + + + super.finalize(); + } + + function setCompLayer(lay) { comp = lay; } + + function clearLayer() + { + // レイヤをクリア + window.updateBeforeCh = 1; + + cancelDrag(); + + if(imageModified) + { + if(frameGraphic == "") + { + // フレーム画像が指定されていない場合 + face = dfBoth; + fillRect(0, 0, imageWidth, imageHeight, (frameOpacity << 24) + frameColor); + } + else + { + loadImages(frameGraphic, frameKey); + setSizeToImageSize(); + } + + face = dfProvince; + colorRect(0, 0, imageWidth, imageHeight, 0); // 領域もクリア + face = dfBoth; + } + + imageModified = false; + + invalidateLinkObjects(); // リンクに関連づけられたオブジェクトを無効化 + + focusable = false; + links.clear(); + numLinks = 0; + inLink = -1; + highlightLink(lastLink, false); // ハイライトを消す + highlightLayer.visible = false; + lastLink = -1; + keyLink = -1; + linkFilled = false; + lastDrawnCh = ""; // 最後に描画した文字 + isLastLine = false; // 最終行か + selClickLock = true; + lastMouseX = cursorX; + lastMouseY = cursorY; + initLineLayer(); + } + + function setPosition(elm) + { + // elm に従ってメッセージレイヤのオプションを設定 + // このタグが position という名前なのは相当初期の KAG + // がそうだったのを引きずってるのね(^^; + left = elm.left if elm.left !== void; + top = elm.top if elm.top !== void; + imageWidth = elm.width if elm.width !== void; + imageHeight = elm.height if elm.height !== void; + setSizeToImageSize(); + + frameGraphic = elm.frame if elm.frame !== void; + frameKey = elm.framekey if elm.framekey !== void; + frameColor = +elm.color if elm.color !== void; + frameOpacity = +elm.opacity if elm.opacity !== void; + imageModified = true; // 強制的にメッセージレイヤをクリアするために + marginL = +elm.marginl if elm.marginl !== void; + marginT = +elm.margint if elm.margint !== void; + marginR = +elm.marginr if elm.marginr !== void; + marginB = +elm.marginb if elm.marginb !== void; + vertical = +elm.vertical if elm.vertical !== void; + draggable = +elm.draggable if elm.draggable !== void; + + clear(); + } + + function clear() + { + // メッセージレイヤをクリアする + clearLayer(); + + // 表示位置を初期位置に + if(vertical) + { + x = imageWidth - marginR; + y = marginT; + } + else + { + x = marginL; + y = marginT; + } + + // その他リセット + indentxpos = 0; + resetFont(); + resetStyle(); + decideSizeChange(); + initLineLayer(); + currentRuby = ""; + } + + function clear2() + { + // メッセージレイヤをクリアするが + // フォントのリセットなどは行わない + // メッセージが下端までいって自動的にページ待ち→メッセージレイヤクリア + // となるときに呼ばれる + + clearLayer(); + + // 表示位置を初期位置に + if(vertical) + { + y = marginT + indentxpos; + x = imageWidth - marginR; + } + else + { + y = marginT; + x = marginL + indentxpos; + } + initLineLayer(); + } + + function resetFont() + { + // フォントのリセット + sizeChanged = true; + + // 各デフォルトの設定を書き戻し + if(!vertical) + { + lineLayer.font.face = face = defaultFace == 'user' ? userFace : defaultFace; + lineLayer.font.angle = 0; + } + else + { + lineLayer.font.face = '@' + (face = defaultFace == 'user' ? userFace : defaultFace); + lineLayer.font.angle = 2700; + } + + lineLayer.font.bold = bold = defaultBold; + lineLayer.font.italic=false; + _fontSize = defaultFontSize; + antialiased = defaultAntialiased; + + chColor = defaultChColor; + _rubySize = defaultRubySize; + _rubyOffset = defaultRubyOffset; + shadow = defaultShadow; + edge = defaultEdge; + shadowColor = defaultShadowColor; + edgeColor = defaultEdgeColor; + + // 改行位置を計算 + if(!vertical) + relinexpos = imageWidth - marginR - marginRCh * _fontSize; + else + relinexpos = imageHeight - marginB - marginRCh * _fontSize; + } + + function setFont(elm) + { + // フォントの設定 + sizeChanged = true; + + if(!vertical) + { + var elmface = elm.face; + if(elmface == 'default') + { + lineLayer.font.angle = 0; + lineLayer.font.face = face = defaultFace; + } + else if(elmface == 'user') + { + lineLayer.font.angle = 0; + lineLayer.font.face = face = userFace; + } + else if(elmface !== void) + { + lineLayer.font.angle = 0; + lineLayer.font.face = face = elmface; + } + } + else + { + var elmface = elm.face; + if(elmface == 'default') + { + var f = '@' + (face = defaultFace); + lineLayer.font.angle = 2700; + lineLayer.font.face = f; + } + else if(elmface == 'user') + { + var f = '@' + (face = userFace); + lineLayer.font.angle = 2700; + lineLayer.font.face = f; + } + else if(elmface !== void) + { + var f = '@' + (face = elmface); + lineLayer.font.angle = 2700; + lineLayer.font.face = f; + } + } + + if(elm.antialiased == 'default') + antialiased = defaultAntialiased; + else if(elm.antialiased !== void) + antialiased = +elm.antialiased; + + if(elm.bold == 'default') + lineLayer.font.bold = defaultBold; + else if(elm.bold !== void) + lineLayer.font.bold = +elm.bold; + + if(elm.italic == 'default') + lineLayer.font.italic = false; + else if(elm.italic !== void) + lineLayer.font.italic = +elm.italic; + + if(elm.size == 'default') + _fontSize = defaultFontSize; + else if(elm.size !== void) + _fontSize = +elm.size; + + if(elm.color == 'default') + chColor = defaultChColor; + else if(elm.color !== void) + chColor = +elm.color; + + if(elm.rubysize == 'default') + _rubySize = defaultRubySize; + else if(elm.rubysize !== void) + _rubySize = +elm.rubysize; + + if(elm.rubyoffset == 'default') + _rubyOffset = defaultRubyOffset; + else if(elm.rubyoffset !== void) + _rubyOffset = +elm.rubyoffset; + + if(elm.shadow == 'default') + shadow = defaultShadow; + else if(elm.shadow !== void) + shadow = +elm.shadow; + + if(elm.shadowcolor == 'default') + shadowColor = defaultShadowColor; + else if(elm.shadowcolor !== void) + shadowColor = +elm.shadowcolor; + + if(elm.edge == 'default') + edge = defaultEdge; + else if(elm.edge !== void) + edge = +elm.edge; + + if(elm.edgecolor == 'default') + edgeColor = defaultEdgeColor; + else if(elm.edgecolor !== void) + edgeColor = +elm.edgecolor; + + if(!vertical) + relinexpos = int(imageWidth-marginR-marginRCh*_fontSize); + else + relinexpos = int(imageHeight-marginB-marginRCh*_fontSize); + } + + function setDefaultFont(elm) + { + // デフォルトフォントの設定 + defaultFace = elm.face if elm.face !== void; + defaultAntialiased = +elm.antialiased if elm.antialiased !== void; + defaultBold = +elm.bold if elm.bold !== void; + defaultFontSize = +elm.size if elm.size !== void; + defaultChColor = +elm.color if elm.color !== void; + defaultRubySize = +elm.rubysize if elm.rubysize !== void; + defaultRubyOffset = +elm.rubyoffset if elm.rubyoffset !== void; + defaultShadow = +elm.shadow if elm.shadow !== void; + defaultShadowColor = +elm.shadowcolor if elm.shadowcolor !== void; + defaultEdge = +elm.edge if elm.edge !== void; + defaultEdgeColor = +elm.edgecolor if elm.edgecolor !== void; + } + + function resetStyle() + { + // スタイルのリセット + reserveLineSize = defaultLineSize; + lineSpacing = defaultLineSpacing; + pitch = defaultPitch; + resetLineSize(); + align = -1; + autoReturn = defaultAutoReturn; + adjustAlign(); + } + + function setStyle(elm) + { + // スタイルの設定 + if(elm.linespacing == 'default') + lineSpacing = defaultLineSpacing; + else if(elm.linespacing !== void) + lineSpacing = +elm.linespacing; + + if(elm.pitch == 'default') + pitch = defaultPitch; + else if(elm.pitch !== void) + pitch = +elm.pitch; + + if(elm.linesize == 'default') + { + reserveLineSize = defaultLineSize; + } + else if(elm.linesize !== void) + { + reserveLineSize = +elm.linesize; + resetLineSize(); + sizeChanged =true; + } + + if(elm.align == 'default') + { + fixLineLayer(); + align = -1; + adjustAlign(); + } + else if(elm.align !== void) + { + fixLineLayer(); + if(elm.align == 'left' || elm.align == 'top') + align = -1; + else if(elm.align == 'center') + align = 0; + else if(elm.align == 'right' || elm.align == 'bottom') + align = 1; + adjustAlign(); + } + + if(elm.autoreturn == 'default') + autoReturn = defaultAutoReturn; + else if(elm.autoreturn !== void) + autoReturn = +elm.autoreturn; + } + + function setDefaultStyle(elm) + { + // デフォルトのスタイルの設定 + defaultLineSpacing = +elm.linespacing if elm.linespacing !== void; + defaultPitch = +elm.pitch if elm.pitch !== void; + defaultLineSize = +elm.linesize if elm.linesize !== void; + defaultAutoReturn = +elm.autoreturn if elm.autoreturn !== void; + } + + + function resetLineSize() + { + // ラインサイズのリセット + lineSize = reserveLineSize > fontSize ? reserveLineSize : fontSize; + } + + function decideSizeChange() + { + // 仮に変更されていたフォント・スタイル情報を確定 + if(!sizeChanged) return; + lineLayer.font.height = - _fontSize; + fontSize = _fontSize; + rubySize = _rubySize; + rubyOffset = _rubyOffset; + sizeChanged = false; + } + + function getLineLayerBaseLine() + { + // 文字表示用のベースラインを計算して返す + // 横書きの場合は文字の下端部分、 + // 縦書きの場合は文字の中央線 + if(!vertical) + return -getLineLayerTopOffset() + lineSpacing + lineSize; + else + // 縦書き + // 中央線(レイヤ左端からの位置) + return 4 + (lineSize>>1); + } + + function initLineLayer() + { + // lineLayer の初期化 + var ll = lineLayer; + resetLineSize(); + lineLayerLinks.count = 0; + lineLayerOriginX = x; + lineLayerOriginY = y; + if(!vertical) + ll.imageWidth = imageWidth + 8; + else + ll.imageHeight = imageHeight + 8; + ll.setSizeToImageSize(); + changeLineSize(/*forceresize=*/true); + lineLayerLength = 0; + lineLayerPos = 4; + ll.visible = false; + ll.face = dfBoth; + var lliw = ll.imageWidth; + var llih = ll.imageHeight; + ll.fillRect(0, 0, lliw, llih, 0); + // 完全透明に + ll.face = dfProvince; + ll.fillRect(0, 0, lliw, llih, 0); + ll.face = dfBoth; + // 領域をクリア + } + + function fixLineLayer() + { + // lineLayer を現在の表示位置に確定 + var ll = lineLayer; + if(ll.visible == false) return; + + var llox = lineLayerOriginX + getLineLayerLeftOffset(); + var lloy = lineLayerOriginY + getLineLayerTopOffset(); + + face = dfBoth; + pileRect( + llox, + lloy, + ll, 0, 0, ll.imageWidth, ll.imageHeight); + + face = dfProvince; + var i; + var lll = lineLayerLinks; + var lllcount = lll.count; + for(i = 0; ilineSize) newlinesize = fontSize; // 拡張 + + var newlinelayersize = newlinesize + lineSpacing; + if(rubySize + rubyOffset > lineSpacing) + { + // ルビが上の行とかぶる + newlinelayersize += rubySize+rubyOffset - lineSpacing; + } + + newlinelayersize += 8; // 袋文字・影描画用の余裕 + if(!vertical) + { + // 横書きの場合 + if(forceresize || lineLayer.imageHeight>1) - 4; + else if(align == 1) + return imageWidth - marginR - marginL - lineLayerLength - 4; + } + else + { + return -lineSize - lineSpacing - 4; + } + } + + function getLineLayerTopOffset() + { + // 行描画用レイヤ内での上オフセットを取得 + if(!vertical) + { + return -(lineLayer.imageHeight - 4 - lineSize - lineSpacing); + } + else + { + if(align == -1) + return -4; + else if(align == 0) + return ((imageHeight - marginB - marginT - lineLayerLength)>>1) - 4; + else if(align == 1) + return imageHeight - marginB - marginT - lineLayerLength - 4; + return -4; + } + } + + function adjustAlign() + { + lineLayer.setPos(lineLayerOriginX + getLineLayerLeftOffset(), + lineLayerOriginY + getLineLayerTopOffset()); + } + + + function reline() + { + // 改行 + // ページを越える場合は true, 越えないで改行できる場合は false + var condition; + if(vertical) + { + condition= lineLayerOriginX + getLineLayerLeftOffset() - lineSpacing - + lineSize <= marginL; + } + else + { + condition= lineLayerBase + lineLayerOriginY + getLineLayerTopOffset() + + lineSize >= imageHeight-marginB; + } + + if(condition) + { + // ページを越える! + return true; + } + else + { + if(inLink != -1) endLinkLine(); + + decideSizeChange(); + + fixLineLayer(); + + if(vertical) + { + y = marginT + indentxpos; + x -= lineSize + lineSpacing; + } + else + { + y += lineSize + lineSpacing; + x = marginL + indentxpos; + } + + var condition; + if(vertical) + condition= x - lineSize*2 <= marginL; + else + condition= y + lineSize*2 >= imageHeight-marginB; + + if(condition) + { + // ページ最終行 + isLastLine=true; + } + + initLineLayer(); + +// if(inLink!=-1) beginLinkLine(); + } + return false; + } + + function processCh(ch) + { + // 文字 ch を描画する + // 改行が行われ、かつそれがページ末端を越える場合は true を返す + // それ以外は false + var vert = vertical; + +// insani +/* // original word-wrapping + if((vert ? y >= relinexpos : x >= relinexpos ) && autoReturn) + { + if(((lastDrawnCh=="" || wwLeading.indexOf(lastDrawnCh)==-1) && + wwFollowing.indexOf(ch)==-1) || + (lastDrawnCh!="" && wwFollowingWeak.indexOf(lastDrawnCh)!=-1 && + wwFollowingWeak.indexOf(ch)!=-1)) + { + // 最後に描画したのが行末禁則文字でない場合 + // しかもこれから描画するのが行頭禁則文字でない + // 場合 + // または弱禁則文字が連続していない場合 + if(reline()) return autoReturn; + } + else if(vert ? ( y>imageHeight ) : (x>imageWidth)) + { + // これから描画するのが強禁則文字ではなくて、 + // 確実に 右端を越える場合 + // ( この場合は余白は考えない ) + if(reline()) return autoReturn; + } + } +*/ // new reduced word-wrapping, most handled in processWrap() below + if(vert ? ( y>imageHeight ) : (x>imageWidth)) + { + // これから描画するのが強禁則文字ではなくて、 + // 確実に 右端を越える場合 + // ( この場合は余白は考えない ) + if(reline()) return autoReturn; + } +// insani + + changeLineSize() if sizeChanged; + + var inlink = inLink != -1; + + beginLinkLine() if inlink; + + var ll = lineLayer; + var llfont = ll.font; + + var cw = llfont.getTextWidth(ch); + + var dx , dy; + + if(vert) + dx = int(lineLayerBase+(fontSize>>1)), dy = int(lineLayerPos); + else + dx = int(lineLayerPos), dy = int(lineLayerBase-fontSize); + + + if(edge) + ll.drawText(dx, dy, ch, chColor, 255, antialiased, edgeEmphasis, edgeColor, edgeExtent, 0, 0); // 文字 + else if(shadow) + ll.drawText(dx, dy, ch, chColor, 255, antialiased, 255, shadowColor, 0, 2, 2); // 文字 + else + ll.drawText(dx, dy, ch, chColor, 255, antialiased); // 文字 + + if(currentRuby != "") + { + // ルビがある + var cw = llfont.getTextWidth(ch); + var orgsize = llfont.height; + llfont.height = rubySize; + var rw = llfont.getTextWidth(currentRuby); + var rx,ry; + if(!vert) + { + rx = int(dx + (cw>>1) - (rw>>1)); + ry = int(dy - rubySize - rubyOffset); + } + else + { + rx = int(dx + rubySize + rubyOffset); + ry = int(dy + (cw>>1) - (rw>>1)); + } + + if(edge) + ll.drawText(rx, ry, currentRuby, chColor, 255, antialiased, edgeEmphasis, edgeColor, edgeExtent, 0, 0); // 文字 + else if(shadow) + ll.drawText(rx, ry, currentRuby, chColor, 255, antialiased, 255, shadowColor, 0, 2, 2); // 文字 + else + ll.drawText(rx, ry, currentRuby, chColor, 255, antialiased); // 文字 + + llfont.height = orgsize; + currentRuby = ''; + } + + ll.visible = true; + + if(inlink) + { + // ハイパーリンクでちゅー + ll.face = dfProvince; + if(!vert) + ll.fillRect(lineLayerPos, lineLayerBase - fontSize, + cw, fontSize, numLinks + 1); + else + ll.fillRect(lineLayerBase - (fontSize>>1), lineLayerPos, + fontSize, cw, numLinks + 1); + + // 領域画像も塗りつぶしてやる + ll.face = dfBoth; + linkFilled = true; + } + + cw += pitch; + + if(vert) y += cw; else x += cw; + + lineLayerPos += cw; + lineLayerLength += cw; + + lastDrawnCh = ch; + + adjustAlign() if(align >= 0); + + return false; + } + +// insani + function processWrap(ch) + { + var vert = vertical; + + var ll = lineLayer; + var llfont = ll.font; + + var cw = llfont.getTextWidth(ch); + + if((vert ? (y+cw) >= relinexpos : (x+cw) >= relinexpos ) && autoReturn) + { + if(reline()) return autoReturn; + } + return false; + } +// insani + + function putGraph(storage, key, ischar) + { + // 画像 storage を描画する + + // テンポラリのレイヤを用意 + var lay = window.temporaryLayer; + + lay.type = ltTransparent; + lay.face = dfBoth; + lay.loadImages(storage, key); // 画像読み込み + var lw, lh; + lw = lay.imageWidth; + lh = lay.imageHeight; + + var cw; + if(vertical) + { + if(lw > lineSize) lineSize = lw; // 拡張 + cw = lh; + } + else + { + if(lh > lineSize) lineSize = lh; // 拡張 + cw = lw; + } + + changeLineSize(); + + if(inLink != -1) beginLinkLine(); + + var repage = false; + + if(autoReturn) + { + // 改行位置に達している? + if(vertical ? ( y > relinexpos ) : (x > relinexpos) ) + { + repage = reline(); + } + } + + if(repage) return true; // 文字は描画しない + + + // 描画 + var cx,cy; + if(vertical) + { + cx = lineLayerBase - (lw>>1); + cy = lineLayerPos; + } + else + { + cx = lineLayerPos; + cy = lineLayerBase - lh; + } + + if(ischar && (shadow || edge) ) + { + lay.face = dfMain; + // 影/袋文字の色でメインを塗りつぶす + + if(edge) + { + // 袋 + lay.fillRect(0, 0, lw, lh, edgeColor); + lineLayer.pileRect(cx+1, cy, lay, 0, 0, lw, lh); + lineLayer.pileRect(cx, cy+1, lay, 0, 0, lw, lh); + lineLayer.pileRect(cx-1, cy, lay, 0, 0, lw, lh); + lineLayer.pileRect(cx, cy-1, lay, 0, 0, lw, lh); + } + else if(shadow) + { + // 影 + lay.fillRect(0, 0, lw, lh, shadowColor); + lineLayer.pileRect(cx+2, cy+2, lay, 0, 0, lw, lh); + } + + } + + if(ischar) + { + lay.face = dfMain; + lay.fillRect(0, 0, lw, lh, chColor); + // メインを文字色で塗りつぶす + } + + lineLayer.pileRect(cx, cy, lay, 0, 0, lw, lh); // 描画 + + // 描画おわり + lastDrawnCh=""; + + if(inLink!=-1) + { + // ハイパーリンクでちゅー + lineLayer.face = dfProvince; + if(vertical) + lineLayer.fillRect(lineLayerBase - (fontSize>>1), lineLayerPos, + fontSize, cw, numLinks+1); + else + lineLayer.fillRect(lineLayerPos, lineLayerBase - fontSize, + cw, fontSize, numLinks+1); + face = dfBoth; + lineLayer.face = dfBoth; + // 領域画像も塗りつぶしてやる + linkFilled=true; + } + + if(vertical) y+=cw; else x+=cw; + + lineLayerPos += cw; + lineLayerLength += cw; + + lineLayer.visible = true; + + if(align >= 0) adjustAlign(); + + return false; + } + + function putHorizonCh(text, expand = false) + { + // 縦中横を描画する + if(!vertical) throw new Exception("縦書きモードでないと使用できません"); + + // フォントを設定し直す + var ll = lineLayer; + var lf = ll.font; + var orgfont = lf.face; + var organgle = lf.angle; + lf.face = orgfont.substring(1); // 先頭の @ マークを取り除く + lf.angle = 0; + + // 描画する文字の横幅を取得 + var cw = lf.getTextWidth(text); + var ch = fontSize; + + // linesize の拡張 + if(expand) + { + if(cw > lineSize) lineSize = cw; // 拡張 + changeLineSize(); + } + + // リンク中の場合はリンクを開始 + if(inLink != -1) beginLinkLine(); + + // 改行/改ページ処理 + var repage = false; + if(autoReturn) + { + // 改行位置に達している? + if(y > relinexpos) repage = reline(); + } + if(repage) + { + // 戻る前にフォントをもとにもどす + lf.face = orgfont; + lf.angle = organgle; + return true; // 文字は描画しない + } + + // 描画 + var dx = lineLayerBase - (cw>>1); + var dy = lineLayerPos; + + if(edge) + ll.drawText(dx, dy, text, chColor, 255, antialiased, 512, edgeColor, 1, 0, 0); // 文字 + else if(shadow) + ll.drawText(dx, dy, text, chColor, 255, antialiased, 255, shadowColor, 0, 2, 2); // 文字 + else + ll.drawText(dx, dy, text, chColor, 255, antialiased); // 文字 + + // 描画おわり + lastDrawnCh=""; + + // フォントを元に戻す + lf.face = orgfont; + lf.angle = organgle; + + // ハイパーリンクの処理 + if(inLink!=-1) + { + // ハイパーリンクでちゅー + ll.face = dfProvince; + ll.fillRect(lineLayerBase - (fontSize>>1), lineLayerPos, + fontSize, cw, numLinks+1); + face = dfBoth; + ll.face = dfBoth; + linkFilled=true; + } + + // 位置更新 + y += ch; + lineLayerPos += ch; + lineLayerLength += ch; + + lineLayer.visible = true; + + // アラインの修正 + if(align >= 0) adjustAlign(); + + // 戻る + return false; + } + + + function invalidateLinkObjects() + { + // リンクアイテムにオブジェクトが割り当てられていた場合無効化 + for(var i = links.count-1; i>=0; i--) + { + if(links[i].type != ltNormal) + invalidate links[i].object; + links[i].type = 0; + } + } + + function beginLinkLine() + { + // リンク開始 + // リンク開始のタグ以降で、実際に文字描画などが起こるときに + // 呼ばれる(行が次に移ったときも呼ばれる) + if(linkFilled) return; + var sx, sy; + if(!vertical) + { + sx = lineLayerPos; + sy = lineLayerBase - fontSize; + } + else + { + sx = lineLayerBase - (fontSize>>1); + sy = lineLayerPos; + } + var n = links[numLinks].lineCount - 1; + links[numLinks].fixed[n] = false; // 未固定 + links[numLinks].x[n] = sx; + links[numLinks].y[n] = sy; + + lineLayerLinks[lineLayerLinks.count] = %[number : numLinks, line : n]; + linkFilled = false; + } + + function endLinkLine() + { + // リンク中にて行が終了したときに呼ばれる + if(!linkFilled) return; + linkFilled = false; + var w, h; + var linkn = links[numLinks]; + var linenum = links[numLinks].lineCount-1; + if(!vertical) + { + w = lineLayerPos - linkn.x[linenum]; + if(lineLayer.font.italic) w += fontSize>>2; // 斜体の時は一応の余裕を持たせる + if(w <= 0) return; + h = fontSize; + } + else + { + w = fontSize; + h = lineLayerPos - linkn.y[linenum]; + if(lineLayer.font.italic) h += fontSize>>2; + if(h <= 0) return; + } + linkn.w[linenum] = w; + linkn.h[linenum] = h; + linkn.lineCount ++; + } + + function beginHyperLink(elm) + { + // 普通のリンクを開始する + links[numLinks] = %[ // 辞書配列を作成 + type : ltNormal, + storage : elm.storage, + target : elm.target, + exp : elm.exp, + countPage : (elm.countpage === void) ? true : +elm.countpage, + hint : elm.hint, + color : (elm.color === void) ? defaultLinkColor : +elm.color, + opacity : (elm.opacity === void) ? defaultLinkOpacity : +elm.opacity, + onenter : elm.onenter, + onleave : elm.onleave, + x : [], + y : [], + w : [], + h : [], + fixed : [], + lineCount : 1 + ]; + inLink = 0; + } + + function endHyperLink() + { + inLink = -1; + endLinkLine(); + links[numLinks].lineCount--; + numLinks++; + focusable = true; // フォーカスを受け取れるように + lastMouseX = cursorX; + lastMouseY = cursorY; + selClickLock = true; + setSelProcessLock(false); // 選択ロック解除 + comp.setSelProcessLock(false); // 選択ロック解除 + } + + function findLink(x,y) + { + // x, y 位置にあるリンクをさがし、見つかれば そのリンク番号を返す + + if(selClickLock) return -1; + + var i=0; + // 領域画像を使った判定 + if(lineLayer.visible) + { + if(lineLayer.left <= x && lineLayer.top <= y && + lineLayer.imageWidth + lineLayer.left > x && + lineLayer.imageHeight + lineLayer.top > y) + { + i = lineLayer.getProvincePixel(x - lineLayer.left, y - lineLayer.top); + } + } + if(i == 0) i = getProvincePixel(x, y); + if(i == -1) return -1; + if(i != 0) return i - 1; + return -1; + } + + function addButton(elm) + { + // グラフィカルボタンを配置 + var object = new LinkButtonLayer(window, this); + object.loadImages(elm.graphic, elm.graphickey); + object.linkNum = numLinks; + object.setPos(x, y); + object.hint = elm.hint; + object.visible = true; + object.onenter = elm.onenter; + object.onleave = elm.onleave; + object.hitThreshold = + (elm.recthit === void || +elm.recthit) ? 0 : 64; + + links[numLinks] = %[ + type : ltButton, + graphic : elm.graphic, + graphickey : elm.graphickey, + storage : elm.storage, + target : elm.target, + exp : elm.exp, + countPage : (elm.countpage === void) ? true : +elm.countpage, + object : object, + onenter : elm.onenter, + onleave : elm.onleave, + x : [x], + y : [y], + w : [object.width], + h : [object.height], + fixed : [true], + lineCount : 1 + ]; + + numLinks++; + focusable = true; // フォーカスを受け取れるように + setSelProcessLock(false); // 選択ロック解除 + comp.setSelProcessLock(false); // 選択ロック解除 + } + + function addEdit(elm) + { + // 単一行エディットを配置 + var object = new LinkEditLayer(window, this, vertical); + var of = object.font; + var lf = lineLayer.font; + of.face = lf.face; + of.angle = lf.angle; + of.bold = lf.bold; + of.italic = lf.italic; + of.height = lf.height; + object.linkNum = numLinks; + object.text = elm.name!; + object.exp = elm.name; + object.antialiased = antialiased; + object.color = elm.bgcolor if elm.bgcolor !== void; // color でない事に注意 + object.textColor = elm.color if elm.color !== void; // textColor でないことに注意 + object.maxChars = elm.maxchars if elm.maxchars !== void; + object.bgOpacity = elm.opacity if elm.opacity !== void; + + var len = elm.length === void ? 200: +elm.length; + if(vertical) + { + object.setPos(lineLayer.left + lineLayerBase - ((lf.height + 6)>>1), + lineLayer.top + lineLayerPos); + object.height = len; + object.width = lf.height + 6; + y += len; + } + else + { + object.setPos(lineLayer.left + lineLayerPos, + lineLayer.top + lineLayerBase - fontSize - 3); + object.width = elm.length === void ? 200: +elm.length; + object.height = lf.height + 6; + x += len; + } + object.visible = true; + lineLayerPos += len; + lineLayerLength += len; + + links[numLinks] = %[ + type : ltEdit, + exp : elm.name, + object : object, + x : [object.left], + y : [object.top], + w : [object.width], + h : [object.height], + fixed : [true], + lineCount : 1 + ]; + + numLinks ++; + focusable = true; + setSelProcessLock(false); // 選択ロック解除 + comp.setSelProcessLock(false); // 選択ロック解除 + } + + function addCheckBox(elm) + { + var object = new LinkCheckBoxLayer(window, this, vertical); + object.linkNum = numLinks; + object.vertical = vertical; + object.checked = elm.name!; + object.exp = elm.name; + object.color = elm.bgcolor if elm.bgcolor !== void; // color でない事に注意 + object.glyphColor = elm.color if elm.color !== void; // glyphColor でないことに注意 + object.bgOpacity = elm.opacity if elm.opacity !== void; + + var cw; + var lw = object.width; + var lh = object.height; + if(vertical) + { + if(lw > lineSize) lineSize = lw; else lw = object.width = lineSize; // 拡張 + cw = lh; + } + else + { + if(lh > lineSize) lineSize = lh; else lh = object.height = lineSize; // 拡張 + cw = lw; + } + + changeLineSize(); + + var cx,cy; + if(vertical) + { + cx = lineLayerBase - (lw>>1); + cy = lineLayerPos; + } + else + { + cx = lineLayerPos; + cy = lineLayerBase - lh; + } + + object.setPos(cx + lineLayerOriginX + getLineLayerLeftOffset(), + cy + lineLayerOriginY + getLineLayerTopOffset()); + + if(vertical) y+=cw; else x+=cw; + lineLayerPos += cw; + lineLayerLength += cw; + + object.visible = true; + + links[numLinks] = %[ + type : ltCheckBox, + exp : elm.name, + object : object, + x : [object.left], + y : [object.top], + w : [object.width], + h : [object.height], + fixed : [true], // 固定!!! + lineCount : 1 + ]; + + numLinks ++; + focusable = true; + setSelProcessLock(false); // 選択ロック解除 + comp.setSelProcessLock(false); // 選択ロック解除 + } + + function highlightLink(n, b = false) + { + // n 番目のリンクを + // b : true : ハイライトする + // b : false : ハイライトを消す + if(selProcessLock) return; + if(b) + { + if(n < 0 || n >= numLinks) return; + var linkn = links[n]; + if(linkn === void) return; + if(linkn.type != ltNormal) return; + var xofs = lineLayerOriginX + getLineLayerLeftOffset(); + var yofs = lineLayerOriginY + getLineLayerTopOffset(); + + if(linkn.onenter != '') linkn.onenter!; + + // ハイライトする + if(!vertical) + { + // 横 + // 全画面更新をさけるため + // 最小と最大の位置を得る + var min = linkn.y[0]; + if(!linkn.fixed[0]) min += yofs; // 固定されていない場合は補正 + var max = linkn.y[0] + linkn.h[0]; + if(!linkn.fixed[0]) max += yofs; + + var i; + for(i = 0; i < linkn.lineCount; i++) + { + var m; + m = linkn.y[i]; + if(!linkn.fixed[i]) m += yofs; + if(min > m) min = m; + m = linkn.y[i] + linkn.h[i]; + if(!linkn.fixed[i]) m += yofs; + if(max < m) max = m; + } + + highlightLayer.setPos(0, min); + highlightLayer.setImageSize(imageWidth, max - min); + highlightLayer.setSizeToImageSize(); + } + else + { + // 縦 + var min = linkn.x[0]; + if(!linkn.fixed[0]) min += xofs; + var max = linkn.x[0] + linkn.w[0]; + if(!linkn.fixed[0]) max += xofs; + + var i; + for(i = 0; i < linkn.lineCount; i++) + { + var m; + m = linkn.x[i]; + if(!linkn.fixed[i]) m += xofs; + if(min > m) min = m; + m = linkn.x[i] + linkn.w[i]; + if(!linkn.fixed[i]) m += xofs; + if(max < m) max = m; + } + + highlightLayer.setPos(min, 0); + highlightLayer.setImageSize(max - min, imageHeight); + highlightLayer.setSizeToImageSize(); + } + + highlightLayer.colorRect(0, 0, + highlightLayer.imageWidth, + highlightLayer.imageHeight, 0, -255); + highlightLayer.face = dfProvince; + highlightLayer.colorRect(0, 0, + highlightLayer.imageWidth, + highlightLayer.imageHeight, 0, 255); + highlightLayer.face = dfBoth; + + var i; + for(i = 0; i < linkn.lineCount; i++) + { + var x = linkn.x[i]; + var y = linkn.y[i]; + if(!linkn.fixed[i]) x += xofs, y += yofs; + + highlightLayer.colorRect( + x - highlightLayer.left, + y - highlightLayer.top, + linkn.w[i], linkn.h[i], linkn.color, linkn.opacity); + } + + highlightLayer.visible = true; + + cursor = window.cursorPointed; + + if(parent.isPrimary && comp !== void && left == comp.left && top == comp.top && + width == comp.width && height == comp.height) + { + // 裏ページレイヤも同様に設定 + var tl = highlightLayer; + var bl = comp.highlightLayer; + bl.assignImages(tl); + bl.setPos(tl.left, tl.top, tl.width, tl.height); + bl.visible = true; + } + + // ヒントを設定 + hint = links[n].hint; + } + else + { + if(n >= 0 && n < numLinks) + { + if(links[n] !== void && links[n].type == ltNormal) + { + if(links[n].onleave != '') links[n].onleave!; + } + } + + highlightLayer.visible = false; + cursor = crDefault; + + if(/*parent.isPrimary && */comp !== void) + comp.highlightLayer.visible = false; + + showParentHint = false; + } + } + + function processLink(n) + { + // リンク番号 n を処理する + var ln = links[n]; + if(ln === void) return; + + // 裏画面のハイライトを非表示 + if(comp !== void) comp.highlightLayer.visible = false; + + // 実行 + ln.exp! if ln.exp != ''; + + if(ln.storage != '' || ln.target != '') + { + window.lockMessageLayerSelProcess(); // 選択をロック + if(System.getKeyState(VK_RETURN) || System.getKeyState(VK_SPACE)) + window.hideMouseCursor(); + // キーボードによる操作の場合はマウスカーソルを隠す + window.process(ln.storage, ln.target, ln.countPage); + } + } + + function onButtonClick(num) + { + // 番号 num のグラフィカルボタンがクリックされた + processLink(num); + } + + + function setSelProcessLock(b) + { + // 選択のロックを設定 + // 選択のロックを行うのは、いったん選択肢を選択したら + // 他の選択肢を選択できないようにするため + if(selProcessLock != b) + { + selProcessLock = b; + var lks = links; + for(var i = 0; i < numLinks; i++) + { + var item = lks[i]; + var type = item.type; + if(type == ltButton) item.object.eventTransparent = b; + else if(type == ltEdit || type == ltCheckBox) item.object.enabled = !b; + } + } + } + + function storeSelProcessLock() + { + // 右クリックサブルーチンに入る前に呼ばれ、 + // 現在の selProcessLock の状態を退避する + storedSelProcessLock = selProcessLock; + } + + function restoreSelProcessLock() + { + // 右クリックルーチンから抜けるときに呼ばれ、 + // 右クリックルーチンに入る前の selProcessLock の状態を + // 取り戻す + setSelProcessLock(storedSelProcessLock); + } + + function commit() + { + // フォーム要素の内容をコミット + var lks = links; + for(var i = 0; i < numLinks; i++) + { + var item = lks[i]; + var type = item.type; + if(type == ltEdit || type == ltCheckBox) item.object.commit(); + } + } + + function isDragPos(x, y) + { + // x y の座標が ドラッグでつかむことのできる座標かどうか + if(!draggable) return false; + if(x >= marginL && y >= marginT && x < imageWidth - marginR && y < imageHeight - marginB) + return false; + if(x<0 || y<0 || x>=imageWidth || y>=imageHeight) return false; + return getMaskPixel(x, y) >= 64; + } + + function internalHitTest(x, y) + { + // onHitTest イベントハンドラ(内部関数) + + if(isDragPos(x, y)) + { + cursor = window.cursorDraggable; + showParentHint = true; + return true; // 不透過 + } + + if(selProcessLock) return false; // 透過 + + if(selClickLock) + { + // 最後にクリックされた位置からマウスがある程度移動しているかどうか + // この判定を行うのは、クリック連打による思わぬ選択肢の選択ミスを + // 防ぐため + if(lastMouseX-3 > x || lastMouseX+3 < x || + lastMouseY-3 > y || lastMouseY+3 < y) + { + selClickLock = false; + } + } + + var n; + n = findLink(x, y); + if(n == -1) + { + return false; // とりあえず透過 + } + else + { + cursor = window.cursorPointed; + return true; // 透過しない + } + } + + function onHitTest(x, y, b) + { + // onHitTest イベントハンドラ + var b = internalHitTest(x - imageLeft, y - imageTop); + return super.onHitTest(x, y, b); + } + + function internalOnMouseDown(x, y, button) + { + // onMouseDown イベントハンドラ(内部関数) + if(button == mbLeft && !selProcessLock) + { + if(!selClickLock) + { + var n = findLink(x, y); + + if(n != -1) + { + processLink(n); + return; + } + } + } + + if(isDragPos(x, y)) + { + // ドラッグ可能位置の場合 + if(window.inStable) + { + dragOriginX = x; + dragOriginY = y; + dragging = true; // ドラッグの開始 + } + } + } + + function onMouseDown(x, y, button) + { + lastMouseDownX = x; + lastMouseDownY = y; + + internalOnMouseDown(x - imageLeft, y - imageTop, button); + + super.onMouseDown(...); + } + + function onMouseUp(x, y, button) + { + dragging = false; + + super.onMouseUp(...); + } + + function cancelDrag() + { + // ドラッグのキャンセル + dragging = false; + } + + function internalMouseMove(x, y) + { + // onMouseMoveなど から呼ばれる + if(dragging) + { + // ドラッグ中 + var px = parent.cursorX; + var py = parent.cursorY; + if(px < 0) px = 0; + if(py < 0) py = 0; + if(px >= parent.width) px = parent.width -1; + if(py >= parent.height) py = parent.height -1; + var l = px - dragOriginX; + var t = py - dragOriginY; + setPos(l, t); + return; + } + + + if(selProcessLock) + { + // 選択ロック中は処理を行わない + if(cursor == window.cursorPointed) cursor = crDefault; // なんとなく・・・ + return; + } + + var n = findLink(x, y); // x, y 位置にリンクがあるかみる + + if(n != lastLink) + { + if(lastLink != -1) + { + highlightLink(lastLink, false); + } + if(n != -1) + { + highlightLink(n, true); + } + lastLink = n; + } + } + + + function onMouseMove(x, y) + { + // onMouseMove イベントハンドラ + super.onMouseMove(...); + + internalMouseMove(x - imageLeft, y - imageTop); + } + + function onMouseLeave() + { + // onMouseLeave イベントハンドラ + if(lastLink != -1) + { + highlightLink(lastLink, false); + lastLink = -1; + } + super.onMouseLeave(...); + } + + function setFocusToLink(n, force = false) + { + // マウスカーソルを リンク番号 n の位置に移動させたり、 + // フォーカスを与えたりする + // force=false の場合はキーボード操作が行われた場合のみ + // マウスカーソルを移動するが、force=true のばあいはキーボードの状態に + // 関わらずマウスカーソルを移動し、フォーカスを設定する + var linkn = links[n]; + if(linkn === void) return; + var left = linkn.x[0]; + var top = linkn.y[0]; + var width = linkn.w[0]; + var height = linkn.h[0]; + var x, y; + if(linkn.type == ltEdit) + { + // カーソルはじゃまなので端っこに置く + if(vertical) + { + x = left + (width>>1); + y = top; + } + else + { + x = left; + y = top + (height>>1); + } + } + else + { + x = left + (width>>1); + y = top + (height>>1); + } + if(!linkn.fixed[0]) + { + // unfixed + x += lineLayerOriginX + getLineLayerLeftOffset(); + y += lineLayerOriginY + getLineLayerTopOffset(); + } + + // キーボードで操作が行われた場合にのみカーソルを + // 移動させるため、該当するキーが押されているかチェックを行う + var sgks = System.getKeyState; + var process = force || sgks(VK_LEFT) || sgks(VK_UP) || sgks(VK_RIGHT) || + sgks(VK_DOWN) || sgks(VK_TAB); + if(process) + { + selClickLock = false; + cursorX = x; + cursorY = y; + internalMouseMove(x, y); + if(force) keyLink = n; // 一応再設定 + } + if(linkn.type == ltEdit || linkn.type == ltCheckBox) + { + var obj = linkn.object; + if(force) obj.focus(); + return obj; + } + if(force) focus(); + return void; + } + + function onKeyDown(key, shift) + { + // キーが押された + if(window.preProcessKeys(key, shift)) return; + + if(!focusable || !numLinks) { return super.onKeyDown(...); } + + var l, r; + if(vertical) + { + l=VK_RIGHT; + r=VK_LEFT; + } + else + { + l=VK_LEFT; + r=VK_RIGHT; + } + + if(!selProcessLock && ((key == VK_UP && !(shift & ssShift)) || key == l || + (key == VK_TAB && (shift & ssShift)))) + { + selClickLock = false; + if(keyLink == -1 || keyLink == 0) + { + var l = focusPrev(); + if(l !== null) return; + keyLink = numLinks - 1; + } + else + { + keyLink--; + } + var obj = setFocusToLink(keyLink); + if(obj !== void) obj.focus(); + } + else if(!selProcessLock && (key == VK_DOWN || key == r || (key == VK_TAB && !(shift & ssShift)))) + { + selClickLock = false; + if(keyLink == -1 || keyLink == numLinks -1) + { + var l = focusNext(); + if(l !== null) return; + keyLink = 0; + } + else + { + keyLink ++; + } + var obj = setFocusToLink(keyLink); + if(obj !== void) obj.focus(); + } + else if(key == VK_SPACE || key == VK_RETURN) + { + if(selProcessLock || keyLink == -1) + window.checkProceedingKey(key, shift); + else + processLink(keyLink); + } + else + { + window.processKeys(key, shift); // window に処理をまかせる + } + } + + function findPrevFocusable(control, layer) + { + if(control.linkNum != 0) return this; else return prevFocusable; + } + + function findNextFocusable(control, layer) + { + if(control.linkNum != numLinks -1) return this; else return layer; + } + + function onBeforeFocus(layer, blured, direction) + { + // フォーカスを得る前に呼ばれる + // 最初のリンクまでカーソルを移動させる + + if(!selProcessLock) + { + // キーボードで操作が行われた場合にのみカーソルを + // 移動させるため、該当するキーが押されているかチェックを行う + var sgks = System.getKeyState; + var process = sgks(VK_LEFT) || sgks(VK_UP) || sgks(VK_RIGHT) || + sgks(VK_DOWN) || sgks(VK_TAB); + + if(process && (blured == null || blured.parent != this)) + { + if(direction) + { + // forward + keyLink = 0; + } + else + { + // backward + keyLink = numLinks-1; + } + var obj = setFocusToLink(keyLink); + if(obj !== void) + { + super.onBeforeFocus(obj, blured, direction); + return; + } + } + + if(blured != null && blured.parent == this) + { + if(direction) + { + if(keyLink == -1 || keyLink == numLinks -1) + keyLink = 0; + else + keyLink++; + } + else + { + if(keyLink == -1 || keyLink == 0) + keyLink = numLinks - 1; + else + keyLink--; + } + + var obj = setFocusToLink(keyLink); + if(obj !== void) + { + super.onBeforeFocus(obj, blured, direction); + return; + } + } + } + + super.onBeforeFocus(...); + } + + function locate(newx, newy) + { + var dx = +newx + marginL - x; + var dy = +newy + marginT - y; + + if(newx !== void) + x = +newx + marginL; + + if(newy !== void) + y = +newy + marginT; + + if(!vertical && newy === void) + { + // 横位置のみの変更 + lineLayerPos += dx; + lineLayerLength += dx; + } + else if(vertical && newx === void) + { + // 縦位置のみの変更 + lineLayerPos += dy; + lineLayerLength += dy; + } + else + { + if(newx === void || newy === void) + { + if(vertical) y = marginT; else x = marginL; + } + if(inLink!=-1) endLinkLine(); + fixLineLayer(); + decideSizeChange(); + initLineLayer(); + } + } + + function processReturn() + { + // 改行を処理する + if(reline()) + { + return autoReturn; + } + return false; + } + + function processGraph(elm) + { + // インライン画像を elm に従って表示する + var key = adjustColorKey(elm.key); + var char = true; + char = +elm.char if elm.char !== void; + if(putGraph(elm.storage, key, char)) return autoReturn; + return false; + } + + function setRuby(text) + { + // 次の文字に対するルビを設定する + currentRuby = text; + } + + function showBreakGlyph(glyphobj, storage, key) + { + // 画面に行待ち/ページ待ち記号を表示する + glyphobj.parent = this; // 親を自分にする + glyphobj.loadImages(storage, key); // 画像を読み込む + glyphobj.bringToFront(); // 最前面に + + if(glyphFixedPosition) + { + glyphobj.setPos(glyphFixedLeft, glyphFixedTop); + } + else + { + if(!vertical) + { + glyphobj.setPos(lineLayerPos + lineLayerOriginX + getLineLayerLeftOffset(), + y + lineSize + lineSpacing - glyphobj.height); + } + else + { + glyphobj.setPos(x - lineSpacing- (lineSize>>1) - (glyphobj.width>>1), + lineLayerPos + lineLayerOriginY + getLineLayerTopOffset()); + } + } + glyphobj.visible = true; + } + + function showLineBreakGlyph(glyphobj) + { + // 行待ち記号を表示 + showBreakGlyph(glyphobj, lineBreakGlyph, lineBreakGlyphKey); + } + + function showPageBreakGlyph(glyphobj) + { + // ページ待ち記号を表示 + showBreakGlyph(glyphobj, pageBreakGlyph, pageBreakGlyphKey); + } + + function setGlyph(elm) + { + // クリック待ち記号を設定 + if(elm.line !== void) + { + lineBreakGlyph = elm.line; + lineBreakGlyphKey = elm.linekey; + } + if(elm.page !== void) + { + pageBreakGlyph = elm.page; + pageBreakGlyphKey = elm.pagekey; + } + glyphFixedPosition = +elm.fix if elm.fix !== void; + glyphFixedLeft = +elm.left if elm.left !== void; + glyphFixedTop = +elm.top if elm.top !== void; + } + + function setIndent() + { + // インデントを現在位置に設定 + if(vertical) indentxpos = y - marginT; else indentxpos = x - marginL; + } + + function resetIndent() + { + // インデントを解除 + indentxpos = 0; + } + + function assignComp() + { + // 対になるレイヤの内容をコピー + assign(comp); + } + + function beginTransition(elm) + { + // elm に従ってトランジションを行う + super.beginTransition(elm, comp); + } + + function internalAssign(src, copyvisiblestate) + { + // レイヤの情報をコピー + // ( コピーする情報が膨大なのでちょっと実行時間がかかる ) + + // このレイヤ自身の画像、位置、透明度などのコピー + assignImages(src); + if(copyvisiblestate) + assignVisibleState(src); // assignImages は可視・不可視などの情報はコピーしないため + + focusable = src.focusable; + + // links のコピー + { + invalidateLinkObjects(); // 一応オブジェクトはすべて無効化 + var tl = links, sl = src.links; + tl.count = sl.count; + for(var i = sl.count-1; i>=0; i--) + { + if(sl[i] === void) continue; + var tl_d = (tl[i] = %[]); + var sl_d = sl[i]; + (Dictionary.assign incontextof tl_d)(sl_d); + // ただし、x, y, w, h, fixed の各メンバは実際に内容を assign + // しないと駄目 + (tl_d.x = []).assign(sl_d.x); + (tl_d.y = []).assign(sl_d.y); + (tl_d.w = []).assign(sl_d.w); + (tl_d.h = []).assign(sl_d.h); + (tl_d.fixed = []).assign(sl_d.fixed); + + // タイプに従って + var type = tl_d.type; + if(type == ltButton) + { + // ボタン + var sl_d_object = sl_d.object; + var object = new LinkButtonLayer(window, this); + // object は再作成 + object.assign(sl_d_object); + tl_d.object = object; + } + else if(type == ltEdit) + { + // 単一行エディット + var sl_d_object = sl_d.object; + var object = new LinkEditLayer(window, this); + // object は再作成 + object.assign(sl_d_object); + tl_d.object = object; + } + else if(type == ltCheckBox) + { + // チェックボックス + var sl_d_object = sl_d.object; + var object = new LinkCheckBoxLayer(window, this); + // object は再作成 + object.assign(sl_d_object); + tl_d.object = object; + } + } + } + + + // lineLayerLinks のコピー + { + var tl = lineLayerLinks, sl = src.lineLayerLinks; + tl.count = sl.count; + for(var i = sl.count-1; i>=0; i--) + { + (Dictionary.assign incontextof (tl[i] = %[]))(sl[i]); + } + } + + // lineLayer の位置、サイズ、画像、フォント情報をコピー + { + var tl = lineLayer, sl = src.lineLayer; + tl.assignImages(sl); + tl.assignVisibleState(sl); + var tf = tl.font, sf = sl.font; + tf.face = sf.face; + tf.angle = sf.angle; + tf.bold = sf.bold; + tf.italic = sf.italic; + tf.height = sf.height; + } + + // そのほか + highlightLayer.visible = false; + keyLink = -1; + imageModified = true; + + // その他の情報のコピー + // [start_assign_vars] と [end_assign_vars] の間は + // perl によって自動生成されるので、このマークを消したり、 + // 二つのマークの間を編集したりしないこと。 + // [start_assign_vars] + frameGraphic = src.frameGraphic; + frameKey = src.frameKey; + frameColor = src.frameColor; + frameOpacity = src.frameOpacity; + marginL = src.marginL; + marginT = src.marginT; + marginR = src.marginR; + marginB = src.marginB; + marginRCh = src.marginRCh; + x = src.x; + y = src.y; + relinexpos = src.relinexpos; + isLastLine = src.isLastLine; + indentxpos = src.indentxpos; + linkFilled = src.linkFilled; + numLinks = src.numLinks; + selProcessLock = src.selProcessLock; + storedSelProcessLock = src.storedSelProcessLock; + defaultLinkColor = src.defaultLinkColor; + defaultLinkOpacity = src.defaultLinkOpacity; + defaultFontSize = src.defaultFontSize; + fontSize = src.fontSize; + _fontSize = src._fontSize; + defaultLineSize = src.defaultLineSize; + reserveLineSize = src.reserveLineSize; + lineSize = src.lineSize; + defaultRubySize = src.defaultRubySize; + rubySize = src.rubySize; + _rubySize = src._rubySize; + defaultRubyOffset = src.defaultRubyOffset; + rubyOffset = src.rubyOffset; + _rubyOffset = src._rubyOffset; + defaultLineSpacing = src.defaultLineSpacing; + lineSpacing = src.lineSpacing; + defaultPitch = src.defaultPitch; + pitch = src.pitch; + defaultShadow = src.defaultShadow; + shadow = src.shadow; + defaultEdge = src.defaultEdge; + edge = src.edge; + defaultShadowColor = src.defaultShadowColor; + shadowColor = src.shadowColor; + defaultEdgeColor = src.defaultEdgeColor; + edgeColor = src.edgeColor; + defaultBold = src.defaultBold; + bold = src.bold; + defaultFace = src.defaultFace; + userFace = src.userFace; + face = src.face; + defaultChColor = src.defaultChColor; + chColor = src.chColor; + defaultAntialiased = src.defaultAntialiased; + antialiased = src.antialiased; + vertical = src.vertical; + currentRuby = src.currentRuby; + lastDrawnCh = src.lastDrawnCh; + edgeExtent = src.edgeExtent; + edgeEmphasis = src.edgeEmphasis; + sizeChanged = src.sizeChanged; + nextClearFlag = src.nextClearFlag; + lineLayerBase = src.lineLayerBase; + lineLayerPos = src.lineLayerPos; + lineLayerLength = src.lineLayerLength; + lineLayerOriginX = src.lineLayerOriginX; + lineLayerOriginY = src.lineLayerOriginY; + align = src.align; + defaultAutoReturn = src.defaultAutoReturn; + autoReturn = src.autoReturn; + lineBreakGlyph = src.lineBreakGlyph; + lineBreakGlyphKey = src.lineBreakGlyphKey; + pageBreakGlyph = src.pageBreakGlyph; + pageBreakGlyphKey = src.pageBreakGlyphKey; + glyphFixedPosition = src.glyphFixedPosition; + glyphFixedLeft = src.glyphFixedLeft; + glyphFixedTop = src.glyphFixedTop; + draggable = src.draggable; + selClickLock = src.selClickLock; + lastMouseX = src.lastMouseX; + lastMouseY = src.lastMouseY; + // [end_assign_vars] + } + + function assign(src) + { + internalAssign(src, true); + } + + function store() + { + // 現在の状態を辞書配列に記録し、その辞書配列を返す + // [start_store_vars] と [end_store_vars] の間は + // (略) + var dic = super.store(); + // [start_store_vars] + dic.frameGraphic = frameGraphic; + dic.frameKey = frameKey; + dic.frameColor = frameColor; + dic.frameOpacity = frameOpacity; + dic.marginL = marginL; + dic.marginT = marginT; + dic.marginR = marginR; + dic.marginB = marginB; + dic.marginRCh = marginRCh; + dic.defaultLinkColor = defaultLinkColor; + dic.defaultLinkOpacity = defaultLinkOpacity; + dic.defaultFontSize = defaultFontSize; + dic.defaultLineSize = defaultLineSize; + dic.defaultRubySize = defaultRubySize; + dic.defaultRubyOffset = defaultRubyOffset; + dic.defaultLineSpacing = defaultLineSpacing; + dic.defaultPitch = defaultPitch; + dic.defaultShadow = defaultShadow; + dic.defaultEdge = defaultEdge; + dic.defaultShadowColor = defaultShadowColor; + dic.defaultEdgeColor = defaultEdgeColor; + dic.defaultBold = defaultBold; + dic.defaultFace = defaultFace; + dic.defaultChColor = defaultChColor; + dic.vertical = vertical; + dic.edgeExtent = edgeExtent; + dic.edgeEmphasis = edgeEmphasis; + dic.defaultAutoReturn = defaultAutoReturn; + dic.lineBreakGlyph = lineBreakGlyph; + dic.lineBreakGlyphKey = lineBreakGlyphKey; + dic.pageBreakGlyph = pageBreakGlyph; + dic.pageBreakGlyphKey = pageBreakGlyphKey; + dic.glyphFixedPosition = glyphFixedPosition; + dic.glyphFixedLeft = glyphFixedLeft; + dic.glyphFixedTop = glyphFixedTop; + dic.draggable = draggable; + // [end_store_vars] + return dic; + } + + function restore(dic) + { + imageModified = true; + // 状態を dic から読み出す + // [start_restore_vars] と [end_restore_vars] の間は + // (略) + // [start_restore_vars] + frameGraphic = dic.frameGraphic if dic.frameGraphic !== void; + frameKey = dic.frameKey if dic.frameKey !== void; + frameColor = dic.frameColor if dic.frameColor !== void; + frameOpacity = dic.frameOpacity if dic.frameOpacity !== void; + marginL = dic.marginL if dic.marginL !== void; + marginT = dic.marginT if dic.marginT !== void; + marginR = dic.marginR if dic.marginR !== void; + marginB = dic.marginB if dic.marginB !== void; + marginRCh = dic.marginRCh if dic.marginRCh !== void; + defaultLinkColor = dic.defaultLinkColor if dic.defaultLinkColor !== void; + defaultLinkOpacity = dic.defaultLinkOpacity if dic.defaultLinkOpacity !== void; + defaultFontSize = dic.defaultFontSize if dic.defaultFontSize !== void; + defaultLineSize = dic.defaultLineSize if dic.defaultLineSize !== void; + defaultRubySize = dic.defaultRubySize if dic.defaultRubySize !== void; + defaultRubyOffset = dic.defaultRubyOffset if dic.defaultRubyOffset !== void; + defaultLineSpacing = dic.defaultLineSpacing if dic.defaultLineSpacing !== void; + defaultPitch = dic.defaultPitch if dic.defaultPitch !== void; + defaultShadow = dic.defaultShadow if dic.defaultShadow !== void; + defaultEdge = dic.defaultEdge if dic.defaultEdge !== void; + defaultShadowColor = dic.defaultShadowColor if dic.defaultShadowColor !== void; + defaultEdgeColor = dic.defaultEdgeColor if dic.defaultEdgeColor !== void; + defaultBold = dic.defaultBold if dic.defaultBold !== void; + defaultFace = dic.defaultFace if dic.defaultFace !== void; + defaultChColor = dic.defaultChColor if dic.defaultChColor !== void; + vertical = dic.vertical if dic.vertical !== void; + edgeExtent = dic.edgeExtent if dic.edgeExtent !== void; + edgeEmphasis = dic.edgeEmphasis if dic.edgeEmphasis !== void; + defaultAutoReturn = dic.defaultAutoReturn if dic.defaultAutoReturn !== void; + lineBreakGlyph = dic.lineBreakGlyph if dic.lineBreakGlyph !== void; + lineBreakGlyphKey = dic.lineBreakGlyphKey if dic.lineBreakGlyphKey !== void; + pageBreakGlyph = dic.pageBreakGlyph if dic.pageBreakGlyph !== void; + pageBreakGlyphKey = dic.pageBreakGlyphKey if dic.pageBreakGlyphKey !== void; + glyphFixedPosition = dic.glyphFixedPosition if dic.glyphFixedPosition !== void; + glyphFixedLeft = dic.glyphFixedLeft if dic.glyphFixedLeft !== void; + glyphFixedTop = dic.glyphFixedTop if dic.glyphFixedTop !== void; + draggable = dic.draggable if dic.draggable !== void; + // [end_restore_vars] + super.restore(dic); + } + + function atEndOfTransition(src, withchildren, exchange) + { + // atEndOfTransition オーバーライド + super.atEndOfTransition(...); + if(src == null) + { + //・メッセージレイヤ、前景レイヤが、srcなしでトランジション (children=true) + // 何もしなくてよいが、終了後それらの子レイヤは自動的に非表示になる。 + } + else + { + //・メッセージレイヤ、前景レイヤが、srcありでトランジション (children=true) + // 重要な情報をトランジション元と交換、exchange=false の場合はさらに + // トランジション元の情報をトランジション先にコピー。ただし、このコピーの際に + // ウィンドウの可視・不可視の情報はコピーしない。 + assign(src, false); + exchangeInfo(); + window.swapMessageLayer(id); + } + } + + function assignTransSrc() + { + // トランジションもとをコピーする + assign(comp, true); + } + + function exchangeInfo() + { + // comp と情報を取り替える + // すでに 画像の内容、ツリー構造は取り変わっているので + // 名前などを取り替える + // また、ウィンドウの管理情報も更新するようにする + var src = comp; + var tmp = src.name; + src.name = name; + name = tmp; + } + + function setHiddenStateByUser(b) + { + // ユーザが右クリックなどでメッセージレイヤを一時的に隠すときに + // 呼ばれる + if(b) + { + visibleBeforeUserInvisible = visible; + invisibleByUser = true; // ユーザにより一時的に不可視 + visible = false; + } + else + { + invisibleByUser = false; // 可視 + visible = visibleBeforeUserInvisible; + } + } + +} + + +// TJS スクリプトはここで終わり +" +END_OF_TJS_SCRIPT +# "; /* + +# assign/store/restore でコピーすべき変数の再生成を行う perl スクリプト + +open FH, "MessageLayer.tjs" or die; +undef($/); +$content = ; + +$list_assign = ''; +$list_store = ''; +$list_restore = ''; +while($content =~ /\/\*(\w+)\*\/var\s+(\w+)/gs) +{ + $a = $1; + $v = $2; + if($a =~ /C/) + { $list_assign .= "\t\t$v = src.$v;\n"; } + if($a =~ /S/) + { + $list_store .= "\t\tdic.$v = $v;\n"; + $list_restore .= "\t\t$v = dic.$v if dic.$v !== void;\n"; + } +} + +$content =~ +s/\t\t\/\/ \[start_assign_vars\]\n.*?\t\t\/\/ \[end_assign_vars\]/\t\t\/\/ \[start_assign_vars\]\n$list_assign\t\t\/\/ \[end_assign_vars\]/s; +$content =~ +s/\t\t\/\/ \[start_store_vars\]\n.*?\t\t\/\/ \[end_store_vars\]/\t\t\/\/ \[start_store_vars\]\n$list_store\t\t\/\/ \[end_store_vars\]/s; +$content =~ +s/\t\t\/\/ \[start_restore_vars\]\n.*?\t\t\/\/ \[end_restore_vars\]/\t\t\/\/ \[start_restore_vars\]\n$list_restore\t\t\/\/ \[end_restore_vars\]/s; + +open FH, ">MessageLayer.tjs" or die; +print FH $content; + + +# */ diff --git a/bin/xp3tools/README.txt b/bin/xp3tools/README.txt new file mode 100644 index 0000000..6cc44bf --- /dev/null +++ b/bin/xp3tools/README.txt @@ -0,0 +1,176 @@ + XP3 / KIRIKIRI TOOLSET DOCUMENTATION + + Edward Keyes, ed-at-insani-dot-org + Last modified: 2006-07-08 + + +I'M-TOO-BUSY-TO-READ-DOCUMENTATION SUMMARY + +* xp3-extract.exe to extract XP3 archives + +* xp3-repack.exe to repack them + +* xp3-wordwrap.exe to word-wrap scripts + +* To make the wordwrapping work, you will need to do something with the + three .tjs files included in this tool set. You'll need to read below + to find out exactly what, when you're ready to do this. + +* The "library.zip" file is part of the Python library. Don't unpack it. + + +RUNNING THE COMMANDS + +The utilities were written in Python, and precompiled binaries for the +Windows platform have been included, along with the appropriate DLLs and +support files (which are mostly packed up in library.zip and are accessed +directly by the executables, so you don't have to do anything). If you are +running on a different platform or prefer running directly from the source, +the Python code has also been included. The command-line options are the +same in both cases. + + xp3-extract.exe [encryption] + +This command unpacks an XP3 archive to a new (or existing) directory. It +will create subdirectories as needed if the archive contains them. The +XP3 archive allows unicode filenames, and hopefully your operating system +does too, otherwise you may get some errors with Japanese characters. + +The optional encryption parameter specifies what to do with files tagged +as encrypted. The toolset currently implements at least the "fate_trial" +and "fate_full" encryption schemes for the trial and full commercial +versions of Fate Stay Night. If you run across a game which seems to use +a different encryption scheme and want the toolset updated to include it, +please contact me with an example archive or few. + + xp3-repack.exe [encryption] + +This is the opposite command from xp3-extract, taking a directory of files +(including subdirectories) and packing them into a new XP3 archive. +Compression will be used when it saves more than 5% of a file's size, +otherwise they will be stored uncompressed for speed of access. + +The optional encryption parameter specifies whether all of the files should +be encrypted by the specified method; if not specified, files will be left +unencrypted. + + xp3-wordwrap.exe + +While the standard Kirikiri engine can display English text, it does not know +how to word-wrap automatically, since Japanese text doesn't require it. To +enable this, it is necessary to patch several Kirikiri system files (see +below) and to preprocess your script files with the xp3-wordwrap tool to add +wordwrapping hint commands. + +The preprocessing tends to destroy the editability of your script, so it is +recommended that you keep separate copies of your files and only process +them when you create a patch. You don't need to process .ks files which +don't contain text requiring word-wrapping. + + +HOW TO TRANSLATE A GAME + +The script files are usually stored in the "scenario" directory in an XP3 +archive, as .ks files. These files are in the Shift-JIS character encoding +and are editable with a normal text editor. They consist of a mix of actual +script text and formatting/presentation commands, usually denoted by square +brackets. Some of them, such as the "first.ks" file, may be pure commands +and not contain any script text. + +Additional Japanese phrases for the user interface can also be scattered +through .tjs files in the "system" directory. The most fruitful thing to +do is to just search in this directory and in "scenario" for any strings +you run across while exploring the game, to find out which files to edit. + +Images may need to be translated as well, and they are present in other +subdirectories. Many games will just use standard JPEG and PNG image formats, +but some may use Kirikiri-specific formats like TLG, requiring an additional +conversion step before editing. Beta tools exist for the TLG5 image format, +but I've not included them in this toolset, and the TLG6 format is also being +used in some games but is presently unexamined. If you run across a game +using one of these that you want to translate, contact me for assistance. + + +WHAT TO DO WITH THE .TJS FILES + +To enable the word-wrapping, some additional functionality needs to be +integrated into the Kirikiri system files. Specifically, modifications +need to be made to: + + HistoryLayer.tjs + MainWindow.tjs + MessageLayer.tjs + +which are typically stored in the "system" subdirectory of an XP3 archive. + +Premodified versions of those files are included in this toolset, taken from +the game "Until We Meet Again". If you're feeling lucky and/or lazy, you +can just toss these files into your patch as is and cross your fingers. + +However, if your game is using a somewhat different version of the Kirikiri +engine, this may cause problems. The better approach is to manually edit +the three corresponding files in your game to insert the modified sections. +All modifications are denoted by "insani" comment strings, so you can search +for them, find the parallel sections in your game files, and copy-and-paste +to match. There are just a couple of modified sections per file, so this +shouldn't take you very long, and is recommended instead of the just-try-it +method. + + +HELPFUL TIPS + +Usually as part of its startup script, a Kirikiri game will look for files +named "patch.xp3", "patch2.xp3", etc. and use the contents to override files +in the main archive(s). You can use this to distribute your own patches +without needing to modify the original archives at all, and it's great for +maintaining multiple versions of your patch for testing purposes. + +Note that when constructing a patch archive, it should be flat (without +subdirectories), even if the files you override are in a subdirectory: the +engine goes solely by the bare filename, so also make sure your names are +globally unique if you add new files. + +While you're editing the .tjs files, you may also want to play with the +margin settings defined there. + +Sometimes files will be marked as encrypted in an archive even though they +are actually not. In other words, examine them to see if they're okay first +before getting worried about the warning messages you get from xp3-extract. + + +POSSIBLE TROUBLE SPOTS + +Unicode filenames can be a point of trouble. The tools make a good effort +to use the encoding provided by your operating system, but XP3 archives use +16-bit unicode filenames internally. In some cases I've seen a filesystem +mangle the unicode slightly so that the filenames read back are not bit-for- +bit identical to the ones originally used when the archive was extracted. +Just bear this in mind if your patch provokes some inexplicable "missing +file" errors even though you're sure you've included those files. + +Some archives purposefully include a small "do not copy this data" text +file with a huge name. While the tools will attempt to extract this file +normally, many operating systems will choke on large filenames, and this +will provoke an error message. The extraction will continue regardless in +this case, but that (probably useless) file will be skipped. + +Some games do not come with separate XP3 archive files, but instead +incorporate one directly into their executable. The tools are unfortunately +not intelligent enough to deal with this situation yet. If you're handy with +a hex editor you can copy-and-paste the XP3 file out of the executable. Look +for probably the final instance of the signature string: + + XP3\x0D\x0A\x20\x0A\x1A\x8B\x67\x01 + +to denote the start of the embedded archive. Or if you have no idea what I +just said, you can just contact me and I can do it for you. + +I'm not completely sure whether the encryption options for the full Fate +Stay Night game are correct, since I do not own a copy myself for proper +testing. It should be pretty close, at least. + +The word-wrapping code may do odd things in unanticipated cases, and won't +handle cases of programmatically-generated text well (such as a variable for +the name of the protagonist). If you run across a case where it is failing, +please contact me for a fix or consider manually editing the wrapping hints +to match what should be happening. diff --git a/bin/xp3tools/insani.py b/bin/xp3tools/insani.py new file mode 100644 index 0000000..1b546c3 --- /dev/null +++ b/bin/xp3tools/insani.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python + +# insani.py +# +# Utility routines to support visual-novel game file extraction and +# modification. +# +# Last modified 2006-06-12, Edward Keyes, ed-at-insani-dot-org + +LITTLE_ENDIAN = 1 +BIG_ENDIAN = -1 + +BYTE_LENGTH = 1 +SHORT_LENGTH = 2 +INT_LENGTH = 4 +LONG_LENGTH = 8 + +ERROR_NONE = 0 +ERROR_WARNING = 1 +ERROR_ABORT = 2 + + +def read_unsigned(infile, size = INT_LENGTH, endian = LITTLE_ENDIAN) : +# Reads a binary unsigned number from the file of the specified +# length and endianness. The size isn't restricted to the predefined +# constants. + + result = long(0) + for i in xrange(size) : + temp=long(ord(infile.read(1))) + if endian == LITTLE_ENDIAN : + result |= (temp << (8*i)) + elif endian == BIG_ENDIAN : + result = (result << 8) | temp + else : + raise 'read_unsigned', 'Unknown endian specification' + return result + + +def write_unsigned(outfile, value, size = INT_LENGTH, endian = LITTLE_ENDIAN) : +# Writes a binary unsigned number of the specified length and +# endianness to the file. Does not check to see if the size is +# sufficient to fully represent the number. The size isn't restricted +# to the predefined constants. + + for i in xrange(size) : + if endian == LITTLE_ENDIAN : + temp = (value >> (8*i)) & 0x00FF + elif endian == BIG_ENDIAN : + temp = (value >> (8*(size-i-1))) & 0x00FF + else : + raise 'write_unsigned', 'Unknown endian specification' + outfile.write(chr(temp)) + + +def read_string(infile) : +# Reads a null-terminated string from the given file. This is not very +# efficient for long strings, but should be okay for reading filenames +# and script lines. Does not return the final \0, but does keep any line +# feeds and carriage returns encountered. Also terminates the string at +# EOF. + + result = '' + temp = infile.read(1) + while (temp != '') and (temp != '\0') : + result += temp + temp = infile.read(1) + return result + + +def write_string(outfile, value) : +# Writes a null-terminated string to the given file. The given string is +# assumed to not have a terminator already. + + outfile.write(value) + outfile.write('\0') + + +def escape_string(value) : +# Escapes a string, using hex values of nonprintable characters, for safe +# printing to the console. Probably a stupid way to do it. + + result = '' + for temp in value : + tempval=ord(temp) + if (tempval<32) or (tempval>126) : + result += '#%02X' % tempval + else : + result += temp + return result + + +def unescape_string(value) : +# Unescapes a string encoded with the above routine, replacing hex values +# by real characters. + + result = '' + i = 0 + while i= 0 : + if severity != ERROR_NONE : + print 'Expected "\\x00" at position 0x%X but found "%s".' % \ + (found_error,escape_string(temp)) + if severity == ERROR_ABORT : + print 'Aborting!' + raise 'assert_zeroes' + return False + else : + return True + + +def write_zeroes(outfile, num) : +# Just writes the specified number of zero bytes to the file. Some lame +# optimization for large numbers. + + if num >= 1024 : + onek = '\0' * 1024 + for i in xrange(num // 1024) : + outfile.write(onek) + for i in xrange(num % 1024) : + outfile.write('\0') + + + +# Perform some unit tests if we're just running this module by itself. + +if __name__ == '__main__' : + from StringIO import StringIO + + teststring_little = '\x01\x02\x03\x04\x00\x00\x00\x00' + teststring_big = '\x00\x00\x00\x00\x04\x03\x02\x01' + testvalue = 0x04030201 + + testfile = StringIO(teststring_little) + value = read_unsigned(testfile,LONG_LENGTH) + assert (value == testvalue) + testfile.close() + + testfile = StringIO(teststring_big) + value = read_unsigned(testfile,LONG_LENGTH,BIG_ENDIAN) + assert (value == testvalue) + testfile.close() + + testfile = StringIO() + write_unsigned(testfile,testvalue,LONG_LENGTH) + assert (teststring_little == testfile.getvalue()) + testfile.close() + + testfile = StringIO() + write_unsigned(testfile,testvalue,LONG_LENGTH,BIG_ENDIAN) + assert (teststring_big == testfile.getvalue()) + testfile.close() + + teststring = 'foobar\0' + testvalue = 'foobar' + + testfile = StringIO(teststring) + assert (testvalue == read_string(testfile)) + testfile.close() + + testfile = StringIO() + write_string(testfile,testvalue) + assert (teststring == testfile.getvalue()) + testfile.close() + + testvalue = '\x01foo\xFE' + testescape = '#01foo#FE' + assert (escape_string(testvalue) == testescape) + assert (unescape_string(testescape) == testvalue) + + teststring = 'foobar\0\0\0\0' + testvalue = 'foobar' + + testfile = StringIO(teststring) + assert (assert_string(testfile,testvalue,ERROR_NONE)) + assert (assert_zeroes(testfile,4,ERROR_NONE)) + testfile.close() + + testfile = StringIO() + write_zeroes(testfile,4) + assert ('\0\0\0\0' == testfile.getvalue()) + testfile.close() + + print 'All unit tests completed successfully.' diff --git a/bin/xp3tools/insani.pyc b/bin/xp3tools/insani.pyc new file mode 100644 index 0000000..f0daef5 Binary files /dev/null and b/bin/xp3tools/insani.pyc differ diff --git a/bin/xp3tools/library.zip b/bin/xp3tools/library.zip new file mode 100644 index 0000000..9bdaa8e Binary files /dev/null and b/bin/xp3tools/library.zip differ diff --git a/bin/xp3tools/w9xpopen.exe b/bin/xp3tools/w9xpopen.exe new file mode 100644 index 0000000..a6d8b80 Binary files /dev/null and b/bin/xp3tools/w9xpopen.exe differ diff --git a/bin/xp3tools/xp3-extract.exe b/bin/xp3tools/xp3-extract.exe new file mode 100644 index 0000000..f4e561e Binary files /dev/null and b/bin/xp3tools/xp3-extract.exe differ diff --git a/bin/xp3tools/xp3-extract.py b/bin/xp3tools/xp3-extract.py new file mode 100644 index 0000000..09066ad --- /dev/null +++ b/bin/xp3tools/xp3-extract.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python + +# KiriKiri .XP3 archive extraction tool +# +# Extracts an .XP3 archive to a directory of files, including any +# subdirectory structure. Does aggressive error-checking, so some +# assertions may have to be disabled with nonstandard XP3 files. +# +# Optionally handles Fate Stay Night encryption. +# +# Last modified 2006-07-08, Edward Keyes, ed-at-insani-dot-org + +import sys, os, zlib +from array import array +from cStringIO import StringIO +from insani import * + + +if len(sys.argv) not in (3,4) : + print 'Please give an XP3 archive filename and a desired output directory on the\ncommand line. Append an optional encryption type.' + sys.exit(0) + + +def read_entry(infile) : +# Reads a file entry and creates a data structure from it. + result = {} + assert_string(infile,'File',ERROR_ABORT) + entrylength = read_unsigned(infile,LONG_LENGTH) + assert_string(infile,'info',ERROR_ABORT) + infolength = read_unsigned(infile,LONG_LENGTH) + result['encrypted'] = read_unsigned(infile) + result['origsize'] = read_unsigned(infile,LONG_LENGTH) + result['compsize'] = read_unsigned(infile,LONG_LENGTH) + filenamelength = read_unsigned(infile,SHORT_LENGTH) + result['filepath'] = u'' + for i in xrange(filenamelength) : + result['filepath'] += unichr(read_unsigned(infile,SHORT_LENGTH)) + assert (infolength == filenamelength*2+22) + assert_string(infile,'segm',ERROR_ABORT) + numsegments = read_unsigned(infile,LONG_LENGTH) // 28 # 28 bytes per seg. + result['segments'] = [] + compsize = origsize = 0 + for i in xrange(numsegments) : + segment = {} + segment['compressed'] = read_unsigned(infile) + segment['offset'] = read_unsigned(infile,LONG_LENGTH) + segment['origsize'] = read_unsigned(infile,LONG_LENGTH) + segment['compsize'] = read_unsigned(infile,LONG_LENGTH) + compsize += segment['compsize'] + origsize += segment['origsize'] + result['segments'].append(segment) + assert (compsize == result['compsize']) + assert (origsize == result['origsize']) + assert_string(infile,'adlr',ERROR_ABORT) + assert (read_unsigned(infile,LONG_LENGTH) == 4) + result['adler'] = read_unsigned(infile) + assert (entrylength == filenamelength*2+numsegments*28+62) + return result + + +def decrypt(outfile, encryption) : +# Performs standard types of XP3 decryption on a file. + if encryption == 'fate_trial' : + outfile.seek(0) + data = array('B',outfile.read()) + for i in xrange(len(data)) : + data[i] ^= 0x0052 + if len(data)>30 : + data[30] ^= 0x0001 + if len(data)>55355 : + data[55355] ^= 0x0020 + outfile.seek(0) + outfile.write(data.tostring()) + elif encryption == 'fate_full' : # Not 100% sure about these values. + outfile.seek(0) + data = array('B',outfile.read()) + for i in xrange(len(data)) : + data[i] ^= 0x0036 + if len(data)>19 : + data[19] ^= 0x0001 + outfile.seek(0) + outfile.write(data.tostring()) + else : + print 'WARNING: File is encrypted but no known encryption type specified' + print 'Known types include: fate_trial, fate_full' + + +arcfile=open(sys.argv[1],'rb') +filesize=os.stat(sys.argv[1]).st_size +dirname=sys.argv[2] +if (len(sys.argv)==4) : + encryption = sys.argv[3] +else : + encryption = 'none' + +# Read header and index structure +assert_string(arcfile,'XP3\x0D\x0A \x0A\x1A\x8B\x67\x01',ERROR_ABORT) +indexoffset = read_unsigned(arcfile,LONG_LENGTH) +assert (indexoffset < filesize) +arcfile.seek(indexoffset) +assert_string(arcfile,'\x01',ERROR_WARNING) +compsize = read_unsigned(arcfile,LONG_LENGTH) +origsize = read_unsigned(arcfile,LONG_LENGTH) +assert (indexoffset+compsize+17 == filesize) +uncompressed = arcfile.read(compsize).decode('zlib') +assert (len(uncompressed) == origsize) +indexbuffer = StringIO(uncompressed) + +# Read through the index structure, extracting each file +while (indexbuffer.tell() < origsize) : + entry = read_entry(indexbuffer) + print 'Extracting %s (%d -> %d bytes)' % \ + (entry['filepath'].encode(sys.getfilesystemencoding()), + entry['compsize'], entry['origsize']) + pathcomponents = entry['filepath'].split(u'/') + # Paths inside the XP3 use forward slashes as separators + filepath = dirname + for elem in pathcomponents: + if not os.path.isdir(filepath) : # Create directory if it's not there + os.mkdir(filepath) # Won't do this for the final filename + filepath=os.path.join(filepath,elem.encode(sys.getfilesystemencoding())) + outbuffer = StringIO() + adler = zlib.adler32('') # Initialize checksum for incremental updates + for segment in entry['segments'] : + arcfile.seek(segment['offset']) + if (segment['compressed']) : + data = zlib.decompress(arcfile.read(segment['compsize'])) + else : + data = arcfile.read(segment['compsize']) + assert (len(data) == segment['origsize']) + outbuffer.write(data) + adler = zlib.adler32(data,adler) + if entry['encrypted'] : + decrypt(outbuffer,encryption) + if (((adler + 0x0100000000L) & 0x00FFFFFFFFL) != entry['adler']) : + # Convert to unsigned 32-bit integer + print 'Checksum error, but continuing...' + try : + # Why worry about exceptions? There's a known problem with archives + # including a "do not copy this!" file with a huge filename that many + # filesystems will choke on. We still want to continue if we hit that. + outfile = open(filepath,'wb') + outfile.write(outbuffer.getvalue()) + outfile.close() + except IOError : + print 'Problems writing %s, but continuing...' % filepath + +indexbuffer.close() +arcfile.close() diff --git a/bin/xp3tools/xp3-repack.exe b/bin/xp3tools/xp3-repack.exe new file mode 100644 index 0000000..4266324 Binary files /dev/null and b/bin/xp3tools/xp3-repack.exe differ diff --git a/bin/xp3tools/xp3-repack.py b/bin/xp3tools/xp3-repack.py new file mode 100644 index 0000000..28d96fa --- /dev/null +++ b/bin/xp3tools/xp3-repack.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +# KiriKiri .XP3 archive repacking tool +# +# Packs a directory of files into an .XP3 archive, including any +# subdirectory structure. +# +# Optionally handles Fate Stay Night encryption. +# +# Last modified 2006-07-08, Edward Keyes, ed-at-insani-dot-org + +import string, sys, os, zlib +from array import array +from cStringIO import StringIO +from insani import * + + +if len(sys.argv) not in (3,4) : + print 'Please give a input directory and a desired output XP3 archive filename on\nthe command line. Append an optional encryption type.' + sys.exit(0) + + +def write_entry(outfile, entry) : +# Writes a file entry data structure to the file. + outfile.write('File') + write_unsigned(outfile,len(entry['filepath'])*2+len(entry['segments'])*28 \ + +62, LONG_LENGTH) + outfile.write('info') + write_unsigned(outfile,len(entry['filepath'])*2+22,LONG_LENGTH) + write_unsigned(outfile,entry['encrypted']) + write_unsigned(outfile,entry['origsize'],LONG_LENGTH) + write_unsigned(outfile,entry['compsize'],LONG_LENGTH) + write_unsigned(outfile,len(entry['filepath']),SHORT_LENGTH) + for char in entry['filepath'] : + write_unsigned(outfile,ord(char),SHORT_LENGTH) + outfile.write('segm') + write_unsigned(outfile,len(entry['segments'])*28,LONG_LENGTH) + for segment in entry['segments'] : + write_unsigned(outfile,segment['compressed']) + write_unsigned(outfile,segment['offset'],LONG_LENGTH) + write_unsigned(outfile,segment['origsize'],LONG_LENGTH) + write_unsigned(outfile,segment['compsize'],LONG_LENGTH) + outfile.write('adlr') + write_unsigned(outfile,4,LONG_LENGTH) + write_unsigned(outfile,entry['adler']) + + +def encrypt(outfile, encryption) : +# Performs standard types of XP3 encryption on a file. This is dentical to +# the decrypt function except for the error message since it's all XOR. + if encryption == 'fate_trial' : + outfile.seek(0) + data = array('B',outfile.read()) + for i in xrange(len(data)) : + data[i] ^= 0x0052 + if len(data)>30 : + data[30] ^= 0x0001 + if len(data)>55355 : + data[55355] ^= 0x0020 + outfile.seek(0) + outfile.write(data.tostring()) + elif encryption == 'fate_full' : # Not 100% sure about these values. + outfile.seek(0) + data = array('B',outfile.read()) + for i in xrange(len(data)) : + data[i] ^= 0x0036 + if len(data)>19 : + data[19] ^= 0x0001 + outfile.seek(0) + outfile.write(data.tostring()) + else : + print 'WARNING: Unknown encryption type specified, none performed' + print 'Known types include: fate_trial, fate_full' + + +dirname=sys.argv[1] +arcfile=open(sys.argv[2],'wb') +if (len(sys.argv)==4) : + encryption = sys.argv[3] +else : + encryption = 'none' + +# Write header +write_string(arcfile,'XP3\x0D\x0A \x0A\x1A\x8B\x67\x01') +write_unsigned(arcfile,0,LONG_LENGTH) # Placeholder for index offset + +# Scan for files, write them and collect the index as we go +indexbuffer = StringIO() +for (dirpath, dirs, filenames) in os.walk(dirname) : + assert (dirpath.startswith(dirname)) + newpath = dirpath[len(dirname):] # Strip off base directory + if newpath.startswith(os.sep) : # and possible slash + newpath = newpath[len(os.sep):] + pathcomponents = newpath.split(os.sep) + newpath = string.join(pathcomponents,'/') # Slashes used inside XP3 + for filename in filenames : + entry = {} + segment = {} + if newpath != '' : + filepath = newpath + '/' + filename + else : + filepath = filename + entry['filepath'] = unicode(filepath,sys.getfilesystemencoding()) + localfilepath = os.path.join(dirpath,filename) + infile = open(localfilepath,'rb') + data = infile.read() + infile.close() + entry['origsize'] = segment['origsize'] = len(data) + if (encryption != 'none') : + entry['encrypted'] = 0x0080000000L + tempbuffer = StringIO() + tempbuffer.write(data) + encrypt(tempbuffer,encryption) + data = tempbuffer.getvalue() + tempbuffer.close() + else : + entry['encrypted'] = 0 + entry['adler'] = (zlib.adler32(data) + 0x0100000000L) & 0x00FFFFFFFFL + # Convert to unsigned 32-bit integer + compressed = zlib.compress(data,9) + if len(compressed)<0.95*len(data) : # Don't store compressed if we + segment['compressed'] = 1 # gain just a few percent from it + data = compressed + else : + segment['compressed'] = 0 + entry['compsize'] = segment['compsize'] = len(data) + segment['offset'] = arcfile.tell() + entry['segments'] = [segment] # Always using a list of one segment + write_entry(indexbuffer,entry) + print 'Packing %s (%d -> %d bytes)' % \ + (entry['filepath'].encode(sys.getfilesystemencoding()), + entry['origsize'], entry['compsize']) + arcfile.write(data) + +# Now write the index and go back and put its offset in the header +indexoffset = arcfile.tell() +data = indexbuffer.getvalue() +compressed = zlib.compress(data,9) +arcfile.write('\x01') +write_unsigned(arcfile,len(compressed),LONG_LENGTH) +write_unsigned(arcfile,len(data),LONG_LENGTH) +arcfile.write(compressed) +arcfile.seek(11) # Length of header +write_unsigned(arcfile,indexoffset,LONG_LENGTH) + +indexbuffer.close() +arcfile.close() diff --git a/bin/xp3tools/xp3-wordwrap.exe b/bin/xp3tools/xp3-wordwrap.exe new file mode 100644 index 0000000..c18e6c9 Binary files /dev/null and b/bin/xp3tools/xp3-wordwrap.exe differ diff --git a/bin/xp3tools/xp3-wordwrap.py b/bin/xp3tools/xp3-wordwrap.py new file mode 100644 index 0000000..eba2998 --- /dev/null +++ b/bin/xp3tools/xp3-wordwrap.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +# Kirikiri Word-wrapper +# +# To enable the use of English text word-wrapping in the game engine, +# you need patched versions of some .tjs system files, plus you need to +# preprocess your scripts with this program to add appropriate wrapping +# hints to the text. +# +# Last modification: 2006-07-08, Edward Keyes, ed-at-insani-dot-org + +import string, re, sys + + +if len(sys.argv) != 3 : + print 'Please give an input .ks script file and an output file on the command line.' + sys.exit(0) + +infile = open(sys.argv[1],'rb') +outfile = open(sys.argv[2],'wb') + +# Some handy regular expressions used below, precompiled for speed +separatebybrackets = re.compile(r'[^\[]+|\[[^\]]*\]\s*') + # Split into '[foo bar]' pieces and intervening text. +separateintowords = re.compile(r'[^ \-]+(?:[ \-]+|$)|\s+') + # Splits regular text by whitespace and hyphens. +matchlinecommand = re.compile(r'\[line(\d+)\]') + # A [line] command. Also captures the value for later use. + +line = infile.readline() +scriptsection = False +while line != '' : + line = line.rstrip() # Strip linefeeds and trailing spaces + if (not scriptsection) and (line!='' ) and (line[0] not in ('@','*',';')) : + pieces = [] + # Split by bracketed commands... + for match in separatebybrackets.finditer(line) : + if match.group()[0] == '[' : + pieces.append(match.group()) + else : + # ... and then into words for non-bracketed text + for submatch in separateintowords.finditer(match.group()) : + pieces.append(submatch.group()) + newpieces = [] + # Now concatenate [line] commands back into regular text if not spaced + addtoprevious = False + for piece in pieces : + if matchlinecommand.match(piece) : + if newpieces[-1][-1] in (' ','-') : + newpieces.append(piece) + else : + newpieces[-1] += piece + addtoprevious = True + else : + if addtoprevious and (newpieces[-1][-1] not in (' ','-')) : + newpieces[-1] += piece + else : + newpieces.append(piece) + addtoprevious = False + line = '' + # Now reconstruct new line with [wrap] commands embedded. + for piece in newpieces : + if (piece[0]!='[') or matchlinecommand.match(piece) : + wraptext = piece + for match in matchlinecommand.finditer(piece) : + wraptext = re.sub(r'\[line'+match.group(1)+r'\]', + '--'*int(match.group(1)), wraptext, 1) + # Replace line commands by that number of dashes in wrap hint + wraptext = wraptext.rstrip() + wraptext = wraptext.replace('"','-') # To avoid quoted quotes + line += '[wrap text="' + wraptext + '"]' + piece + else : + line += piece + else : + if line == '@iscript' : + scriptsection = True + elif line == '@endscript' : + scriptsection = False + outfile.write(line+'\x0D\x0A') # Force Windows linefeeds + line = infile.readline() + +infile.close() +outfile.close()