Przenosiny!

Cześć!

Chciałbym przeprosić wszystkich za ostatni brak postów i w zamian za to dać wam coś nowego. Zupełnie nie chodzi mi o nowe obietnice, że posty będą się pojawiać częściej. Wręcz przeciwnie – tutaj posty nie będą się pojawiać w ogóle!

Postanowiłem zmienić coś w swoim życiu pod względem blogowania i utworzyłem całkiem nowy blog. Aktualnie publikuję swoje myśli pod adresem:

http://blog.radoszewski.pl

Każdy z bystrym okiem od razu zauważy, że nowy blog jest w nieco innym języku. No cóż… Chciałbym aby moje publikacje były dostępne dla wszystkich, a język angielski jest na tyle uniwersalny, że wydaje się świetnie nadawać do takiego zastosowania 🙂

Zapraszam!

Reklamy

Play Framework – Formatowanie JodaTime w templatach

Ostatnio hobbystycznie tworzę projekt w Scali z użyciem Play Framework. Sam framework jest bardzo interesujący, ale jest o nim bardzo wiele napisane w całym internecie dlatego to nie jemu poświęcony jest wpis.

Wpis jest o pewnym małym problemie, na który się dzisiaj natknąłem i niestety internet nie był zbyt pomocny – przynajmniej przy frazach wyszukiwania na jakie wpadłem 🙂

Problem: Chcę wyświetlić na stronie sformatowaną datę, którą mam w postaci obiektu klasy DateTime z biblioteki JodaTime.

Większość znalezionych rozwiązań niestety dotyczy daty zapisanej jako stary dobry java.util.Date. W takim przypadku wystarczy jedynie napisać:

@myDate.format("dd.MM.yyyy")

Oczywiście java.util.Date nie ma takiej metody więc jest tam zastosowana jakaś magia Scali 🙂

Znalazłem pewne rozwiązania dotyczące JodaTime ale były one bardzo brzydkie – wymagały zbyt dużo niepotrzebnego kodu w templacie, a to przecież Scala…

Pomyślałem o zrobieniu własnej Scalowej magii dla dat z JodaTime – w tym przypadku nieocenione okazały się doświadczenia związane z tym co opisałem w poprzednim poście. Utworzyłem sobie nowy obiekt:

object Extensions {
class JodaTimeWrapper[T <: ReadableInstant](t:T) {
  def format(pattern: String): String = {
    val fmt = DateTimeFormat.forPattern(pattern)
    fmt.print(t)
  }
  implicit def convertToJodaTimeWrapper[T <: ReadableInstant](t:T) =
    new JodaTimeWrapper(t)
}

I potem w pliku *.scala.html jedynie:

@import views.Extensions._
@myJodaTimeDate.format("dd.MM.yyyy")

Job done. A o co właściwie chodzi? Analogicznie jak w przypadku DSLa dla SWT utworzyłem klasę JodaTimeWrapper, która na zadanym obiekcie pozwala wywołać metodę format(). Potem tylko deklaracja implicit konwertera typu ReadableInstant na nasz wrapper. Kluczowe jest tutaj właśnie słówko implicit. Dzięki niemu kompilator w trakcie pracy jest w stanie stwierdzić, że obiekt typu DateTime jest również obiektem typu ReadableInstant (bo jest to jego klasa nadrzędna) i wie, że może sobie ten obiekt przekonwertować na JodaTimeWrapper i stamtąd już potrafi wywołać metodę format().

Scala – SWT i DSL

Motywacja

No i stało się. Poszedłem za radą Xiona i zerknąłem bliżej na Scalę. Przez ostatnie tygodnie trochę o niej poczytałem i napisałem kilka niewielkich rzeczy dla nabrania wprawy ale pisanie zwykłego kodu w Scali (poza składnią) nie różni się mocno od dowolnego innego języka (choć to też zależy od tego czy chce się pojechać po bandzie z możliwościami funkcyjnymi Scali 🙂 )

W trakcie zabawy natknąłem się na różne artykuły odnośnie tworzenia Domain Specific Language (DSL). Wówczas nie zwróciłem na to uwagi dlatego, że wykorzystywane tam konstrukcje wydawały się pochodzić zupełnie z innego świata. Ostatnio pomyślałem, że prędzej czy później trzeba będzie wypłynąć na głęboką wodę i się tym zająć. Lepiej więc wcześniej niż później 🙂

Pierwsze próby

Chcąc upiec dwie pieczenie na jednym ogniu zdecydowałem się na implementację DSL dla systemu GUI SWT. Oczywiście na początku nie wiedziałem zupełnie czego chcę ani tym bardziej – jak to osiągnąć. Dlatego postąpiłem tak jak każdy, kto osiąga taki punkt swojego życia – zapytałem Google…

Po kilku mało interesujących przykładach natrafiłem na coś wartościowego. Pomysł jest bardzo interesujący i pokazał mi w którą stronę można się udać. Na podstawie informacji o szczegółach implementacji, które zostały tam pokazane postanowiłem odtworzyć ten system i po około godzinie prób i błędów miałem już okienko i kilka przycisków na ekranie.

Pomysł z wykorzystaniem kilku dwóch list parametrów (jednej do podania listy funkcji ustawiających daną kontrolkę i drugiej określającej rodzica kontrolki) jest dosyć ciekawy. Jedyna rzecz jaka mi przeszkadzała to pewien brak porządku. Wywołania funkcji, które określają zachowanie kontrolki nadrzędnej mogą być poprzeplatane deklaracjami kontrolek dzieci:

group (
  text(&quot;Name&quot;),
  gridLayout(2, false),
  gridData(FILL, FILL, true, true),
  label(text(&quot;First&quot;)), edit(text(&quot;Bullet&quot;)),
  label(text(&quot;Last&quot;)), edit(text(&quot;Tooth&quot;))
),

W powyższym przykładzie text, gridLayout oraz gridData dotyczą elementu group natomiast wszystkie label i edit to już definicje nowych obiektów.

I wreszcie coś swojego

Stwierdziłem, że spróbuję napisać to jakoś inaczej. I po kilku godzinach kombinowania i eksperymentowania z różnymi aspektami języka udało mi się napisać działający kod, który generuje takie samo okienko jak w powyższym wpisie:

val shell = new SwtBuilder {
  shell text &quot;User Profile&quot; gridLayout (2, true)
  group text &quot;Name&quot; gridLayout 2 gridData (SWT.FILL, SWT.FILL, true, true) withChildren {
    label text &quot;First&quot;;
    edit text &quot;Bullet&quot; gridData (SWT.FILL, SWT.CENTER, true, true) name &quot;first&quot;
    label text &quot;Last&quot;;
    edit text &quot;Tooth&quot; gridData (SWT.FILL, SWT.CENTER, true, true) name &quot;last&quot;
  }
  group text &quot;Gender&quot; fillLayout (SWT.HORIZONTAL, 5) gridData (SWT.FILL, SWT.FILL, true, true) withChildren {
    radio text &quot;Male&quot;
    radio text &quot;Female&quot;
  }
  group text &quot;Role&quot; fillLayout (SWT.HORIZONTAL, 5) gridData (SWT.FILL, SWT.FILL, true, true) withChildren {
    checkbox text &quot;Student&quot;
    checkbox text &quot;Employee&quot;
  }
  group text &quot;Experience&quot; fillLayout (SWT.HORIZONTAL, 5) gridData (SWT.FILL, SWT.FILL, true, true) withChildren {
    spinner
    label text &quot;years&quot;
  }&lt;/p&gt;
&lt;p&gt;  button text &quot;Save&quot; gridData (SWT.RIGHT, SWT.CENTER, true, true) onSelect {
    (ctls get &quot;first&quot;, ctls get &quot;last&quot;) match {
      case (Some(f:Text), Some(l:Text)) =&gt;
        println(&quot;Name: &quot; + f.getText + &quot; &quot; + l.getText)
      case _ =&gt; println(&quot;Something is wrong!&quot;)
    }
  }
  button text &quot;Close&quot; gridData (SWT.LEFT, SWT.CENTER, true, true) onSelect {
    shell close
  }
} shell

Wynik działania powyższego kodu

Część tajemnicy tkwi w tym, że scala zezwala na wywoływanie metod z argumentami bez kropki:

val label = new Label(parent, SWT.NONE)
label.setText(&quot;Hello World&quot;) // te dwie linie
label setText &quot;Hello World&quot;  // dają ten sam efekt

Pozostała część twki konwertowaniu w locie obiektów SWT do różnych wrapperów dodających wszelkie metody typu text lub gridData. Funkcje tworzące nowe kontrolki (label, button itp.) tak na prawdę tworzą jedynie nowe obiekty danej klasy:

def button() : Button = new Button(context.value, SWT.PUSH)
def button(style:Int) : Button = new Button(context.value, style)

Jeśli w tym momencie pomyślałeś, że zamiast pisać funkcję dwa razy z różnymi parametrami można ją napisać raz z parametrem domyślnym to… nie 🙂 Zależy mi na tym, żeby w kodzie było jak najmniej bezsensownych nawiasów. Jeśli utworzę tylko jedną wersję funkcji to jeśli nie chcę podać stylu musiałbym dodać parę pustych nawiasów – paskudne!

Wracając do rozwiązywania tajemnicy działania… W klasie SwtBuilder zdefiniowane są klasy typu:

class SetTextWrapper[T &lt;: {def setText(text:String)}](val subject:T) {
  def text(t:String) : T = {
    subject.setText(t)
    return subject
  }
}

Taka klasa bierze jako argument konstruktora dowolną klasę typu T, który posiada metodę setText(text:String) i pozwala wywołać na niej swoją metodę text, która ustawia tekst w obiekcie klasy T. Kluczowe jest tutaj zwracanie oryginalnego obiektu, a powód wyjaśni się za chwilę. Do pary potrzebujemy jeszcze automatycznego konwertera, który będzie używany przez kompilator kiedy ten uzna, że konwersja jest potrzebna:

implicit def convertToSetTextWrapper[T &lt;: {def setText(text:String)}] (t:T) = new SetTextWrapper(t)

To mówi kompilatorowi, że wszystkie obiekty posiadające posiadające typ T mogą być automatycznie (słowo kluczowe implicit) owinięte obiektem klasy SetTextWrapper. Dzięki takiej konstrukcji możliwe jest napisanie:

(new Label(parent, SWT.NONE)).text(&quot;Test&quot;).text(&quot;Hello&quot;).text(&quot;World&quot;)

Zaczynając od początku, mamy obiekt klasy Label. Następnie kompilator spotyka wywołanie metody text, której taki obiekt nie posiada. Wówczas używając zdefiniowanej wyżej funkcji konwertującej owija obiekt w SetTextWrapper i teraz może już spokojnie wywołać metodę text. Ta z kolei zwraca znów obiekt typu Label i historia zaczyna się od początku dla następnego wywołania text.

Biorąc pod uwagę, że mamy zdefiniowaną metodę label(), która generuje nowy obiekt typu Label oraz to, że scala pozwala pisać bez kropek i nawiasów możemy napisać powyższą linię kodu bardziej podobnie do pierwszego przykładu:

label text &quot;Test&quot; text &quot;Hello&quot; text &quot;World&quot;

Oczywiście nic nie stoi na przeszkodzie aby używać pierwszej notacji. Ma ona tę zaletę, że można rozpisać właściwości obiektu na kilka linii zamiast pisać jedną strasznie długą:

group()
  .text(&quot;Name&quot;)
  .gridLayout(2)
  .gridData(SWT.FILL, SWT.FILL, true, true)
  .withChildren {
    label text &quot;First&quot;;
    edit text &quot;Bullet&quot; gridData (SWT.FILL, SWT.CENTER, true, true) name &quot;first&quot;
    label text &quot;Last&quot;;
    edit text &quot;Tooth&quot; gridData (SWT.FILL, SWT.CENTER, true, true) name &quot;last&quot;
  }

A jak działa withChildren?

O! Cieszę się, że pytasz 🙂 Otóż… działa na takiej samej zasadzie jak to co opisano powyżej:

class CompositeWrapper[T &lt;: Composite](c :T) {
    def withChildren(f: =&gt; Unit) : T = {
      context.withValue(c) { f }
      return c
    }
  }

Mamy więc pewien wrapper, który jako argument konstruktora przyjmuje obiekty typu T, które tym razem są obiektami dziedziczącymi (pośrednio bądź bezpośrednio) po typie Composite. Łatwo się domyślić, że typ ten pozwala na pokazywanie kontrolek „w sobie” czyli może być on rodzicem np. dla obiektu klasy Button. Do pary oczywiście jest odpowiednia metoda konwertująca ale wygląda analogicznie to pokazanej wcześniej więc jej nie pokażę.

Jak widać wrapper posiada jedną metodę withChildren, która przyjmuje jako parametr funkcję bezargumentową, a zwraca znów typ T. Szalenie interesujący jest tutaj obiekt context. Jest to obiekt klasy DynamicVariable, który za pomocą metody withValue pozwala w czasie wykonania bloku kodu podanego jako drugi argument podmienić wartość context.value na wartość podaną jako pierwszy argument. Zmienna context.value to oczywiście aktualnie używany rodzic dla wszystkich kontrolek w danym bloku. Zatem jeśli mam zdefiniowaną metodę:

def label() : Label = new Label(context.value, SWT.NONE)

I wykonamy kod:

context.withValue(new Composite(otherParent, compositeStyle)) {
  label text &quot;First&quot;
  label text &quot;Second&quot;
}

To powstaną dwa obiekty Label oraz komponent Composite, który jednocześnie będzie rodzicem dla tych dwóch etykiet.

Ok, a co ze zdarzeniami?

Kolejne świetne pytanie! Samo utworzenie interfejsu to jedynie część sukcesu. Przede wszystkim powinniśmy mieć możliwość jakoś dostać się do naszych kontrolek. Na tę okoliczność przygotowałem specjalny… wrapper 🙂 Wrapper ten działa dla wszystkich obiektów typu Widget (oczywiście ma

konwerter do pary). Pozwala na nadanie kontrolce nazwy i zapisanie jej w mapie obiektów pod daną nazwą (która jest obiektem Scalowej klasy Map).

W taki sposób możemy sobie spokojnie nazwać element, a po utworzeniu GUI w prosty sposób się do niego odwołać. Pozostaje jeszcze kwestia zdarzeń typu wciśnięcie przycisku. W SWT rejestruje się w danym obiekcie implementację interfejsu SelectionListener, i odbywa się to oczywiście dzięki metodzie addSelectionListener. Dlatego możemy utworzyć specjalny wrapper dla wszystkich obiektów posiadających tę metodę (oczywiscie razem z konwerterem):

class SetSelectionWrapper[T &lt;: {def addSelectionListener(listener:SelectionListener)}](val subject:T) {
  def onSelect(f: =&gt; Unit) : T = {
    subject.addSelectionListener(new SelectionAdapter() {
      override def widgetSelected(e:SelectionEvent) : Unit = f
    })
    return subject
  }
}&lt;/p&gt;
&lt;p&gt;implicit def convertToSetSelectionWrapper[T &lt;: {def addSelectionListener(listener:SelectionListener)}](t:T) = new SetSelectionWrapper(t)

I teraz już nie ma żadnych przeszkód by napisać:

button text &quot;Save&quot; gridData (SWT.RIGHT, SWT.CENTER, true, true) onSelect {
  (ctls get &quot;first&quot;, ctls get &quot;last&quot;) match {
    case (Some(f:Text), Some(l:Text)) =&gt;
      println(&quot;Name: &quot; + f.getText + &quot; &quot; + l.getText)
    case _ =&gt; println(&quot;Nobody Expects The Spanish Inquisition!&quot;)
  }
}

Dodam tylko, że ctls to wspomniana wcześniej mapa nazwanych kontrolek.

Podsumowanie

Ogromną zaletą takiego rozwiązania jest duża elastyczność. Nic nie stoi na przeszkodzie aby w bloku kodu dla withChildren używać dowolnych konstrukcji języka – jest to w końcu zwykły kod. Dodatkowo, jakoby za darmo, kompilator w trakcie pracy wytknie nam wszystkie błędy (np. próbę ustawienia tekstu dla kontrolki, która tego nie obsługuje).

Cóż. Jak się okazuje – nie taki diabeł straszny jak go malują. Po przezwyciężeniu początkowej niechęci do składni Scali i poświęceniu sporej ilości czasu na zrozumienie o co tam w ogóle chodzi nauka nowych rzeczy przychodzi już dużo łatwiej a błędy kompilatora stają się twoimi przyjaciółmi 🙂

Let’s make things Groovy

Jakiś czas temu przeczytałem artykuł (http://steve-yegge.blogspot.com/2007/12/codes-worst-enemy.html) w którym autor dość nieprzychylnie wyrażał się o Javie jako języku programowania. Mimo, że nie całkiem zgadzam się ze wszystkim co zostało tam napisane to jednak zacząłem zwracać uwagę na to ile kodu wymaga Java nawet do najprostszych rzeczy. Zerknijmy sobie na prosty przykład – bardzo standardowe „Hello World”:

public class Main {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

Już patrząc na samą instrukcję System.out.println() odnosi się wrażenie, że to jest dużo pisania jak na tak prostą funkcję. Dla porównania możemy spojrzeć na kod Pythona:

print "Hello World"

Super! Krótko zwięźle i na temat. Problem w tym, że my znamy się dobrze na Javie i potrafimy używać przeróżnych bibliotek do tego języka, a w dodatku nie znosimy nazewnictwa metod w takiej_postaci() preferując raczej postaćTakową(). No dobra, może nie wszyscy mają takie uprzedzenia ale ja na pewno tak 🙂 (na pewno nie wydaje się wam, że Python ma za dużo __podkreśleń__?). A jeśli to nie jest problem to może brak standardu dla dokumentacji Pythonowej? Lub to, że część bibliotek dla Pythona dokumentacji nie ma wcale lub sprowadza się ona po prostu do listy wszystkich klas i metod, które czasem są opatrzone lapidarnym komentarzem? (mówię o czymś w stylu: get_none() – w dokumentacji przeczytamy returns None…)

Jeśli chcecie teraz zaproponować mi Jythona to dajcie sobie spokój. Byłem, sprawdziłem, nie podoba mi się 🙂 Najbardziej odrzuca mnie czas startu ale samo tempo pracy również nie powala. Możecie mi wierzyć, że bardzo chciałem sprawić by Python stał się moim językiem ojczystym do programowania, ale ostatecznie zawsze wracałem do Javy.

Potem odkryłem Groovy

O Groovy słyszałem już wcześniej. Nikt jednak nie wydawał się podekscytowany tym językiem i nikt nie zwracał na niego większej uwagi. W takim razie, pomyślałem, ja też nie będę. Ostatnio jednak w wyniku poszukiwania języka innego niż Java ale działającego na JVM (chodziło mi o kompatybilność z bibliotekami) postanowiłem zabrać Groovy na jazdę testową.

To była chyba najbardziej inspirująca zabawa z kodem odkąd zacząłem programowanie! W ciągu weekendu przeczytałem wszystkie interesujące mnie rzeczy dotyczące języka (głównie – co jest tu innego niż w Javie, wszelkie metaprogramowanie oraz ciekawostki na które się natknąłem jak np. DSL w Groovy). Najciekawsze jednak było to, że czułem się jakbym odkrył nowe możliwości w czymś co znam już od dawana. Pisałem po prostu w Javie ale wykorzystywałem dodatkowe możliwości, które zupełnie zmieniły styl kodu i wygodę programowania.

Co jest wartego uwagi w Groovy?

Przede wszystkim – jest to język dynamicznie typowany, choć trzeba zaznaczyć, że jest to nieco ciekawsze niż w przypadku Pythona gdyż możemy podawać typy analogicznie jak w Javie:

// Przykład 1
def greet(String who) {
  println "Hello $who!"
}

def greet2(who) {
  println "Hello $who!"
}

greet("Groovy")
greet2 "Groovy"
greet2 12345

W tym przykładzie widać kilka nietypowych dla Javy konstrukcji, ale zacznijmy od tematu omawianego wcześniej. Mamy zdefiniowane dwie funkcje. Pierwsza z nich posiada zastrzeżenie, że argument who musi być typu String. Próba wywołania takiej metody z argumentem innego typu skończy się wyrzuceniem odpowiedniego wyjątku (oczywiście w czasie wykonania programu). Druga funkcja nie posiada takiego ograniczenia i w tym wypadku możemy podawać właściwie dowolne obiekty. Oczywiście możemy również określić typ zwracany przez funkcję podając typ pomiędzy słowem kluczowym def a jej nazwą.

Dodatkowo w funkcjach i metodach nie musimy używać słowa return (zazwyczaj) gdyż ostatnia wartość jest tą zwracaną. To oznacza, że jeśli napiszemy funkcję, która kończy się linijką zawierającą po prostu liczbę: 25 to ta liczba będzie wartością całej funkcji (chyba, że wcześniej użyto słowa kluczowego return).

Kolejne niespodzianki czekają na nas już w linii numer 2! Zamiast przydługiego System.out.println mamy po prostu println. Mimo, że bardzo przypomina to trochę rozwiązanie zastosowane w języku Python wcale takie nie jest. Tak na prawdę wywoływany jest stary dobry System.out.println. Groovy przy starcie globalnie importuje statycznie wspomnianą metodę. Efekt jest dokładnie taki jakbyśmy napisali na początku każdej klasy Javy:

import static System.out.println;
// wówczas można tej metody używać tak:
println("Hello World");

Dodatkowo Groovy automatycznie importuje kilka przydatnych rzeczy do każdego kompilowanego pliku:

import java.lang.*;
import java.util.*;
import java.net.*;
import java.io.*;
import java.math.BigInteger;
import java.math.BigDecimal;
import groovy.lang.*;
import groovy.util.*;

Idąc dalej w drugiej linijce kodu zauważymy, że argument tej metody nie znajduje się w nawiasie. Jest to oczywiście uproszczenie wprowadzone w Groovy, żeby pozbyć się niepotrzebnych znaków z kodu przez co ma on stać się czytelniejszy. Oczywiście nic nie stoi na przeszkodzie, żeby nawiasów używać. Możemy zatem napisać: println(„Hello World”). Jedyny zgrzyt może pojawić się gdy chcemy wywołać funkcję lub metodę nie posiadającą argumentów. Wówczas kod wygląda identycznie jak dostęp do pola klasy, a jeśli Groovy go nie znajdzie to rzuci wyjątkiem informującym, że takiego pola nie ma. Ja preferuję pisanie nawiasów wszędzie (z wyjątkiem println [nie wiem czemu]) dlatego, że w innym wypadku moim zdaniem kod wygląda niespójnie.

Kontynuując naszą podróż po linijce numer 2 natrafimy na $who w środku stringa. Jest to kolejny cukierek od Groovy. Możemy w ten sposób wstawiać dowolne wartości do ciągu znaków. Co jest jeszcze ciekawsze? Oczywiście to, że dodając klamry możemy w ten sposób wykonać w międzyczasie dowolny kod Groovy:

println "Test: ${def b = 30; ++b}"
// Wyświetli: "Test: 31"

Docierając do końca 2 linijki możemy zauważyć brak średnika. Jest on oczywiście opcjonalny. Niektórym się to podoba, innym nie, ale to nie ma większego znaczenia – każdy pisze jak mu się podoba 🙂 Warto jednak pamiętać, że średnik rozdziela instrukcje i, tak jak w powyższym przykładzie, czasami może się przydać.

Coś więcej?

Hmm… znajdzie się więcej 🙂 Prawdziwą siłą Groovy są domknięcia (ang.: Closures – tak po polsku brzmi jakoś głupawo). Domknięcie jest, ujmując to najprościej, kawałkiem kodu, który nie ma nazwy i może być wywołany zupełnie jak funkcja. Deklaruje się je przy pomocy klamer {}. Do czego można je wykorzystać? Całkiem fajny przykład podał Andrew Montalenti w swoim poście i pozwolę sobie go przytoczyć:

["Rob", "Christopher", "Joe", "John"].findAll{ 
  it.size() < = 4 
}.each { 
  println it 
}
// ==> prints "Rob", "Joe" and "John"

Groovy dodaje kilka przydatnych metod do pracy na tablicach i listach (m. in. findAll i each). Być może niektórym z was przypomina to trochę jQuery – i dobrze 🙂 W tym przykładzie po prostu mówimy, że chcemy znaleźć wszystkie elementy tablicy, których długość jest mniejsza od 4, a potem każdy z nich wyświetlić. Tak na prawdę ten opis jest zupełnie zbędny bo nawet źrednio rozgarnięty programista nawet nie znający Groovy zrozumie o co tu może chodzić 🙂

Closures w Groovy są traktowane trochę specjalnie. Jeśli metoda jako ostatni argument posiada właśnie domknięcie to może ono być podane poza nawiasem tej metody. To znaczy, że mając:

// metoda wywoluje domknięcie z parametrem s
def metoda(String s, Closure c) {
	c(s);
}

Możemy użyć takiej metody na dwa sposoby:

metoda("hello", { println it })

metoda("hello") { println it }

Polecam jednak odwiedzić stronę Groovy i poczytać więcej. Jeśli ktoś zna już Javę to nauka Groovy jest stosunkowo bezbolesna. Właściwie jest to wręcz przyjemność bo możemy zamienić często długi kod javy na krótsze odpowiedniki w Groovy.

A wydajność?

Są pewne argumenty przeciw używaniu Groovy. Jak każdy język dynamiczny posiada on pewien narzut obliczeniowy, który powoduje, że jest on wolniejszy niż zwykła Java. Jak dotąd w celu zwiększenia wydajności pewnej częśći kodu można było użyć adnotacji @CompileStatic. Niestety użycie jej mogło spowodować, że kod zachowa się nieco inaczej niż byłoby to w Groovy.

Na nasze szczęście przyszła Java 7, a wraz z nią JVM uzyskał nową funkcję o nazwie InvokeDynamic. Powinna ona pozwolić Groovy na znaczne zbliżenie się do wydajności Javy. Na razie nie ma tego wiele, ale niektórzy twierdzą, że jego wykorzystanie faktycznie poprawiło znacząco predkość.

Nie obiecuję, że jeszcze napiszę coś o Groovy bo zawsze kiedy coś podobnego obiecuję to ostatecznie i tak tego nie robię 🙂 Dlatego po prostu miejmy nadzieję, że się uda 😉

Wake on LAN i wybór systemu multiboot

Dlaczego?

Ostatnio zauważyłem pewien problem z wykorzystaniem Wake On LAN. Włączanie systemu przez sieć przy użyciu telefonu komórkowego jest bardzo wygodne zwłaszcza jeśli mogę to zrobić będąc jeszcze przed domem 😉 Dyskretnie omijając temat tego czy to zdrowe (zarówno fizycznie jak i psychicznie) przejdę od razu do rzeczy.

Problem nie jest zbyt skomplikowany. Uruchamiając komputer przez Wake On LAN (dalej WOL) chciałbym jednocześnie określić, który system ma się uruchomić. Pierwsze pobieżne próby i internetowe poszukiwania gotowych rozwiązań pokazały mi, że nie da się tego zrobić. Bardziej wnikliwe spojrzenie na sprawę pozwoliło znaleźć na pewne rozwiązanie (niestety nie udało mi się ponownie znaleźć tego linka :(), które działało następująco:

  1. Uruchomienie domyślnego systemu
  2. Zmodyfikowanie w nim domyślnego systemu wybranego w GRUBie
  3. Zrestartowanie maszyny

Nie trzeba być biegłym w technice komputerowej żeby zrozumieć, że takie rozwiązanie jest mało satysfakcjonujące. Chociażby dlatego, że uruchomienie maszyny trwa o wiele dłużej niż normalnie. Innym problemem jest też dostęp do konfiguracji GRUB. Zazwyczaj jest on na partycji EXT2, a w Windows (zwłaszcza nowszych) niełatwo znaleźć oprogramowanie pozwalające modyfikować takie partycje.

Pomyślałem więc:

Obrazek

i zacząłem zgłębiać temat. Początkowo naiwnie wierzyłem, że skoro GRUBv2 jest taki świetny i tak wiele potrafi to zapewne da się z niego jakoś pogadać z siecią lub coś w tym stylu. Byłoby to rozwiązanie najprostsze. Niestety po przejrzeniu znakomitej większości jego dokumentacji stwierdziłem, że jednak takiej możliwości nie ma.

Ostatecznie zwróciłem swój wzrok na PXE. Nie ukrywam, że byłem już nieco zrezygnowany ponieważ pomyślałem, że skoro nie znalazłem wzmianki o takim jego wykorzystaniu do tej pory to zapewne i z tego niewiele wyjdzie.

Ku mojemu zaskoczeniu PXELINUX wchodzący w skład projektu Syslinux podołał temu zadaniu. Należy jednak wspomnieć, że do korzystania z PXE potrzebne są działające i odpowiednio skonfigurowane serwery DHCP oraz TFTP. Te do działania potrzebują pewnego sprzętu. DHCP co prawda każdy ma w domu działające na jego routerze, ale rzadko daje się je konfigurować w odpowiednim stopniu. Co do TFTP to jest ono chyba praktycznie niespotykane w domowych routerach.

Na moje szczęście sam posiadam router z wgranym oprogramowaniem DD-WRT. Jest to router oparty na Linuksie więc możemy się na niego zalogować poprzez SSH i konfigurować wszystkie usługi wedle uznania. Można także uruchomić na nim serwer TFTP, ale ja nie skorzystałem z tej opcji ze względu na to, że mój biedny router posiada jedynie 16MB pamięci RAM. Nie chciałem go obciążać dodatkowymi usługami więc do tego celu wykorzystałem zakupione całkiem niedawno Raspberry Pi. Po moim przydługim wstępie proponuję przyjrzeć się następnej sekcji by zobaczyć „jak to jest zrboione”.

Konfiguracja

Wykorzystany sprzęt:

  • Komputer docelowy
  • Raspberry Pi
  • Router z oprogramowaniem DD-WRT

Zacząć należy od poprawnej konfiguracji serwera DHCP. Najpierw ustawiłem serwer w taki sposób aby Raspberry Pi zawsze otrzymywało adres IP 192.168.0.98. W DD-WRT można to zrobić łatwo poprzez stronę konfiguracyjną routera. Następną rzeczą było dodanie do konfiguracji opcji potrzebnych do uruchomienia PXE. Na DD-WRT działa uproszczony serwer udhcpd, który posiada niestety zupełnie inną składnię pliku ustawień niż znany i lubiany dhcpd. Tak czy inaczej wszystko sprowadza się do podania pewnych wartości poprzedzonych odpowiednimi słowami kluczowymi. W tym przypadku znów można było użyć strony konfiguracyjnej DD-WRT aby wpisać następujące wartości:

boot_file pxelinux.0
siaddr 192.168.0.98
sname rpi

Ostatecznie strona z konfiguracją wygląda tak:

Obrazek

Kolejną rzeczą jest odpowiednie ustawienie serwera TFTP. W tym celu zalogowałem się na Raspberry Pi i wykonałem polecenia:

root@raspberrypi# apt-get install tftpd xinetd rcconf
root@raspberrypi# rcconf

W ten sposób zainstalowałem serwer tftpd, superserwer (jak go nazywają) xinet.d, oraz narzędzie do konfiguracji autostartu usług. Drugie polecenie wyświetli nam listę wszystkich usług, które startują razem z systemem. Jeśli przy xinet.d nie ma gwiazdki to należy spacją sprawić aby była i zapisać zmiany.

Następnie należy skonfigurować xinet.d tak aby obsługiwał tftpd. W tym celu należy utworzyć plik: /etc/xinet.d/tftpd i wpisać do niego treść:

service ftp {
disable = no
socket_type = stream
protocol = tcp
wait = yes
user = nobody
server = /usr/sbin/in.ftpd
server_args = /tftpboot
port = 69
}

Ostatecznie pozostaje już tylko zrestartować superserwer i utworzyć katalog główny dla tftpd:

root@raspberrypi# service xinet.d restart
root@raspberrypi# mkdir /tftpboot
root@raspberrypi# chown pi:users /tftpboot

Po przygotowaniu serwerów przyszedł czas na dodanie infrastruktury PXE. Na początek należy pobrać najnowszą paczkę z Syslinux. Można to zrobić pod tym adresem, ale dla mnie o wiele wygodniej było po prostu zainstalować paczkę syslinux w moim Archu:

morti@radon$ sudo pacman -S syslinux

Jestem pewien, że dla Ubuntu i wszystkich pozostałych dystrybucji również taka paczka będzie. Polecenie zaowocowało utworzeniem katalogu /usr/lib/syslinux wraz z interesującą zawartością, choć mnie interesowały tylko niektóre pliki które skopiowałem na Raspberry Pi do katalogu /tftpboot:

morti@radon$ cd /usr/lib/syslinux
morti@radon$ scp pxelinux.0 vesamenu.c32 chain.c32 pi@rpi:/tftpboot/

Pierwszy ze skopiowanych plików (pxelinux.0) jest wymagany, żeby PXE w ogóle wystartowało. Kolejne dwa to obiekty com (przynajmniej tak się do nich odnoszą na wiki syslinuxa). Pierwszy z nich pozwoli nam oglądać piękne graficzne menu (zupełnie jak przy GRUBie), a drugi – załadować Windowsa (o czym za chwilkę).

Pójdźmy teraz z powrotem na Raspberry Pi i utwórzmy podstawowy plik konfiguracyjny. (Dla ułatwienia pracy do testów serwera PXE wykorzystuję maszynę wirtualną o adresie MAC: 08:00:27:6E:6B:99):

pi@raspberrypi$ mkdir /tftpboot/pxelinux.cfg
pi@raspberrypi$ cd /tftpboot/pxelinux.cfg
pi@raspberrypi$ touch 01-08-00-27-6e-6b-99
pi@raspberrypi$ touch default.virtual
pi@raspberrypi$ touch graphics.conf

Powyżej utworzyłem katalog, a w nim trzy pliki. Pierwszy z nich to plik konfiguracyjny dla mojej wirtualnej maszyny. Jak widać nazwa to po prostu adres MAC poprzedzony 01 na początku. Istotnym okazał się fakt, że cyfry szesnastkowe muszą być literkami małymi.  Drugi plik będzie modyfikowany przez zewnętrzne skrypty i będzie zawierał wpis dotyczący tego, który z systemów zostanie wybrany domyślnie. Ostatni z plików to już tylko konfiguracja wyglądu, którą włączymy sobie do pliku głównego.

Zaczynając ponownie od początku zdefiniujmy zawartość pliku o dziwnej nazwie:

PROMPT 0
UI vesamenu.c32
TIMEOUT 100

MENU TITLE Moje cudowne menu!
MENU AUTOBOOT System wystartuje za # sekund…

MENU INCLUDE  pxelinux.cfg/graphics.conf
MENU INCLUDE pxelinux.cfg/default.virtual

label localhdd
menu label ^Pierwszy dysk twardy
kernel chain.c32
append hd0

label windows
menu label ^Windows 7
kernel chain.c32
append hd0 2

Na początku ustawiamy aby system nie pytał użytkownika, który system uruchomić. Opcja PROMPT sprawiła mi trochę problemów. Przecież chciałem mieć ewentualnie wybór manualny. Później okazało się, że działa nieco inaczej niż zakładałem. Na początku użytkownik nie jest pytany i uruchamiany jest zdefiniowany niżej moduł vesamenu.c32, który wyświetla menu graficzne, w którym z kolei można już bez problemu wybrać żądany system. Dalej ustawiany jest timeout na 10 sekund (tak to jest wartość 100). Oczywiście określa to ilość czasu jaką użytkownik ma na ewentualną reakcję. Dalej zdefiniowane są mało istotne w tym wypadku tytuł menu oraz tekst informujący za ile sekund zostanie wybrana opcja domyślna, a po nich załączane są pliki z konfiguracją wyglądu ekranu bootloadera oraz domyślnie wybraną opcją do uruchomienia.

Prawdziwie interesujące rzeczy znajdują się niżej. Zdefiniowane są tutaj dwa wpisy. Pierwszy uruchamia bootloader z pierwszego dysku twardego, który w moim przypadku wywołuje GRUBa domyślnie ustawionego na start Arch Linuksa. Drugi wpis uruchamia system Windows bezpośrednio z drugiej partycji pierwszego dysku twardego. Oba wpisy zrealizowane są w podobny sposób i korzystają z modułu chain.c32, który pozwala po prostu przekazać sterowanie dalej.

W tej chwili wystarczy do pliku /tftpboot/pxelinux.cfg/default.virtual wpisać zawartość default localhdd lub default windows i oczywiście ustawić w maszynie wirtualnej aby uruchamiała się przy użyciu PXE. Naszym oczom powinno ukazać się menu zezwalające na wybór jednej z dwóch opcji z jedną z nich oznaczoną jako domyślną. Ostatecznie, żeby nie biły nas po oczach domyślne, bardzo niefortunnie dobrane kolory, pxelinuxa możemy wypełnić treścią plik /tftpboot/pxelinux.cfg/graphics.conf:

MENU COLOR tabmsg 37;40      #80ffffff #00000000
MENU COLOR hotsel 30;47      #40000000 #20ffffff
MENU COLOR sel 30;47      #40000000 #20ffffff
MENU COLOR scrollbar 30;47      #40000000 #20ffffff
MENU BACKGROUND blue.png
MENU MASTER PASSWD yourpassword
MENU WIDTH 80
MENU MARGIN 22
MENU PASSWORDMARGIN 26
MENU ROWS 6
MENU TABMSGROW 15
MENU CMDLINEROW 15
MENU ENDROW 24
MENU PASSWORDROW 12
MENU TIMEOUTROW 13
MENU VSHIFT 6
MENU PASSPROMPT Podaj haslo:
NOESCAPE 1
ALLOWOPTIONS 0

Powyższa konfiguracja zakłada istnienie pliku /tftpboot/blue.png o wymiarach 640×480 pikseli.

Jedyne co pozostało to utworzyć analogiczną konfigurację dostosowaną do każdego komputera wymagającego takich zabiegów i przestawienie ich w tryb bootowania przez PXE. Zapewne pomocnym byłoby utworzenie dodatkowo skryptów, które zmieniają plik /tftpboot/pxelinux.cfg/default.virtual (lub podobny – dla każdej maszyny inny) oraz wysyłają magiczny pakiet WOL do sieci lokalnej.

W taki sposób utworzyłem sobie konfigurację, która pozwala uruchomić jeden z zainstalowanych lokalnie systemów operacyjnych. Zaletą takiego rozwiązania jest to, że w przypadku gdy zepsuję sobie na przykład bootloader to wystarczy, że przez PXE uruchomię np. Tiny Core Linux i będę mógł bez problemów wszystko naprawić. Nigdy więcej poszukiwań LiveCD 😉 Choć zapewne lepiej nic nie psuć…

Dalsze plany

Nie trzeba być specjalnie bystrym, żeby zobaczyć, że takie rozwiązanie jest dalekie od ideału. Musiałbym się zalogować na Raspberry Pi za każdym razem kiedy chcę uruchomić odpowiedni system operacyjny. Nie jest to zbyt wielkie ułatwienie w stosunku do tego co było wcześniej 🙂 Z tego powodu planuję rozszerzyć funkcjonalność tego zestawu o dwie rzeczy.

Pierwszą z nich będą dwa fizyczne przyciski podpięte pod Raspberry Pi. Oba będą włączać mój komputer stacjonarny, ale każdy z nich będzie ustawiał inny system operacyjny do uruchomienia. To już będzie duży zysk dla mnie ze względu na to, że przycisk włączania komputera mam głęboko pod biurkiem, a oczekiwanie na pojawienie się GRUBa w celu wyboru systemu po prostu mnie nie cieszy 😉 Nie raz już zdarzyło się, że restartowałem system dlatego, że uruchomił się nie ten, który chciałem. (Oczywiście po tym znów odchodziłem od komputera i ponownie włączał się ten, którego nie chciałem ;))

Druga funkcjonalność będzie o wiele zabawniejsza ponieważ mam zamiar utworzyć Jabberowego bota, któremu będę mógł przez komunikator internetowy z innego komputera lub z telefonu wywołać procedurę uruchamiania odpowiedniego systemu.

Myślę, że takie ustawienie będzie dość wygodne w użytkowaniu. Jeśli kogoś to nie przekonuje i nie widzi potrzeby takiego cudowania, by po prostu uruchomić system operacyjny to… trudno 😉 Różni ludzie mają różne potrzeby 🙂

Jak już uda mi się stworzyć opisane powyżej rozszerzenia to zapewne zaprezentuję je w jakimś kolejnym wpisie, ale nie spodziewałbym się tego zbyt szybko ^^.

Ubuntu, dwa monitory – disper, starcie drugie!

Wcześniej napisałem post o tym jak w prosty sposób zmieniać monitory w Ubuntu (i właściwie dowolnym Linuxie). Dzisiaj chciałbym rozwinąć nieco temat ponieważ samo łatwe zmienianie ekranu przestało mi wystarczać 😉

Sprawa wygląda następująco: Posiadam dwa monitory o różnej wielkości. System po uruchomieniu wykorzystuje tylko jeden monitor ze względu na to, że przez większość czasu i tak nie jest mi potrzebny drugi. No i nie zapominajmy o mniejszych rachunkach za prąd!

Problem

Gdyby oba monitory były takiej samej wielkości to na tym można by skończyć. Jest jednak inaczej. Dzisiaj po zainstalowaniu Cairo Dock postanowiłem sprawdzić jak poradzi sobie z dynamiczną zamianą monitorów. Okazało się, że nie najlepiej. Moje monitory wirtualnie skonfigurowane są następująco:

Generalnie utworzony jest jeden duży pulpit (2704 x 1050) i monitory są jedynie „oknami” do patrzenia na jego części. Z takim rozwiązaniem stowarzyszone są dwa podstawowe problemy:

  • kiedy myszką wyjedziemy w obszar szary znika ona z pola widzenia,
  • pasek zadań wyrównany do dołu i do lewej chowa się w szarej strefie i nie da się go obsługiwać.

Pierwszy problem może być uciążliwy jeśli na dole lewego monitora mamy pasek zadań. Trafienie w niego staje się nie lada wyzwaniem, ponieważ nie wystarczy tylko beztrosko przeciągnąć myszy w dół ekranu. Jeśli chodzi o ten drugi to sprawa wygląda jeszcze gorzej jeśli (tak jak w moim przypadku) pasek częściowo wychodzi jeszcze na większy monitor (po zmianie z jednego na dwa).

Jednak jeśli na dwóch monitorach wyłączyłem i uruchomiłem jeszcze raz Cairo Dock to ładnie rozkładał się on na lewym monitorze (można też skonfigurować go do uruchamiania na prawym). Jedyne co pozostało to po przełączeniu monitorów po prostu uruchomić ten program jeszcze raz. Ale zawsze robić to ręcznie? Nieeeee, przecież mamy Linuxa 😉

Rozwiązanie

Jak się okazuje, disper pozwala w katalogu ~/.disper/hooks  (gdzie ‚~’ to katalog domowy użytkownika) przechowywać haki, czyli skrypty lub programy, które wykona po zmianie rozdzielczości. Możemy utworzyć zatem prosty skrypt, który po prostu zabije proces Cairo Dock i później uruchomi go ponownie. Będzie on wyglądać pewnie tak:

#!/bin/bash
killall cairo-dock
cairo-dock &> /dev/null &

Oczywiście musi być umieszczony w katalogu ~/.disper/hooks i może się nazywać dowolnie. To jednak niestety nie wszystko. Trzeba jeszcze powiedzieć disperowi żeby uruchamiał ten plik po swoim działaniu. Robi się to dodając przy jego uruchamianiu parametr –plugins=user. Czyli program wywołamy na przykład tak:

disper -e --prefix=user

To rozwiąże nam problem paska. No ale oczywiście nie chcemy podawać tego parametru za każdym razem. Disper i tutaj wyszedł nam na przeciw. Możemy bowiem w jego pliku konfiguracyjnym znajdującym się w ~/.disper/config wpisać w każdej linii jeden parametr, który chcemy by był dodawany do każdego wywołania programu. Wystarczy zatem w konsoli wywołać polecenie:

echo "--prefix=user" >> ~/.disper/config

To tyle jeśli chodzi o sam pasek. Pozostaje jednak jeszcze problem myszy. Tutaj z pomocą przyjdzie nam inny program, o wiele mówiącej nazwie XCreateMouseVoid. Jego głównym (i jedynym) zastosowaniem jest tworzenie pojedynczego prostokątnego obszaru, do którego nie będzie można wjechać kursorem myszy. Pomysł jest zatem taki, żeby wypełnić całą szarą strefę takim właśnie obszarem. Wtedy miejsce dostępne do hasania kursorowi będzie odpowiadało temu co widzimy na monitorach i o to chodzi.

Aby program uruchomić należy go ściągnąć i skompilować. Sprawa jest jednak nadzwyczaj prosta i jak zwykle konsola sprawi, że życie jest przyjemniejsze:

sudo -i
(podajemy swoje hasło użytkownika)
apt-get install build-essential git
cd /opt
git clone https://github.com/cas--/XCreateMouseVoid.git
cd XCreateMouseVoid
make

I tyle. Teraz, żeby utworzyć przestrzeń wolną od ruchu kursora wystarczy wywołać program z odpowiednimi parametrami:

/opt/XCreateMouseVoid/XCreateMouseVoid 0 768 1024 1050

I teraz nie ma już problemów z uciekającym kursorem. Jedyne co pozostało to dodać powyższą linię do naszego skryptu w ~/.disper/hooks i wszystko załatwione. W razie gdyby przyszło mi do głowy później wykonywanie jakichś akcji przy przełączaniu monitorów rozwinąłem trochę skrypt do czegoś takiego:

#!/bin/bash
function common()
{
 killall XCreateMouseVoid
 killall cairo-dock 
 cairo-dock &> /dev/null &
}
function one()
{
 common
}
function two()
{
 common
 /opt/XCreateMouseVoid/XCreateMouseVoid 0 768 1024 1050 &> /dev/null &
}
function more()
{
 common
}
set $DISPER_DISPLAYS
if [ $DISPER_STAGE != "switch" ]; then
 exit 0;
fi
case "$#" in
 "1") one ;;
 "2") two ;;
 *) more ;;
esac

Ogólnie jego działanie jest dosyć proste. Za każdym razem gdy przełączamy monitory zabijane są XCreateMouseVoid i cairo-dock oraz uruchamiany jeszcze raz cairo-dock. Dodatkowo w przypadku gdy przełączamy w tryb dwóch monitorów tworzymy przestrzeń wyłączoną z ruchu kursora.

Myślę, że takie informacje będą pomocne wielu ludziom, którzy przychodzą tu z pytaniem o dwa monitory dla Ubuntu czy dowolnego innego Linuxa. Mi się na pewno przyda w razie gdybym kiedyś jeszcze tego potrzebował 😉

Zmiany, zmiany…

Dzisiaj zmieniła się skórka strony. Oprócz skórki chciałbym poinformować o tym, że od niedawna walczę nie tylko z kodem pisanym na konwencjonalne procesory, które możemy znaleźć w komputerach. Teraz postanowiłem pokombinować trochę z mikrokontrolerami. Na początek – AVR Atmega8. Od dawna byłem zainteresowany tworzeniem własnych urządzeń elektronicznych, a już najlepiej jeśli potrafiłyby potem rozmawiać z komputerem celem osiągnięcia jakiegoś wyższego celu (np. świecenia diodą LED 😉 ).

Początki były bardzo marne ponieważ nie mogłem się zebrać w sobie by kupić książkę i usiąść nad tematem. Z nieoczekiwaną pomocą przyszła kolejna studencka sesja egzaminacyjna. Niektórzy ludzie zamiast się uczyć sprzątają w domu, inni spotykają się ze znajomymi, a jeszcze inni… zaczynają poświęcać więcej uwagi na swoje niedoszłe hobby 😉 Tak było w moim przypadku i po przescroolowaniu całych kilometrów stron internetowych traktujących o wykorzystaniu mikrokontrolerów udało mi się zrozumieć czego potrzebuję aby zacząć.

Nie chciałem kupować Arduino głównie ze względu na cenę, choć nie tylko. Co prawda mamy wówczas do dyspozycji bardzo dużo bibliotek i ogromne wsparcie ze strony użytkowników, ale ja wolę wiedzieć wszystko od początku. Przecież naukę pisania programów w C mam już dawno za sobą więc to nie stanowi bariery. Dlatego postanowiłem kupić jedynie luźne komponenty i zacząć zabawę po swojemu.

Za jakieś 50-60 zł kupiłem cale tony wszelkich komponentów elektronicznych na Allegro i jak przyszły od razu rzuciłem się do kombinowania. Na początku głównym zadaniem było w ogóle poprawne podłączenie mikrokontrolera i zaprogramowanie go. W Windowsie do programowania można używać pięknych zintegrowanych środowisk podczas gdy w Linuxie, na którym pracuję takich cudów nie ma. Jak się okazało – nie są one mi tak bardzo potrzebne (choć jestem pewien, że wielu początkującym mocno ułatwią pracę i oszczędzą nerwów). Pierwsze połączenia mikrokontrolera z programatorem ISP wyglądały tak:

Szybko jednak stwierdziłem, że mikrokontroler na płytce stykowej zajmuje za dużo miejsca (nie mówiąc już o tej plontaninie kabli… Po ładnych kilku chwilach lutowania na płytce prototypowej powstał wynalazek następujący:

No i w końcu samą płytkę stykową można było wykorzystać do bardziej zacnych celów:

Lojalnie ostrzegam, że od tej pory na blogu mogą pojawiać się treści związane tematycznie z elektroniką cyfrową 🙂 Możliwe także (choć na to bym nie liczył :P), że zwiększy się częstotliwość dodawania wpisów. Tak czy inaczej – ja mam dużo radości, z tego, że na wyświetlaczu mogę napisać coś od siebie 😉 Jeśli komuś cisną się na palce pytania to nie krępujcie się i zadawajcie je w komentarzach.