Quite a few of my coding projects have a large text field where the user is expected to do a lot of typing. Actually, it would appear that none of my projects have a large text field. I suppose I should make markdowner and scripsi official jezra.net projects. Anyway, both of the previously mentioned apps have a large text field where the user types and types and types( hey, they are text editing programs ).
- scripsi is a special purpose dual paned text editor written in Vala
- markdowner is a special purpose text editor written in Python
Both applications use the GTK+ toolkit to create the user interface.
The Problem
When a GTK TextView widget is used to create a large text input area, if the user's cursor travels past the viewable area of the TextView, which happens when typing to the bottom of the screen, the cursor is below the visible area and the user will need to stop typing and scroll the TextView until it is visible. If the user were a typing ninja then I suppose they wouldn't care about typing without knowing what text is showing up in the TextView. Well, I am not a typing ninja.
The Solution
There is probably a more elegant solution to the problem, but I couldn't find it so you are stuck with what I figured out, and this is my solution:
When the text changes, get the coordinates of the cursor and scroll to those coordinates. This will ensure that the cursor is always visible to the user. How would you like to see a working example in Vala?
Enter the Vala
using Gtk;
public static void main( string[] args) {
//initialize gtk
Gtk.init(ref args);
//make a window
Window window = new Window();
//make a textbuffer
TextBuffer textBuffer = new TextBuffer(null);
//make a textview with our buffer
TextView textView= new TextView.with_buffer(textBuffer);
//let the textview wrap at word or char
textView.set_wrap_mode(WrapMode.WORD_CHAR);
//we need to know when the text buffer changes
textBuffer.changed.connect( ()=>{
//make a textiterator
TextIter iter = TextIter();
//what is the cursors x coordinate?
int x = textView.virtual_cursor_x;
//what is the cursors y coordinate?
int y = textView.virtual_cursor_y;
//get the text iter at the cursor's coordinates
textView.get_iter_at_location(out iter,x,y);
//scroll to the cursor's text iter
textView.scroll_to_iter(iter,0,false,0,0);
});
//make a scrolledwindow to hold the textviw
ScrolledWindow scroll = new ScrolledWindow (null, null);
//set the scrollwindow's scrolling policy
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
//add the textview to the scollwindow
scroll.add (textView);
//add the scrollwindow to our window
window.add(scroll);
//show all the widgets
window.show_all();
//run the gtk main loop
Gtk.main();
}
Save the above code in a file named "cursor.vala" and compile with
Now I need to port the code to Python and implement the change in markdowner.
Now quit reading, and go fix a problem with a dirty hack.
Plenty of things happen that are almost blog worthy but just don't have the substance for a full post. For the most part, this is what microblogging is for. However, there are somethings that are too big for a posting on identi.ca. So what is a Jez to do?
Why, have a fun filled frolicking Friday feature fest, of course. The idea is simple: aggregate the little things into one post. Booyah!
Jezra.net
Recently, I've make two changes to my blog, only one of which you the viewer will actually experience.
the blog code has been updated to allow me to write using the Markdown format. I'm actually using PHP Markdown written by Michel Fortin. From a visitor's standpoint, this doesn't really do much, but it certainly makes it easier for me to compose a new posting.
images in blog postings will now be utilizing lightbox, a javascript library for presenting "click to view larger image" images in a pleasant way. There is actually a lightbox2 that I should be using, but I'm happy with the way things are working right now.
Code
heybuddy, the Python identi.ca client, has a new stable release: 0.0.7 "Glens Falls". The biggest feature of this release is probably the inclusion of avatars. Go get it!
In December of 2009, there was a thread in the Linux Outlaws forum about coding a "crap alert" in various programming languages. I wrote the alert in Python and then ported the code to Vala. Recently I cleaned up the Vala code, compiled the code to run on my N810 and made an application package for Maemo. Hopefully the compiled app will also run on an N900, but I don't have one to test on. hint hint
Here is the Vala code:
valac --pkg hildon-1 --pkg gstreamer-0.10 --pkg gtk+-2.0 crapalert.vala -o crapalert
*
* or
*
valac -D TESTBUILD --pkg hildon-1 --pkg gstreamer-0.10 --pkg gtk+-2.0 crapalert.vala -o crapalert
*
*/
using Gtk;
using Gdk;
using Gst;
using Hildon;
public class CrapAlert:Hildon.Program
{
bool is_playing;
Element playbin;
bool is_fullscreen;
Hildon.Window window;
construct{
}
public CrapAlert()
{
#if TESTBUILD
string resources = Path.build_filename(Environment.get_current_dir(),"Resources",null);
#else
string resources = Path.build_filename("/","usr","share","crapalert");
#endif
/* set some variables for later use */
is_playing = false;
is_fullscreen=false;
//where is the image for the app?
string image_src = Path.build_filename(resources,"crapalert.png",null);
string icon_src = Path.build_filename(resources,"icon.png",null);
stdout.printf("image_scr: %s\n",image_src);
//where is the audio file?
string audio_src = Path.build_filename(resources,"crapalert.wav",null);
stdout.printf("audio_scr: %s\n",audio_src);
/* create a gstreamer playbin */
playbin = Gst.ElementFactory.make("playbin", "player");
//set the uri of the playbin to the audio file source
playbin.set_property("uri", "file://" + audio_src);
//get the bus of the playbin
var bus = playbin.get_bus();
//tell the bus to watch for signals from the playbin
bus.add_signal_watch();
//connect "message" signals to a function
bus.message.connect( player_message );
/* build the interface */
// create a new window
window = new Hildon.Window();
// connect the delete_event so we can quit the app
window.destroy.connect( (w)=> {
quit();
} );
window.key_press_event.connect( (w,e)=> {
check_event(e);
} );
// we need a button box to hold the image
Gtk.Button button = new Gtk.Button();
//connect the button to the play_audio function
button.clicked.connect( (w)=>{
play_audio();
} );
//create an image from the image_src
Gtk.Image image = new Gtk.Image.from_file(image_src);
//add the image to the button
button.set_image(image);
//add the button to the window
window.add(button);
//set the window icon
window.set_icon_from_file( icon_src )
//show the app
window.show_all();
}
public void run()
{
//start gtk
Gtk.main();
}
private void quit()
{
playbin.set_state(Gst.State.NULL);
Gtk.main_quit();
}
public void play_audio()
{
//are we already playing?
if(!is_playing)
{
is_playing = true;
//make the playbin play
playbin.set_state(Gst.State.PLAYING);
}
}
private void player_message(Message message)
{
// check for the end of stream message from the playbin
var t = message.type;
if (t == Gst.MessageType.EOS )
{
playbin.set_state(Gst.State.NULL);
is_playing=false;
}
}
private void check_event(EventKey event) {
if (event.keyval==65475) {
toggle_fullscreen();
}
}
private void toggle_fullscreen() {
if (is_fullscreen) {
window.unfullscreen();
is_fullscreen=false;
}else{
window.fullscreen();
is_fullscreen=true;
}
}
}
public static void main(string[] args)
{
//init gtk and gstreamer
Gtk.init(ref args);
Gst.init(ref args);
//make a new instance of the crapalert
CrapAlert ca = new CrapAlert();
//run the crap alert
ca.run();
}
Here is the crapalert running on my N810. If anyone is interested in the installable binary, it is available at http://www.jezra.net/downloads/crapalert-0.2.deb.
Technology
About two weeks ago, my telephone answering machine died. To be honest, it had been one cassette in the grave for a very long time. Anyway, a short trip to a local thrift store and $6 later; I had taken the leap for the 80s right into the 90s with a digital answering machine/cordless phone combination. That's right, a push button phone. How tech is that?
A few days after connecting my new phone, I received an invitation to Google Voice and I thought I should try out the service. I will be doing a proper review in a month or two when I have had time to properly use Google Voice.
Speaking of speaking, I've been trying to set up a Mumble server and I could use some help testing the machine with someone far away. If you can install Mumble v1.2.2, have a microphone, and live more than 4000 miles from the San Francisco Bay Area, contact me. Actually, if you can install Mumble 1.2.2 then you should contact me.
Now quit reading, and go do something.
Quite a while ago, I made a basic metronome application and named it hubcap in honor of Linux Outlaws host Dan Lynch. Anyone that has ever heard me play music knows just how much I really need a metronome, but that is beyond the point, and I promise that this won't be some pointless April 1st crap.
Fortunately, the Linux Outlaws has two hosts and I had a yearning to hack code and process data.
Say hello to Shnerkel!Shnerkel is an aggregator and player of the Linux Outlaws ogg feed. Sauce: shnerkel.tar.gz
--Requirments--
gstreamer-0.10
gtk+-2.0
webkit-1.0
libxml-2.0
Vala ( for compiling )
The main impetus for creating this application was to play with webkit in Vala. As I see it, there are a few bonus results of creating this app.
1. Since the app plays the ogg version of the Linux Outlaws audcast, the statics for numbers of downloads of the mp3 and ogg versions will hopefully tip towards ogg.
2. It is a fairly easy way to increase the expose of Linux Outlaws and Ogg, although I'm probably preaching to the choir on both accounts.
3. Almost a full dozen people will have something real to read on April 1st.
The Good
With shnerkel, there is no more waiting to download the audcast. The audio file is streamed over HTTP by the gstreamer library. Shnerkel uses the same audio player class as sap, which really cut down on development time. Thanks Open Source.
The Bad
You will notice the lack of a progress bar. For some reason gstreamer doesn't return the duration of an ogg file being played over HTTP. What the hell is up with that? It is either a problem with Gstreamer or a problem with the Ogg format.
The Ugly
plenty. Before you complain, go look in the mirror. Oh snap! You got burned by that one! In the appwindow.vala file, I pull information from a GTK TreeStore as follows:
It seems to me that passing 'description' and 'file' to the function as references is rather un-vala like and the function should instead use 'out' to pass data to the strings. Oh well...string description=""; string file=""; tree_selection = tv.get_selection(); tree_selection.get_selected(out model, out iter); episodeTreeStore.get(iter,3,&description,2,&file,-1);
It's almost midnight, but I don't think I'll stay up and write the first page of my movie script for the ScriptFrenzy challenge.
Now quit reading, and go find the elusive Dirk Shnerkelberger.
Do you know what else deals with the relationship of elements in a grid? MineSweeper!
MineSweeper happens to be one of my favorite computer games. At this point, it should be noted that there are few implementations of minesweeper for Linux. Of the two versions that I found, one requires Gnome and one Required KDE. Seriously? Why does a simple game need to require Gnome or KDE libs? Oh well, I guess I'll just have to write my own.
Enter the Vala:
/* Copyright 2010 jezra lickter licensed GPL version 3 http://www.gnu.org/licenses/gpl-3.0.html compile with: valac --pkg gtk+-2.0 main.vala -o sweeper */ using Gtk; using Gdk; public class Cell:EventBox { signal void flip_cell(); signal void flag_cell(); public int row; public int col; private Button button; private Label label; const string ONE= "blue"; const string TWO= "dark green"; const string THREE= "red"; const string FOUR= "purple"; const string FIVE= "orange"; const string SIX= "yellow"; private Color red; construct { button = new Button(); //box = new Box(); } public Cell(int r, int c) { Color.parse("red",out red); row=r; col=c; //create the cell button.set_relief(ReliefStyle.HALF); button.event.connect( (obj,event)=> { parse_event(event); } ); //box.pack_start(button, true, true, 0); add(button); label=new Label(null); set_size_request(30,30); //add(box); } public bool parse_event(Gdk.Event event) { if(event.button.type == Gdk.EventType.BUTTON_PRESS) { if (event.button.button==1) // && event.button.button<=3) { flip_cell(); }else if( event.button.button==3 ) { flag_cell(); } return true; }else{ return false; } } public void flag() { button.set_label("*"); } public void end_flag() { if(button.get_label()!="*") { show_label("*"); } } public void show_label(string str) { string color; switch(str) { case "1": color = ONE; break; case "2": color = TWO; break; case "3": color = THREE; break; case "4": color = FOUR; break; case "5": color = FIVE; break; case "6": color = SIX; break; /*case "7": color = SEVEN; break; */ default: color = "black"; break; } label.set_markup("<span foreground=\"%s\">%s</span>".printf(color,str) ); remove(button); add(label); label.show(); } public void show_baddy() { modify_bg(StateType.NORMAL, red ); label.set_markup("<span background=\"red\" font_weight=\"heavy\">*</span>"); remove(button); add(label); //box.pack_start(label,true,true,0); label.show(); } public void disable() { button.set_state(Gtk.StateType.INSENSITIVE); } } public class Application:Gtk.Window { MainLoop loop; bool[,] mineArray; bool[,] flippedArray; private Button startButton; private ComboBox levelCombo; private Table cellTable; private int rows {get;set;} private int cols {get;set;} private int mines; private Cell[,] cells; private VBox vbox; private bool game_playing; private bool game_needs_new_table; //define the small/medium/large values const int small_rows=6; const int small_cols=10; const int small_mines=10; const int medium_rows=10; const int medium_cols=16; const int medium_mines=26; const int large_rows=12; const int large_cols=20; const int large_mines=40; private int required_flips; private int flip_count; construct { type=Gtk.WindowType.TOPLEVEL; set_resizable(false); game_playing=false; game_needs_new_table=false; //we default to small rows = small_rows; cols = small_cols; mines = small_mines; } public Application() { destroy.connect(quit); vbox = new VBox(false,0); HBox hbox = new HBox(false,0); startButton = new Button.with_label("Start"); levelCombo = new ComboBox.text(); levelCombo.append_text("Small"); levelCombo.append_text("Medium"); levelCombo.append_text("Large"); //levelCombo.append_text("Custom"); levelCombo.set_active(0); levelCombo.changed.connect( level_changed ); Label dif = new Label("Size:"); startButton.clicked.connect( start_game ); hbox.pack_start(startButton,false,false,0); hbox.pack_start(dif,false,false,0); hbox.pack_start(levelCombo,false,false,0); hbox.set_spacing(5); vbox.pack_start(hbox,false,false,0); add(vbox); show_all(); //create the first table new_table(); cellTable.set_sensitive(false); } private void level_changed() { string val = levelCombo.get_active_text(); switch(val) { case "Small": rows = small_rows; cols = small_cols; mines = small_mines; break; case "Medium": rows = medium_rows; cols = medium_cols; mines = medium_mines; break; case "Large": rows = large_rows; cols = large_cols; mines = large_mines; break; } game_needs_new_table=true; start_game(); //we should probably resize the window } public void start_game() { if(game_needs_new_table) { new_table(); } game_playing=true; //disable the start button startButton.set_sensitive(false); cellTable.set_sensitive(true); } public void end_game() { //disable unflipped cells for(int r=0; r<rows; r++) { for(int c=0; c<cols; c++) { if(!flippedArray[r,c]) { //if this is a mine, mark it as such if(mineArray[r,c]) { cells[r,c].end_flag(); } cells[r,c].disable(); } } } game_needs_new_table=true; //enable the start button game_playing = false; startButton.set_sensitive(true); } public void new_table() { vbox.remove(cellTable); cellTable = make_table(); vbox.pack_start( cellTable,true,true,0 ); cellTable.show_all(); } private Table make_table() { //reset the required flips required_flips = rows*cols - mines; //reset the flip_count flip_count=0; mineArray = new bool[rows,cols]; flippedArray = new bool[rows,cols]; cells = new Cell[rows,cols]; Table table = new Table(rows,cols,true); for( int r=0;r<rows;r++ ) { for ( int c=0; c<cols; c++ ) { flippedArray[r,c]=false; mineArray[r,c]=false; cells[r,c] = new Cell(r,c); cells[r,c].flip_cell.connect( (cell)=>{ flip_cell(cell); } ); cells[r,c].flag_cell.connect( (cell)=>{ flag_cell(cell); } ); //add the cell to the table lrtb table.attach(cells[r,c],c,c+1,r,r+1,AttachOptions.FILL ,AttachOptions.FILL ,0,0); } } //make the mines int minecount=0; int randomrow; int randomcol; Rand rand = new Rand(); while(minecount<mines) { //pick a random row randomrow= rand.int_range(0,rows); //pick a random col randomcol = rand.int_range(0,cols); if(!mineArray[randomrow,randomcol]) { mineArray[randomrow,randomcol]=true; minecount++; } } return table; } public void flag_cell( Cell c ) { c.flag(); } private void flip_cell(Cell c) { //consider this flipped flippedArray[c.row,c.col]=true; if(mineArray[c.row,c.col]) { c.show_baddy(); end_game(); }else{ detect_neighboring_mines(c.row,c.col); flip_count+=1; if(flip_count==required_flips) { //we did it! end_game(); } } } private void detect_neighboring_mines(int r, int c) { string label=""; int mine_count = 0; int r_min = r-1; int r_max = r+1; int c_min = c-1; int c_max = c+1; if(r==0) { r_min=r; } else if (r == rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == cols-1) { c_max=c; } int tr = r_min; int tc = c_min; while(tr <= r_max) { tc = c_min; while(tc <= c_max) { if ( tr!=r || tc!=c ) { if ( mineArray[tr,tc]) { mine_count+=1; } } tc+=1; } tr+=1; } if(mine_count>0) { label = mine_count.to_string(); }else{ expand_from_empty_cell(r,c); } cells[r,c].show_label( label ); } private void expand_from_empty_cell(int r, int c) { int r_min = r-1; int r_max = r+1; int c_min = c-1; int c_max = c+1; if(r==0) { r_min=r; } else if (r == rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == cols-1) { c_max=c; } int tr = r_min; int tc = c_min; while(tr <= r_max) { tc = c_min; while(tc <= c_max) { if ( tr!=r || tc!=c ) { if ( !flippedArray[tr,tc]) { //check this cell flip_cell(cells[tr,tc]); //detect_neighboring_mines(tr,tc); } } tc+=1; } tr+=1; } } public void run() { loop = new MainLoop(); loop.run(); } public void quit() { loop.quit(); } } public static void main( string[] args) { Gtk.init(ref args); Application app = new Application(); app.run(); }
YUCK! That is some ugly code. Normally, when I write some code, I will first write the steps I want the code to take as comments and I will then write the actual code of the app. Obviously, that was not the case this time. The passion took me.
Requirements
GTK
GDK
Vala
Compiling
valac --pkg gtk+-2.0 main.vala -o sweeper
Screenshots


The game is over when all of the non-mine squares have been selected. Yes, I know that there is no timer, or flag counter and honestly, I don't care; I just want to play.
Now stop reading, and go sweep something.
At least for newbies like me. Thanks for sharing the code!
I really like the language, but currently I'm more focussed on others. Vala still is in an early stage I think, and breaks source compability too often for me :(
In comparison to my previous Game of Life implementation, this version starts faster; much much faster. In the time it takes the Python/Clutter version to make 100 cells, the Vala/SDL version creates 100000 cells. I attribute the speed increase on the following factors:
- Vala code is compiled to machine code, while Python code needs to be executed by the Python runtime.
- SDL is a much lower level library than Clutter.
- Since Clutter utilizes OpenGL for graphics, my graphics card, which is rather old, my not be the best OpenGL renderer
To test the code, save the following as game_of_life.vala
or you can just download game_of_life.vala/*compile with valac --pkg sdl game_of_life.vala -o game_of_life */ using SDL; public class Cell { public Rect on_screen_rect; public Rect self_rect; public Surface surface; public weak Screen screen; public bool alive {get;set;} public bool updated; public int16 size; private uint32 life_color; private uint32 dead_color; public Cell(Screen s, uint32 flags,int16 x, int16 y, int16 size,uint32 lcolor,uint32 bg_color ) { life_color = lcolor; dead_color = bg_color; updated=false; screen = s; on_screen_rect.w = size; on_screen_rect.h = size; on_screen_rect.x = x; on_screen_rect.y = y; self_rect.w = size; self_rect.h = size; self_rect.x=0; self_rect.y=0; this.size = (int16)size; alive=false; //create the surface surface = new Surface.RGB(flags, size, size, 32, 0,0,0, 255); } public void set_life(bool life) { alive=life; if (alive) { surface.fill(null,life_color); }else{ surface.fill(null,dead_color); } } public void draw() { surface.blit(self_rect,screen,on_screen_rect); } } public class Game { private const int SCREEN_WIDTH = 800; private const int SCREEN_HEIGHT = 600; private const int SCREEN_BPP = 32; private const int DELAY = 100; private weak SDL.Screen screen; private bool do_loop; private Cell[,] cells; private int16 num_cols; private int16 num_rows; private uint32 bg_color; private int random_max; public Game(int16 cell_size, int random_max) { this.random_max = random_max; //initialize the video uint32 surface_flags = SurfaceFlag.DOUBLEBUF | SurfaceFlag.HWACCEL | SurfaceFlag.HWSURFACE; screen = Screen.set_video_mode (SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, surface_flags); if (screen == null) { GLib.error ("Could not set video mode."); } //we have a screen, define some colors bg_color=screen.format.map_rgb(0,0,0); uint32 life_color=screen.format.map_rgb(0,255,0); SDL.WindowManager.set_caption ("Game of Life: Vala, SDL", ""); //make all of the cells //'''fill the screen with rectangles''' num_cols = (int16)SCREEN_WIDTH/cell_size; num_rows = (int16)SCREEN_HEIGHT/cell_size; uint32 cells_to_create = num_cols*num_rows; int16 y; int16 x; cells = new Cell[num_rows,num_cols]; stdout.printf( "%0.f cells to create\n",cells_to_create); for ( int16 row=0; row<num_rows; row++) { y = row*cell_size; for ( int16 column=0; column<num_cols; column++) { x=column*cell_size; //set the column index to a rect that is cell_size by cell_size cells[row,column] = new Cell(screen,surface_flags,x,y,cell_size,life_color,bg_color); } cells_to_create-=num_cols; } stdout.printf( "cells created\n" ); do_loop = true; } private void seed_life(bool reseed=false) { Rand rand = new Rand(); stdout.printf( "adding random life\n" ); //loop through the rows for(int r=0; r<num_rows;r++) { //loop through the columns for(int c=0; c<num_cols ; c++) { //generate a number int i = rand.int_range(0,random_max); if (i==0) { //r,c is alive cells[r,c].set_life(true); }else if (reseed) { cells[r,c].set_life(false); } } } } public void run() { //seed life into the cells seed_life(); //do stuff while (do_loop) { this.draw (); this.process_events (); detect_life(); SDL.Timer.delay (DELAY); } } private void quit() { do_loop = false; } private void process_events() { Event event = Event (); while (Event.poll (event) == 1) { switch (event.type) { case EventType.QUIT: quit(); break; case EventType.KEYDOWN: this.on_keyboard_event (event.key); break; } } } private void on_keyboard_event (KeyboardEvent event) { if(event.keysym.sym==KeySymbol.q) { quit(); } if(event.keysym.sym==KeySymbol.r) { seed_life(true); } } private void draw() { screen.fill (null, bg_color); foreach( Cell c in cells ) { c.draw(); } screen.update_rect(0,0,screen.w,screen.h); } private int living_neighbors(int r, int c) { int alive = 0; int r_min = r-1; int r_max = r+1; int c_min = c-1; int c_max = c+1; if(r==0) { r_min=r; } else if (r == num_rows-1) { r_max=r; } if ( c==0 ) { c_min=c; } else if (c == num_cols-1) { c_max=c; } int tr = r_min; int tc = c_min; while(tr <= r_max) { tc = c_min; while(tc <= c_max) { if ( tr!=r || tc!=c ) { if ( cells[tr,tc].alive) { alive+=1; } } tc+=1; } tr+=1; } return alive; } public void detect_life() { int r; int c; int index; Array<int>? dead_array = new Array<int>(false,false,(uint)sizeof(int)); Array<int>? alive_array = new Array<int>(false,false,(uint)sizeof(int)); bool alive; int ln; for(r=0; r<num_rows; r++) { for(c=0; c<num_cols; c++) { alive = cells[r,c].alive; ln = living_neighbors(r,c); if( alive && ( ln<2 || ln>3 )) { //this cell will die; boohoo dead_array.append_val(r); dead_array.append_val(c); } else if(!alive && ln==3) { //this cell is now alive; damn zombies alive_array.append_val(r); alive_array.append_val(c); } } } //kill what needs killing uint final_index = dead_array.length-1; for(index=0 ; index<final_index ; index+=2) { r = dead_array.index(index); c= dead_array.index(index+1); cells[r,c].set_life(false); } //frankenstein what needs life final_index = alive_array.length-1; for(index=0 ; index<final_index ; index+=2) { r = alive_array.index(index); c= alive_array.index(index+1); cells[r,c].set_life(true); } } } public static void main(string[] args) { int16 cell_size = 2; int random_max = 15; SDL.init (InitFlag.VIDEO); Game game = new Game(cell_size,random_max); game.run(); SDL.quit(); }
The code is compiled with
valac --pkg sdl game_of_life.vala -o game_of_life
As in the Python/Clutter example, pressing "q" will quit the game, and pressing "r" will reset the game.
Now stop reading, and get mesmerized by little green blocks.
I'd mucho appreciate it if you put it up in a way where we can get a raw version out easily.
:)
1. when running screen, backspace didn't work. Since I typically have a screen session running when I'm using a terminal, this was a high priority problem. Fortunately, a bit of research in the VTE documentation turned up the class function "set_backspace_binding", which sets what type of input is sent when a user presses the backspace key. I opted for sending an ASCII Backspace character.
2. Being able to copy and paste from a terminal window is a feature that I have become accustomed to, yet muterm was definitely lacking. Easy enough, check key-presses and if the user has pressed "control+shift+c", copy the selected text in the terminal to the clipboard. Do I really need to mention what "control+shift+v" does? OK, it pastes text from the clipboard to the terminal.
Here is the new code with the fixes
using Gtk; using Vte; private class Term { private Terminal term; private Term() { //start with a gtk window Window w; //we also need a vte terminal //create our new window w = new Window(Gtk.WindowType.TOPLEVEL); //create the new terminal term = new Terminal(); //connect exiting on the terminal with quiting the app term.child_exited.connect ( (t)=> { Gtk.main_quit(); } ); //connect keypress events from the window term.key_press_event.connect((w,event) => {this.process_event(event); } ); //fork a command to run when the terminal is started term.fork_command(null,null,null,null, true, true,true); //set the default terminal backspace binding term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE); //add the terminal to the window w.add(term); //maximize the window w.maximize(); //undecorate the window w.set_decorated(false); //try to set the icon try{ w.set_icon_from_file("/usr/share/pixmaps/gnome-term.png"); }catch(Error er) { //we don't really need to print this error stdout.printf(er.message); } //show our window and all children of our window w.show_all(); } private bool process_event(Gdk.EventKey event) { uint keyval; uint state; //we are looking for control+shift+c and control+shift+c keyval = event.keyval; state = event.state; //print some data if(keyval>0 && state>0) if(state==5) { if(keyval==67) { //this is a copy request term.copy_clipboard(); return true; }else if(keyval==86) { //this is a paste request term.paste_clipboard(); return true; } } return false; } private void run() { //start the gtk mainloop Gtk.main(); } private static void main(string[] args) { Term t; Gtk.init(ref args); t = new Term(); t.run(); } }
- Try to find another terminal app that does what I need
- Edit the Sakura soure code to add the maximize and undecorate option
- Code my own app
I elected for "code my own app" and I called it muterm because it is a (m)aximized (u)ndecorated (term)inal emulator. This would also be a good introduction for anyone interested in adding a terminal emulator to a Vala project.
Here is my main.vala code:
using Gtk; using Vte; private class Term { private Term() { //start with a gtk window Window w; //we also need a vte terminal Terminal term; //create our new window w = new Window(Gtk.WindowType.TOPLEVEL); //create the new terminal term = new Terminal(); //connect exiting on the terminal with quiting the app term.child_exited.connect ( (t)=> { Gtk.main_quit(); } ); //fork a command to run when the terminal is started term.fork_command(null,null,null,null, true, true,true); //add the terminal to the window w.add(term); //maximize the window w.maximize(); //undecorate the window w.set_decorated(false); //try to set the icon try{ w.set_icon_from_file("/usr/share/pixmaps/gnome-term.png"); }catch(Error er) { //we don't really need to print this error stdout.printf(er.message); } //show our window and all children of our window w.show_all(); } private void run() { //start the gtk mainloop Gtk.main(); } private static void main(string[] args) { Term t; Gtk.init(ref args); t = new Term(); t.run(); } }
The code is compiled with:
valac --pkg gtk+-2.0 --pkg vte main.vala -o muterm
It took me a while to realize that I didn't need to have values for all of the Vte.Terminal.fork_command() function. Howerver, I should allow passing a directory as an arguement and have muterm start in that directory as the `pwd`. Copying and pasting text in the terminal would also be a great idea. Maybe, we'll see what the future holds.
I'd show a screenshot, but really, how different do terminals really look?
term.set_backspace_binding(TerminalEraseBinding.ASCII_BACKSPACE);
Issue 1: How do I convert an integer into a string in Vala?
Answer: Vala strings are the same as GLib strings - Gstrings, yea, laugh it up. As such one can use the "printf" function of the string object to change an integer to a string. Take the following code as an example:
//nearly all vala apps will use Glib
using GLib;
//create a class that will we will call to print a string
public class Printer {
//create a function that we can use to print a string
public void print_line(string str)
{
stdout.printf("%sn",str);
}
}
//create the main class
private class Main {
//create the entrance to the application
private static void main()
{
//make an instance of the printer
var printer = new Printer();
printer.print_line("hello world");
//loop 5 times
for(int i=0; i<5; i++)
{
// convert the integer value of i to a string
string i_string = "%d".printf(i);
//print the value of i
printer.print_line("i = "+i_string);
}
}
}
OK, did you copy the code and paste into your favorite editor?
Let's look at line 25:
I created a string object named 'i_string' and set it equal to a string object that is print formatting an integer.
Issue #2: How do I list the actual name of the package that I can include when I'm compiling with valac?
Answer: When compile a Vala app that uses libraries like GTK, XML or Webkit, one needs to add '--pkg [packagename]' to the compile arguments used by valac. A list of the available packages can be found by doing the following:
1. on the commandline enter 'which valac' to find the install location of the valac compiler, since my compiler is installed in /usr/local/bin.....
2. on the commandline, I enter 'ls /usr/local/share/vala/vapi' and I will see a list of all of the packages I can use.
Had my valac been installed in /usr/bin, I would search for packages in /usr/share/vala/vapi
Issue #3: OK, this isn't really an issue, but it was a oddity that I had to accept. All Vala apps have an entry point in a function named "main", that is to say, that Vala code starts execution at a function named "main" and this function can create an instance of the class that "main" is a function of. To me this seemed very weird. Oh well, I got over it. Re-writing the above code to show the main function creating an instance of the class that main is a function of results in the following code:
//nearly all vala apps will use Glib
using GLib;
//create the printer class
private class Printer {
//create a function that we can use to print a string
public void print_line(string str)
{
stdout.printf("%sn",str);
}
//create the entrance to the application
private static void main()
{
//make an instance of the printer
var printer = new Printer();
printer.print_line("hello world");
//loop 5 times
for(int i=0; i<5; i++)
{
// convert the integer value of i to a string
string i_string = "%d".printf(i);
//print the value of i
printer.print_line("i = "+i_string);
}
}
}
By the way, I've been happily writing all of my Vala code using valide: a Vala specific IDE.
string number="1";
int value = number.to_int();
For example, instead of
string i_string = "%d".printf(i);
you could have written
string i_string = i.to_string();
Thank you very much for the post though. Vala has caught my interest.
Justin
Yes, I should/could have used 'to_string()' to convert the int to a string.
Thanks for the catch.
I'm learning VALA too and writing a blog my self... Good luck.
Also, I'm still working on that FOSScon blog post; depending on how things go, you might even see it today!