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 (
), 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.