Sunday, May 08, 2016

Graphical User Interface Nightmares in Java

Compared to Windows Forms, doing GUI work in Java is painful because you have to deal with a lot more detail. Recently, I was trying to customize tab drawing in JTabbedPane to make the font of the selected tab bold and its background color green. I ended up creating a class (MyTabbedPaneUI) that extends BasicTabbedPaneUI and overrides paintTabBackground().
public class MyFrame extends javax.swing.JFrame {
    public MyFrame() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        javax.swing.JTabbedPane jtp = new javax.swing.JTabbedPane();
        getContentPane().add(jtp);
        jtp.setUI(new MyTabbedPaneUI());
        jtp.add("My Tab 1", new javax.swing.JPanel());
        javax.swing.JLabel jl1 = new javax.swing.JLabel(jtp.getTitleAt(0));
        jtp.setTabComponentAt(0, jl1);
        jtp.add("My Tab 2", new javax.swing.JPanel());
        javax.swing.JLabel jl2 = new javax.swing.JLabel(jtp.getTitleAt(1));
        jtp.setTabComponentAt(1, jl2);
    }
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new MyFrame().setVisible(true);
            }
        });
    }
}
public class MyTabbedPaneUI extends javax.swing.plaf.basic.BasicTabbedPaneUI {
    /**
     * NOTE: Do not perform lengthy operations (e.g. setting font to bold) 
     * inside this paint method because it causes high CPU load and has 
     * side effects like not being able to update java3D drawings.
     */    
    @Override
    protected void paintTabBackground(Graphics g, int tabPlacement, 
                       int tabIndex, int x, int y, int w, int h, 
                       boolean isSelected) {
        for (int i = 0; i < tabPane.getTabCount(); i++) {
            Color bgColor = Color.YELLOW;
            javax.swing.JLabel jl = (javax.swing.JLabel) 
                tabPane.getTabComponentAt(i);
            if (jl != null) {
                if (i != tabIndex) {
                    bgColor = Color.GREEN;
                    //jl.setFont(jl.getFont().deriveFont(Font.PLAIN));//BAD
                } else {
                    //jl.setFont(jl.getFont().deriveFont(Font.BOLD));//BAD
                }
            }
            Rectangle rect = rects[i];
            int pad = 2;
            g.setColor(bgColor);
            g.fillRect(rect.x+pad, rect.y+pad, rect.width-2*pad,
                rect.height-2*pad);
        }
    }
}
Setting font to bold (by uncommenting the lines commented as "BAD") in BasicTabbedPaneUI.paintTabBackground causes more than 7x CPU usage:


I have witnessed cases where CPU usage shot up to 100% and my app could not update a java3D drawing on another window. You can imagine that it was not easy to find out why.

Other examples of why Java should be avoided for GUI work:
  • Changing color of a JTable cell is a lot of work.
  • For background color to have any effect, JLabel has to be opaque while JTextArea has to be not opaque (opposite of their defaults)!
  • Due to the unintuitive layout mechanism, what I see on design view is completely different from what I see when I run the application.
  • When I change the layout, sometimes all the components dissappear (their width and height becomes zero).
  • Setting the width/height to "Preferred" sometimes causes the component to shrink to zero size. From what I understand, the layout mechanism is there to ensure proper resizing, i.e. to have a similar look when screen resolutions, font size etc. change. As usual, when trying the solve the most general case, you make it more difficult to solve simple cases.
  • To properly set size of a JFrame or JDialog, calling setPrefferedSize() is not enough, you have to call pack() afterwards.
  • When, in design mode, you rearrange buttons, labels or when you just change the border of panels, NetBeans might convert them to local variables and you have to manually convert them back to private to be able to reference them.
  • 10.04.2019: If you have rows of panels and want to use a JPanel instead of JTable to house subpanels, you have to call JPanel.setPreferredSize() and then revalidate() / repaint() for JScrollPane to update itself. JTable complicates matters due to it's editor / renderer mechanism, so use JPanel whenever you have a panel inside a table.
  • 10.04.2019: If you have a JFormattedTextField with latitude format in it, you might need to call JFormattedTextField.commit() when you edit the text and want degree sign to remain in field.
My layout strategy:
  • Create a frame
  • Add a panel with null layout.
  • Add subpanels to group components
  • Set the layout of subpanels. The layouts I use most often:
    • null
      • Advantage: You can set the location and size of components.
      • Disadvantage: If your form is resizable, components won't resize.
    • GridLayout;
    • BoxLayout
  • With null layout, to set location and size, use setBounds() instead of setPreferredSize().

Thursday, March 31, 2016

How to improve quality of legacy software

When developing software from scratch, I follow these steps: Use cases (aka concept of operation), requirements, design, code & unit tests, system test planning, system test.

Recently, I was asked how to improve the quality of a software written by other people a couple of years ago. I was informed that it was working, the only missing thing was compliance to software development procedures. I recommended to do the above sequence in reverse, i.e. first planning and executing full system tests. That way, we can answer the most important question, "is it working correctly". After we are satisfied with the tests, we can review and document the design and refactor code.

Even if we have to stop after testing, we will have added value to the existing product by being able to demonstrate in a repeatable manner that it is working. If we started the development sequence from the beginning and had to stop midway due to other priorities (which always happens), we would have wasted our time.

Wednesday, March 30, 2016

Model simplification strategies for testing

When developing complex models/algorithms, it is usually difficult to evaluate full model test results. The only option to verify complex model output is to compare it with some other implementation, for example Matlab toolboxes.

In addition to using 3rd parties for comparison, you should add functionality that lets you to simplify the model to a form whose results can be easily interpreted by a technical person. For example, if you are working on an algorithm that converts Geodetic coordinates to ECEF, you could have a function that temporarily sets the ellipsoid to a sphere (by making the eccentricity zero). It is easy to calculate expected results for a perfect sphere.

Similarly, if you develop a kinetic 6DoF flight simulation, you should have functions or flags that let you easily turn off complicating factors like aerodynamics (by multiplying coefficients with zero), wind, variable gravity, Coriolis, ellipsoidal Earth and terrain elevation. Your aim is to simplify your model to a ballistic flight in vacuum with constant gravity over a flat and non-rotating Earth. You can then use high school physics to calculate expected trajectories and compare them with your model outputs.

To clear doubts about your code, you should be able to quickly show that it obeys basic geometrical/physical laws.

Saturday, March 19, 2016

Error in NASA code

NASA World Wind java code is available on the internet. While looking at the their SDK2 EGM96.java file, I discovered that the simple two dimensional linear (bilenear) interpolation code has an error. The original code:
...
double ul = this.gePostOffset(topRow, leftCol);
double ll = this.gePostOffset(bottomRow, leftCol);
double lr = this.gePostOffset(bottomRow, rightCol);
double ur = this.gePostOffset(topRow, rightCol);

double u = (lon - lonLeft) / INTERVAL.degrees;
double v = (latTop - lat) / INTERVAL.degrees;

double pll = (1.0 - u) * (1.0 - v);
double plr = u * (1.0 - v);
double pur = u * v;
double pul = (1.0 - u) * v;

double offset = pll * ll + plr * lr + pur * ur + pul * ul;
...

I prepared a schematic to visualize the algorithm:


As you can see at the end of above code snippet, the offset formula starts with pll*ll. pll is (1.0 - u) * (1.0 - v). The correct multiplier of the ll term has to be v*(1-u).

A quick sanity check: Consider the case when lat=latBottom and lon=lonLeft, i.e. the lower left corner. In that case we expect the offset to be equal to ll. Plugging in the values we get v=1, 1-v = 0 and u=0, 1-u = 1. If we use the original (wrong) formula we get offset = 1*0*ll + 0*0*lr + 0*1*ur + 1*0*lu = 0 which is clearly wrong. If we use the corrected formula we get offset = 1*1*ll + 0*0*lr + 0*1*ur + 1*0*lu = ll which is the expected result.

The easiest way to fix the code is to swap the topRow and bottomRow indices when calling getPostOffset() function.

I guess the error was not detected since results do not differ too much because, although the points are used in wrong order, they are close to each other nonetheless, so the result might not have looked suspicious.

I tried to log a bug report on their Jira site, but I could not since I don't have an account. I was able to file a bug report on GitHub.

Lessons learnt:
  • Thoroughly test 3rd party code, even if it is from NASA or Mathworks. Every code is guilty unless proven innocent. Ignore this advice and you will find yourself chasing strange errors for a very long time.
  • Separate interpolation code into a function, write lots of unit test for that function to verify that it works correctly.

Good error messages

A good error message should contain the following:
  • Short description
  • What triggered the error?
  • What was expected?
Example: User inputs a negative value (-5) into a function foo that only accepts positive integers. A message similar to the following should be displayed:
Negative input!
The input value was -5. Function foo only accepts positive integers.

Tuesday, February 23, 2016

Çocuk yetiştirmek

Artık 3.5 yaşında olan oğlumuz Argun'un büyüme macerası ilginç derslerle doluydu. Bu derslerin Argun'un yapısı ile yakından alakalı olduğunun ve benzer çocuklarda (hareketli, neşeli erkek çocuk) daha çok işe yarayacağının farkındayım ama sanırım başka çocuklar için de geçerli şeyler vardır.

Başlangıçta çirkin bir ördek yavrusu olan Argun sonradan şirin bir çocuğa dönüştü:

Ana hatları ile gelişimi şu şekilde oldu:
  • 1. yaş: Yürüdü (herhangi bir yerden destek almadan beş adım atabildi)
  • 1.5 yaş: Kreşe verdik, ancak üç haftalık deneme süresinin ardından Argun'un henüz kreşe hazır olmadığına karar verip geri aldık.
  • 2. yaş: Cümle kurmaya başladı, emziği bıraktırdık.
  • 2.5 yaş: Kreşe tekrar başladı. Bezini bıraktırdık.
Yakın çevremdeki ebeveynleri izlediğimde çocukla ilgilenmenin çocukların özgürlüğünü ellerinden alacak boyutlara vardığını görüyorum. Muhtemelen onlara beni sorarsanız benim aşırı ilgisiz olduğumu söyleyeceklerdir (!)

Eski zamanlarda geçim derdiyle uğraşmaktan çocukla uğraşmaya pek zaman kalmıyordu, yedirecek şey sınırlıydı, götürecek doktor yoktu. İyi tarafı çocukların daha özgür olmalarıydı. Şimdilerde maddi olanaklardaki iyileşmeler ebeveynlerin ilgi, şefkat adı altında çocuklarının her işine karışan, sürekli kreşi telefonla arayan, en sıradan ateşte acillere koşan stres ve endişe küplerine dönüşmesine neden oluyor. Hem çocuklar zarar görüyor, hem de kendileri. Zaten yeterince zor olan şehir hayatı çocukla birlikte çekilmez hale gelebiliyor.

Çocuğun en önemli ihtiyacının özgürlük olduğunu düşünüyorum. Özgürlükten kastım kendi yardım talep etmedikçe veya geri dönülmez zarara uğrama ihtimali olmadıkça çocuğu rahat bırakmak. Küçük çocukların neredeyse tek öğrenme biçimi deneme yanılma. Denemesine izin vermek gerekiyor. Örneğin parka gitmişsek ve Argun birkez bile canı yanıp ağlamadan dönüyorsak bunu kayıp olarak görürüm çünkü sınırlarını yeterince zorlamamış ve yeterince öğrenmemiş demektir. Aşağıdaki videoda Argun bizim gözetimimizde iğne ile dikiş öğreniyor ve arada parmağına iğneyi batırıyor:


Bundan sonraki dönemde bizim işlevimiz Argun'a öğrenmeyi sevdirmek. Matematikten tarihe kadar her konunun ilginç bir özü, zengin gelişim öyküsü var. Biz bunları örneklerle, gezilerle, deneylerle verebiliriz. İlham için YouTube'daki Veritasium ve MinuteEarth kanallarına veya benim Science Experiments listeme bakılabilir.

Her çocuğun öğrenme biçimi farklı. Özellikle spor ve dans gibi bedenin karmaşık şekillerde kullanımını gerektiren aktivitelerde benim gibilere alt adımların anlatılması gerekir. Anlatılmayıp "benim yaptığım gibi yap" yöntemi uygulandığında başarılı olmam tesadüflere bağlı oluyor. Ortaokulda bir voleybol antrenmanında yeni gelen beden öğretmeninin benim bir hareketi yapamamam sonrası "sen nasıl okul birincisi olmuşsun, hayret" alaycılığı hala aklımdadır. "Seni başımıza öğretmen diye dikenlere lanet olsun" derdim içimden yüzüne karşı (!) Çocuğunuzun iyi bir öğrenim hayatı geçirmesini istiyorsanız bunu sadece okullara bırakamazsınız, kolları sıvayacaksınız. Hazır yeri gelmişken spor ve dans eğitiminde kullanılabilecek güzel bir kitap önereyim: The Inner Game of Tennis.
Dersleri listeleyecek olursak:
  • Çocukta karakter sorunu varsa ebeveyn önce kendine bakmalıdır. Kendizini iyileştirin yeter, çocukla uğraşmayın, çocuğa ozmozla geçer (!)
  • Ebeveynin rahatı da en az çocuğunki kadar önemlidir. Çocuğa ne kadar çok emek harcarsanız o kadar çok yorulursunuz ve çocuğa/eşinize/çevrenize kötü davranma olasılığınız artar. Enerjiyi idareli kullanın.
  • Ağlamasına müsaade edin ama ağlama için evin belli bir yerini kullabileceğini, her yerde ağlayamayacağını söyleyin. Ağladığı zaman kucaklayıp ağlama yerine bırakın, ağlaması bittiğinde geri gelebileceğini söyleyin.
  • Eğer yemek, banyo, uyku gibi önemli bir konuda çocuk itiraz ediyorsa bir-iki cümle ikna denenebilir, olmuyorsa aksiyona geçilir. Örnek: Sebze değil, pilav yemek istiyor. Sebze yedikten sonra pilav yiyebileceğini söylüyorum, tabii ki kabul etmiyor. Mutfaktan çıkarıyorum, ağlıyor, mutfakta ağlamanın yasak olduğunu, ağlama yerinin antre olduğunu söyleyip çırpınan Argun'u kollarından kaldırarak antreye götürüp bırakıyorum. Birkaç git-gelden sonra (15 dakika) ikna oluyor ve sebzesini yiyor.
  • Yemek yemeyi kendi talep etmeli, bir gün boyunca aç kalması göze alınmalı, iki öğün arasında yiyecek verilmemeli. Yemeği kendi talep ettiğinde hem yemeye ebeveyni tarafından zorlanıp özgürlüğü elinden alınmış olmaz, hem de yemekle sıkıntıyı değil, keyfi ilişkilendirir.
  • Çocuğa akıl vermeyi minimumda tutun. Örneğin yolda yürürken düşme riski var ancak düşerse en fazla bir yeri moraracaksa "düşersin" gibi uyarılarda bulunmayın. Parkta oynarken neyi nasıl yapması gerektiğini söylemeyin, kendisi düşe kalka keşfetsin.
  • Gün içinde çok sayıda eğitim fırsatı var. Örneğin masadaki kuruyemişleri yere saçmışsa bunun bir fırsat olduğunu hatırlayın. Kabukları toplamasını isteyin. "Topladım" dediğinde yerde kalan küçük parçaları gösterip "bunları da toplar mısın" deyin, bu işlemi en küçük parça bile toplanana kadar tekrar edin. Kritik olan sizin sabırlı, sakin ve kararlı olmanız. Böyle basit bir olay bile çocuğa dikkatten işini iyi yapmaya kadar çok şey öğretir.
  • Çocuğun işin içinde olduğu organizasyonları sakın aceleye getirmeyin, geniş bir zaman ayırın, yoksa sabırlı olmanız mümkün olmaz.
Kolaylıklar dilerim.

Güncelleme 21 Kasım 2016: 4 yaşını geçen Argun artık tablet bağımlısı oldu, eğer müdahale etmezsek sabahtan akşama kadar öğle yemeğini dahi ihmal ederek oynayabiliyor. Oynama süresini hafta içi 1 saat, hafta sonu 3 saat civarında sınırlıyoruz. Tabletin birtakım zararları (instant gratification, boyun fıtığı, yaşına uygun olmayan videolar izlemesi vb.) olabileceğini biliyorum. Bu zararlar süre sınırlama ve tablet tutucularla kabul edilebilir boyutlara indirilebilir. YouTube Kids bir gün Türkiye'de kullanıma sunulursa zararlı video da büyük ölçüde halledilir.

Elbette çocuğun öncelikli ihtiyacı başka çocuklar. Ancak şehir ortamında çocukları haftada birden fazla bir araya getirmek zor. Tabletin epeyce yararı var. Örneğin ebeveyne nefes alacak zaman kalıyor, oyunlar zekayı geliştiriyor, videolarla kendini eğitip oyuncakların kullanımını öğreniyor. En önemlisi de Argun yemek, uyku gibi tartışılmaz kurallara mızmızlandığında (istisnasız her gün) sakince "böyle devam edersen bugün tablet yasak" demek onu %50 oranında hizaya getiriyor. Diğer %50'de ceza vererek tablet süresini de doğal olarak sınırlanmış oluyoruz.

4 yaşın bir diğer güzelliği mantıkla ikna edilebilirliğinin artması. Örneğin sevmediği bir yemeği yemesi için aşağıdaki dialog işe yarayabiliyor:

Argun: Ben bu yemeği sevmiyorum!
Şamil: Ben de sevmiyorum ama güçlenmek için yiyorum, yoksa kolay hasta oluruz. Hasta olmak istiyor musun?
Argun: Haayıır (suratını ekşiterek gönülsüze yemeğini yer).

Tabi her zaman işe yaramıyor, o zaman tablet ceza tehdidi imdada yetişiyor :P

Güncelleme 2 Temmuz 2017: 4.5 yaşındaki Argun yemek yemekten hoşlanmıyor, görev olarak yapıyor. Bu nedenle ona bırakırsak savsaklıyor. "Ne yemek istersin" gibi sorular uzun bir pazarlıkla sonuçlanıyor, sürekli oyalanıyor ve masadan kalkması neredeyse 1 saat sürüyor. En etkili yöntem ona sormadan tabağına yemesi gereken her şeyi koyup mutfaktaki duvar saatinde 10 dakika sonra çizgisine işaret edip yelkovan oraya geldiğinde tabağındaki her şeyi bitirmiş olması gerektiğini, yoksa o gün için tabletle oynayamayacağını söylemek. Eğer 10 dakika sonra bitmemişse o gün için tablet cezası verip beş dakika sonra bitmemişse yarın da cezalı olacağını söylüyorum. Her ek beş dakika için ek bir gün ceza alıyor. Yaşasın cezalı eğitim :P

Thursday, February 11, 2016

Finding the Intersection of Lines

The classical y = m*x+n line equation fails when finding intersections with vertical lines. Normally, the intersection point of two lines is found as follows:

Y = m1*x+n1 = m2*x+n2 --> x = (n2-n1)/(m1-m2)

Consider the following example, which clearly has an intersection at x = 1 and y = 0.5:

The classical equation for line AB results in m1 = infinity and n1 = -infinity, and for CD results in m2 = 0.5, n2 = 0. When you plug these values into x = (n2-n1)/(m1- m2), you get x = (0 - (-infinity)) / (infinity - 0.5) = infinity/infinity = NaN.

You could try to solve this problem using a bunch of if statements to check if a line is vertical, horizontal etc which would be prone to bugs. Or you could use parametric line equations:

Applying the above parametric formula to our example problem, we obtain the following equations:

Line1: P1 = A + (B - A) * t
Line2: P2 = C + (D - C) * u

To find the intersection point we equate P1 to P2:

A + (B - A) * t  = C + (D - C) * u.

Using scalar components:
Ax + (Bx-Ax)*t = Cx + (Dx-Cx)*u --> Ax-Cx = (Ax-Bx)*t + (Dx-Cx)*t
Ay + (By-Ay)*t = Cy + (Dy-Cy)*u --> Ay-Cy = (Ay-By)*t + (Dy-Cy)*t

Rearranging into matrix form:

| Ax-Bx, Dx-Cx | |t|  = |Ax-Cx|
| Ay-By, Dy-Cy | |u| = |Ay-Cy|

We can solve this M*TU = K system and obtain t and u values: TU = M^-1*K. t and u are -0.25 and 0.5 respectively. Finally to get intersection x,y coordinates, we can use

xIntersect = Ax + (Bx-Ax)*t
yIntersect = Ay + (By-Ay)*t

Which gives us the correct result of (1, 0.5)

An additional benefit of the parametric line equation is that it can easily be extended to three dimensional space. It is also used in calculating intersection lines with a planes.

Lesson learnt: Don't use y = m*x+n, instead use P = A + (B - A) * t