Header

  1. View current page

    딥뿔이 자라나는 노트

Profile_image?t=1225424611&type=big
나를 바꾼 똑똑한 생활 습관, 스프링노트 - 여러분도 지금 시작해보세요!
38

08 드래그-앤-드롭과 타이머 - 타자 애플리케이션 (2)

이전글: 07 문자열 그리기와 키보드 이벤트 - 타자 애플리케이션 (1) / 루비 코코아 프로그래밍


이제 지금까지 만든 타자 애플리케이션을 좀 더 발전시켜본다. 드래그 & 드롭, 타이머, 시트, 포매터, 인쇄 등 다소 고급 기능을 추가할 것이다. 이 글은 코코아 프로그래밍 책의 20장 ~ 25장에서 구현된 예제를 루비 코코아로 옮긴 것이다. 참고로, 타자 애플리케이션은 여기 구현 말고도 루비 코코아 예제 프로램 폴더의 TypingTutor 라는 이름으로 또 다른 구현을 찾아볼 수 있다. 함께 살펴보는 것도 좋을 것이다.


드래그 & 드롭

역시 드래그 & 드롭을 구현하기 위해서는 손이 많이 간다. 드래그 & 드롭를 위해 다음 메서드를 구현했다. 역시 GUI 프로그래밍에서 손이 제일 많이 가는 곳 중 하나다.


  • draggingSourcrOperationMaskForLocal
  • mouseDragged
  • draggedImage_endedAt_operation
  • draggingEntered
  • draggingExited
  • prepareForDragOperation
  • performDragOperation
  • concludeDragOperation


위 메서드 순서가 드래그 & 드롭을 할 때 발생하는 이벤트의 시간순과 거의 비슷하다. 특이한 점이라면 휴지통으로 드래그를 하면 draggedImage_endedAt_operation 메시지가 오는데 이 때 operation이 NSDragOperationDelete다. 그래서 이미지를 휴지통으로 드래그해서 화면에서 이미지를 지우는 것을 구현할 수 있었다. 휴지통은 생각보다 유용하다?


NSTimer

프로그램의 메인 로직과 관계없이 별도로 돌아가야하는 GUI 로직이 있다. 예를 들면 프로그레스바를 업데이트하는 것이 그 대표적인 예다. 두가지 접근이 있을 수 있는데, 첫째는 스레드를 활용하는 것이고, 두번째는 타이머나 이벤트를 활용하는 것이다. 코드의 완결성 측면에서는 전자가 유리하겠지만, 난이도나 테스팅, 버그 확률, 복잡도를 생각하면 후자의 손을 들어주고 싶다. 코코아에서는 NSTimer, NSRunLoop 등을 이용해 이런 필요를 해결한다. 책에서 설명하길, 멀티스레딩이 필요한 경우는 정말 드물다고 한다.


타이머를 만들고 없앤다.


  1.   def stop_go(sender)
        if sender.state == 1
          OSX::NSLog 'Starting'
          @timer = OSX::NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats(0.2, self, 'checkThem:', nil, true)
        else
          OSX::NSLog 'Stopping'
          @timer.invalidate
        end
      end


그리고 타이머에서는 ProgressView를 갱신한다.


  1.   def checkThem
        show_another_letter if @in_letter_view.string == @out_letter_view.string
       
        @count += 1
        @count = 0 if @count > TICKS
       
        @progress_view.doubleValue = 100.0 * @count / TICKS
      end


그 결과는 아래 화면과 같다.


그림_3.png


시트

맥을 처음 접했을 때 제일 어색했던 UI가 바로 시티(Sheet)였다. 시트는 윈도나 다른 GUI 시스템의 모달 다이얼로그라고 보면 무방하다. 아예 부모 윈도우에 딱 붙어서, 모달임을 명확하게 한다는 점에서는 좋지만, 부모 윈도우를 가려버린다는 점에서는 무척 어색했다.


이 예에제서는 ProgressView의 속도를 조정할 목적으로 Sheet를 사용했다.


  1.   def adjust_speed(sender)
        @speed_slider.intValue = @ticks
        OSX::NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo(@speed_window, @in_letter_view.window, self, 'sheetDidEnd_returnCode_contextInfo', nil)
      end
     
      def end_speed_window(sender)
        @speed_window.orderOut(sender)
        OSX::NSApp.endSheet_returnCode(@speed_window, 1)
      end
     
      def sheetDidEnd_returnCode_contextInfo(sheet, code, context)
        @ticks = @speed_slider.intValue
        @count = 0
      end


그나저나 패널을 여는 메서드도 그랬고, 이번에 시트를 시작하는 메서드도 영 글어서 적응이 안된다. 세상에 OSX::NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo라니 도대체 몇자인가? 


그림_4.png

비슷한 방법으로 시트를 이용해 경고창도 띄울 수 있고, Textmate에서 매일 보는 Drawer도 만들 수 있다.


루비에서 포인터 다루기

Objective-C에는 포인터가 있다. 메모리 관리는 caller가 책임지고, callee는 할당된 메모리를 사용하기만 하는 것이다. 하지만 루비에는 포인터가 없다. 이 간극을 채워주기위해 존재하는 것이 OSX::ObjcPtr 클래스이다. 아래에서 포매터를 구현할 때 사용할 getObjectValue_forString_errorDescription(obj, string, error) 메서드의 경우 obj가 OSX::ObjcPtr 객체이다. 이 함수에서는 매치된 NSColor 객체를 obj에 채워야하는데, 이 때는 일반적인 대입문 대신 ObjcPtr#assign 메서드를 호출한다.


  1. obj.assign @color_list.colorWithKey(match)


반대로 포인터를 요구하는 Objective-C 메서드를 호출할 때는 어떻게 해야할까? 포인터 대신 ObjcPtr 객체를 만들어 넘기면 된다. 이 때는 할당할 메모리 공간을 정해주기 위해 아래와 같은 메서드를 사용한다.


  • ObjcPtr.allocate_as_bool
  • ObjcPtr.allocate_as_int
  • ObjcPtr.allocate_as_int8
  • ObjcPtr.allocate_as_int16
  • ObjcPtr.allocate_as_int32


NSFormatter

앞에서 리스트의 항목을 % 형태로 표현하기 위해 NSNumberFormatter를 사용한 적이 있다. 포매터를 직접 구현하기 위해서는 OSX::NSFormatter 클래스를 상속해서 아래 두 메서드를 구현하기만 하면 된다.


  • stringForObjectValue
  • getObjectValue_forString_errorDescription


각각 문자열과 객체를 상호 변환하는 메서드다. 이를 이용해 영문 문자열을 NSColor 객체로 변환하는 ColorFormatter를 구현했다.


그림_5.png


별로 어려운 코드가 없으므로, 자세한 구현은 ColorFormatter.rb를 읽어보기 바란다.


인쇄

인쇄도 드래그 앤 드롭 만큼이나 까다롭고 귀찮은 주제 중 하나이다. 하지만 화면에 보이는 것을 그대로 기본 설정을 이용해 출력하는 것은 어렵지 않다.


  1.   def print(sender)
        op = OSX::NSPrintOperation.printOperationWithView_printInfo(@in_letter_view, OSX::NSPrintInfo.sharedPrintInfo)
        op.showPanels = true
        op.runOperation
      end 


더 자세한 설정(예를 들어 페이지 설정)을 하려면 더 많은 코딩이 필요하다. 여기서는 이 정도로 만족하기로 한다.


validateMenuItem

가끔 현재 상황(Context)에 따라 특정 메뉴 항목을 비활성화 시켜야하는 경우가 있다. 예를 들어 타자 애플리케이션에서는 예시문을 복사해서 붙여넣기 하는 행위를 막아야한다. 이를 위해서는 간단히 validateMenuItem 메시지를 처리해주시면 된다.


  1.   def validateMenuItem(item)
        ['copy:', 'cut:'].include?(item.action.to_s) ? copyable : true
      end


마치며

지금까지 구현된 내용은 아래 파일에서 확인할 수 있다.



다음 글은 이 시리즈의 마지막이다. 09 루비 코코아 프로그래밍 책거리에서는 마지막으로 지금까지 코코아와 루비 코코아에 대해 느낀 점을 정리해보기로 하겠다(전체보기 - 루비 코코아 프로그래밍).


Tags

History

Last edited on 10/05/2007 20:24 by deepblue

Comments (0)

You must log in to leave a comment. Please sign in.