인디자인 탭 기능은 그다지 친절하지 않다. 탭 인터페이스1를 켜면 판면에 딱 맞춰진 눈금자가 뜨는데, 왼쪽과 가운데, 오른쪽 정렬처럼 보이는 어떤 기호들을 이리저리 눌러보면서 '아 이렇게 작동하는구나' 우여곡절을 겪지 않고서는 그 기능을 한눈에 알기 어렵다. 더구나 글자나 단어 단위로 딱맞춰서 탭을 설정하고 싶다면 커서로 이리저리 끄는 인터페이스(물론 값을 입력할 수도 있지만, 그러기 위해선 당연히 값을 알아야 한다)는 뭔가 불충분하다.
탭(혹은 그와 크게 다르지 않은 첫줄 들여쓰기)을 어느 거리에서 멈추게 할 것인지를 결정하기 위해서는 특정한 기준이 필요하다. 단순하게는 깔끔하게 정수로 떨어지게 할 수도(10pt, 5mm 등) 있을 것이고, 본문 글자 수에 맞게 하고 싶을 수도 있다. 전자라면 특별히 아쉬울 게 없지만, 후자라면 필연적으로 산수를 해야 한다. 정말 엄밀하게 '한 글자 수의 거리'를 재고 싶으면 본문 폰트 값만 아는 건 불충분하다. 한국어 조판의 특성상 어떤 폰트인지 자간 값이 얼만큼 들어가는지까지도 생각해야 한다. 예를 들어 SM 계열의 폰트로 10.5pt로 본문 디자인을 잡는다고 했을 때, 폰트의 자간 값은 대체로 -100 전후일 때가 많다. SM 계열의 폰트는 천분각으로 만들어졌기 때문에 한 글자의 값은 10.5pt(폰트 크기) * (1000-100(자간 값)/1000)이다. 하지만 산돌 계열로 같은 크기의 폰트를 사용한다면 산돌 계열 폰트는 그 규격상 가로가 더 짧고 자간 값을 더 적게 주므로, 10.5pt(폰트 크기) * (945-50(자간 값)/1000)와 같이 계산해야 한다.
상황이 이러니 한 두글자를 기준으로 탭이나 들여쓰기 값을 딱 맞춰서 주겠다는 생각을 웬만하면 버리게 된다. 폰트별 전각 기준을 다 외우고 있는 게 아니라면, 자간 값까지 더하는 산수는 피곤하기 때문이다. 그럴 땐 기계의 힘(?)을 이용해야 한다. 탭 인터페이스의 불친절함은 CS 초기부터 얘기되었던 모양이다. 'TabUtilities.jsx'는 2009년에 이미 세 번째 버전(3.0.0)이 나왔고, 스크립트 창 [응용 프로그램] 폴더에 있다. [응용 프로그램]에 있는 스크립트들은 CS의 유구한 역사(?)를 경유하면서 프로그램 내부로 들어온 것들이다. [응용 프로그램]에 있는 스크립트들 중에서 'TabUtilities.jsx'가 (한국어 조판에 있어서)활용도가 가장 높은 것 같아서 인터페이스와 주석을 번역해봤다.
'탭 설정하기'에는 채움 문자 기능을 제외하면 네 개의 옵션이 있다. '단 오른쪽 끝' '현재 커서 위치' 왼쪽 들여쓰기' '커서 위치에 내어쓰기'. '단 오른쪽 끝'은 오른쪽 끝 부분에 오른쪽 균등 배치 탭을 삽입해준다. 한마디로 오른쪽 들여쓰기 탭을 탭 설정으로 구현해주는 경우다. '현재 커서 위치'는 커서가 있는 삽입점을 기준으로 왼쪽 균등 배치 탭을 삽입해준다. 스크립트를 열면 다른 작업을 할 수 없으므로, 이 옵션을 선택하려면 우선 커서 위치를 확정한 후 스크립트를 실행하는 게 좋다(이 단점을 극복하고자 UI를 바꿔보려 했지만 대차게 실패했다, 추가 노트 참고). '왼쪽 들여쓰기'는 현재 들여쓰기 값을 기준으로 그 위치에 왼쪽 균등 배치 탭을 삽입해준다. 마지막 '커서 위치에 내어쓰기'는 커서가 있는 삽입점을 기준으로 들여쓰기 값을 주고, 그만큼 첫 줄 들여쓰기 값을 빼줘서 내어쓰기를 만들어준다. 개인적으로 네 번째 옵션이 가장 유용한 듯싶다.
- [CP]스크립트 설치하기 ← 설치하는 법을 모르겠다면 참고!
코드 해설
app.doScript(main, ScriptLanguage.JAVASCRIPT , [], UndoModes.ENTIRE_SCRIPT);
function main(){
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
var myObjectList = new Array;
if (app.documents.length != 0){
if(app.activeDocument.stories.length != 0){
if (app.selection.length != 0){
switch (app.selection[0].constructor.name){
case "InsertionPoint":
case "Character":
case "Word":
case "TextStyleRange":
case "Line":
case "Paragraph":
case "TextColumn":
case "Text":
case "TextFrame":
myDisplayDialog();
break;
}
}
else{
alert ("텍스트를 선택한 후 다시 시도하십시오.");
}
}
}
else{
alert ("문서를 열고, 개체를 선택한 후 다시 시도하십시오.");
}
}
우선 처음은 선택된 것을 식별하는 작업으로 출발한다. app.doScript 메소드를 추가해서 깔끔하게 실행취소가 되도록 바꿨다(app.doScript(main, ScriptLanguage.JAVASCRIPT , [], UndoModes.ENTIRE_SCRIPT);). main() 함수의 첫줄 userInteractionLevel은 스크립트가 켜져 있을 때 대화상자와 경고 알림을 통제하는 정도인데, 여기서는 모든 걸 통제하는 것으로 설정한다(app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;). 그리고 myObjectList를 새로운 배열로 변수 선언한다(var myObjectList = new Array;). 다음으로 문서가 열려져 있는지 살피는 조건문이 나오는데(if (app.documents.length != 0){), 그렇지 않을 경우 경고 알림이 뜬다(alert ("문서를 열고, 개체를 선택한 후 다시 시도하십시오.");). 문서가 열려 있으면, 스토리가 하나라도 있는지 확인한 후(if(app.activeDocument.stories.length != 0){), 선택한 게 하나라도 있는지 확인한다(if (app.selection.length != 0){). 그렇지 않은 경우 다른 경고 알림이 뜬다(alert ("텍스트를 선택한 후 다시 시도하십시오.");).
선택한 게 하나라도 있으면 그 선택지에 대한 검사를 switch 조건문으로 시작하는데(switch (app.selection[0].constructor.name){), 그 경우를 살펴보면 '삽입점' '문자' '단어' '텍스트 스타일 범위' '줄' '단락' '텍스트 단' '텍스트' '텍스트 프레임' 등 텍스트 프레임 내용의 구성요소임을 알 수 있다(case "InsertionPoint": case "Character": case "Word": case "TextStyleRange": case "Line":case "Paragraph": case "TextColumn": case "Text": case "TextFrame":). 그리고 그 경우에 해당되는 것이 새로운 배열로 되는 것이다(myDisplayDialog();). 그외의 경우면 조건문을 빠져나오는 것(break;)으로 함수가 마무리된다.
function myDisplayDialog(){
var myDialog, myTabButtons;
with(myDialog = app.dialogs.add({name:"탭 설정하기"})){
with(dialogColumns.add()){
with(borderPanels.add()){
staticTexts.add({staticLabel:"탭을 다음 위치에 설정:"});
with(myTabButtons = radiobuttonGroups.add()){
radiobuttonControls.add({staticLabel:"단 오른쪽 끝", checkedState:true});
radiobuttonControls.add({staticLabel:"현재 커서 위치"});
radiobuttonControls.add({staticLabel:"왼쪽 들여쓰기"});
radiobuttonControls.add({staticLabel:"커서 위치에 내어쓰기"});
}
}
with(borderPanels.add()){
staticTexts.add({staticLabel:"채움 문자"});
with(dialogColumns.add()){
myTabLeaderField = textEditboxes.add({editContents:""});
}
}
}
}
var myResult = myDialog.show();
if(myResult == true){
var myTabType = myTabButtons.selectedButton;
var myTabLeader = myTabLeaderField.editContents;
myDialog.destroy();
myAddTabStop(myTabType, myTabLeader);
}
else{
myDialog.destroy();
}
}
이제 대화상자의 기능에 대한 함수다(function myDisplayDialog(){). 우선 변수로 대화상자와 버튼에 대해서 선언된다(var myDialog, myTabButtons;).
대화상자 틀이 추가가 되는데, 그 이름을 한국어로 고쳐 '탭 설정하기'로 했다(with(myDialog = app.dialogs.add({name:"탭 설정하기"})){). 아울러 대화상자 틀을 추가하고 따로 지시를 하지 않으면 '확인' '취소' 버튼도 그대로 딸려온다. 이어서 대화상자에 한 열을 추가하고(with(borderPanels.add()){), 두 열을 하나로 묶는 테두리 패널을 만든다(with(borderPanels.add()){). 서두에 예고했던 myTabButtons 변수의 내용이 채워지는데(with(myTabButtons = radiobuttonGroups.add()){), 라디오 버튼 그룹을 추가하는 것이다. 그리고 그룹의 내용들이 연달아 정의된다(radiobuttonControls.add({staticLabel:"단 오른쪽 끝", checkedState:true});radiobuttonControls.add({staticLabel:"현재 커서 위치"});radiobuttonControls.add({staticLabel:"왼쪽 들여쓰기"});radiobuttonControls.add({staticLabel:"커서 위치에 내어쓰기"});) 각각 '단 오른쪽 끝' '현재 커서 위치' '왼쪽 들여쓰기' '커서 위치에 내어쓰기'이고, 첫 번째 '단 오른쪽 끝' 항목이 선택되어 있도록 한다.
그 아래쪽에 다시 테두리 패널을 만든다(with(borderPanels.add()){). 텍스트는 (탭 인터페이스에도 있는) '채움 문자'로 하고(staticTexts.add({staticLabel:"채움 문자"});), 새 열을 추가해서(with(dialogColumns.add()){) 텍스트를 입력할 수 있는 필드를 놓는다(myTabLeaderField = textEditboxes.add({editContents:""});).
그렇게 대화상자의 중심 기능에 대한 규정이 끝나면, 대화상자를 보이게 하는 변수가 선언된다(var myResult = myDialog.show();). 대화상자가 참일 경우(확인 버튼이 눌릴 경우), 그에 따라 선택된 것을 myTabType으로 선언하고(var myTabType = myTabButtons.selectedButton;), 입력된 값을 myTabLeader로 선언한다(var myTabLeader = myTabLeaderField.editContents;). 그리고 대화상자는 꺼진다(myDialog.destroy();). 이제 그렇게 입력된 값이 이후에 선언되는 함수로 간다(myAddTabStop(myTabType, myTabLeader);). 대화상자가 거짓이면(취소 버튼이 눌릴 경우), 별다른 거 없이 꺼진다(else{ myDialog.destroy();).
function myAddTabStop(myTabType, myLeader){
var myParagraphs, myTabPosition, myTabAlignment, myParagraph;
switch(myTabType){
case 0:
myParagraphs = app.selection[0].paragraphs;
for(myCounter = 0; myCounter < myParagraphs.length; myCounter ++){
myParagraph = myParagraphs.item(myCounter);
myTabPosition = myParagraph.insertionPoints.item(0).parentTextFrames[0].textFramePreferences.textColumnFixedWidth;
myTabAlignment = TabStopAlignment.rightAlign;
myParagraph.tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition});
}
break;
case 1:
myInsertionPoint = app.selection[0].insertionPoints.item(0);
myTabPosition = myInsertionPoint.horizontalOffset - myFindColumnEdge(myInsertionPoint);
myTabAlignment = TabStopAlignment.leftAlign;
myInsertionPoint.paragraphs.item(0).tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition})
break;
case 2:
myParagraphs = app.selection[0].paragraphs;
for(myCounter = 0; myCounter < myParagraphs.length; myCounter ++){
myParagraph = myParagraphs.item(myCounter);
myTabPosition = myParagraph.leftIndent;
myTabAlignment = TabStopAlignment.leftAlign;
myParagraph.tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition});
}
break;
case 3:
myParagraphs = app.selection[0].paragraphs;
myInsertionPoint = app.selection[0].insertionPoints.item(0);
myTabPosition = myInsertionPoint.horizontalOffset - myFindColumnEdge(myInsertionPoint);
myTabAlignment = TabStopAlignment.leftAlign;
for(myCounter = 0; myCounter < myParagraphs.length; myCounter ++){
myParagraph = myParagraphs.item(myCounter);
myParagraph.leftIndent = myTabPosition;
myParagraph.firstLineIndent = -myTabPosition;
myParagraph.tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition});
}
break;
}
}
이제 탭 설정을 다룬 함수다. myAddTabStop의 값을 그대로 이어받아 함수가 선언된다(function myAddTabStop(myTabType, myLeader){). 우선 네 개의 변수가 우선적으로 규정되는데(var myParagraphs, myTabPosition, myTabAlignment, myParagraph;), 조건문과 결부된다. myTabType의 성격(?)을 따지는 것이므로 switch 구문이 쓰였다(switch(myTabType){). 첫 번째 경우에서 현재 선택한 것의 단락으로 myParagrphs가 선언된다(myParagraphs = app.selection[0].paragraphs;). 그리고 조건문 속 반복문 for이 쓰이는데(for(myCounter = 0; myCounter < myParagraphs.length; myCounter ++){), myCounter를 단락의 개수만큼 상정하기 위해서다. myParagraph는 myParagraphs의 항목으로 정의되고, 그때 탭 위치는 그 단락 속 삽입점의 부모가 되는 텍스트 프레임, 그 텍스트 프레임에 단이 있는 경우 해당 단의 고정 너비로 설정한다(myTabPosition = myParagraph.insertionPoints.item(0).parentTextFrames[0].textFramePreferences.textColumnFixedWidth;). 아울러 탭 설정 정렬은 오른쪽 정렬로 선언된다(myTabAlignment = TabStopAlignment.rightAlign;). 그리고 탭 설정은 앞선 변수들에 맞춰서(오른쪽 정렬 탭, 단의 고정너비만큼의 거리 등) 추가된다(myParagraph.tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition});). 이것이 라디오 버튼 중 '단 오른쪽 끝'에 대한 규정이다.
다음 경우는 삽입점에 대한 선언으로 시작한다(myInsertionPoint = app.selection[0].insertionPoints.item(0);). 현재 선택한 것의 삽입점을 기준으로 그 폭을 이후에 함수로 선언되는 myFindColumnEdge(단의 왼쪽 끝에 대한 규정)만큼 뺀 값으로 탭 위치가 결정된다(myTabPosition = myInsertionPoint.horizontalOffset - myFindColumnEdge(myInsertionPoint);). 탭 설정 정렬은 왼쪽 정렬이다(myTabAlignment = TabStopAlignment.leftAlign;). 그렇게 현재 삽입점 위치에 앞서 선언된 변수 내용을 바탕으로 탭 설정이 추가된다(myInsertionPoint.paragraphs.item(0).tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition})). '현재 커서 위치'에 대한 규정도 이렇게 마무리된다.
세 번째 경우는 첫 번째와 거의 비슷한데, 달라지는 게 있다면 탭 위치가 들여쓰기로 규정된다는 것이다(myTabPosition = myParagraph.leftIndent;). 라디오 버튼 '왼쪽 들여쓰기'는 그렇게 왼쪽 들여쓰기 값만큼 탭 값을 설정한다.
마지막은 두 번째 경우와 닮아 있다. 삽입점 위치에서 왼쪽 단의 위치만큼 뺀 값을 구하고 왼쪽 정렬로 탭 설정을 하는 것까지는 같은데, 그렇게 구한 값을 왼쪽 들여쓰기(myParagraph.leftIndent = myTabPosition;)와 첫 줄 왼쪽 들여쓰기(myParagraph.firstLineIndent = -myTabPosition;)에 준다. 그러고 나서 같은 위치에 탭 설정을 추가한다(myParagraph.tabStops.add({alignment:myTabAlignment, leader:myLeader, position:myTabPosition});).
function myFindColumnEdge(myInsertionPoint){
var myCounter, myLeftInset, myRightInset, myX1, myX2, myColumnEdge;
var myPagePosition = myInsertionPoint.horizontalOffset;
var myTextFrame = myInsertionPoint.parentTextFrames[0];
var myColumnWidth = myTextFrame.textFramePreferences.textColumnFixedWidth;
var myGutterWidth = myTextFrame.textFramePreferences.textColumnGutter;
var myTextFrameWidth = myTextFrame.geometricBounds[3]-myTextFrame.geometricBounds[1];
var myXOffset = myPagePosition - myTextFrame.geometricBounds[1];
var myArray = new Array;
for (myCounter = 0; myCounter < myTextFrame.textFramePreferences.textColumnCount; myCounter ++){
if(myCounter == 0){
if(myTextFrame.textFramePreferences.insetSpacing.length == 4){
myLeftInset = myTextFrame.textFramePreferences.insetSpacing[1];
myRightInset = myTextFrame.textFramePreferences.insetSpacing[3];
}
else{
myLeftInset = myTextFrame.textFramePreferences.insetSpacing[0];
myRightInset = myTextFrame.textFramePreferences.insetSpacing[0];
}
myX1 = myTextFrame.geometricBounds[1] + myLeftInset;
myX2 = myX1 + myColumnWidth;
}
else{
if(myCounter == myTextFrame.textFramePreferences.textColumnCount){
myX2 = myTextFrame.geometricBounds[1] -myRightIndent;
myX1 = myX2 - myTextWidth;
}
else{
myX1 = myTextFrame.geometricBounds[1] + (myColumnWidth*myCounter) + (myGutterWidth * myCounter);
myX2 = myX1 + myColumnWidth;
}
}
myArray.push([myX1, myX2]);
}
for(myCounter = 0; myCounter < myArray.length; myCounter ++){
if((myPagePosition >= myArray[myCounter][0])&&(myPagePosition <=myArray[myCounter][1])){
myColumnEdge = myArray[myCounter][0];
break;
}
}
return myColumnEdge;
}
이제 단의 끝(가장자리)에 대한 정보를 구하면 된다(function myFindColumnEdge(myInsertionPoint){). 여기서는 여섯 개의 변수가 우선적으로 규정된다(var myCounter, myLeftInset, myRightInset, myX1, myX2, myColumnEdge;). 마찬가지로 조건문과 결부된다. 몇 가지 변수는 직접 선언되는데, 현재 삽입점을 기준으로 폭을 규정하고(var myPagePosition = myInsertionPoint.horizontalOffset;), 현재 삽입점의 부모 텍스트 프레임(var myTextFrame = myInsertionPoint.parentTextFrames[0];), 단의 폭(var myColumnWidth = myTextFrame.textFramePreferences.textColumnFixedWidth;) 단 사이 간격의 폭(var myGutterWidth = myTextFrame.textFramePreferences.textColumnGutter;), 텍스트 프레임의 폭(var myTextFrameWidth = myTextFrame.geometricBounds[3]-myTextFrame.geometricBounds[1];), 현재 삽입점의 폭에서 텍스트 프레임 왼쪽 모서리의 폭을 뺀 현재 삽입점의 X 오프셋 값(var myXOffset = myPagePosition - myTextFrame.geometricBounds[1];)이 규정된다. 아울러 새로운 배열은 myArray라는 이름으로 여기서는 선언된다(var myArray = new Array;).
여기서 반복문 for는 단의 개수를 세기 위해 쓰인다(for (myCounter = 0; myCounter < myTextFrame.textFramePreferences.textColumnCount; myCounter ++){) 단의 개수가 0일 경우(if(myCounter == 0){), 우선 사각형 같이 인세트 간격의 개수가 네 개인 경우를 상정한다(if(myTextFrame.textFramePreferences.insetSpacing.length == 4){). 그때 왼쪽 인세트는 인세트 간격 중 두 번째, 즉 왼쪽으로(myLeftInset = myTextFrame.textFramePreferences.insetSpacing[1];), 오른쪽 인세트는 인세트 간격 중 네 번째, 즉 오른쪽으로(myRightInset = myTextFrame.textFramePreferences.insetSpacing[3];) 선언한다. 그렇지 않은 경우, 즉 원처럼 인세트 간격이 하나밖에 없을 때는(else{), 왼쪽과 오른쪽 인세트 모두 인세트 간격 첫 번째로 한다(myLeftInset = myTextFrame.textFramePreferences.insetSpacing[0];myRightInset = myTextFrame.textFramePreferences.insetSpacing[0];). 그렇게 했을 때, myX1은 텍스트 프레임 왼쪽 끝에서 왼쪽 인세트 간격을 더한 값(myX1 = myTextFrame.geometricBounds[1] + myLeftInset;), myX2는 myX1에서 텍스트 단 폭을 더한 값(myX2 = myX1 + myColumnWidth;)이다.
그러면 단의 개수가 여러 개이고, 그것이 myCounter와 값이 같은 경우에는(if(myCounter == myTextFrame.textFramePreferences.textColumnCount){), myX2를 왼쪽 모서리 폭에서 오른쪽 들여쓰기를 뺀 값(myX2 = myTextFrame.geometricBounds[1] -myRightIndent;)으로, myX1을 myX1에서 myTextWidth를 뺀 값을 규정한다. 그런데 myRightIndent와 myTextWidth 모두 따로 선언되지 않은 변수라서, 이런 데도 스크립트가 작동하는 이유는 뭔지 아직 찾지 못했다.... 아울러 myCounter가 텍스트 프레임 단의 개수보다 더 많은 경우라면 산수가 좀 복잡해지는데, 그때 myX1은 텍스트 프레임 왼쪽 모서리 폭에 단의 폭을 myCounter 수와 곱한 값과 단 사이 간격의 폭을 myCounter 수와 곱한 값을 더해야 한다(myX1 = myTextFrame.geometricBounds[1] + (myColumnWidth*myCounter) + (myGutterWidth * myCounter);). myX2는 myX1에서 단의 폭을 하나 뺀 값이 된다.
그렇게 세 가지 경우의 수로 구한 값을 배열로 추가한다(myArray.push([myX1, myX2]);). 스크립트 마지막 부분에는 배열의 수보다 myCounter가 적을 것을 가정하고 반복문이 나오는데(for(myCounter = 0; myCounter < myArray.length; myCounter ++){). 현재 삽입점 기준 폭보다 myX1이 크거나 같고, 현재 삽입점 기준 폭보다 myX2가 작거나 같을 경우에 한해서(if((myPagePosition >= myArray[myCounter][0])&&(myPagePosition <=myArray[myCounter][1])){), 단의 가장자리를 규정한 myColumn을 배열의 첫 번째, 즉 myX1으로 한다. 그렇게 선언된 myColumnEdge를 반환하면서 마지막 함수가 끝나고, 스크립트가 마무리 된다.
삽입점과 단의 위치를 규정하는 스크립트의 마지막 부분이 가장 어려웠다. 풀지 못한 부분도 몇 군데 보여서, 추후에 비슷하지만 이유를 알 것 같은 경우를 발견하면 첨언하도록 하겠다.
추가 노트
몇몇 부분은 바꾸는 데 성공했지만, 스크립트 UI 자체를 바꾸려는 시도는 대차게 실패했다. 인디자인 스크립트 UI를 만드는 방식은 두 가지다. 하나는 인디자인 익스텐드스크립트 객체 모델을 이용하는 것이고, 다른 하나는 scriptUI를 쓰는 거다(전자가 비교적 오래전부터 쓰여서인지 '클래식 UI'로 불리기도 하는 것 같다). 클래식 UI가 좋은 점은 속성이 세분화되어 있어서 만들기 비교적 용이하지만, '팔레트(palette)' 대화상자 유형이 없고 어도비 다른 앱에서는 쓸 수 없다는 문제가 있다. scriptUI는 다른 어도비 앱에서도 쓸 수 있는 확장성과 팔레트 대화상자 유형이 있다는 장점이 있지만, 속성이 제한적인 까닭에 기능을 구현하는 게 더 수고스럽다. 난 애초에 다른 앱에서 스크립트를 잘 쓰지 않는데도, 굳이 이 스크립트를 scriptUI로 바꾸려고 했던 건 팔레트 대화상자 때문이다. 일반적인 대화상자로 구현된 스크립트는 실행하면 다른 작업을 할 수 없게 정지 상태가 된다. 하지만 팔레트는 대화상자를 켜놓고도 다른 작업을 실행할 수 있다는 획기적인 장점이 있다. '탭 설정하기' 스크립트 특성상 커서 위치에 따라 변화를 줄 수 있는 게 많아서, 대화상자를 켜도 커서를 움직일 수 있는 상태였으면 하는 바람이 있었다. 단지 그거 때문에 시작한 일이었지만, 생각보다 고칠 게 많았다. 결과적으로 UI 자체를 만드는 데는 성공했지만 입력한 데이터를 기존의 함수와 연결하지 못했다. 아쉽지만 아직 역량이 부족한 것으로 생각할 수밖에... 😢 실패담인 게 씁쓸하지만 노트로 남겨둔다.
1 [문자] → [탭], 맥 기준 단축키로는 cmd+opt+t