RubyCocoa poza środowiskiem XCode

Zgodnie z zapowiedzią w komentarzu przedstawię kilka problemów, na które naciąłem się zaczynając z RubyCocoa od trochę innej strony – nie używając XCode.

Zacznijmy od końca:

require 'OSX/cocoa'
require 'rubygems'
require 'feed-normalizer'
require 'open-uri'
include OSX

NSApplication.sharedApplication

class SubscriberController < NSObject

  def initialize
    @user = 'neafel'
    @limit = 5

    @statusItem = NSStatusBar.systemStatusBar.statusItemWithLength(NSVariableStatusItemLength)
	image = NSImage.alloc.initWithContentsOfFile("tv.png")
    @statusItem.setImage(image)

    timer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats(60, self, :updateMenu, nil, true)
  end
  
  def updateMenu
    statusMenu = NSMenu.new
    
    url = 'http://pipes.yahoo.com/pipes/pipe.run?YouTubeUsername=%s&_id=58e4f59f9e5e3282aaffdcbaf05ba68d&_render=rss&itemLimit=%d' % [@user, @limit]
    feed = FeedNormalizer::FeedNormalizer.parse open(url)

    feed.entries.each do |entry|
      menuItem = NSMenuItem.alloc.initWithTitle_action_keyEquivalent(entry.title, :visit, "")
      menuItem.setTarget(self)
      menuItem.setRepresentedObject(entry.url)
      statusMenu.addItem(menuItem)
    end
    
    menuItem = NSMenuItem.alloc.initWithTitle_action_keyEquivalent('Quit', :quit, "q")
    menuItem.setTarget(self)
    statusMenu.addItem(menuItem)

	@statusItem.setMenu(statusMenu)
  end
  
  def visit(item)
    NSWorkspace.sharedWorkspace.openURL(NSURL.URLWithString(item.representedObject))
  end

  def quit(item)
    NSApp.terminate(item)
  end

end

c = SubscriberController.new
c.updateMenu

NSApp.run

Kod pobiera 5 ostatnich(@limit) filmów z subskrybcji danego użytkownika YouTube(@user) i wyświetla je w menu dostępnym po kliknięciu na ikonę w górnej belce pozwalając przejść bezpośrednio do filmu klikając na daną pozycję. Prawdopodobnie jest to całkowicie bezużyteczne, ale to dobry przykład na początek. Obejmuje wszystko co potrzebne by zacząć zabawę z RubyCocoa.

Inicjalizacja aplikacji

Zacznijmy od utworzenia pliku subscriber.rb z następujacą zawartością:

require 'OSX/cocoa'
include OSX

NSApplication.sharedApplication

NSApp.run

Całkiem proste, nie? Pierwsza linijka zdaje się być oczywista. Ładuje zawartośćn Cocoa Foundation i AppKit do modułu OSX. Od tego momentu możemy odwoływać się do klas tych frameworków przez prefiks OSX:: (np. OSX::NSApplication, OSX::NSString czy OSX::NSWindow). Dla wygody dopisałem drugą linijkę, która importuje klasy Cocoa do głównej przestrzeni nazw.

Dalej następuje właściwa inicjalizacja aplikacji. Każda aplikacja oparta na Cocoa musi posiadać jedną instancję klasy NSApplication, która łączy proces z serwerem okien i zarządza kolejką zdarzeń. Instancję tę tworzy metoda sharedApplication przydzielając ją do zmiennej globalnej NSApp. Zaś jej metoda run uruchamia główną pętle programu. Robimy to dopiero po załadowaniu reszty kodu aplikacji, więc ten będzie się znajdował pomiędzy tymi dwoma liniami.

To tyle. Na tym etapie po wywołaniu w terminalu ruby subscriber.rb… nie zobaczymy nic ;) . Proces jednak zostaje uruchomiony i można go zobaczyć w Activity Monitor. Czas na logikę aplikacji.

Kontroler

Kod RubyCocoa można pisać w pełni 'strukturalnie', kładąc wszystkie zmienne i metody bez używania klasy kontrolera. Tak robiłem do momentu, kiedy próbując przypisać akcję elementowi menu zauważyłem, że obiekty, które mają otrzymać wiadomość(wywołanie metody) od obiektu Cocoa muszą dziedziczyć po klasie NSObject. W przeciwnym razie dostaniemy nic nie mówiący błąd: [BUG] Bus Error w konsoli. Dlatego stworzyłem klasę SubscriberController i myślę, że to dobra praktyka w każdym przypadku.

Reszta kodu, zakładając że czytelnik ma jakieś pojęcie o Ruby wydaje się być oczywista. Podczas tworzenia instancji kontrolera do zmiennej @statusItem przypisywany jest element w górnej belce systemu z ikonką telewizorka (TV), pod którym znajdować się będzie nasze menu. Później uruchamiamy timer, który odpala updateMenu naszego kontrolera(target:self) co minutę. Wywołując metody obiektów Cocoa z parametrami w nazwie podawanej w dokumentacji należy zamienić dwukropki na podkreślenia(btw. jaka jest oficjalna polska nazwa tego znaku?) co daje nam scheduledTimerWithTimeInterval_target_selector_userInfo_repeats.

W metodzie updateMenu tworzymy nową instancję klasy NSMenu, której jak można się domyśleć używamy do wszelkiego rodzaju menu w aplikacjach Cocoa. Gem feed-normalizer pobiera ostatnie filmy z feeda, który znalazłem tutaj.

Elementy menu

feed.entries.each do |entry|
  menuItem = NSMenuItem.alloc.initWithTitle_action_keyEquivalent(entry.title, :visit, "")
  menuItem.setTarget(self)
  menuItem.setRepresentedObject(entry.url)
  statusMenu.addItem(menuItem)
end

Chcemy by po kliknięciu tytulu w menu ukazał nam się rzeczony film. Trochę czasu zajęło mi głowienie się jak przekazać URL filmu do metody otwierającej przeglądarkę wskazywanej przez symbol. I… do niczego nie doszedłem. Wiedziałem natomiast, że przekazana będzie instancja obiektu w który kliknąłem tak jak ma to miejsce prawdopodobnie przy każdym innym zdarzeniu. Zacząłem więc szukać w dokumentacji jakieś właściwości, która pomoże mi przemycić adres do wywołanej metody. Znalazłem - representedObject. Rozwiązanie całkiem poprawne, więc przypisujemy rządaną wartość za pomocą metody setRepresentedObject by odczytać ją przy otwieraniu przeglądarki.

To w zasadzie wszystko na co chciałem zwrócić uwagę. Nie będę omawiał każdego argumentu każdej metody, bo ktoś to już zrobił a to nie jest żaden kurs. Tekst jest chaotyczny, ale publicysta ze mnie taki jak i programista, więc jeśli ktoś ma jakieś wątpilowości to… zostawić komentarz.

Komentarze

I want more :) wreszcie cos po polsku o RubyCocoa :) biore sie w pazdzierniku do nauki :)

Favicon NuLL 31 VII 2008 @ 15:34

Hah, jest jakiś odzew. :) Dobrze, więc pewnie będzie coś nowego. Chociaż RubyCocoa to nie jest rzecz, o której się powinno pisać osobno. Sam dopiero się tym zainteresowałem i by zrozumieć jak to działa najpierw piszę kod w Obj-C a potem portuję na Ruby. Wspomagając się docami jakoś to idzie.

Favicon neaf 31 VII 2008 @ 15:40

Jest coś takiego jak <EXCERPT>

Favicon BeteNoire 31 VII 2008 @ 18:06

A w sumie fakt… Zapomniałem kopiując z TextMate.

Favicon neaf 31 VII 2008 @ 21:51

Dodaj komentarz

Weryfikacja antyspamowa