2016. 3. 9.

상용구 스크립트

[출처:http://autohotkey.co.kr/b/1-849]


Array_New() {
 global Array
 return new Array()
}
; 팩토리 메소드. 배열을 구분자로 구분된 문자열로부터 생성한다.
; @param str 구분자로 구분된 문자열
; @param delim 구분자. 기본값 ","
; @return 배열
Array_ValueOf(str, delim=",") {
 StringSplit, token, str, %delim%
 arr := Array_New()
 Loop, %token0%
  arr.mem[A_Index] := token%A_Index%
 return arr
}
class Array {
 ;*******************************************************************************
 ; 생성자
 ; 멤버는 반드시 생성자에서 "." 연산자로 정의한다.
 ;*******************************************************************************
 __New() {
  this.mem := Object()
  this.lastError := 0
 }
 ;*******************************************************************************
 ; public 함수
 ;*******************************************************************************

 ; 배열의 엘리먼트 개수를 반환한다.
 ; @return 엘리먼트 개수
 Length() {
  len := this.mem.MaxIndex()
  return len == "" ? 0 : len
 }

 ; 배열에 새 엘리먼트를 추가한다. 새 엘리먼트의 인덱스는 (이전배열의 길이 + 1)이 된다.
 ; @param value 추가할 엘리먼트의 값
 Add(value) {
  this.mem[this.Length() + 1] := value
 }
 ; 배열을 구분자로 구분된 문자열로 나타내어 반환한다.
 ; @delim 구분자. 기본값 ","
 ; @return 구분자로 구분된 문자열
 ToString(delim=",") {
  result := ""
  len := this.Length()
  Loop, % len
   result .= this.mem[A_Index] . delim
  If (len > 0)
   StringTrimRight, result, result, StrLen(delim)
  return result
 }

 ; 배열의 특정 엘리먼트의 값을 설정한다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param idx 대상 엘리먼트 인덱스. 단, [1,배열길이]여야만 한다.
 ; @param value 새 엘리먼트 값
 ; @return 새 엘리먼트 값
 Set(idx, value) {
  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(idx, 1, this.Length()) ? 1 : 0))
   return
  return this.mem[idx] := value
 }
 ; 배열의 특정 엘리먼트 값을 반환한다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param idx 대상 엘리먼트의 인덱스. 단, [1,배열길이]여야만 한다.
 ; @return 엘리먼트 값
 Get(idx) {
  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(idx, 1, this.Length()) ? 1 : 0))
   return
  return this.mem[idx]
 }
 ; 마지막으로 호출한 메소드의 실패여부를 알 수 있는 에러코드를 반환한다. 파라미터로 인덱스를 지정하는 메소드와 Pop()은 실행 후 이 메소드를 호출하여 성공여부를 알 수 있다.
 ; @return 에러코드. 0이면 성공, 1이면 실패.
 GetLastError() {
  return this.lastError
 }

 ; 배열 전체를 대상으로 특정 엘리먼트를 검색한다. 만일, 이 메소드가 자주 호출되며 배열을 정렬하여도 아무런 문제가 되지 않는다면, 이보다 훨씬 빠른 BinarySearch()를 사용하는 것이 좋다.
 ; @param value 조회할 엘리먼트 값
 ; @return 배열에 엘리먼트가 존재한다면 그 인덱스를 반환한다. 그렇지 않다면, 0을 반환한다.
 IndexOf(value) {
  for k,v in this.mem
   If (v == value)
    return k
  return 0
 }

 ; 배열을 정렬한다.
 ; @param asc 오름차순 여부. true이면 오름차순으로 false이면 내림차순으로 정렬한다. 기본값 true
 Sort(asc=true) {
  this._Sort(asc, 1, this.Length())
 }

 ; 배열에 엘리먼트를 삽입한다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param idx 삽입할 위치의 인덱스. 단, [1,배열길이+1]여야만 한다.
 ; @param value 삽입할 엘리먼트의 값
 Insert(idx, value) {
  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(idx, 1, this.Length() + 1) ? 1 : 0))
   return
  this.mem.Insert(idx, value)
 }
 ; 배열에서 특정 엘리먼트를 제거한다.
 ; @param idx 대상 엘리먼트의 인덱스. 단, [1,배열길이]여야만 한다.
 ; @return 제거된 엘리먼트의 값
 Remove(idx) {
  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(idx, 1, this.Length()) ? 1 : 0))
   return
  return this.mem.Remove(idx)
 }
 ; 배열의 일정 범위를 대상으로 특정 엘리먼트를 바이너리 서치 알고리즘을 사용하여 검색한다. 대상 배열은 이 함수를 호출하기 전에 반드시 오름차순 정렬(Sort()사용)되어야 하며 정렬되지 않았다면 부정확한 결과가 반환된다. 또한 만일 값이 동일한 다수의 엘리먼트가 존재한다면, 이 중 어느 것이 반환될 지 보장되지 않는다. 범위 파라미터를 생략하면 전체 배열을 대상으로 검색한다. 만일 to 인덱스가 배열의 마지막 인덱스(exclusive)보다 크다면, 이 값으로 보정된다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param key 검색할 엘리먼트 값
 ; @param from 검색될 첫 엘리먼트의 인덱스, inclusive (이 값은 반드시 [1, 마지막 인덱스+1]이어야 한다.)
 ; @param to 검색될 마지막 엘리먼트의 인덱스, exclusive (이 값은 반드시 [1, 마지막 인덱스+1]이며 "from" 인덱스보다 같거나 커야 한다.)
 ; @return 배열에 검색 키가 존재한다면 그 인덱스를 반환한다. 그렇지 않으면 -(삽입위치)을 반환한다. 삽입위치는 이 위치에 Insert()를 사용하여 키값을 삽입하여도 범위 내의 엘리먼트들이 정렬 상태를 유지하는 위치를 말한다.
 BinarySearch(key, from="", to="") {
  ; 인덱스 값을 지정하지 않았다면
  ; [1,마지막 인덱스+1)로 설정
  last := this.Length() + 1
  from := Array__ValidateIndex(from, 1)
  to := Array__ValidateIndex(to, last)

  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(from, 1, last) || !Array__CheckIndex(to, 1, last) || from > to ? 1 : 0))
   return

  ; 검색
  Loop {
   mid := Floor((from + to) / 2)
   If (from >= to)
    return -mid
   aVal := this.mem[mid]
   If (key == aVal)
    return mid
   Else IF (key < aVal)
    to := mid
   Else
    from := mid + 1
  }
 }

 ; Add()와 동일한 작업을 수행한다. 단순히 스택처럼 동작함을 의미하기 위해서 존재한다.
 ; @param value 추가할 엘리먼트의 값
 Push(value) {
  this.Add(value)
 }

 ; 배열의 마지막 엘리먼트를 제거하고 그 값을 리턴한다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @return 마지막 엘리먼트 값
 Pop() {
  If (this.lastError := (this.Length() == 0 ? 1 : 0))
   return
  return this.mem.Remove()
 }

 ; 배열의 동일성 테스트를 수행한다.
 ; @param other 배열2
 ; @return 동일한지 여부
 Equals(other) {
  If (this == other)
   return true
  If (!IsObject(other)
    ; || this.__Class != other.__Class
    || this.Length() != other.Length())
   return false
  ; 엘리먼트 별 비교
  for idx,value in this.mem
   If (value != other.mem[idx])
    return false
  return true
 }
 ; 배열의 특정 범위의 엘리먼트를 특정 값으로 채운다. 범위 파라미터를 생략하면 전체 엘리먼트를 대상으로 값을 채운다. 만일 to 인덱스가 배열의 마지막 인덱스(exclusive)보다 크다면, 우선 [from,마지막 인덱스(exclusive))의 엘리먼트가 지정된 값으로 채워진 뒤, (to-마지막 인덱스(exclusive))개의 지정된 값을 가지는 엘리먼트가 새로 추가된다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param value 채울 값
 ; @param from 값을 채울 첫 엘리먼트의 인덱스, inclusive (이 값은 반드시 [1, 마지막 인덱스+1]이어야 한다.)
 ; @param to 값을 채울 마지막 엘리먼트의 인덱스, exclusive (이 값은 반드시 "from" 인덱스보다 같거나 커야 한다.)
 Fill(value, from="", to="") {
  ; 인덱스 값을 지정하지 않았다면
  ; [1,마지막 인덱스+1)로 설정
  last := this.Length() + 1
  from := Array__ValidateIndex(from, 1)
  to := Array__ValidateIndex(to, last)

  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(from, 1, last) || from > to ? 1 : 0))
   return

  ; 채우기
  Loop, % to - from
   this.mem[from + A_Index - 1] := value
 }
 ; 특정 배열의 특정 범위의 엘리먼트를 복사하여 새로운 배열을 생성한다. 범위 파라미터를 생략하면 전체 배열을 복사한다. 만일 to 인덱스가 배열의 마지막 인덱스(exclusive)보다 크다면, 넘어선 범위의 엘리먼트 값은 default 파라미터로 지정한 값으로 설정되며, 새 배열의 길이는 (to-from)이 된다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param from 복사할 첫 엘리먼트의 인덱스, inclusive (이 값은 반드시 [1, 마지막 인덱스+1]이어야 한다.)
 ; @param to 복사할 마지막 엘리먼트의 인덱스, exclusive (이 값은 반드시 "from" 인덱스보다 같거나 커야 한다.)
 ; @param default to 인덱스가 마지막 인덱스(exclusive)보다 클 때, 새로 추가될 엘리먼트들의 기본값이다.
 ; @return 새로운 배열
 CopyOf(from="", to="", default="") {
  ; 인덱스 값을 지정하지 않았다면
  ; [1,마지막 인덱스+1)로 설정
  last := this.Length() + 1
  from := Array__ValidateIndex(from, 1)
  to := Array__ValidateIndex(to, last)

  ; 인덱스 값 유효성 테스트
  If (this.lastError := (!Array__CheckIndex(from, 1, last) || from > to ? 1 : 0))
   return

  ; 새 배열 생성
  newArr := Array_New()
  Loop, % to - from
  {
   srcIdx := from + A_Index - 1
   newArr.mem[A_Index] := srcIdx < last ? this.mem[srcIdx] : default
  }
  return newArr
 }
 ; 배열의 i번째 엘리먼트와 j번째 엘리먼트를 스왑한다. 성공하면 GetLastError()가 0을, 실패하면 1을 반환한다.
 ; @param i 인덱스
 ; @param j 인덱스
 Swap(i, j) {
  ; 인덱스 값 유효성 테스트
  len := this.Length()
  If (this.lastError := (!Array__CheckIndex(i, 1, len) || !Array__CheckIndex(j, 1, len) ? 1 : 0))
   return
  this._Swap(i, j)
 }
 ; 특정 배열을 복제한 새로운 배열을 생성한다. shallow copy임.
 ; @return 복제된 배열
 Clone() {
  ; 빈 배열 생성
  cloned := Array_New()

  ; shallow copy
  cloned.mem := this.mem.Clone()

  return cloned
 }

 ; 배열의 엘리먼트들을 무작위로 섞는다.
 Shuffle() {
  Random,, %A_TickCount%
  len := this.Length()
  Loop, %len% {
   Random, a, 1, %len%
   Random, b, 1, %len%
   this._Swap(a, b)
  }
 }

 ;*******************************************************************************
 ; mem 멤버 위임 메소드
 ;*******************************************************************************

 __Set(idx, value) {
  ; "mem"이나 "lastError"의 경우, 명시적으로 처리하지 않으면
  ; default behavior로 처리된다.
  If idx not in mem,lastError
   return this.Set(idx, value)
 }

 __Get(idx) {
  ; "mem"이나 "lastError"의 경우, 명시적으로 처리하지 않으면
  ; default behavior로 처리된다.
  If idx not in mem,lastError
   return this.Get(idx)
 }

 _NewEnum() {
  return this.mem._NewEnum()
 }

 MinIndex() {
  return this.mem.MinIndex()
 }

 MaxIndex() {
  return this.mem.MaxIndex()
 }

 SetCapacity(maxItems) {
  return this.mem.SetCapacity(maxItems)
 }

 GetCapacity() {
  return this.mem.GetCapacity()
 }
 ;*******************************************************************************
 ; private 함수
 ; 오토핫키에서는 private 키워드를 지원하지 않으므로
 ; _로 시작하는 이름을 가지도록 하여 구분한다.
 ;*******************************************************************************

 ; 배열의 [left,right] 엘리먼트를 대상으로 퀵소트를 수행한다.
 ; @param asc 오름차순 여부
 ; @param left 정렬할 엘리먼트의 시작 인덱스 (inclusive)
 ; @param right 정렬할 엘리먼트의 끝 인덱스 (inclusive)
 _Sort(asc, left, right) {
  If (left < right) {
   pivot := this._Partition(asc, left, right)
   this._Sort(asc, left, pivot - 1)
   this._Sort(asc, pivot + 1, right)
  }
 }
 ; left번째 엘리먼트를 피벗으로 하여, [left+1,right] 엘리먼트를 분할한다.
 ; @param asc 오름차순 여부
 ; @param left 정렬할 엘리먼트의 시작 인덱스 (inclusive)
 ; @param right 정렬할 엘리먼트의 끝 인덱스 (inclusive)
 ; @return 분할 후, 피벗의 인덱스
 _Partition(asc, left, right) {
  pivot := this.mem[left]
  i := left
  j := right + 1
  Loop {
   Loop {
    i++
    aVal := this.mem[i]
    If (i == right || (asc ? aVal >= pivot : aVal <= pivot))
     break
   }
   Loop {
    j--
    aVal := this.mem[j]
    If (j == left || (asc ? aVal <= pivot : aVal >= pivot))
     break
   }
   If (i >= j)
    break
   this._Swap(i, j)
  }
  this._Swap(left, j)
  return j
 }

 ; 배열의 i번째 엘리먼트와 j번째 엘리먼트를 스왑한다.
 ; @param i 인덱스
 ; @param j 인덱스
 _Swap(i, j) {
  tmp := this.mem[i]
  this.mem[i] := this.mem[j]
  this.mem[j] := tmp
 }
}
;*******************************************************************************
; private static 메소드
; 오토핫키에서는 private static 키워드를 지원하지 않으므로
; _로 시작하는 이름을 가지는 함수로 정의하도록 한다.
;*******************************************************************************
; 인덱스가 유효하면 그대로, 아니면 디폴트 값을 반환한다.
; @param idx 인덱스
; @param default 디폴트 값
; @return 유효한 인덱스
Array__ValidateIndex(idx, default) {
 return idx != "" ? idx : default 
}
; 인덱스가 유효한지 체크한다.
; @param idx 인덱스
; @param from lower bound
; @param to upper bound
Array__CheckIndex(idx, from, to) {
 return Array__IsInteger(idx) && from <= idx && idx <= to
}
; 변수에 저장된 값이 정수인지 여부를 반환한다.
; @param var 대상변수
; @return 변수에 저장된 값이 정수인지 여부
Array__IsInteger(var) {
 If var is integer
  return true
 Else
  return false
}


;----- 상용구 스크립트 시작


;───── IniBook 객체 속성 및 연산 선언
IniBook      := Object()
IniBook.isKeyExist   := "IniBook_isKeyExist"
IniBook.isContentExist  := "IniBook_isContentExist"
IniBook.addKey    := "IniBook_addKey"
IniBook.deleteKey   := "IniBook_deleteKey"
IniBook.getContent   := "IniBook_getContent"
IniBook.getSimilarKeys  := "IniBook_getSimilarKeys"


;───── ShortcutManager 객체 속성 및 연산 선언
ShortcutManager    := Object("IniBook", IniBook)
ShortcutManager.save  := "ShortcutManager_save"
ShortcutManager.load  := "ShortcutManager_load"
ShortcutManager.loadSeveral := "ShortcutManager_loadSeveral"
shortcut_manager   := Object("base", ShortcutManager)



;───── ShrotcutManager 객체 연산 정의
ShortcutManager_save(this)
{
 global key := ""
 content  := ""
 Send, {ctrldown}c{ctrlup}
 content  = %clipboard%
 inputbox, key, 단축키 저장, 다음의 내용을 저장할 상용구를 입력하고 확인을 누르세요!`n`n%content%, not_hide, 450, 180
 if (errorlevel == true)
 {
  msgbox, 저장을 취소하였습니다.
  return
 }
 if (this.IniBook.isKeyExist(key) == false)
 {
  write_decision := "Y"
 }
 else
 {
  exist_content := this.IniBook.getContent(key)
  msgbox, 1, 결정, %key%는 아래의 내용으로 이미 등록된 키입니다`n`n%exist_content% `n`n다음의 내용으로 덮어쓰시겠습니까?`n`n%content%
  IfMsgBox Ok
  {
   write_decision := "Y"
  }
  else
  {
   write_decision := "N"
  }
 }
 if (write_decision == "Y")
 {
  this.IniBook.addKey(key, content)
  msgbox, 저장되었습니다.`n`n단축 문자열 : %key%  `n`n내용 : %content%
 }
 else
 {
  msgbox, 저장을 취소하였습니다.
 }
}


ShortcutManager_load(this)
{
 key   := ""
 content  := ""

 original_clipboard := clipboard
 send, {ctrldown}{shiftdown}{left}{shiftup}c{ctrlup}
 key   := clipboard
 if (this.IniBook.isKeyExist(key) == false)
 {
  similar_key_array := this.IniBook.getSimilarKeys(key)
 
  msgbox % "키 " key " 가 없습니다. 유사키는 다음과 같습니다.`n`n" similar_key_array.tostring()
  return
 }
 content  := this.IniBook.getContent(key)
 clipboard := content
 send, {ctrldown}v{ctrlup}
 clipboard := original_clipboard

 return
}


ShortcutManager_loadSeveral(this)
{
 line_seperator   = ____________line_seperator____________
 AutoTrim, on
 original_strings =
 send, {end}{shiftdown}{home}{shiftup}{ctrldown}c{ctrlup}
 StringSplit, shortcut_array, clipboard, %A_Space%
 Loop, %shortcut_array0%
 {
  current_shortcut := shortcut_array%a_index%
  IniRead, origin_string, G:programautohotkeyinishortcut.ini, shortcut, %current_shortcut%^
  original_strings = %original_strings% %origin_string%
 }
 StringReplace, clipboard, original_strings, %line_seperator%, `n, All
 send, {ctrldown}v{ctrlup}
 return
}


;───── IniBook 객체 연산 정의
IniBook_isKeyExist(key)
{
 IniRead, content, G:programautohotkeyinishortcut.ini, shortcut, %key%^

 if (content == "ERROR")
 {
  return false
 }
 else
 {
  return true
 }
}
IniBook_addKey(key, content)
{
 line_seperator   = ____________line_seperator____________
 StringReplace, content, content, `r`n, %line_seperator%, All
 IniWrite, %content%, G:programautohotkeyinishortcut.ini, shortcut, %key%^
 return
}
IniBook_deleteKey(key)
{
 IniDelete, G:programautohotkeyinishortcut.ini, section2, %key%^
 return
}
IniBook_getContent(key)
{
 line_seperator   = ____________line_seperator____________
 IniRead, content, G:programautohotkeyinishortcut.ini, shortcut, %key%^
 StringReplace, content, content, %line_seperator%, `n, All
 return %content%
}

IniBook_getSimilarKeys(key)
{

 key_array   := Object()
 key_array["length"] := 0
 key_array_string :=
 index    = 1
 seperator   = ^
 key_array   := array_new()

 Loop, read, G:programautohotkeyinishortcut.ini
 {
  Loop, parse, A_LoopReadLine, %seperator%
  {
   if (A_Index == "1")
   {   
    IfInString, A_LoopField, %key%
    {
     key_array.add(A_LoopField)
    }
   }
  }
 }

 return Object("base", key_array)
}



;───── 단축키 지정
SCROLLLOCK:: shortcut_manager.save()
    return

PAUSE::   shortcut_manager.load()
    return
#PAUSE::  shortcut_manager.loadSeveral()
    return