.neaf's weblog

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.

4 komentarze

Moja całkowicie amatorska przygoda z programowaniem zaczęła się się od Visual Basic'a (na początku 6 lecz z szybką migracją na .NET). Dalej przyszła kolej na Ruby i Pythona jednocześnie. Praca z nimi ograniczała się do prostych skryptów i używania frameworków, Merb czy też odpowiednio dla Pythona Django. Nigdy wcześniej nie dotknąłem języka pochodnego od C (PHP się liczy? :D ) ani o podobnych założeniach, nigdy nie bawiłem się w programowanie na 'niższym' poziomie, aż do dni ostatnich, kiedy wypadało napisać coś na nowo zdobytą platformę.

Jeśli chodzi o pisanie aplikacji okienkowych pod Mac OS jako pierwszy pod rozwagę pada zestaw Objective-C + Cocoa. Opcji jest jeszcze kilka zarówno po stronie języka jak i toolkita GUI, ale na początek wziąłem się za te wyżej wymienione.

Kolega przerabiając C++ na Pol.Śl. opowiadał mi jakie to wskaźniki nie są fajne i inne dziwne rzeczy, które jak już zrozumiałem to były dla mnie oczywiste ze względu na Ruby czy jaką to fajną klasę do obsługi właściwości nie napisał, że aż mnie zdziwienie złapało, że nie ma takich rzeczy z miejsca w języku, gdy ja używam attr_accessor i jest. Nigdy nie rozumiałem idei nagłówków i… byłem szczęśliwy, że mogę po prostu pisać kod i widzieć efekty z każdą dodaną linijką.

Po paru dniach, kilku 'Hello World' i innych TestApp'ach dogłębnie załapałem wskaźniki, nagłówki, MVC i poczytałem trochę o zarządzaniu pamięcią. W zasadzie wszystko co z pomocą manuali pozwala na napisanie czegoś co robi coś konkretnego i można to nazwać aplikacją.

Koleżanka z pewnych przyczyn ostatnimi czasy na GG siedzi cały czas ukryta, jednak gdy siada przed komputerem sprawdza co nowego na ePulsie. To podsunęło mi pomysł na pierwszą użyteczną porcję kodu. Po całym dniu bojów napisałem prosty tool, który wyświetla mi ikonkę na górnej belce reprezentującą jej status na rzeczonym serwisie. W pełni filozoficznie, obiektowo 'itetakie'. Tutaj zakończył się mój rozwój w kwestii Obj-C na najbliższy czas.

Przyzwyczajony do tego, że wszystko mam na miejscu, dostęp do całej gamy iteratorów, do tego, że mogę przypisać do zmiennej co chcę bez żadnego deklarowania i do wielu innych rzeczy, dzięki którym kod w Ruby po prostu się pisze i ten kod po prostu działa nie mogłem się przełamać, by brnąć w to dalej.

W takiej sytuacji nie szło się nie zainteresować RubyCocoa. Szybko więc przepisałem mojego szpiega. Urzekła mnie pełna współpraca z XCode oraz Interface Builderem. Możliwość pisania w ulubionym języku przy zachowaniu wszystkich właściwości 'natywnej' aplikacji dla Mac OS.

W grupie, w której pracujemy nad projektami używamy Merba, więc możliwość załadowania do aplikacji wybranej biblioteki ORM czy nawet uruchomienia całego środowiska frameworku to potęga jeśli chodzi o integrację strony z interfejsem okienkowym.

To wszystko kieruje me zainteresowanie właśnie na RubyCocoa. Teraz, gdy emocje związane z poznawaniem Mac OS'a opadły, gdy mam wolne do grudnia jeśli chodzi o pracę i trochę więcej czasu ze względu na inne mniej ważne rzeczy prawdopodobnie odbije się to na tym blogu. Pierwsza notka na temat RubyCocoa już w przygotowaniu. :)

10 komentarzy

Żyję

10 VII 2008 Internet , Merb , Ogólne , Ruby

Żyję — Na jakiś czas trochę zniknąłem ze świata ( sieci jak i tego rzeczywistego ). Co się przez ten czas działo? Głównie klepałem HTML'a. :)

W kwietniu zacząłem pracę nad nad traineo – stroną, która ma pomagać 'dbać o linię'. Celem było przepisanie syfiatego silnika z PHP na nowy oparty o framework Merb. Pracowałem nad tym z drugim człowiekiem ( o imieniu Foy ), który miał już jakieś doświadczenie w pracy z Ruby. Szło całkiem nieźle, w zasadzie aplikacja była już na ukończeniu, lecz zaczęły się pojawiać problemy z zapłatą ( moją pierwszą, Foy'a którąś z kolei ), więc postanowiliśmy zakończyć współpracę z właścicielem traineo.

Co więc z moim iMac'iem? Został w końcu zakupiony i wysłany przesyłką lotniczą do Polski. Niestety system śledzenia paczek firmy PolAmer aktualnie nie działa, więc pozostaje mi tylko czekać na list od urzędu celnego wzywający do zapłaty podatku.

Co się działo potem? Moja znajomość z Foy'em zaczęła się od hobbystycznego projektu FreeGovernment, a później dopiero wkręcił mnie do traineo. FreeGov miało być pośrednikiem między zwykłymi obywatelami a rządem USA. Ambitne, nie? … Po zakończeniu pracy nad traineo wróciliśmy do tego projektu.

Trochę działo się jednak beze mnie ze względu na zakończenie roku szkolnego. Gdy wróciłem do pracy ( 13 dni przed planowaną datą rozpoczęcia działalności ) część kodu front-endu nad, którym miałem pracować już była napoczęta. Ze względu na bliski deadline nie miałem czasu wyczyścić wszystkiego co złe mogło się zdarzyć w HTML, więc brnąłem w to byle by zdążyć. Efekt jest taki, że cały HTML/CSS jest do wymiany w najbliższych tygodniach.

FreeGovernment

Tak czy inaczej FreeGovernment.org już działa. By wolności stało się za dość kod aplikacji wydany jest na licencji GPL. Strona daje użytkownikom możliwość tworzenia prawa, brania udziału w sondach oraz dyskusji na forum. Użytkownikiem może być zarówno osoba prywatna jak i grupa osób pod jednym loginem. Ze względu na fakt iż nie każdy ma możliwość wertowania codziennie nowych sond stworzony został system doradztwa. Strona daję ci możliwość doboru grupy doradców, których głosy przekładają się na twoje głosy zostawiając Ci cały czas opcję podjęcia personalnej decyzji.

Swoją działalność podejmie też FreeGovernment Party, której reprezentantów rolą będzie jedynie pośredniczenie między stroną a rządem. Czy coś z tego wyjdzie? Czas pokaże. Foy wraz z dziewczyną wierzy, że tak. W moim interesie też jest by projekt się rozwinął. Będzie dobrą referencją dla mnie i… [ następna notka :) ] . Pomóc ma nam w tym wpis na TechCrunch, który powinien się pojawić na dniach.

Strona ma jeszcze trochę niedoróbek. Pozostaję też wymiana kodu front-endu, więc czeka mnie jeszcze trochę pracy. Potem trzeba mieć tylko nadzieję, że projekt szybko nie polegnie.

2 komentarze