DeepintoPharo:Chapter 05: Difference between revisions
Onionmixer (talk | contribs) (형식수정) |
Onionmixer (talk | contribs) (번역수정) |
||
Line 580: | Line 580: | ||
===스타트 업 액션(Start-up actions) 관리=== | ===스타트 업 액션(Start-up actions) 관리=== | ||
구식이라는 이유로 Pharo에서 제거된 개인설정도 많지만 여전히 많은 수의 개인설정이 남아 있는 상태다. Settings Browser의 사용이 쉽다고는 하지만 가령 하위집합에 대한 개인설정만 하더라도 매번 새 이미지를 작업해야 하기 때문에 지루할 수 있다. 이에 대한 해결책은 당신이 선호하는 선택들을 모두 설정하도록 스크립트를 구현하는 방법이다. 이를 위한 최선의 방법은 그러한 목적에 맞는 구체적인 클래스를 생성하는 것이다. 이후 패키지에 포함시켜 새로운 이미지를 원할 때마다 재로딩하는 것이 가능하다. | 구식이라는 이유로 Pharo에서 제거된 개인설정도 많지만 여전히 많은 수의 개인설정이 남아 있는 상태다. Settings Browser의 사용이 쉽다고는 하지만 가령 하위집합에 대한 개인설정만 하더라도 매번 새 이미지를 작업해야 하기 때문에 지루할 수 있다. 이에 대한 해결책은 당신이 선호하는 선택들을 모두 설정하도록 스크립트를 구현하는 방법이다. 이를 위한 최선의 방법은 그러한 목적에 맞는 구체적인 클래스를 생성하는 것이다. 이후 패키지에 포함시켜 새로운 이미지를 원할 때마다 재로딩하는 것이 가능하다. 이러한 클래스 유형을 Setting style이라 부른다. | ||
Revision as of 11:23, 18 February 2014
- 제 5 장 Settings 프레임워크
Settings 프레임워크
Alain Plantec 참여 (alain.plantec@univ-brest.fr)
애플리케이션이 성숙해가면서 기본 선택영역 색상, 기본 폰트 또는 기본 폰트 크기와 같은 변형(variation)을 제공해야 하는 경우가 종종 생긴다. 때때로 그러한 변형은 가능한 소프트웨어 맞춤설정(customization)에 대한 사용자 선호도를 나타낸다. Pharo의 1.1 버전 발매 이후부터 Pharo는 사용자 설정을 관리하기 위해 Settings 프레임워크를 포함하여 사용해오고 있다. 애플리케이션은 Settings를 이용해 그 구성을 제공한다. Settings는 Pharo의 개인설정을 관리하는 것으로 제한되지 않고 어떤 애플리케이션이든 사용을 권장한다. Settings의 장점으로는 개입적(intrusive)이지 않고, 소프트웨어의 모듈식 분해(modular decomposition)를 지원하며, 해당 애플리케이션이 시작된 이후에도 애플리케이션으로 추가가 가능하다는 점을 들 수 있다. Settings 프레임워크를 이제부터 살펴보겠다.
Settings 아키텍처
설정은 개인설정 정의와 조작에 대한 객체 지향 접근법을 지원한다. 그 말은 다음의 의미를 지닌다.
- 각 패키지나 서브시스템은 그 고유의 맞춤설정 포인트를 정의해야 한다 (보통 변수나 클래스 변수로서 표현). 서브시스템의 코드는 그러한 맞춤설정 값에 자유롭게 접근하고, 그 값을 이용해 개인설정을 반영하도록 그 행위를 변경한다.
- 서브시스템은 그 개인설정을 설명하여 최종 사용자가 조작할 수 있다. 하지만 서브시스템의 코드는 어떤 경우에도 그 행위를 조정하기 위해 setting 객체를 명시적으로 참조하지 않을 것이다.
서브시스템의 제어 흐름은 Settings를 수반하지 않는다. 이것이 바로 Pharo 1.0에서 개인설정 시스템(preference system)과 Settings 의 주요 차이점이다.
어휘
개인설정(preference)은 변수 값으로서 관리되는 특별한 값이다. 기본적으로 이러한 기본설정 값은 싱글톤의 인스턴스 변수나 클래스 변수에 보관되며, 단순한 접근자의 사용을 통해 직접 관리된다. Pharo는 사용자 인터페이스 테마, 데스크탑 배경색, 또는 부울 플래그와 같은 수많은 개인설정을 포함하여 소리의 사용을 허용하거나 금한다. 5.3 절에서는 개인설정을 정의하는 방법을 보여줄 것이다.
Setting 은 개인설정 값의 선언(설명)이다. 설정 브라우저를 통한 브라우징과 업데이트를 위해서는 개인설정 값이 설정에 의해 설명되어야 한다. 그러한 설정은 구체적 pragma로 태그된 특정 메서드에 의해 빌드된다. 이러한 구체적 pragma는 메서드를 자동으로 설정으로서 식별하는 데에 사용되는 분류 태그 역할을 한다 (그림 5.1 참고). 5.3절은 설정을 선언하는 방법을 설명한다.
Pharo 사용자들은 기존의 개인설정을 살펴보고 특정 사용자 인터페이스를 통해 그 값을 최종적으로 변경한다. 이것이 바로 5.2절에 소개된 Settings Browser의 주요 역할이다.
그림) UI-Basic에는 맞춤설정 포인트가 있다.
UI-Basic Setting이 그들을 설명한다.
Setting은 시스템 설정을 수집하고 설정 브라우저를 이동시킨다.
그림 5.1은 Settings가 구축한 아키텍처의 중요한 요점, 즉 Settings 패키지는 언로딩이 가능하다는 점과 개인설정을 정의하는 패키지는 Settings 패키지에 의존하지 않는다는 점을 보여준다. 이러한 아키텍처는 아래의 요점에 따라 지원된다:
맞춤설정 포인트. 각 애플리케이션은 맞춤설정 포인트를 정의한다. 그림 5.1에서 패키지 UI-Basic의 RealStateAgent 클래스는 창(windows)이 나타나는 장소를 정의하는 클래스 변수 UsedStrategy를 정의한 다. 패키지 UI-Basic 의 흐름은 모듈식(modular)이며 완전(self-contained)하기 때문에 RealStateAgent 클 래스는 settings 프레임워크에 의존하지 않는다. RealStateAgent 클래스는 매개변수화되도록 설계되어 왔 다.
맞춤설정 포인트의 설명. Settings 프레임워크는 UsedStrategy setting의 설명을 지원한다. 그림 5.1에서 UI- Basic Setting 패키지는 메서드를 지원하는데 클래스 RealStateAgent 또는 다른 클래스에 대한 확장 (extension)이 될 수도 있다. 여기서 요점은 setting을 선언하는 메서드가 Setting 클래스를 직접 참조하지 아니하고 빌더를 이용해 setting을 설명한다는 점이다. 이런 방식을 통해 설명은 참조를 소개하지 않고도 UI-Basic 패키지에서도 표시될 수 있다.
사용자 표현에 관한 설정 수집하기. Settings 패키지는 사용자가 자신의 개인설정을 변경 시 여는 Settings Browser와 같은 설정을 관리하는 데 사용될 툴들을 정의한다. Settings Browser는 설정들을 수집하고 그 설명을 이용해 개인설정의 값을 변경한다. 의존성과 프로그램의 제어 흐름은 항상 패키지 Settings로부터 개인설정을 가진 패키지를 향할 뿐, 그 반대로는 작용하지 않는다.
Settings Browser
그림 5.2에 소개된 Settings Browser 는 주로 현재 선언된 설정을 모두 살펴보고 관련 개인설정 값을 변경하도록 허용한다.
Settings Browser를 열기 위해서는 World 메뉴(World▷System▷Settings)를 사용하거나 아래 표현식을 평가하면 된다:
SettingBrowser open
설정은 중간 패널의 여러 개의 트리로 표시된다. 설정 검색과 필터링은 상단 툴바에서 이용 가능하고, 하단 패널은 현재 선택된 설정의 설명(좌측 하단 패널)과 현재 패키지 집합(우측 하단 패널)을 표시한다.
개인설정 값 찾아보기와 변경하기
설정 선언은 중간 패널에서 볼 수 있는 트리에 조직된다. 어떤 설정에 관한 설명을 확인하려면 설정을 클릭하면 되는데, 설정을 선택하면 좌측 하단 패널이 선택된 설정에 관한 정보로 업데이트된다.
개인설정 값의 변경은 간단히 브라우저를 통해 이루어지며, 각 행에는 우측에 위젯이 위치하므로 당신은 이 값을 업데이트할 수 있다. 위젯의 종류는 개인설정 값의 실제 타입에 따라 달라진다. 개인설정 값은 어떤 종류든 가능하지만 설정 브라우저(setting browser)는 현재 Boolean, Color, FileName, DirectoryName, Font, Number, Point, String 타입에 대한 특정 입력 위젯만 표시할 수 있는 실정이다. 드롭리스트, 비밀번호 필드, 또는 슬라이더를 이용해 범위 입력 위젯도 사용 가능하다. 물론 가능한 위젯의 목록은 닫혀 있는데, 설정 브라우저가 새로운 종류의 개인설정 값을 지원하도록 만들거나 다른 입력 위젯을 사용하도록 만드는 것이 가능하기 때문이다. 이와 관련된 요점은 5.8절에서 설명한다.
실제 설정 타입이 String, FileName, DirectoryName, Number, Point 중 하나라면 사용자는 값을 변경하기 위해 편집 가능한 드롭리스트 위젯에 텍스트를 입력해야 한다. 이런 경우, 리턴 키(또는 cmd-s를 이용)를 눌러 입력 값을 확인해야 한다. 그러한 설정 값이 자주 변경되는 경우 드롭리스트 위젯이 유용한데, 한 번의 클릭으로 이전에 입력한 값을 검색 및 이용할 수 있기 때문이다! 게다가 FileName이나 DirectoryName 인 경우 파일명이나 디렉터리명 선택자 대화상자를 열기 위한 버튼이 추가된다.
그 외의 가능한 실행은 모두 컨텍스트 메뉴에서 접근 가능하다. 선택된 설정에 따라 차이가 있을지도 모른다. 두 가지 가능한 버전을 그림 5.3에 표시하겠다.
- Expand all (a): 모든 설정 트리 노드를 재귀적으로 확장한다. 키보드 단축키 cmd-a 를 통해서도 접근 가능하다.
- Collapse all (a): 모든 설정 트리 노드를 재귀적으로 축소한다. 키보드 단축키 cmd-A 를 통해서도 접근 가능하다.
- Expand all from here: 현재 선택된 설정 트리 노드를 재귀적으로 확장한다.
- Browse (b): 설정을 선언하는 메서드 상에 시스템 브라우저를 연다. 키보드 단축키 cmd-b 를 통하거나 설정을 더블 클릭해도 접근 가능하다. 설정 구현을 변경하길 원하거나, 몇 가지 예제를 통해 프레임워크를 이해하도록 구현 방식을 살펴보고자 할 때 유용하다 (설정의 선언 방식은 5.3절에 설명되어 있다).
- Display export action string: 설정은 스타트업 액션으로 내보낼 수 있으며, 이 메뉴 옵션은 스타트 업 액션(start-up action)이 어떻게 코딩되는지 표시하도록 해준다 (스타트업 액션 관리는 5.7절에 설명된다).
- Set to default (d): 선택된 설정 값을 기본 값으로 설정한다. 가령 설정의 효과를 살펴보기 위해 이리저리 시도해보다가 결국 기본 값으로 돌아가기로 결정할 때 매우 유용하다.
- Empty list (e): 입력 위젯이 편집 가능한 드롭리스트인 경우 이 메뉴 항목은 기록된 목록을 비움으로써 이전에 입력된 값을 잊어버리도록 해준다.
설정 검색과 필터링
Pharo는 수많은 설정을 포함하므로 그 중에 하나를 찾기란 지루한 작업이 될 수 있다. 이 때 Settings Browser 상단 바의 검색 텍스트 필드에 텍스트를 입력함으로써 설정 목록을 필터링 할 수 있다. 그러면 당신이 입력한 텍스트가 포함된 설명이나 이름의 설정이 표시될 것이다. "Regexp" 체크박스를 체크할 경우 텍스트는 정규 표현식이 될 수 있다.
설정 목록의 필터링 방식으로, 패키지별로 선택하는 방법이 있다. "Choose package" 버튼을 누르면 일부 설정이 선언된 패키지의 목록이 담긴 대화상자가 열린다. 하나 또는 여러 개를 선택하면 선택된 패키지에 선언된 설정만 표시된다. 하단 우측 텍스트 패인은 선택된 패키지의 이름으로 업데이트된다.
Pharo를 어디서, 언제 사용하느냐에 따라 개인설정을 반복하여 변경해야 하는 경우가 있다. 예를 들어, 다른 사람들에게 시범을 보이고 있다면 더 큰 폰트를 원할 것이고, 직장이라면 프록시를 설정해야 할 테지만 집에서는 이 어느 것도 필요가 없다. 당신이 어디에 있고 무엇을 하느냐에 따라 개인설정 집합을 변경해야 한다는 점은 너무 지루한 일이 될 수도 있다. Settings Browser를 이용하면 현재 개인설정 값 집합을 명명된 스타일로 저장하여 후에 재로딩할 수 있다. 설정 스타일 관리는 ??(이 부분 확인 부탁드립니다.)절에서 소개한다.
설정 선언하기
Pharo의 모든 전역적 개인설정은 Settings Browser를 이용해 살펴보거나 변경할 수 있다. 개인설정은 싱글톤의 인스턴스 변수이거나 클래스 변수인 것이 보통이다. Settings Browser에서 그 값을 변경할 수 있으려면 설정을 그에 맞게 선언해야 한다. 설정은 특정 클래스 메서드에 의해 선언되어야 하며 이는 다음과 같이 구현되어야 한다: 빌더를 인자로 취하고, <systemsettings> pragma로 태그된다.
aBuilder라는 인자는 설정 선언을 빌드하는 데에 있어 API 또는 facade의 역할을 한다. pragma는 Settings Browser 가 현재 설정 선언을 동적으로 발견하도록 허용한다.
중요한 점은 설정 선언이 패키지 특정적이어야 한다는 사실이다. 다시 말해, 각 패키지는 고유의 설정을 선언해야 할 책임이 있다. 특정 패키지의 경우, 구체적인 설정이 그 클래스들 중 하나 또는 여러 개에 의해 선언되거나 동료(companion) 패키지에 의해 구현된다. (Pharo 1.0 의 경우처럼) 클래스 또는 패키지를 정의하는 전역적 설정은 없다. 직접적인 이점은 패키지가 로딩되면 그 설정도 자동으로 로딩되고, 패키지가 언로딩되면 그 설정도 자동으로 언로딩된다는 점이다. 뿐만 아니라 Setting 선언은 어떤 Setting 클래스도 참조해선 안 되고 빌더 인자를 참조해야 한다. 이를 통해 당신의 애플리케이션이 Settings로부터 의존적이지 않고, 극적으로 작은 footprint 애플리케이션을 정의하고자 할 때 Setting을 제거할 수 있도록 보장할 수 있다.
caseSensitiveFinds 개인설정의 예제를 살펴보자. 이는 부울형(boolean) 개인설정으로, 텍스트 검색에 사용된다. 그 값이 true라면 다음 검색은 대·소문자에 민감해진다. 이 개인설정은 TextEditor 클래스의 CaseSensitiveFinds 클래스 변수에 보관된다. 그 값은 쿼리 및 변경이 가능한데, 아래와 같이 각각 TextEditor class>>caseSensitiveFinds 와 TextEditor class>>caseSensitiveFinds: 에 의해 실행된다.
TextEditor class>>caseSensitiveFinds
^ CaseSensitiveFinds ifNil: [CaseSensitiveFinds := false]
TextEditor class>>caseSensitiveFinds: aBoolean
CaseSensitiveFinds := aBoolean
이러한 개인설정(예: CaseSensitiveFinds 클래스 변수)에 대한 설정(setting)을 정의하고 그것을 Settings Browser에서 확인 및 변경 가능하도록 아래와 같은 메서드가 구현된다. 결과는 그림 5.4의 스냅샷에 표시된다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
target: TextEditor;
label: 'Case sensitive search' translated;
description: 'If true, then the "find" command in text will always make its searches in
a case-sensitive fashion' translated;
parent: #codeEditing.
이제 설정 선언을 자세히 연구해보자.
헤더
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
...
해당 클래스 메서드는 CodeHolderSystemSettings 클래스에서 선언된다. 해당 클래스는 설정에 집중하며 설정 선언 외에는 어떤 것도 포함하지 않는다. 그러한 클래스를 정의하는 것은 의무가 아니며, 사실 어떤 클래스든 설정 선언을 정의할 수 있다. 우리는 레이어링(layering)을 목적으로 한 개인설정 정의 중 하나가 아니라 그와 다른 패키지로 설정 선언이 패키징되는 방식으로 정의하였다.
이 메서드는 빌더를 인자로 취한다. 그리고 해당 객체는 setting 빌딩을 위한 facade에 대해 API의 역할을 하므로, 메서드의 내용은 설정의 하위트리를 선언하고 구성하기 위해 빌더로 전송하는 메시지들로 구성되어야 한다.
pragma
설정 선언은 <systemsettings> pragma로 태그된다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
...
사실상 settings browser가 열리면 먼저 <systemsettings> pragma로 모든 메서드를 검색함으로써 모든 설정 선언을 수집한다. 또한 Settings Browser가 열려 있는 동안 설정 선언 메서드를 컴파일할 경우, 자동으로 새 설정으로 업데이트된다.
설정 구성
설정은 인자로서 전달되는 식별자와 함께 setting: 메시지를 빌더로 전송함으로써 구현된다. 식별자가 #caseSensitiveFinds인 예제를 소개하겠다:
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
...
빌더에게 setting: 메시지를 전송하면 그 자체가 설정 노드의 래퍼(wrapper)인 setting node builder가 생성된다. 기본적으로 인자로서 전달된 기호는 Settings Browser가 기본설정 값을 얻기 위해 사용하는 선택자로 간주된다. 개인설정 값을 변경하기 위한 선택자는 기본적으로 getter 선택자에 콜론을 추가하여 빌드된다 (예: 본문에서는 caseSensitiveFinds: 가 되겠다). 이러한 선택자들은 기본적으로 메서드가 구현된 클래스에 해당하는 대상(target)으로 전송된다 (예: CodeHolderSystemSettings). 따라서 caseSensitiveFinds와 caseSensitiveFinds: 접근자들이 CodeHolderSystemSettings 에 구현될 경우 이 단일 행으로 된 설정 선언으로 충분하다.
사실상 기본 initializations가 당신의 요구를 충족하지 못하는 경우도 자주 발생한다. 물론 자신의 특정 상황을 고려하기 위해 설정 노드 구성을 조정하는 방법도 있다. 가령, caseSensitiveFinds 설정에 해당하는 getting/setter 접근자는 TextEditor 클래스에 구현된다. 이후 우리는 대상이 TextEditor가 되도록 명시적으로 설정해야 하는데, 이는 업데이트된 정의에 표시하듯이 인자로서 전달된 대상 클래스 TextEditor와 함께 target: 메시지를 설정 노드로 전송함으로써 가능하다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
target: TextEditor
위와 같이 간략한 버전이라도 온전히 작동하며 그림 5.5에서 보이는 바와 같이 Settings Browser가 컴파일하고 고려하기에 충분하다.
그림 5.5: caseSensitiveFinds 설정의 단순한 첫 버전
불행히도 그 표현은 사용자 친화적이라고 할 수 없는데, 그 이유는 다음과 같다:
- settings browser에 표시된 라벨이 식별자이다 (그로 접근하기 위해 접근자를 빌드하는 데 사용된 기호).
- 해당 설정에 이용할 수 있는 설명이나 해설이 없다.
- 새로운 설정이 설정 트리의 루트에 추가된다.
위와 같은 단점을 극복하기 위해서는 각각 문자열을 인자로서 취하는 label: 과 description: 메시지를 이용해 라벨과 설명으로 자신의 설정 노드를 좀 더 구성할 수 있다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
target: TextEditor;
label: 'Case sensitive search' translated;
description: 'If true, then the "find" command in text will always make its searches
in a case-sensitive fashion' translated;
parent: #codeEditing.
라벨과 설명 문자열로 translated 를 전송해야 함을 잊지 말아야 하는데, 이를 전송 시 다른 언어로 해석 기능이 크게 향상될 것이다.
분류와 설정 트리 구성과 관련해 향상 방법이 여러 가지가 있는데, 다음 절에서 상세히 다루도록 하겠다.
대상(target)에 관한 추가 정보
설정의 대상은 개인설정 값을 얻고 변경하기 위한 수신자인데, 대부분의 경우 클래스에 해당한다. 실제로 개인설정 값은 클래스 변수에 보관되는 것이 보통이다. 따라서 클래스 측 메서드는 설정으로 접근하기 위한 접근자로서 사용된다.
하지만 수신자는 싱글톤 객체가 될 수도 있다. 다수의 개인설정이 사실상 이에 해당한다. 예를 들자면, Free Type 폰트 개인설정은 모두 FreeTypeSettings 싱글톤의 인스턴스 변수에 보관된다. 따라서 여기에서 수신자는 FreeTypeSettings 인스턴스로, 아래 표현식을 평가함으로써 얻을 수 있다:
FreeTypeSettings current
이에 따라 사용자는 이 표현식을 이용해 그에 상응하는 설정의 대상을 구성할 수 있겠다. 예를 들어, #glyphContrast 개인설정은 아래와 같이 선언이 가능하다:
(aBuilder setting: #glyphContrast)
target: FreeTypeSettings current;
label: 'Glyph contrast' translated;
...
간단하지만 안타깝게도 이와 같은 싱글톤 대상을 선언하는 것은 좋은 생각이 아니다. 이 선언은 Setting style 기능과 호환이 가능하다 (??절 참조)(이 부분도 확인 부탁드립니다). 이러한 경우 싱글톤을 얻기 위해 대상 클래스로 전송하는 메시지 선택자와 대상 클래스를 따로 표시해야 한다. 따라서 아래 예제와 같이 targetSelector: 메시지를 이용해야 한다:
(aBuilder setting: #glyphContrast)
target: FreeTypeSettings;
targetSelector: #current;
label: 'Glyph contrast' translated;
...
기본값에 관한 추가 정보
Settings Browser가 설정 입력 위젯을 빌드하는 방법은 실제 개인설정 값의 타입에 따라 좌우된다. 개인설정에 대한 값으로 nil 을 갖는 것은 Settings Browser 에 문제가 되는데, 사용해야 할 입력 위젯을 알아내지 못하기 때문이다. 따라서 기본적으로 개인설정을 양호한 입력 위젯으로 적절하게 표시하려면 항상 nil이 아닌 값으로 설정되어야 한다. 기본값은 평상시와 같이 초기화를 통해 개인설정으로 설정이 가능하며, 개인설정의 접근자 메서드에 프로그램화된 느긋한 초기화나 #initialize 메서드를 이용하면 되겠다.
Settings Browser 와 관련해 최선의 방법은 느긋한 초기화를 사용하는 것이다 (5.3절에 제공된 #caseSensitiveFinds 개인설정 예제 참조). 사실 5.2절에서 설명한 바와 같이 Settings Browser 컨텍스트 메뉴에서 개인설정 값을 기본값으로 리셋하거나 모든 개인설정 값을 전역적으로 리셋하는 방법도 있다. 이는 개인설정 값을 nil 로 리셋되도록 설정하면 가능하다. 그 결과 개인설정은 그에 집중하는 접근자를 이용하는 즉시 자동으로 기본값에 설정된다.
접근자가 구현되는 방식을 항상 변경할 수 있는 것은 아니다. 아마도 개인설정 접근자가 당신이 변경을 허용하지 않는 다른 패키지에서 유지되기 때문일 것이다. 이 방법이 아니면, 아래 예에서 볼 수 있듯이 설정 노드로 default: 메시지를 전송함으로써 설정의 선언으로부터 기본값을 나타내는 방법도 있다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
default: true;
...
자신의 설정 구성하기
Settings Browser 내에서 설정은 트리로 구성되며, 관계된 설정들은 같은 부모의 자식으로 표시된다.
부모 선언하기
자신의 설정을 다른 설정의 자식으로 선언하는 가장 간단한 방법은 부모 설정의 식별자를 인자로서 전달되도록 하여 parent: 메시지를 사용하는 것이다. 아래 예제에서 부모 노드는 #codeEditing 식별자로 선언된 기존의 노드이다.
CodeHolderSystemSettings class>>caseSensitiveFindsSettingsOn: aBuilder
<systemsettings>
(aBuilder setting: #caseSensitiveFinds)
target: TextEditor;
label: 'Case sensitive search' translated;
description: 'If true, then the "find" command in text will always make its searches in a case-sensitive fashion' translated;
parent: #codeEditing.
#codeEditing 노드 또한 시스템 내 어딘가에서 선언되어 있다. 예를 들어 그룹으로 정의되기도 하는데, 이를 이제부터 살펴보도록 하자.
그룹 선언하기
그룹은 어떤 값도 없는 단순한 노드로서, 자식들의 그룹화에만 사용된다. #codeEditing 이 식별하는 노드는 빌더에게 인자로서 전달되는 식별자와 함께 group: 메시지를 전송하면 생성된다. 그림 5.4에서와 같이 #codeEditing 노드는 그 자체가 #codeBrowsing 노드의 자식으로 선언되기 때문에 루트에 위치하지 않음을 명심하라.
CodeHolderSystemSettings class>>codeEditingSettingsOn: aBuilder
<systemsettings>
(aBuilder group: #codeEditing)
label: 'Editing' translated;
parent: #codeBrowsing.
하위트리 선언하기
고유의 설정을 기존 노드의 자식으로 선언할 수 있다면 패키지가 기존 표준 설정을 강화하고자 할 때 매우 유용하게 작용할 수 있겠다. 하지만 애플리케이션 특정적인 설정의 경우 매우 지루한 일이 되기도 한다.
따라서 하나의 메서드에 설정의 하위트리를 직접 선언하는 것도 가능하다. 보통 루트 그룹은 애플리케이션 설정에 대해 선언되고, 자식 설정들도 동일한 메서드 내에서 선언된다. 이는 루트 그룹으로 with: 메시지를 전송하면 간단히 실행 가능하다. with: 메시지는 블록을 인자로서 취한다. 이 블록에서 모든 새로운 설정은 루트 그룹의 자식으로서 암묵적으로 선언된다 (with: 메시지의 수신자).
그림 5.6: 하나의 메서드에 하위트리 선언하기: Configurable formatter 설정 예제.
예로 그림 5.6을 살펴보면 재팩토링 브라우저 구성 가능 포맷터를 볼 수 있다. 이러한 설정의 하위트리는 아래 제공된 RBConfigurableFormatter class>>settingsOn: 메서드에 완전히 선언된다. 두 개의 자식, #formatCommentWithStatements 와 #indentString 으로 #configurableFormatter 라는 새 루트 그룹을 선언함을 볼 수 있을 것이다.
RBConfigurableFormatter class>>settingsOn: aBuilder
<systemsettings>
(aBuilder group: #configurableFormatter)
target: self;
parent: #refactoring;
label: 'Configurable Formatter' translated;
description: 'Settings related to the formatter' translated;
with: [
(aBuilder setting: #formatCommentWithStatements)
label: 'Format comment with statements' translated.
(aBuilder setting: #indentString)
label: 'Indent string' translated]
선택적 하위트리
특정 개인설정의 값에 따라 표시할 의미가 없는 설정은 숨기길 원할지도 모른다. 가령, 데스크탑의 배경색이 평범하다면 gradient 배경에 관련된 설정은 보일 필요가 없다. 하지만 사용자가 gradient 배경을 원한다면 두 번째 색상, gradient 방향, gradient 시작점(origin) 설정이 표시되어야 한다. 그림 5.7을 살펴보자:
- 좌측에 Gradient 위젯은 체크가 해제되었는데 실제 값이 false임을 의미하며, 이런 경우 자식이 없다는 뜻이다.
- 우측에 Gradient 위젯이 체크되어 있는데 설정 값이 true로 설정되었으며 그 결과 gradient 배경을 설정하는 데에 유용한 설정이 표시된다.
그림 5.7: 선택적 하위트리의 예제. 우측 - 어떤 gradient도 선택되지 않았다. 좌측 - gradient가 선택되어 추가 개인설정이 이용 가능하다.
선택적 설정을 처리하기란 간단한데, 선택적 설정이 부울형 부모 설정의 자식으로서 선언되면 끝이다. 이런 경우, 자식 설정은 부모의 값이 true일 때에만 표시된다. 데스크탑 gradient 예제에서 설정은 아래와 같이 PolymorphSystemSettings 에 선언된다:
(aBuilder setting: #useDesktopGradientFill)
label: 'Gradient';
description: 'If true, then more settings will be available to define the desktop
background color gradient';
with: [
(aBuilder setting: #desktopGradientFillColor)
label: 'Other color';
description: 'This is the second color of your gradient (the first one is given by the "Color" setting' translated.
(aBuilder pickOne: #desktopGradientDirection)
label: 'Direction';
domainValues: {#Horizontal. #Vertical. #Radial}.
(aBuilder pickOne: #desktopGradientOrigin)
label: 'Origin';
domainValues: {
'Top left' translated -> #topLeft. ...
부모 설정 값은 PolymorphSystemSettings class >>useDesktopGradientFill 를 평가함으로써 주어진다. 이것이 true를 리턴하면 자식에 해당하는 #desktopGradientFillColor, #desktopGradientDirection, #desktopGradientOrigin 가 표시된다.
설정 정렬하기
기본적으로 형제(sibling) 설정들은 라벨의 알파벳 순으로 정렬된다. 이러한 기본 행위를 변경하길 원하는 수도 있다. 설정 정렬은 두 가지 방식으로 변경이 가능한데, 기본 정렬을 단순히 금지시키거나 정렬을 분명히 명시하는 방법이다.
아래 #appearance 그룹의 예제에서와 같이 부모 노드로 noOrdering 메시지를 전송함으로써 어떤 정렬도 실행되지 않도록 지시할 수 있다. 이후 그 자식들은 선언된 순서대로 정렬된다.
appearanceSettingsOn: aBuilder
<systemsettings>
(aBuilder group: #appearance)
label: 'Appearance' translated;
description: 'All settings concerned with the look''n feel of your system' translated;
noOrdering;
with: [... ]
인자로서 전달되는 숫자와 함께 order: 메시지를 전송하여 형제들 간 설정 노드의 정렬을 나타낼 수도 있다. 숫자는 Integer 또는 Float이 가능하다. 정렬 번호에 해당하는 노드는 항상 다른 노드들보다 앞에 위치해야 하며, 각 정렬 번호에 따라 정렬된다. 순서가 항목에 주어지면 다른 형제들에게는 어떤 정렬도 적용되지 않는다.
예를 들어, #standardFonts 그룹이 선언되는 방식을 살펴보자:
(aBuilder group: #standardFonts)
label: 'Standard fonts' translated;
target: StandardFonts;
parent: #appearance;
with: [
(aBuilder launcher: #updateFromSystem)
order: 1;
targetSelector: #current;
script: #updateFromSystem;
label: 'Update fonts from system' translated.
(aBuilder setting: #defaultFont)
label: 'Default' translated.
(aBuilder setting: #codeFont)
label: 'Code' translated.
(aBuilder setting: #listFont)
...
이 예제에서 런처 #updateFromSystem이 첫 번째 노드가 되도록 선언되고, #defaultFont, #codeFont, #listFont 식별자로 된 다른 형제들은 선언된 순서로 위치한다.
좀 더 정밀한 값 도메인 제공하기
기본적으로 개인설정에 가능한 값 집합은 제한되지 않으며 개인설정의 실제 타입에 의해 주어진다. 가령, 색상 개인설정의 경우 위젯은 당신이 원하는 색상을 선택하도록 허용하며, 숫자의 경우 위젯은 사용자가 어떤 숫자든 입력하도록 허용한다. 하지만 일부 사례에서는 값의 특정 집합만이 바람직하다. 가령 표준 브라우저나 사용자 인터페이스 테마 설정의 경우 유한의 클래스 집합에서 선택해야 하며, 자유형(free type) 캐시 크기의 경우 0-50000 범위만 허용된다. 이러한 경우 위젯이 특정 값만 수락한다면 좀 더 편할 것이다. 이러한 문제를 해결하기 위해 도메인 값 집합을 범위나 값 리스트로 제한할 수 있다.
범위 설정 선언하기
예를 들어, 그림 5.8에 소개된 전체 화면 여백(margin) 개인설정을 고려해보자. 그 값은 창을 확대할 때 창 주위에 허용되는 여백의 크기를 픽셀로 표현한다.
그 값은 정수지만 -100이나 5000로 설정하는 것은 말이 되지 않는다. 최소 -5부터 최대 100까지가 양호한 범위의 값이 된다. 이 범위를 이용해 설정 위젯을 제약할 수도 있겠다. 아래 예제에서 보이듯이 간단한 설정에 비해 두 가지의 차이점이 있다:
새 설정 노드가 setting: 메시지 대신 range: 메시지를 이용해 생성된다.
유효한 범위는 설정 노드로 range: 메시지를 전송하여 주어지고, Interval이 인자로서 주어진다.
screenMarginSettingOn: aBuilder
<systemsettings>
(aBuilder range: #fullScreenMargin)
target: SystemWindow;
parent: #windows;
label: 'Full screen margin' translated;
description: 'Specify the amount of space that is let around a windows when it''s
opened fullscreen' translated;
range: (-5 to: 100).
리스트에서 선택하기
개인설정 값이 특정 값의 리스트 중 하나로 제한된 경우 그것을 선언하여 settings browser가 드롭리스트를 사용하도록 만드는 것이 가능하다. 드롭리스트는 사전에 정의된 유효 값으로 초기화된다. 가령, 창 위치 전략(window position strategy) 예를 고려해보자. 그에 해당하는 위젯이 settings browser 내에서 실행되는 모습을 그림 5.9에서 소개한다. 허용되는 값은 'Reverse Stagger', 'Cascade', 'Standard' 가 있다.
아래 예제는 창 위치 전략 설정을 간소화한 선언을 보여준다.
windowPositionStrategySettingsOn: aBuilder
<systemsettings>
(aBuilder pickOne: #usedStrategy)
label: 'Window position strategy' translated;
target: RealEstateAgent;
domainValues: #(#'Reverse Stagger' #Cascade #Standard)
간단한 설정에 비해 유일한 두 가지 차이점은 다음과 같다:
- #setting: 메시지 대신 pickOne: 메시지로 새 설정 노드가 생성된다.
- 새로 선언된 설정 노드로 domainValues: 메시지를 전송함으로써 승인된 값의 리스트가 주어지고, Collection이 인자로서 주어진다 (기본적으로 첫 번째 인자).
이러한 창 전략 예제와 관련해, 개인설정으로 설정된 값은 #'Reverse Stagger'이거나 #Cascade 또는 #Standard가 될 것이다.
다행히 이러한 값들은 그다지 편리하지 못하다. 가령 프로그래머는 일종의 Strategy 객체나 직접적으로 선택자 역할을 할 수 있는 Symbol로 된 다른 값을 원할지도 모른다. 사실상 이 두 번째 해결책은 RealEstateAgent 클래스 관리자들에 의해 선택되었다. RealEstateAgent usedStrategy가 리턴한 값을 살펴보면 결과가 #'Reverse Stagger', #Cascade, #Standard 중 하나가 아니라 다른 기호(symbol)임을 눈치챌 것이다. 그리고 창 위치 전략 설정이 어떻게 구현되었는지 살펴보면 선언이 이전에 주어진 기본 해결책과 다르다는 사실을 알게 될 것이다: domainValues: 인자는 단순한 Symbols의 배열이 아니라 아래 선언에서 볼 수 있듯이 Associations의 배열이다:
windowPositionStrategySettingsOn: aBuilder
<systemsettings>
(aBuilder pickOne: #usedStrategy)
...
domainValues: {'Reverse Stagger' translated -> #staggerFor:initialExtent:world:. '
Cascade' translated -> #cascadeFor:initialExtent:world:. 'Standard' translated ->
#standardFor:initialExtent:world:};
Settings Browser 관점에서 보면 리스트 내용은 정확히 동일하며 사용자는 그 차이를 구별할 수가 없는데, Associations의 배열이 domainValues: 에게 인자로서 주어진다면 Associations의 키는 사용자 인터페이스에 대해 사용되기 때문이다.
개인설정 자체의 값과 관련해 RealEstateAgent usedStrategy를 살펴보면 결과가 #staggerFor:initialExtent:world:, #cascadeFor:initialExtent:world:, 그리고 #standardFor:initialExtent:world: 중에 해당하는 값임을 눈치챌 것이다. 사실 Associations의 값은 설정에 가능한 모든 실제 값을 계산하는 데에 사용된다.
가능한 값의 리스트는 어떤 종류든 가능하다. 또 다른 예로, 사용자 인터페이스 테마 설정이 PolymorphSystemSettings 클래스에서 어떻게 선언되는지를 살펴보자:
(aBuilder pickOne: #uiThemeClass)
label: 'User interface theme' translated;
target: self;
domainValues: (UITheme allThemeClasses collect: [:c | c themeName -> c])
이 예제에서 domainValues: 는 연관(associations)의 배열을 취하며, Settings Browser가 열릴 때마다 계산된다. 각 연관은 테마명을 키로, 테마를 구현하는 클래스를 값으로 하여 구성된다.
스크립트 시작하기
외부 구성 툴을 시작(launch)하거나 스크립트의 도움을 받아 특정 패키지 또는 시스템을 구성하도록 허용하길 원한다고 가정해보자. 그러한 상황에서는 런처(launcher)를 선언할 수 있다. 런처는 일반 설정처럼 라벨로 표시되는데, 일반 설정과 다른 점은 라벨에 어떤 값도 입력되지 않는다는 사실이다. 대신 Launch 라고 라벨이 붙은 버튼이 Settings Browser 에 통합되고, 버튼을 클릭하면 연관된 스크립트가 시작된다.
예를 들어, 시스템이 True Type Fonts를 사용하려면 호스트 시스템에서 이용 가능한 모든 폰트를 수집하여 시스템을 업데이트해야만 하는데, 이는 아래 표현식을 평가함으로써 가능하다:
FreeTypeFontProvider current updateFromSystem
Settings Browser로부터 해당 스크립트를 실행하는 것도 가능하다. 해당하는 런처는 그림 5.10에서 소개하겠다. 그러한 런처의 통합은 꽤 간단하다. 그저 그에 맞는 설정을 선언하기만 하면 된다! 예를 들어, TT 폰트에 대한 런처가 선언되는 방식을 살펴보자.
GraphicFontSettings class>> standardFontsSettingsOn:
<systemsettings>
(aBuilder group: #standardFonts)
...
(aBuilder launcher: #updateFromSystem) ...
target: FreeTypeFontProvider;
targetSelector: #current;
script: #updateFromSystem;
label: 'Update fonts from system' translated.
간단한 설정에 비교하면 두 가지 차이점이 있다:
- 새 설정 노드는 빌더로 launcher: 메시지를 전송함으로써 생성된다.
- script: 메시지는 인자로서 전달된 스크립트의 선택자와 함께 설정 노드로 전송된다.
스타트 업 액션(Start-up actions) 관리
구식이라는 이유로 Pharo에서 제거된 개인설정도 많지만 여전히 많은 수의 개인설정이 남아 있는 상태다. Settings Browser의 사용이 쉽다고는 하지만 가령 하위집합에 대한 개인설정만 하더라도 매번 새 이미지를 작업해야 하기 때문에 지루할 수 있다. 이에 대한 해결책은 당신이 선호하는 선택들을 모두 설정하도록 스크립트를 구현하는 방법이다. 이를 위한 최선의 방법은 그러한 목적에 맞는 구체적인 클래스를 생성하는 것이다. 이후 패키지에 포함시켜 새로운 이미지를 원할 때마다 재로딩하는 것이 가능하다. 이러한 클래스 유형을 Setting style이라 부른다.
Setting styles 를 관리하는 데에 Settings Browser 는 두 가지 방면에서 유용하다. 첫째, 개인설정 값을 어떻게 변경하는지 발견하도록 도와주고, 둘째, 당신을 위해 특정 스타일을 생성 및 업데이트 할 수 있다.
스크립트 설정
개인설정 변수는 접근자 메서드로 모두 접근이 가능하기 때문에 간단한 스크립트에서 개인설정 집합을 초기화하는 것도 당연히 가능하다. 단순화를 위해 Setting 스타일로 구현해보도록 하자.
예를 들어, 배경색을 변경하고 모든 폰트를 기본값보다 크게 설정하도록 스크립트를 구현할 수 있겠다. 그에 맞는 Setting 스타일 클래스를 생성해보자. 이를 MyPreferredStyle이라 부를 수 있겠다. 스크립트는 MyPreferredStyle의 메서드에 의해 정의된다. 이 선택자는 설정과 관련된 스크립트 평가에 대한 표준 도구(standard hook)이기 때문에 해당 메서드를 loadStyle 이라 부르겠다.
MyPreferredStyle>>loadStyle
| f n |
"Desktop color"
PolymorphSystemSettings desktopColor: Color white.
"Bigger font"
n := StandardFonts defaultFont. "get the current default font"
f := LogicalFontfamilyName: n familyName pointSize: 12. "font for my preferred size"
StandardFonts setAllStandardFontsTo: f "reset all fonts"
PolymorphSystemSettings는 PolyMorph 와 관련된 모든 설정이 선언되는 클래스다. StandardFonts 는 Pharo 의 기본 폰트를 관리하는 데에 사용되는 클래스다.
이제 데스크탑 색상 설정이 PolymorphSystemSettings에서 선언되고 DefaultFonts 클래스가 폰트 관리를 허용함을 어떻게 알아낼 것인지를 질문해야 한다. 좀 더 일반적으로 말하자면, 이 모든 설정은 어디에서 선언되고 관리되는지가 되겠다.
이에 대한 답은 꽤 간단한데, Settings Browser를 사용하면 될 일이다! 5.2절에서 설명하였듯, cmd-b 또는 항목을 더블 클릭하면 현재 설정 노드의 선언에 브라우저가 열린다. 컨텍스트 메뉴를 사용해도 된다. 선언을 살펴보면 개인설정 값에 대한 선택자와 대상 클래스를 (개인설정 변수가 보관된) 제공할 것이다.
다음으로, MyPreferredStyle 자체가 시스템에서 로딩될 때 StyleMyPreferredStyle>>#loadStyle가 자동으로 실행되길 원한다. 이 목적을 위해 우리가 유일하게 해야 할 일은 MyPreferredStyle 클래스에 대한 initialize 메서드를 구현하는 일이다:
MyPreferredStyle class>>initialize
self new loadStyle
Settings Browser에 스타일 통합하기
어떤 스크립트든 Settings Browser에 통합하여 후에 로딩, 브라우징 또는 그로부터 제거가 가능하다. 이 목적에 부합하려면 그에 대한 이름을 선언하고 Settings Browser가 발견하도록 확실히 해두기만 하면 된다. 자신의 스타일 클래스에서 클래스 측에 styleName 이라는 메서드를 구현하라. 앞 절의 예제에서는 아래와 같이 구현되어야겠다:
MyPreferredStyle class>>styleName
"The style name used by the SettingBrowser"
<settingstyle>
^ 'My preferred style'
MyPreferredStyle class>>styleName는 어떤 인자도 취하지 않고 자신의 스타일 이름을 String 으로서 리턴해야 한다. <settingstyle> pragma를 이용해 Settings Browser 로 하여금 MyPreferredStyle이 설정 스타일 클래스라는 사실을 알도록 한다.
해당 메서드가 컴파일되고 나면 Setting Browser를 열어 Style 탑 메뉴를 팝업시킨다. 그림 5.11과 같이, 자신의 스타일명으로 구성된 리스트가 포함된 대화상자가 열릴 것이다.
그림 5.11: 자신만의 스타일이 표시된 스타일 로드 대화창
Settings Browser 확장하기
5.2절에 설명한 바와 같이 Settings Browser 는 기본적으로 간단한 개인설정 타입을 관리할 수 있다. 이러한 기본값만으로도 보통은 충분하다. 하지만 더 복잡한 개인설정 값의 처리 능력이 매우 유용하게 작용하는 상황들이 몇 가지 있다.
예를 들어 텍스트 선택영역(text selection) 개인설정을 중심적으로 살펴보자. 일차 선택영역, 3가지 유형의 선택적 텍스트 선택영역, 이차 선택영역, 찾기 및 바꾸기 선택영역, 선택영역 바(selection bar)가 있다. 모든 선택영역에 대해 배경색을 설정 가능하다. 일차와 이차 선택영역, 그리고 찾기 및 바꾸기 선택영역에 대한 텍스트 색상도 선택이 가능하다.
선택영역 설정을 개별적으로 선언하기
지금까지는 기본 가능성에 따라 설정을 텍스트 선택영역의 각 특성에 따라 선언이 가능하여 그에 해당하는 개인설정을 Settings Browser로부터 개별적으로 변경할 수 있었다. 특정 유형의 선택영역에 선언된 설정은 모두 설정 그룹의 자식으로 그룹화할 수 있다. 곧바로 적용시켜보면, 선택적 텍스트 선택영역에 대해 단순한 그룹 대신 부울형(boolean) 설정을 이용할 수 있겠다.
예를 들어, 이차 선택영역을 살펴보자. 해당 유형의 텍스트 선택영역은 선택적이며, 사용자는 그에 대한 배경색과 텍스트 색상을 설정할 수 있다. 그에 해당하는 개인설정은 ThemeSettings의 인스턴스 변수로서 선언된다. 그들의 값은 연관된 ThemeSettings 인스턴스를 얻음으로써 현재 테마로부터 읽고 변경하기가 가능하다. 따라서 두 가지 색상 설정은 아래와 같이 #useSecondarySelection 부울형 설정의 자식으로 선언될 수 있겠다.
(aBuilder setting: #useSecondarySelection)
target: UITheme;
targetSelector: #currentSettings;
label: 'Use the secondary selection' translated;
with: [
(aBuilder setting: #secondarySelectionColor)
label: 'Secondary selection color' translated.
(aBuilder setting: #secondarySelectionTextColor)
label: 'Secondary selection text color' translated].
그림 5.12는 Settings Browser 에서 이러한 설정 선언들을 보여준다. 모양과 느낌은 깔끔하지만 두 가지가 관찰된다:
- 각 선택영역 유형마다 세 개의 행을 취하는데, 각 선택영역을 보는 데에 너무 많은 수직 공간을 차지하여 약간 불편한 감이 있다.
- 기본이 되는 모델이 명시적으로 디자인되지 않았고, 한 종류의 선택영역에 대한 설정이 Settings Browser 에 그룹화되었으나 그에 대한 개인설정 값은 ThemeSettings의 구분된 인스턴스 변수로서 선언되었다. 다음 절에서는 첫 번째 해결책을 더 나은 디자인으로 개선하는 방법을 살펴보겠다.
그림 5.12: 기본 설정값으로 선언된 이차 선택영역 설정
개선된 선택영역 개인설정 디자인
텍스트 선택영역 개인설정의 개념을 디자인하는 것이 더 나은 해결책이 되겠다. 그러면 각 선택영역 개인설정마다 관리하는 데에 세 개가 아니라 하나의 값만 갖게 된다. 텍스트 선택영역 개인설정은 기본적으로 두 가지 색상으로 구성되는데, 하나는 배경색, 나머지는 텍스트 색상이다. 일차 선택영역을 제외하고 각 선택영역은 선택적이다. 그렇다면 아래와 같이 텍스트 선택영역 개인설정을 디자인할 수 있을 것이다:
Object subclass: #TextSelectionPreference
instanceVariableNames: 'backgroundColor textColor mandatory used'
classVariableNames: 'FindReplaceSelection PrimarySelection SecondarySelection
SelectionBar'
poolDictionaries: ''
category: 'Settings-Tools'
TextSelectionPreference는 네 개의 인스턴스 변수로 구성된다. 그 중 두 개는 색상과 관련된다. mandatory 인스턴스 변수가 false로 설정되었다면 사용된 부울형 인스턴스 변수는 변경이 가능하다. 대신 mandatory가 true로 설정되었다면 사용된 인스턴스 변수는 true로 설정되고 변경이 불가하다.
TextSelectionPreference 에도 네 개의 클래스 변수가 있는데, 가능한 텍스트 선택영역 개인설정 유형마다 하나씩 해당한다. getters와 setters 또한 Settings Browser 로부터 이러한 개인설정을 관리할 수 있도록 구현될 것이다. 예로 PrimarySelection를 살펴보자:
TextSelectionPreference class>>primarySelection
^ PrimarySelection
ifNil: [PrimarySelection := self new
textColor: Color black;
backgroundColor: (Color blue alpha: 0.5);
mandatory: true;
yourself]
mandatory 속성이 true로 초기화되었음을 눈치챌 것이다.
선택영역 바 개인설정의 또 다른 예는 다음과 같다:
TextSelectionPreference class>>selectionBar
^ SelectionBar
ifNil: [SelectionBar := self new
backgroundColor: Color lightBlue veryMuchLighter;
mandatory: false;
yourself]
여기서 개인설정은 선택적으로, 그리고 어떤 텍스트 색상도 없이 선언됨을 눈치챌 것이다.
이러한 개인설정이 Settings Browser로부터 변경 가능하려면 두 가지 메서드를 선언해야 한다. 첫 번째는 설정 선언을, 두 번째는 뷰(view)를 구현하기 위함이다.
설정 선언은 아래와 같이 구현된다:
TextSelectionPreference class>>selectionPreferenceOn: aBuilder
<systemsettings>
(aBuilder group: #selectionColors)
label: 'Text selection colors' translated;
parent: #appearance;
target: self;
with: [(aBuilder setting: #primarySelection) order: 1;
label: 'Primary'.
(aBuilder setting: #secondarySelection)
label: 'Secondary'.
(aBuilder setting: #findReplaceSelection)
label: 'Find/replace'.
(aBuilder setting: #selectionBar)
label: 'Selection bar']
보다시피 이 선언에서 새로운 내용은 전혀 없다. 변한 내용이라면 개인설정의 값이 사용자가 정의한 클래스라는 점이다. 사실 사용자 정의 또는 애플리케이션 특정적 개인설정 클래스의 경우 해야 할 일은 뷰에 대해 보완(supplementary) 메서드를 하나 구현하는 것 뿐이다. 이 메서드는 settingInputWidgetForNode: 로 명명되어야 하며, 클래스 메서드로서 구현되어야 한다.
SettingInputWidgetForNode: 메서드는 Settings Browser 에 대한 입력 위젯을 빌드하는 책임을 갖고 있다. 해당 메서드는 SettingDeclaration을 인자로 취한다. SettingDeclaration은 기본적으로 모델이며, 그 인스턴스들은 Settings Browser가 관리한다.
각 SettingDeclaration 인스턴스는 개인설정 값 홀더의 역할을 한다. 사실 Settings Browser 에서 볼 수 있는 각 설정은 내부적으로 SettingDeclaration 인스턴스에 의해 표현된다.
우리 예제에서 텍스트 선택영역 개인설정마다 그 색상을 변경 가능하고, 선택영역이 선택적인 경우 활성화/해제를 원한다. 색상과 관련해서는, 선택영역 개인설정 값에 따라 배경색만 항상 표시된다. 사실 개인설정 값의 텍스트 색이 nil인 경우, 이는 텍스트 색상을 갖고 있는 것이 의미가 없음을 의미하므로, 그에 해당하는 색상 선택자가 빌드되지 않는다.
settingInputWidgetForNode: 메서드는 아래와 같이 구현 가능하다:
TextSelectionPreference class>>settingInputWidgetForNode: aSettingDeclaration
| preferenceValue backColorUI usedUI uiElements |
preferenceValue := aSettingDeclaration preferenceValue.
usedUI := self usedCheckboxForPreference: preferenceValue.
backColorUI := self backgroundColorChooserForPreference: preferenceValue.
uiElements := {usedUI. backColorUI},
(preferenceValue textColor
ifNotNil: [ { self textColorChooserForPreference: preferenceValue } ]
ifNil: [{}]).
^ (self theme newRowIn: self world for: uiElements)
cellInset: 20;
yourself
이 메서드는 몇 가지 기본 요소를 한 행으로 추가하여 그 행을 리턴한다. 첫 번째를 보면 #preferenceValue 를 SettingDeclaration으로 전송함으로써 실제 개인설정 값, 즉 TextSelectionPreference의 인스턴스를 얻게 됨을 눈치챌 수 있을 것이다. 이후 사용자 인터페이스 요소를 실제 TextSelectionPreference 인스턴스를 기반으로 빌드할 수 있다.
첫 번째 요소는 checkbox 이거나 #usedCheckboxForPreference: 호출에 의해 리턴된 빈 공간이다. 해당 메서드는 아래와 같이 구현된다:
TextSelectionPreference class>>usedCheckboxForPreference: aSelectionPreference
^ aSelectionPreference optional
ifTrue: [self theme
newCheckboxIn: self world
for: aSelectionPreference
getSelected: #used
setSelected: #used:
getEnabled: #optional
label: ''
help: 'Enable or disable the selection']
ifFalse: [Morph new height: 1;
width: 30;
color: Color transparent]
다음 요소들은 두 개의 색상 선택자들이다. 예를 들어, 배경색 선택자는 아래와 같이 빌드된다:
TextSelectionPreference class>>backgroundColorChooserForPreference:
aSelectionPreference
^ self theme
newColorChooserIn: self world
for: aSelectionPreference
getColor: #backgroundColor
setColor: #backgroundColor:
getEnabled: #used
help: 'Background color' translated
이제 Settings Browser 에서 사용자 인터페이스는 이전 버전에서 각 선택영역마다 세 개의 행이 아니라 그림 5.13에서 보이는 바와 같이 하나의 행으로만 표시된다.
요약
이번 장에서는 개인설정을 모듈식으로 관리하기 위한 새로운 프레임워크, Settings를 소개하였다. Settings에서 중점은 모듈식 제어 흐름을 지원한다는 데에 있어서, 패키지가 맞춤설정 포인트를 정의하는 데 책임이 있고, 이를 국부적으로 사용할 수 있으며, Settings를 이용해 그러한 맞춤설정 포인트를 설명하는 것이 가능하다. 마지막으로 Settings Browser는 그러한 설정 설명들을 수집하여 사용자에게 제시한다. 이후 Settings Browser 로부터 맞춤설정된 패키지로 제어의 흐름이 이동한다.
그림 5.13: 특정 개인설정 클래스를 이용해 구현된 텍스트 선택영역 설정