Let’s write a very simple text editor in CoreGTK

In this post I’ll quickly show how you can write a very simple text editor using CoreGTK. This example is purposely very basic, has no real error handling and few features but it does show how to use CoreGTK in various ways.

To start with I quickly threw something together in GLADE (which if you aren’t aware is an excellent drag and drop GUI editor for GTK+).

coregtk_glade
Very basic shell in GLADE

Next I created a SimpleTextEditor class that will house the majority of my logic and stubbed out my callbacks and methods.

@interface SimpleTextEditor : NSObject
{
    CGTKTextView *txtView;
    CGTKWidget *window;
}

-(void)show;

// Callbacks
-(void)winMain_Destroy;
-(void)btnNew_Clicked;
-(void)btnOpen_Clicked;
-(void)btnSave_Clicked;

// Helper methods to deal with the text view
-(NSString *)getText;
-(void)setText:(NSString *)text;

@end

Now the fun part begins: filling in the implementation of the methods. First create the init and dealloc methods:

-(id)init
{
    self = [super init];
    
    if(self)
    {
        CGTKBuilder *builder = [[CGTKBuilder alloc] init];
        if(![builder addFromFileWithFilename:@"gui.glade" andErr:NULL])
        {
            NSLog(@"Error loading GUI file");
            return nil;
        }
        
        NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
            [CGTKCallbackData withObject:self andSEL:@selector(winMain_Destroy)], @"winMain_Destroy",
            [CGTKCallbackData withObject:self andSEL:@selector(btnNew_Clicked)], @"btnNew_Clicked",
            [CGTKCallbackData withObject:self andSEL:@selector(btnOpen_Clicked)], @"btnOpen_Clicked",
            [CGTKCallbackData withObject:self andSEL:@selector(btnSave_Clicked)], @"btnSave_Clicked",
            nil];
        
        [CGTKBaseBuilder connectSignalsToObjectsWithBuilder:builder andSignalDictionary:dic];
        
        // Get a reference to the window
        window = [CGTKBaseBuilder getWidgetFromBuilder:builder withName:@"winMain"];
        
        // Get a reference to the text view
        txtView = [[CGTKTextView alloc] initWithGObject:[[CGTKBaseBuilder getWidgetFromBuilder:builder withName:@"txtView"] WIDGET]];
        
        [builder release];
    }
    
    return self;
}
-(void)dealloc
{
    [txtView release];
    [window release];
    [super dealloc];
}

OK let’s break down what we’ve done so far.

CGTKBuilder *builder = [[CGTKBuilder alloc] init];
if(![builder addFromFileWithFilename:@"gui.glade" andErr:NULL])
{
    NSLog(@"Error loading GUI file");
    return nil;
}

First thing is to parse the GLADE file which is what this code does. Next we need to connect the signals we defined for the different events in GLADE to the callback methods we defined in our code:

NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:
    [CGTKCallbackData withObject:self andSEL:@selector(winMain_Destroy)], @"winMain_Destroy",
    [CGTKCallbackData withObject:self andSEL:@selector(btnNew_Clicked)], @"btnNew_Clicked",
    [CGTKCallbackData withObject:self andSEL:@selector(btnOpen_Clicked)], @"btnOpen_Clicked",
    [CGTKCallbackData withObject:self andSEL:@selector(btnSave_Clicked)], @"btnSave_Clicked",
    nil];

[CGTKBaseBuilder connectSignalsToObjectsWithBuilder:builder andSignalDictionary:dic]

Finally extract and store references to the window and the text view for later:

// Get a reference to the window
window = [CGTKBaseBuilder getWidgetFromBuilder:builder withName:@"winMain"];

// Get a reference to the text view
txtView = [[CGTKTextView alloc] initWithGObject:[[CGTKBaseBuilder getWidgetFromBuilder:builder withName:@"txtView"] WIDGET]];

Before we can test anything out we need to fill in a few more basic methods to show the window on command and to exit the GTK+ loop when we close the window:

-(void)show
{
    [window showAll];
}

-(void)winMain_Destroy
{
    [CGTK mainQuit];
}

Now we can actually use our SimpleTextEditor so let’s write a main method to create it:

int main(int argc, char *argv[])
{    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    /* 
     * This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. 
     */
    [CGTK autoInitWithArgc:argc andArgv:argv];
    
    // Create and display editor
    SimpleTextEditor *editor = [[SimpleTextEditor alloc] init];
    
    // Check for error
    if(editor == nil)
    {
        return 1;
    }
    
    // Show the window    
    [editor show];
    
    // Start GTK+ loop
    [CGTK main];

    // Release allocated memory
    [editor release];
    [pool release];

    // Return success
    return 0;
}

Compile and run this and you’ll be presented with a cool Simple Text Editor window!

Our very... err... simple text editor
Our very… err… simple text editor

So far so good. Let’s keep filling in our stubbed in methods starting with our helper methods that will allow us to manipulate the underlying text buffer:

-(NSString *)getText
{
    gchar *gText = NULL;
    GtkTextBuffer *buf = NULL;
    GtkTextIter start, end;
    NSString *nsText = nil;
    
    // Grab reference to text buffer
    buf = [txtView getBuffer];
    
    // Determine the bounds of the buffer
    gtk_text_buffer_get_bounds (buf, &start, &end);
    
    // Get the gchar text from the buffer
    gText = gtk_text_buffer_get_text(buf, &start, &end, FALSE);
    
    // Convert it to an NSString
    nsText = [NSString stringWithUTF8String:gText];
    
    // Free the allocated gchar string
    g_free(gText);

    // Return the text
    return nsText;
}

-(void)setText:(NSString *)text
{
    // Get reference to text buffer
    GtkTextBuffer *buf = [txtView getBuffer];
    
    // Set contents of text buffer
    gtk_text_buffer_set_text(buf, [text UTF8String], -1);
}

At this point we have everything we need to implement our New button click callback method:

-(void)btnNew_Clicked
{
    [self setText:@""];
}

Like I said this is a pretty basic example so in a real world application I would hope you would prompt the user before blowing away all of their text!

All that’s left to do at this point is to implement the Open and Save callback methods. For these I’m going to create a new class, MultiDialog, to show how you can still really dig into the GTK+ C code when you need to.

@interface MultiDialog : NSObject
{
}

+(NSString *)presentOpenDialog;
+(NSString *)presentSaveDialog;

@end

And here is the implementation:

@implementation MultiDialog

+(NSString *)presentOpenDialog
{
    // Variables
    CGTKFileChooserDialog *dialog = nil;
    gchar *gText = NULL;
    gint result;
    NSString *filename = nil;

    // Create the dialog itself
    dialog = [[CGTKFileChooserDialog alloc] initWithTitle:@"Open File" andParent:nil andAction:GTK_FILE_CHOOSER_ACTION_OPEN];
    
    // Add cancel and open buttons
    gtk_dialog_add_button ([dialog DIALOG],
                   "_Cancel",
                   GTK_RESPONSE_CANCEL);
    gtk_dialog_add_button ([dialog DIALOG],
                   "_Open",
                   GTK_RESPONSE_ACCEPT);
    
    // Run the dialog
    result = gtk_dialog_run (GTK_DIALOG ([dialog WIDGET]));

    // If the user clicked Open
    if(result == GTK_RESPONSE_ACCEPT)
    {
        // Extract the filename and convert it to an NSString
        gText = gtk_file_chooser_get_filename ([dialog FILECHOOSERDIALOG]);
        filename = [NSString stringWithUTF8String:gText];
    }

    // Cleanup
    g_free(gText);
    gtk_widget_destroy ([dialog WIDGET]);
    [dialog release];
    
    return filename;
}

+(NSString *)presentSaveDialog
{
    // Variables
    CGTKFileChooserDialog *dialog = nil;
    gchar *gText = NULL;
    gint result;
    NSString *filename = nil;

    // Create the dialog itself
    dialog = [[CGTKFileChooserDialog alloc] initWithTitle:@"Save File" andParent:nil andAction:GTK_FILE_CHOOSER_ACTION_SAVE];
    
    // Add cancel and save buttons
    gtk_dialog_add_button ([dialog DIALOG],
                   "_Cancel",
                   GTK_RESPONSE_CANCEL);
    gtk_dialog_add_button ([dialog DIALOG],
                   "_Save",
                   GTK_RESPONSE_ACCEPT);

    // Set settings
    gtk_file_chooser_set_do_overwrite_confirmation ([dialog FILECHOOSERDIALOG], TRUE);
    gtk_file_chooser_set_current_name([dialog FILECHOOSERDIALOG], "Untitled document");
    
    // Run the dialog
    result = gtk_dialog_run (GTK_DIALOG ([dialog WIDGET]));

    // If the user clicked Save
    if(result == GTK_RESPONSE_ACCEPT)
    {
        // Extract the filename and convert it to an NSString
        gText = gtk_file_chooser_get_filename ([dialog FILECHOOSERDIALOG]);
        filename = [NSString stringWithUTF8String:gText];
    }

    // Cleanup
    g_free(gText);
    gtk_widget_destroy ([dialog WIDGET]);
    [dialog release];
    
    return filename;
}

@end

There is quite a bit of code there but hopefully the comments make it pretty easy to follow. Now that we have our MultiDialog class we can use it in our SimpleTextEditor methods:

-(void)btnOpen_Clicked
{
    NSString *text = [NSString stringWithContentsOfFile:[MultiDialog presentOpenDialog]];    
    [self setText:text];
}

-(void)btnSave_Clicked
{
    NSString *filename = [MultiDialog presentSaveDialog];
    NSString *text = [self getText];
        
    NSError *error;
    BOOL succeed = [text writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:&error];

    if(!succeed)
    {
        NSLog(@"%@:%s Error saving: %@", [self class], _cmd, [error localizedDescription]);
    }
}

And there you have it a very simple text editor that lets you open text file and save them. You can find the full source for this application under the examples directory of the CoreGTK github repository.

 

This post originally appeared on my website here.



Be the first to comment

Leave a Reply

Your email address will not be published.


*