.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

22 lipca odebrałem w Warszawie pudło z komputerm z agencji celnej firmy PolAmer, z której usług korzystałem sprowadzając sprzęt z USA. Czas na pierwsze wrażenia i przemyślenia.

Zaskoczony trochę byłem rozmiarem pudła. Wydawało mi się, że zmieściłbym tam jakąś miejszą obudowę i monitor od zwykłego PCta. Ucieszony strasznie otworzyłem je jeszcze na parkingu, by sprawdzić czy wszystko w środku jest w porządku. Klawiatury z wierzchu mi nie podprowadzili, więc jest OK.

57 komentarzy czytaj więcej