0. Introduction -------------------------------------------------------------------- This work is based on the nano-X project. The purpose of this Nanox component written in smalltalk is to make it easy to use the nano-X system. Therefore the Nanox will contain several widgets, and simple operations. Here we will distinguish between nano-X and Nanox. nano-X is the nano-X server, which is written in C. Nanox is the smalltalk component to be run on the pal-vm. 1. Nano-X -------------------------------------------------------------------- To be able to use the Nanox component, you will need the shared library libnano-X and the nano-X executable. You can acquire the nano-X source files at http://www.microwindows.org, e.g. using CVS as decribed on http://www.microwindows.org/CVS.html Choose the config file for your system. These files can be found in the microwin/src/Configs directory, and copy the file to the src directory. E.g.: cp Configs/config.X11 config Remember to enable shared libraries in the config file. Shared libraries is enabled if: SHAREDLIBS = Y It is approximately at line 67. You do not need to build microwin and object frameworks. Then run the command: make from microwin/src/ If it doesnt build you might try a different config file. For us it did not compile on linux, so we had to make a minor change in microwin/src/mwin/winevent.c line 23 where we removed the keyword "static". Then it compiled ( still with warnings though ). If you get a error concerning the keyword fvolatile, then set DEBUG = N in the config file, run make realclean and try again. Now you should be able to run the nano-X server with: ./bin/nano-X The library libnano-X.so should also have been built. If not remember SHAREDLIBS = Y in config file or use build file just below. If there is a problem with the file /usr/lib/libjpeg.a then you might want try changing the LIBJPEG = /usr/lib/libjpeg.a to LIBJPEG = /usr/lib/libjpeg.so instead. This is a approx line 139 in the config file. Now you can test if it works with some of the demos which are included in nano-X. Try for example: ./bin/nano-X & sleep 1; ./bin/world 'sleep 1' is there to give the nano-X server time to initialize. If it cant find the shared library, make sure you have set the LD_LIBRARY_PATH to point to the library. We had some problems with the library, so if it doesnt work you might want to use this Makefile from the src/../ dir: # # This file is made to make the nano-X.so library work # since some problems arises when using the normal makefile # So you can use this file to make libnano-X.so. # Should be run from the src directory. # #compile the 6 files included in the libnano-X.so Current_dir := $(CURDIR)/src Object_dir := $(Current_dir)/obj/nanox Lib_dir := $(Current_dir)/lib Src_dir := $(Current_dir)/nanox all: gcc -c -fpic $(Src_dir)/client.c -o $(Object_dir)/client.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/nxdraw.c -o $(Object_dir)/nxdraw.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/nxutil.c -o $(Object_dir)/nxutil.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/nxtransform.c -o $(Object_dir)/nxtransform.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/nxproto.c -o $(Object_dir)/nxproto.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/error.c -o $(Object_dir)/error.o -I$(Current_dir)/include gcc -c -fpic $(Src_dir)/clientfb.c -o $(Object_dir)/clientfb.o -I$(Current_dir)/include gcc -shared -o $(Lib_dir)/libnano-X.so $(Object_dir)/client.o $(Object_dir)/nxdraw.o $(Object_dir)/nxutil.o $(Object_dir)/nxtransform.o $(Object_dir)/nxproto.o $(Object_dir)/error.o $(Object_dir)/clientfb.o Now it should be working. To use the shared library with the pal-dev-vm you will have to place the library in the same directory as or some other directory included in the LD_LIBRARY_PATH. If you place the nano-X in the same directory as your executable then you can run a program with: ./nano-X & sleep 1; pal-dev-vm -d sample.prc 1a. Building the Nanox smalltalk component. ------------------------------------------------------------------------------ Run: pal-st -c ${Source_dir}/Nanox.pcs -op ${Destination_dir} from palcom/developer/utils/display/nanoX/src. There is also a Makefile to do it. 2. The smalltalk code ------------------------------------------------------------------------------ Below we describe in short how the Nanox code works. The Nanox class is the only class you should call. The class controls all the objects you create and it receives events and propagates them to relevant object which takes action and sends a notification to your client program. Ex. a PushButton ---------------- Client Nanox PushButton create button-> Creates button-> returns button to client. call listen-> Nanox now listens for events. Receives an event forwards it to the button-> Takes action Calls client with notify Receives call. Takes action So it is important that you have a function 'notify: param' in your client. Because all widgets will expect the client to have such a function. The param which is returned from the widgets is itself. Notice that Label does not need a owner since this cannot take events. The nano-X server server will give the event to the window where the event was created, and then to parent window. The event will be accepted at the window if the eventmask for the window is set to make the window receive the event. The Nanox will give the event at the widget and propagate it, and it will also give the event to the parent window. So for one mouseclick, there might be two events. One for the widget and one for the Window. But you can self determine what events the window should receive, so you may set the eventmask for the window to zero, then the window will accept no events. Read more about this in : http://www.microwindows.org/Nano-XTutorial.html Nanox Methods and widgets: ** Creates a Nanox object, and establishes a connection to ** the nano-server. If it cannot establish a connection to ** a server, it will retry until success. Nanox new. Widgets: For all of these methods: ** ownr is the object that receives events from this window. ** window, the window to draw the widget in. ** bgc is the backgroundcolor and fgc is foregroundcolor. ** bgc, fgc is colors created with the 'color' method. ** x,y,w,h is ints. x,y is coordinates for the upper left corner ** unless something else is specified. ----------------------- ** This method creates a new window ** ** window, type Window, if this is the first window to create, put in nil ** else the new window is created in the specified window. ** Nil means the new window is created in the root window. ** b is a int. ** eventmask is an integer telling which events this window should ** react on. ** See tables in the bottom of this tutorial. ** returns a Window object. newWindow: window owner: ownr posx: x posy: y width: w height: h bordersize:b interiorcolor: bgc bordercolor: fgc events: eventmask. ** This method draws a string ** If the width w is too small, the string will just ** be drawn further. ** bool if true creates a box around the string. ** returns nothing. ** note that the coordinates are for the lower left corner. newLabel: window bgcolor: bgc fgcolor: fgc text: txt posx: x posy: y width: w height: h box: bool ** This creates a a box with radiobuttons. ** anArray is an array with strings, for each entry in the array a ** checkboxbutton ** is created with the string to describe it. ** align if 0 then the buttons are aligned vertical if 1 then ** horizontal. The box will try to place the buttons in a sensible ** way in the box, but it cant change the fontsize at present. ** Returns a RadioButton object. newRadioButton: ownr window: window bgcolor: bgc fgcolor: fgc buttons: anArray posx: x posy: y width: w height: h alignment: align ** Creates a pushbutton ** The text is placed approximately in the middle. newPushButton: ownr window: window bgcolor: bgc fgcolor: fgc text: txt posx: x posy: y width: w height: h ** Creates a box of checkboxbuttons ** anArray is an array with strings, for each entry in the array a ** checkboxbutton ** is created with the string to describe it. ** align if 0 then the buttons are aligned vertical if 1 then ** horizontal. The box will try to place the buttons in a sensible ** way in the box, but it cant change the fontsize at present. ** Returns a CheckBox object. newCheckBox: ownr window: window bgcolor: bgc fgcolor: fgc buttons: anArray posx: x posy: y width: w height: h alignment: align ** A textbox, as you can type to. ** amount, the amount of chars on each line ** linecount, the amount of lines. if linecount = 0, then there is ** no limit on the amount of lines ** writebool, if true then it is possible to type chars to it. ** returns a TextBox object. newTextBox: ownr window: window bgcolor: bgc fgcolor: fgc posx: x posy: y width: w height: h charAmount: amount maxLines: linecount write: writebool ** An inputline, like the textbox, but only one line. ** amount, the amount of chars on the line ** Returns a TextBox object with only one line. newInputLine: ownr window: window bgcolor: bgc fgcolor: fgc posx: x posy: y width: w height: h charAmount: amount ** A outputline, like the textbox with writebool=false and only one line. ** amount, the amount of chars on the line. ** Returns a TextBox object with only one line. Which you cannot type to. newOutputLine: ownr window: window bgcolor: bgc fgcolor: fgc posx: x posy: y width: w height: h charAmount: amount ** Draws a image to a specified window. ** string the path where to find the image to be drawn. ** Returns nothing. drawImageFromFile: window posx: x posy: y width: w height: h path: string ** returns a color ( which actually is a very large int. ) ** alpha, b, g,r are ints. alpha transparency, b blue, g green, r ** red. All the numbers are 0-255. ** Notice it is a class method, so it should be called with Nanox color .. color: alpha blue: b green: g red: r ** Removes the object from active listeners and calls the objects ** destroy method. ** Can only destroy objects of type: ** Window, PushButton, RadioButton, CheckBox, TextBox, labels do not need ** to be destroyed, they should just be painted over. destroy: object Events: ** This method should be called as the last method, since this ** starts a while loop, which listens for events, and propagates them ** to the relevant object. ** So before this is called, there will not be received any events. listen Gr methods: ** All of these methods are class methods. ** These methods are as described in the nano-X API. GrOpen GrNewWindow: windowno posx: x posy: y width: w height: h bordersize:b interiorcolor: c1 bordercolor: c2 GrMapWindow: windowno GrSelectEvents: window eventmask: mask GrGetNextEvent: address GrPeekEvent: address GrClearWindow: window flag: bool GrPoint: idint contextid: gcidint posx: x posy: y GrFlush GrClearArea: window posx: x posy: y width: w height: h flag: flag GrFillRect: window context: gc posx: x posy: y width: w height: h GrNewGC GrSetGCForeground: gc color:color GrSetGCBackground: gc color:color GrText: window context: gc posx: x posy: y text: txt size: size flag: flag GrCopyGC: gc GrGetGCInfo: gcontext info: ginfo GrLine: id context: gc x1: x1 y1: y1 x2: x2 y2: y2 GrRect: id context: gc coordx: x coordy: y width: w height: h GrFillRect: id context: gc coordx: x coordy: y width: w height: h GrEllipse: id context: gc coordx: x coordy: y width: w height: h GrFillEllipse: id context: gc coordx: x coordy: y width: w height: h GrDrawImageFromFile: id context: gc posx: x posy: y width: w height: h path: path flags: flags GrDestroyGC: gc GrDestroyWindow: windowid. You can find information of the different values that are used in the file: microwin/src/include/nano-X.h in the nano-X source file directory, or at: ftp://microwindows.org/pub/microwindows/nano-X-docs.pdf If you have need to make your own drawings, you can use the Gr methods, like GrDrawRectangle, and drawing of text is also easy with the use of GrText. Notice that several of these drawing methods use a Graphics Context. a graphics context is actually just an integer pointing a the real context, which is stored in the nano-X server. To make a graphics context you do this: gc := Nanox GrNewGC. Nanox GrSetGCForeground: gc color: blue. Nanox GrSetGCBackground: gc color:white. So if you then use: GrText, with this gc, the textcolor will be blue, and the backgroundcolor will be white. When you are done using the graphics context, you should destroy it with GrDestroyGC. Client Methods for the widgets: With client methods I mean methods which are intented for clients to use. Beacuse some methods in the widgets are used by the Nanox when propagating an event. And some methods are only for private use. Window: destroy destroys the window, and all subwindows. id returns the integer id of the window, is only used to see if window could be created. If the window cannot be created then the id is -1. RadioButton: reset Resets all buttons to not pressed. getStatus returns an array with length = #buttons. If the entry in the array is 0, the button hasn't been pressed, if it is 1, then this button has been pressed. destroy destroys the object. PushButton: destroy destroys the object. CheckBox: reset Resets all buttons to not pressed. getStatus returns an array with length = #buttons. If the entry in the array is 0, the button hasn't been pressed, if it is 1, then this button has been pressed. destroy destroys the object. TextBox: Notice that linenumbers start at 0. destroy destroys the object. getDataAsStrings Returns an array with strings. One array entry for each string. getRawData Returns an array of TextLine objects. One TextLine object for each line. Use this function if you setData: data Sets the data of the TextBox to the data. The data has to be in the format which is returned by the function getRawData. If there are more lines in the data to be inserted than the textbox allows, then this function will simply omit the surplus lines. The charamount for will now be the charamount for the inserted data. setLine: string number: no Sets the line specified by the number argument to the specified string. If the string is longer than the specified charamount for that line, this function will fill up the string, and throw the rest away. getLine: no returns the line with number no, as a string. If no is not a valid linenumber, the empty string is returned. if setLine and getLine are to be used on a "inputline or outputline" remember to use no = 0. If you would like to use a method in Nano-X which is not present in the smalltalk code you can make the call directly to the libnano-X.so with something like this: args := Array new:0. val := system nativeIntegerCall:#'nano-X:GrOpen' with:args. where args is the arguments which the method takes. In this example it is GrOpen, which does not take any arguments. 3. A sample program. ---------------------------------------------------------------------------- Here we describe a simple smalltalk program with some simple operations to illuminate how to use the Nanox component. First to compile the program you will need to "require" the Nanox component. sample.pcs: component sample = { class sample requires "Nanox.prc" as Nanox import Nanox mainclass sample } compiling is done by: > pal-st -c ${Source_dir}/sample.pcs -op ${Destination_dir} The source file: sample.st: sample = ( | nanox window white black pbut | run = ( | val | nanox := Nanox new. white := Nanox color: 255 blue: 255 green: 255 red: 255. black := Nanox color: 255 blue: 0 green: 0 red: 0. "Window 1 is the root window " window := nanox newWindow: 1 owner: self posx: 0 posy: 0 width: 120 height: 80 bordersize: 4 interiorcolor: white bordercolor: black events: 0. ( window id = -1 ) ifTrue: [ 'Cannot create window' println. ] ifFalse: [ ] . pbut := nanox newPushButton: self window: window bgcolor: white fgcolor: black text: 'button' posx: 5 posy: 5 width: 40 height: 20. nanox newLabel: window bgcolor: white fgcolor: black text: 'Hello World!' posx: 5 posy: 60 width: 90 height: 30 box: true. nanox listen. ) notify: obj = ( 'Button pushed' println. ) ) Explanation: Nanox new: creates a Nanox object, which will administer events and opens a connection to a nano-X server, if none available it will retry until success. Then some colors to be used are created. newWindow creates a Window object with self as owner, that will make events from this object be sent to here. Notice that events: 0 means that this window will not act on any events. The statement ( window id = -1 ) ifTrue: [ 'Cannot create window' println. ] ifFalse: [ ] . checks if the window could be created. Next a PushButton and a label are created. And at the end listen is called, to make the Nanox object listen for events. listen should be called at the end, because it starts an infinite loop looking for events. The notify method has to be made, if the object should be able to receive events, since objects will try to make a callback to their owner. If you have several objects which listens for events, you can differentiate them by the the argument obj. Since the objects which received an event sends itself as argument. In this example it is only the PushButton pbut which is able to receive a event, so notify will be called with pbut as argument. 4. Something about the implementation: ------------------------------------------------------------------------------ 1. There is some problems with the way text is organized in the buttons, that might not be optimal. 2. It could be nice to make a wrapper class to the event. 3. Maybe make the objects send the event with them to the notify method, if no event is accessible, then just send nil. So the notify method should be: notify: object event: event = (...) 4. TextBox impl. would be good if you would be able to use a fixed width font. There is still some problems with the implementation of the TextBox. 5. Could be nice to have a TextEditor. A possible structure of it could be something with a new window with a toolbar, a scrollbar and a TextBox inside. Also the posibility to load and save a file, would be nice. 6. Make changes to the structure, so for example a new label should be made directly, like button := button new. And then window add: button to add the button to the window, and then something like button start, to make it listen for events. To implement this you would have to split the newPushButton method in Nanox.st up. So you call the PushButtons own constructor, Then change the constructor, so it will not draw the button, and set the drawing inside a PushButton method: draw. So when the button is added to a window, the window calls the draw method with itself as argument, so the button know where to draw itself. Finally the button start method will have to know the Nanox object to make it listen on it, so it will have to call Nanox method: insertInListeners. 7. Too implement a lot of these stuff, you might want to look at the FLTK, which is a toolkit that has made a lot of widgets that builds on the nano-X. Info about FLTK is found at: http://embedded.centurysoftware.com/docs/nx/ 8. There is an unsolved problem with the fonts, I can see the nano-X has possibility of some fonts in the source kode: microwin/src/fonts but i cannot make it work. If you want to implement some change of fonts, remember that you will have to change the font in the graphics context, see: http://www.microwindows.org/Nano-XTutorial.html under "Graphics Context". 9. Implement GrNewInputWindow, a window made for listening only, see the description in the nano-X tutorial. That way you could place these invisible windows over a picture, so when clicked on the picture, this window would cath the event. This window should be implemented like Window.st. 10. Problem with drawing where you need to make a graphics context. It would be clever to hide the graphics context in the Nanox object. So you set colors in the Nanox class. 11. Make the newLabel to return an object like Window and PushButton etc. Tables: Eventmasks: As taken from the file: microwin/src/include/nano-X.h GR_EVENT_TYPE_NONE 1 GR_EVENT_TYPE_EXPOSURE 2 GR_EVENT_TYPE_BUTTON_DOWN 4 GR_EVENT_TYPE_BUTTON_UP 8 GR_EVENT_TYPE_MOUSE_ENTER 16 GR_EVENT_TYPE_MOUSE_EXIT 32 GR_EVENT_TYPE_MOUSE_MOTION 64 GR_EVENT_TYPE_MOUSE_POSITION 128 GR_EVENT_TYPE_KEY_DOWN 256 GR_EVENT_TYPE_KEY_UP 512 GR_EVENT_TYPE_FOCUS_IN 1024 GR_EVENT_TYPE_FOCUS_OUT 2048 GR_EVENT_TYPE_FDINPUT 4096 GR_EVENT_TYPE_UPDATE 8192 GR_EVENT_TYPE_CHLD_UPDATE 16384 So if you want to listen on for example FOCUS_IN and FOCUS_OUT then the mask is 1024 | 2048 By the way there exist some more types of events than these mentioned here. The rest can be looked up in th nano-X.h file.