Programmer's Manual

Programming experience with NeuroKernel is meant to be easy and simple without any bloat. All system interfaces are easily accessible. It is designed after the popular event based model so that the learning curve will be short for developers familiar with event based programming such as Swing and SWT.

1. NeuroKernel Application

NeuroKernel applications always extend NApplication class to execute, and main method must be overridden. It is possible to configure an application with annotations that comes with the API. Annotations may also be used to reduce the code size and generate deployment files for the compiled application when the target is also the client side. An application can be compiled for both server and client side without changing a single line of code. Only thing to figure out here is that to see if the supported Java packages are available from the selected transpiler.

1.1 Execution Entry Point

NeuroKernel application entry point is the main method overridden from the NApplication class. The main method of a NeuroKernel application should not be confused with static main method of Java application. NeuroKernel also needs to use static main method in some scenarios.

import com.neurokernel.client.*;
 
public class HelloWorld extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame=getMainFrame();
 
          new NButton(mainFrame,"Hello").addListener(event ->
               NMessageDialog.showInfo(mainFrame,"Hello World"),
               NEventTypes.ACTION);
 
          setVisible("Hello");
      }
}

As seen from above it is pretty similar to Swing and SWT style programming which also would help legacy applications to be easily ported to NeuroKernel.

public class MyApplication extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
      }
}

Here, argc is the number of arguments and argv is the array of arguments similar to C programming language to distinguish it from the standard Java main method.

2. Application Main Frame

When an application execution request reaches Kernel, the task processor module asks window manager to allocate an application main window. This top level window is the root to all other windows, dialogs or tool windows created by the application. Windows and controls are created in a tree hierarchy where the top level main window is the root of all. Destroying the main window means exiting the application entirely. Applications can reach the main window by calling getMainFrame method. This method returns a NFrame class instance which lets configuring its decorations. NFrame class is a direct sub class of NWindow.

3. Accessing System Interfaces

Developers can access system interfaces provided by the task container using getSystem method of NApplication class. Classes that extends NContainer class will also able to access getSystem method directly for rapid development.

import com.neurokernel.client.*;
import com.neurokernel.system.ISystem;
import com.neurokernel.adapter.IActionListener;
 
public class SystemInterfaceExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame=getMainFrame();
          new NButton(mainFrame,"Get Name").addListener((IActionListener) e -> {
              ISystem system=getSystem(); //from NApplication class
              String appName=system.getApplicationName();
              String codeBase = system.getCodeBase(); // from NContainer class
              NMessageDialog.showInfo(mainFrame, appName+" "+codeBase);
          });
 
          mainFrame.setTitle("System Interface");
          mainFrame.setBounds(20,20,200,200);
          mainFrame.setVisible(true);
      }
}

4. Reading Resources

Resources can either be embedded to the executable code using LoadResources annotation or can be loaded from a remote resource or service. The getResource methods can be accessed from NApplication class. It is also possible to set the application icon image using loadIcon method. Task container will try to load the application icon automatically; however, this behaviour can be turned off using the LoadResources annotation.

import com.neurokernel.client.*;
import com.neurokernel.system.annotation.LoadResources;
import com.neurokernel.system.annotation.Source;
 
@LoadResources(
        resources = {
            @Source(value = "resources/myicon.gif", name = "appicon")
            @Source(value = "resources/banner.png", name = "banner"),
        }, 
        loadIcon = false
)
public class ResourcesExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          loadIcon((NImage)getResource("appicon"));
          new NImageView(getMainFrame(), (NImage)getResource("banner"));
          setVisible("Resources");
      }
}

4.1 Images

The class used for image references in NeuroKernel is NImage. It implements IPixMap interface which means it can be also be used when drawing graphics. Adding an image resource to a NeuroKernel application is quite easy. LoadResources annotation is used to embed images to the final executable. If the target is only for the Java byte code then adding images to the resources folder in your application Jar file will be just sufficient. When using the JavaScript or WebAssembly as a target, generators are used to embed the image resource to the final executable. If a specific transpiler is to be used for an entire project, it may be easier to use the supplied way of loading resources by the transpiler without going through the generators. NImage uses various ways to create image references which may be seen in the Javadoc reference.

4.2 Raw data

Loading raw data including text is not different than loading image files. Generators decide how to store the data in the binary if the compilation target is not Java byte code.

5. Localization

System default localization interface object can be accessed using getLocalization method of NApplication and NContainer classes. It is also available from ISystem interface. The method will return an ILocalization interface object. Component layout orientation is determined by the current locale used although it can be set manually.

import com.neurokernel.client.*;
import com.neurokernel.client.adapter.IActionListener;
import com.neurokernel.system.ILocalization;
 
public class LocalizationExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame=getMainFrame();
 
          new NButton(mainFrame,"Get Date").addListener( (IActionListener) e -> {
                  ILocalization locale=getLocalization(); //from NApplication class
                  String date= locale.getDateFormat().getDateString(System.currentTimeMillis(), true);
                  NMessageDialog.showInfo(mainFrame, date);
              });
 
          setVisible("Localization Interface");
      }
}

6. Using UI Controls

NeuroKernel API has a rich set of UI controls to rapidly prototype an application. The controls can be created and added to a layout instantly. The top most level abstract class of all controls is the NControl class. Control class branches into components and cells. Containers extend the NComponent class. All toplevel window controls and containers extend the NContainer class. Cells are simple yet powerful controls that can take only NColumn as its parent. NCell is the abstract super class of all cells. A more detailed description and types of UI controls please see the User Interface Controls.

7. Event Handling

NeuroKernel is an event based system. Input/Output, Service calls, System calls and UI controls all use the event based infrastructure of NeuroKernel. Most system events can be listened using IDataListener interface which is generally the expected interface by the corresponding methods. UI controls expect IEventListener as the listener. It is a generic UI event listener which is specialized using event adapters. Developers are free to make further specialization by implementing the IEventListener. UI controls also offer another listener type called observer. Observers only purpose is to get the current value of the UI control. It is possible to write a bean framework based on the observer features of the UI controls.

import com.neurokernel.client.*;
import com.neurokernel.adapter.IActionListener;
import com.neurokernel.client.constants.*;
 
public class EventListenerExample extends NApplication {
      private NFrame mainFrame;
      private NSlider slider;
 
      @Override
      public void main(int argc, String[] argv) {
          mainFrame = getMainFrame();
          NGridLayout gridLayout=mainFrame.getLayout();
          gridLayout.setSize(3,1);
 
          NTextField textfield = new NTextField(mainFrame);
          slider = new NSlider(mainFrame, Orientation.HORIZONTAL, 0, 255, 0);
          slider.addObserver(textfield);
 
          new NButton(mainFrame,"Get Value").addListener((IActionListener) e -> 
                  NMessageDialog.showInfo(mainFrame, slider.getValue()));
 
          mainFrame.setTitle("Listener/Observer Example");
          mainFrame.show(20,20);
      }
}

Generic listeners as seen above can be used to efficiently handle all events fired on a UI control. This gives great control to the developer with less code. It is also possible to add custom Observers using IObserver interface. All UI controls in NeuroKernel implement IObserver interface by default.

import com.neurokernel.client.*;
import com.neurokernel.system.io.*;
import com.neurokernel.adapter.IActionListener;
 
public class DataListenerExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          new NButton(getMainFrame(), "Get Date").addListener((IActionListener) e -> {
                  NFile myFile = new NFile("/myfile.txt");
                  IFileSystem fileSystem=myFile.getFileSystem(getSystem());
                  fileSystem.exists(myFile, response -> 
                       NMessageDialog.showInfo(getMainFrame(),response.hasError()
                            ? "File does not exists"
                            : "File exists");
          });
 
          setVisible("IDataListener Interface");
      }
}

In the case of data listeners, it is also very straight forward to use them like event listeners. Above example, checks if a file exists in the default file system or not. As you see from the above example, system determines the file system of the file automatically by going through the currently mounted file systems. Developers can mount their own file systems if they wish. The addListener method can use any listeners implementing IEventListener interface as we have mentioned in this section above.

8. Layout Management

Every container in NeuroKernel API has a layout manager which can be accessed with getLayout method of NContainer class. NLayoutManager is the abstract super class of all layout managers accepted by containers. NeuroKernel API comes with ready to use layout managers such as grid layout manager. Developers can write their own layout managers if needed. It is also possible to port Swing or SWT layout managers with ease. First thing to note in the below example, components are automatically placed on the layout to make prototyping faster. It is possible to rearrange the order with various methods available through the base layout manager class.

import com.neurokernel.client.*;
 
public class GridLayoutExample extends NApplication {
     @Override
     public void main(int argc, String[] argv) {
          NFrame mainFrame = getMainFrame();
          NGridLayout grid = mainFrame.getLayout();
          grid.setSize(2,2); // 2x2 grid
 
          new NButton(mainFrame,"1");
          new NButton(mainFrame,"2");
          new NButton(mainFrame,"3");
          new NButton(mainFrame,"4");
 
          mainFrame.setTitle("Layout Manager");
          mainFrame.setBounds(20,20,200,200);
          mainFrame.setVisible(true);
     }
 }

NGridLayout is a very optimized layout and gives more with less coding. Sub grid layout definitions eliminate nested panel creation to sub layouts. When a layout of a container is set to null using setLayout method, NCoordinateLayout is automatically assigned which requires absolute positioning of the components in the container.

9. Windows and Dialogs

Windows and dialogs in NeuroKernel are designed to be similar with modern desktop systems. Dialogs only block their immediate parent window. It is also possible to open nested dialogs. Window manager handles them efficiently in kernel level. Tool windows are also available which always stay on top of their immediate parent window. Dialogs can not have tool windows. Window system is one of the most advanced features of NeuroKernel.

import com.neurokernel.client.*;
import com.neurokernel.adapter.*;
 
public class DialogExample extends NApplication {
 
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame=getMainFrame();
 
          new NButton(mainFrame,"Hello!").addListener((IActionListener) e -> 
                new AskQuestion(getMainFrame()).setVisible(true));
 
          mainFrame.setTitle("Hello There");
          mainFrame.show(20,20);
      }
 
      /**
       * Command Dialog Example
       */
      static class AskQuestion extends NCommandDialog {
 
          public AskQuestion(NWindow parent) {
              super(parent, "Enter Name", false);
              NContainer content=getContentPane();
              content.getLayout().setPadding(5, 0, 5, 0);
              NTextField name = new NTextField(content);
              name.setPromptText("Enter Your Name");
              getOKButton().getForm().addField(name);
 
              addDialogListener(e -> 
                   NMessageDialog.showInfo(getParentWindow() ,name.getText()));
 
              setSize(300, 80);
          }
      }
}

In the above example, a command dialog is created which has cancel and okay buttons by default. All components in NeuroKernel are keyboard accessible. For instance, return key means pressing okay button with this dialog. As seen in this example, with using very little code, a lot can be done.

import com.neurokernel.client.*;
 
public class MyApplication extends NApplication {
    @Override
    public void main(int argc, String[] argv) {
       NFrame mainFrame=getMainFrame();
       mainFrame.setTitle("Tool Frame Example");
       mainFrame.setBounds(20,20,300,300);
       mainFrame.setVisible(true);
 
       NToolFrame toolA = new NToolFrame(mainFrame, "Sticky");
       toolA.setWindowSticky(true);
       toolA.setBounds(30, 30, 250, 250);
       toolA.setVisible(true);
 
       NToolFrame toolB = new NToolFrame(mainFrame, "Always on Top");
       toolB.setAlwaysOnTop(true);
       toolB.setBounds(50, 50, 200, 200);
       toolB.setVisible(true);
    }
}

Child windows can only be painted if their parent is painted and ready. The parent does not need to be visible, but must be painted at least using paintComponent method. System automatically paints the window if setVisible method is called.

9.1 MDI vs Single Window

NeuroKernel API has multi document support if some use cases would need it. NDesktopPane is the container that holds the internal frames. It is possible to manage the internal frames differently by extending the super class of NDesktopPane which is NDesktopView. Internal frames function similar to normal windows.

import com.neurokernel.client.*;
 
public class MDIExample extends NApplication {
    @Override
    public void main(int argc, String[] argv) {
        NFrame mainFrame=getMainFrame();
 
        NDesktopPane pane=new NDesktopPane(mainFrame);
 
        NInternalFrame frameA=new NInternalFrame(pane);
        new NButton(frameA,"Hello");
        frameA.setBounds(5,5,200,200);
 
        NInternalFrame frameB=new NInternalFrame(pane);
        new NButton(frameB,"World");
        frameB.setBounds(15,15,200,200);
 
        mainFrame.setTitle("MDI Example");
        mainFrame.setBounds(20,20,400,400);
        mainFrame.setVisible(true);
    }
}

A popup menu in NeuroKernel are actually a type of popup window which is one of the top level window elements but requires a parent window to hold its place in the hierarchy. A custom menu can be crafted by extending NPopupWindow class although API offers several sub classes which are readily configured.

import com.neurokernel.client.*;
 
public class MenuExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame = getMainFrame();
          mainFrame.setLayout(new NFrameLayout(NFrameLayout.MENU_BAR));
 
          NMenuBar menubar = new NMenuBar(mainFrame);
          NMenu menu = new NMenu(toplevel);
          new NMenuCell(menu, "Open");
          new NMenuCell(menu, "Exit").addSeparator();
          menubar.addMenu("File", menu);
 
          menu.getColumn().addListener(event -> {
            if (event.getEventType() == NEventTypes.ACTION) {
                if (event.getRow() == 0) {
                   NMessageDialog.showInfo(mainFrame,"Open a File?");
                } else {
                   exit();
                }
             }
          });
 
          new NButton(mainFrame,"Hello").addListener(event ->
                  NMessageDialog.showInfo(mainFrame,"Hello There!"),
              NEVentTypes.ACTION);
 
          setVisible("Menu Example");
      }
}

9.3 Column and Cells

Cells are simple controls that accept a column container as its parent. NeuroKernel API uses column and cells extensively in its container structures. Basically, NColumn is the base class for all cell holding containers such as list or tree. Cells are added as single rows to a column. There are extensive set of methods that makes manipulating a column structure easier. Tables also use columns and cells.

import com.neurokernel.client.*;
 
public class ColumnExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          NFrame mainFrame = getMainFrame();
          mainFrame.getLayout().setPadding(5);
 
          NList list = new NList(new NScrollView(mainFrame));
          list.addData("Data Cell 1","Data Cell 2","Data Cell 3","Data Cell 4",
                          "Data Cell 5","Data Cell 6","Data Cell 7","Data Cell 8");
          list.attachClientEvent(ClientEvent.POINTER_DOUBLE_CLICK);
 
          list.addListener(event -> 
                  NMessageDialog.showInfo(mainFrame, list.getSelectedCell().getValue()), 
                  NEventTypes.POINTER_DOUBLE_CLICK);
 
          setVisible("Column Example");
      }
}

9.4 Using Graphics

Graphics is one of the strong features of NeuroKernel API. It is effectively available even when the application run at the server side. It has a optimized protocol that makes caching of drawings at the display server possible for performance. Below example shows how a display list is used to cache the drawing. It is possible to turn a plain NComponent into a drawable canvas similar to Swing although a more specialzed NCanvas class is available for the purpose.

import com.neurokernel.client.*;
import com.neurokernel.client.graphics.*;
 
public class GraphicsExample extends NApplication {
   @Override
   public void main(int argc, String[] argv) {
      NComponent canvas = new NComponent(getMainFrame()) {
          IDisplayList mygraphics;
 
           @Override
           public void paint(IGraphics graphics) {
               IGraphics2D context = (IGraphics2D) graphics;
               if(mygraphics==null) {
                   mygraphics=context.newList();
                   context.setStrokeStyle(new NColor(128, 128, 255));
                   context.setFillStyle(new NColor(0, 0, 255));
                   context.setLineWidth(5);
                   double pi = 2 * Math.PI;
                   context.beginPath();
                   context.arc(50, 50, 100, 0, pi, false);
                   context.closePath();
                   context.fill();
 
                   context.beginPath();
                   context.arc(50, 50, 100, 0, pi, false);
                   context.closePath();
                   context.stroke();
                   context.endList();
               }
               context.translate(100, 100);
               context.callList(mygraphics);
           }
       };
 
       setVisible("Drawing Graphics");
    }
}

NCanvas class is the preferred way of drawing graphics. It is possible to give an initial drawing to the canvas that can be cached as seen in the below example. Canvas class has features to utilize offscreen canvas as well if available. It can also double buffer drawing.

import com.neurokernel.client.*;
import com.neurokernel.client.graphics.*;
 
public class CanvasExample extends NApplication {
    @Override
    public void main(int argc, String[] argv) {
       NCanvas canvas=new NCanvas(getMainFrame());
       IGraphics2D ctx = canvas.getGraphics();
       ctx.setFillStyle(NColor.BLUE);
       ctx.rectangle(10, 10, 55, 50);
       ctx.fill();
       ctx.setFillStyle(NColor.RED);
       ctx.rectangle(30, 30, 55, 50);
       ctx.fill();
 
       setVisible("Canvas Example");
    }
}

10. Native Panel

Native panel is available when application is run at the client side. In server side runtime, it acts as a graphics canvas. Native pane can be used to create visual experiences using third party libraries. It also has the ability to hook NeuroKernel graphics interface to an HTML5 canvas element which makes direct manipulation possible without going through protocol layer. Example below shows the use of a HTML canvas element as a IGraphics interface.

import com.neurokernel.client.*;
import com.neurokernel.client.graphics.*;
 
public class NativePaneExample extends NApplication {
    @Override
    public void main(int argc, String[] argv) {
       NNativePane nativePanel = createNativePane(getMainFrame());
       IGraphics2D ctx = nativePanel.getGraphics();
       ctx.setFillStyle(NColor.BLUE);
       ctx.rectangle(10, 10, 55, 50);
       ctx.fill();
       ctx.setFillStyle(NColor.RED);
       ctx.rectangle(30, 30, 55, 50);
       ctx.fill();
 
       setVisible("Native Pane);
    }
}

11. Service Calls

Services at the server side is available using IServiceHandler system interface. There are various options with this interface including making CORS calls. Result of the service class is listened using IServiceListener interface as seen below.

import com.neurokernel.client.*;
import com.neurokernel.system.net.*;
 
public class ServiceExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          IServiceHandler serviceHandler = getSystem().getServiceHandler();
          serviceHandler.callService("GetData", "John Doe", connection -> {
               NFrame mainFrame = getMainFrame();
               if(connection.hasError()) NMessageDialog.showError(mainFrame, "Data is not found!");
               else NMessageDialog.showInfo(mainFrame, connection.readString());
          });
 
          setVisible("Service Example");
      }
}

12. Terminal IO

NeuroKernel also offers an interface to input and output to a terminal. A terminal application is available with the default package which is used for terminal IO (Input/Output). The terminal access request is made with getTerminal method which returns an ITerminal interface object, and ITerminalListener is used to manage the input and output operations. Application must be executed from the terminal command line in order to have terminal IO functionality. ITerminal interface object is also passed to the listener methods for more convenient way of interaction.

import com.neurokernel.client.*;
import com.neurokernel.system.io.*;
import com.neurokernel.system.*;
 
/**
 * This application must be run from NeuroKernel terminal
 */
public class TerminalExample extends NApplication {
 
    @Override
    public void main(int argc, String[] argv) {
      NLabel label=new NLabel(getMainFrame());
       getSystem().getTerminal(stdio -> {
            String input=stdio.readString();
            if (input == null && stdio.getKeyCode() == 0) {
                stdio.writeString("Hello Console!");
                stdio.setPrompt("password:", true);
            } else if (stdio.getKeyCode() > 0) {
                stdio.setPrompt("Command> ");
            } else {
                stdio.writeString("You have entered: " + input);
                if (input.equals("close")) {
                    stdio.close();
                } else {
                   label.setText(input); //also sets the label text
                   stdio.writeString("Press any key to continue...", 
                             NSystemConstants.TERMINAL_WAIT_KEY_INPUT);
                }
            }
       });
 
       setVisible("Terminal Example",20,20,300,300);
    }
}

13. Exiting Application

Exiting an application can be done by calling the exit method of NApplication class, or disposing the application main window. It is possible to kill an application from terminal command line as well.

import com.neurokernel.client.*;
 
public class ExitExample extends NApplication {
      @Override
      public void main(int argc, String[] argv) {
          new NButton(getMainFrame(),"Exit").addListener(e -> exit());
          setVisible("My Application");
      }
}