Contents
Previous
Next
This >chapter >takes >you >through >the >design >of >a >
simple >FLTK-based >text > editor. >
Since >this >will >be >the >first >big >project >
you'll >be >doing >with >FLTK, > lets >define >what >we >
want >our >text >editor >to >do: >
- Menubar/menus >for >all >functions. >
- Edit >a >single >text >file. >
- Load >from >a >file. >
- Save >to >a >file. >
- Cut/copy/delete/paste >functions. >
- Search >and >replace >functions. >
- Keep >track >of >when >the >file >has >been >
changed. >
Now >that >we've >outlined >the >goals >for >our >
editor, >we >can >begin >with > the >design >of >our >
GUI. > Obviously >the >first >thing >that >we >need >is >
a > window: >
>Fl_Window *window;
>
>window = new Fl_Window(640, 480, "Text Editor");
>
Our >text >editor >will >need >some >global >variables >
to >keep >track >of > things: >
>Fl_Window *window;
>Fl_Menu_Bar *menubar;
>Fl_Multiline_Input *input;
>Fl_Window *replace_dlg;
>Fl_Input *replace_find;
>Fl_Input *replace_with;
>Fl_Button *replace_all;
>Fl_Return_Button *replace_next;
>Fl_Button *replace_cancel;
>
>int changed = 0;
>char filename[1024] = "";
>char search[256] = "";
>
The >window> variable >is >our >top-level >window >
described > previously. >We'll >cover >the >other >
variables >as >we >build >the > application. >
The >first >goal >requires >us >to >use >a >menubar >
and >menus >that >define >each > function >the >editor >
needs >to >perform. > The >
Fl_Menu_Item> structure >is >used >to >define >
the >menus >and > items >in >a >menubar: >
>Fl_Menu_Item menuitems[] = {
> { "&File", 0, 0, 0, FL_SUBMENU },
> { "&New", FL_ALT + 'n', (Fl_Callback *)new_cb },
> { "&Open...", FL_ALT + 'o', (Fl_Callback *)open_cb, 0, FL_MENU_DIVIDER },
> { "&Save", FL_ALT + 's', (Fl_Callback *)save_cb },
> { "Save &As...", FL_ALT + FL_SHIFT + 's', (Fl_Callback *)saveas_cb, 0, FL_MENU_DIVIDER },
> { "&Quit", FL_ALT + 'q', (Fl_Callback *)quit_cb },
> { 0 },
>
> { "&Edit", 0, 0, 0, FL_SUBMENU },
> { "&Undo", FL_ALT + 'z', (Fl_Callback *)undo_cb, 0, FL_MENU_DIVIDER },
> { "Cu&t", FL_ALT + 'x', (Fl_Callback *)cut_cb },
> { "&Copy", FL_ALT + 'c', (Fl_Callback *)copy_cb },
> { "&Paste", FL_ALT + 'v', (Fl_Callback *)paste_cb },
> { "&Delete", 0, (Fl_Callback *)delete_cb },
> { 0 },
>
> { "&Search", 0, 0, 0, FL_SUBMENU },
> { "&Find...", FL_ALT + 'f', (Fl_Callback *)find_cb },
> { "F&ind Again", FL_ALT + 'g', (Fl_Callback *)find2_cb },
> { "&Replace...", FL_ALT + 'r', (Fl_Callback *)replace_cb },
> { "Re&place Again", FL_ALT + 't', (Fl_Callback *)replace2_cb },
> { 0 },
>
> { 0 }
>};
>
Once >we >have >the >menus >defined >we >can >create >
the >Fl_Menu_Bar> widget >and >assign >the >menus >
to >it >with: >
>Fl_Menu_Bar *menubar = new Fl_Menu_Bar(0, 0, 640, 30);
>menubar->menu(menuitems);
>
We'll >define >the >callback >functions >later. >
To >keep >things >simple >our >text >editor >will >use >
the >
Fl_Multiline_Input> widget >to >edit >the >text: >
>Fl_Multiline_Input *input = new Fl_Multiline_Input(0, 30, 640, 450);
>
So >that >we >can >keep >track >of >changes >to >the >
file, >we >also >want >to >add > a >"changed" >
callback: >
>input->callback(changed_cb);
>input->when(FL_WHEN_CHANGED);
>
Finally, >we >want >to >use >a >mono-spaced >font >
like >FL_COURIER>: >
>input->textfont(FL_COURIER);
>
We >can >use >the >FLTK >convenience >functions >for >
many >of >the >editor's > dialogs, >however >the >replace >
dialog >needs >its >own >custom >window. > To > keep >
things >simple >we >will >have >a >"find" >string, >a >
"replace" >string, > and >"replace >all", >"replace >next", >
and >"cancel" >buttons. > The >strings > are >just >
Fl_Input> widgets, >the >"replace >all" >and >"cancel" >
buttons >are >Fl_Button> widgets, >and >the >
"replace >next >" >button > is >a >Fl_Return_Button>
widget: >
>Fl_Window *replace_dlg = new Fl_Window(300, 105, "Replace");
>Fl_Input *replace_find = new Fl_Input(70, 10, 200, 25, "Find:");
>Fl_Input *replace_with = new Fl_Input(70, 40, 200, 25, "Replace:");
>Fl_Button *replace_all = new Fl_Button(10, 70, 90, 25, "Replace All");
>Fl_Button *replace_next = new Fl_Button(105, 70, 120, 25, "Replace Next");
>Fl_Button *replace_cancel = new Fl_Button(230, 70, 60, 25, "Cancel");
>
Now >that >we've >defined >the >GUI >components >of >
our >editor, >we >need >to > define >our >callback >
functions. >
changed_cb()>
This >function >will >be >called >whenever >the >user >
changes >any >text >in >the > input> widget: >
>void changed_cb(void) {
> set_changed(1);
>}
>
The >set_changed()> function >is >one >that >we >
will >write >to >set > the >changed >status >on >the >
current >file. > We're >doing >it >this >way > because >
some >of >the >other >callbacks >will >set >the >
changed >status >to >0, > and >also >because >we >want >
to >show >the >changed >status >in >the >window's >
title >bar. >
copy_cb()>
This >callback >function >will >call >
input->copy()> to >copy >the >currently >
selected >text >to >the > clipboard: >
>void copy_cb(void) {
> input->copy();
>}
>
cut_cb()>
This >callback >function >will >call >
input->copy()> to >copy >the >currently >
selected >text >to >the > clipboard >and >then >
input->cut()> to >delete >it: >
>void cut_cb(void) {
> input->copy();
> input->cut();
>}
>
delete_cb()>
This >callback >function >will >call >
input->cut()> to >delete >the >selected >text: >
>void delete_cb(void) {
> input->cut();
>}
>
find_cb()>
This >callback >function >asks >for >a >search >string >
using >the > fl_input()>
convenience >function >and >then >calls >the >
find2_cb()> function >to >find >the >string: >
>void find_cb(void) {
> const char *val;
>
> val = fl_input("Search String:", search);
> if (val != NULL) {
> // User entered a string - go find it!
> strcpy(search, val);
> find2_cb();
> }
>}
>
find2_cb()>
This >function >will >find >the >next >occurrence >of >
the >search >string. > If > the >search >string >is >
blank >then >we >want >to >pop >up >the >search >
dialog: >
>void find2_cb(void) {
> const char *val, *found;
> int pos;
>
> if (search[0] == '\0') {
> // Search string is blank; get a new one...
> find_cb();
> return;
> }
>
> val = input->value() + input->mark();
> found = strstr(val, search);
>
> if (found != NULL) {
> // Found a match; update the position and mark...
> pos = input->mark() + found - val;
> input->position(pos, pos + strlen(search));
> }
> else fl_alert("No occurrences of \'%s\' found!", search);
>}
>
If >the >search >string >cannot >be >found >we >use >
the > fl_alert()> convenience >
function >to >display >a >message >to >that > effect. >
new_cb()>
This >callback >function >will >clear >the >input >
widget >and >current > filename. >It >also >calls >the >
check_save()> function >to >give >the > user >the >
opportunity >to >save >the >current >file >first >as >
needed: >
>void new_cb(void) {
> if (changed)
> if (!check_save()) return;
>
> filename[0] = '\0';
> input->value("");
> set_changed(0);
>}
>
open_cb()>
This >callback >function >will >ask >the >user >for >a >
filename >and >then >load > the >specified >file >into >
the >input >widget >and >current >filename. >It >also >
calls >the >check_save()> function >to >give >the >
user >the > opportunity >to >save >the >current >file >
first >as >needed: >
>void open_cb(void) {
> char *newfile;
>
> if (changed)
> if (!check_save()) return;
>
> newfile = fl_file_chooser("Open File?", "*", filename);
> if (newfile != NULL) load_file(newfile);
>}
>
We >call >the >load_file()> function >to >
actually >load >the >file. >
paste_cb()>
This >callback >function >will >send >a >FL_PASTE >
message >to >the >input> widget >using >the >
Fl::paste()> method: >
>void paste_cb(void) {
> Fl::paste(*input);
>}
>
quit_cb()>
The >quit >callback >will >first >see >if >the >
current >file >has >been > modified, >and >if >so >give >
the >user >a >chance >to >save >it. >It >then >hides >
the >main >window: >
>void quit_cb(void) {
> if (changed)
> if (!check_save())
> return;
>
> window->hide();
>}
>
replace_cb()>
The >replace >callback >just >shows >the >replace >
dialog: >
>void replace_cb(void) {
> replace_dlg->show();
>}
>
replace2_cb()>
This >callback >will >replace >the >next >occurence >of >
the >replacement > string. >If >nothing >has >been >
entered >for >the >replacement >string, >then > the >
replace >dialog >is >displayed >instead: >
>void replace2_cb() {
> const char *find, *val, *found;
> int pos;
>
> find = replace_find->value();
> if (find[0] == '\0') {
> // Search string is blank; get a new one...
> replace_dlg->show();
> return;
> }
>
> val = input->value() + input->position();
> found = strstr(val, find);
>
> if (found != NULL) {
> // Found a match; update the position and replace text...
> pos = input->position() + found - val;
> input->replace(pos, pos + strlen(find), replace_with->value());
> input->position(pos + strlen(replace_with->value()));
> }
> else fl_alert("No occurrences of \'%s\' found!", find);
>}
>
replall_cb()>
This >callback >will >replace >all >occurences >of >the >
search >string >in >the > file: >
>void replall_cb() {
> const char *find, *val, *found;
> int pos;
> int times;
>
> find = replace_find->value();
> if (find[0] == '\0') {
> // Search string is blank; get a new one...
> replace_dlg->show();
> return;
> }
>
> input->position(0);
> times = 0;
>
> // Loop through the whole string
> do {
> val = input->value() + input->position();
> found = strstr(val, find);
>
> if (found != NULL) {
> // Found a match; update the position and replace text...
> times ++;
> pos = input->position() + found - val;
> input->replace(pos, pos + strlen(find), replace_with->value());
> input->position(pos + strlen(replace_with->value()));
> }
> } while (found != NULL);
>
> if (times > 0) fl_message("Replaced %d occurrences.", times);
> else fl_alert("No occurrences of \'%s\' found!", find);
>}
>
replcan_cb()>
This >callback >just >hides >the >replace >dialog: >
>void replcan_cb() {
> replace_dlg->hide();
>}
>
save_cb()>
This >callback >saves >the >current >file. > If >the >
current >filename >is > blank >it >calls >the >"save >
as" >callback: >
>void save_cb(void) {
> if (filename[0] == '\0') {
> // No filename - get one!
> saveas_cb();
> return;
> }
> else save_file(filename);
>}
>
The >save_file()> function >saves >the >current >
file >to >the > specified >filename. >
saveas_cb()>
This >callback >asks >the >user >for >a >filename >and >
saves >the >current >file: >
>void saveas_cb(void) {
> char *newfile;
>
> newfile = fl_file_chooser("Save File As?", "*", filename);
> if (newfile != NULL) save_file(newfile);
>}
>
The >save_file()> function >saves >the >current >
file >to >the > specified >filename. >
undo_cb()>
The >undo >callback >just >calls >the >
undo()> method: >
>void undo_cb(void) {
> input->undo();
>}
>
Now >that >we've >defined >the >callback >functions, >we >
need >our >support > functions >to >make >it >all >
work: >
check_save()>
This >function >checks >to >see >if >the >current >
file >needs >to >be >saved. > If > so, >it >asks >the >
user >if >they >want >to >save >it: >
>int check_save(void) {
> if (!changed) return 1;
>
> if (fl_ask("The current file has not been saved.\n"
> "Would you like to save it now?")) {
> // Save the file...
> save_cb();
>
> return !changed;
> }
> else return (1);
>}
>
load_file()>
This >function >loads >the >specified >file >into >the >
input> widget: >
>void load_file(char *newfile) {
> FILE *fp;
> char buffer[8192];
> int nbytes;
> int pos;
>
> input->value("");
>
> fp = fopen(newfile, "r");
> if (fp != NULL) {
> // Was able to open file; let's read from it...
> strcpy(filename, newfile);
> pos = 0;
>
> while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
> input->replace(pos, pos, buffer, nbytes);
> pos += nbytes;
> }
>
> fclose(fp);
> input->position(0);
> set_changed(0);
> } else {
> // Couldn't open file - say so...
> fl_alert("Unable to open \'%s\' for reading!");
> }
>}
>
When >loading >the >file >we >use >the >
input->replace()> method >to >"replace" >the >
text >at >the >end > of >the >buffer. > The >pos>
variable >keeps >track >of >the >end >of >the >
buffer. >
save_file()>
This >function >saves >the >current >buffer >to >the >
specified >file: >
>void save_file(char *newfile) {
> FILE *fp;
>
> fp = fopen(newfile, "w");
> if (fp != NULL) {
> // Was able to create file; let's write to it...
> strcpy(filename, newfile);
>
> if (fwrite(input->value(), 1, input->size(), fp) < 1) {
> fl_alert("Unable to write file!");
> fclose(fp);
> return;
> }
>
> fclose(fp);
> set_changed(0);
> } else {
> // Couldn't open file - say so...
> fl_alert("Unable to create \'%s\' for writing!");
> }
>}
>
set_changed()>
This >function >sets >the >changed> variable >and >
updates >the > window >label >accordingly: >
>void set_changed(int c) {
> if (c != changed) {
> char title[1024];
> char *slash;
>
> changed = c;
>
> if (filename[0] == '\0') strcpy(title, "Untitled");
> else {
> slash = strrchr(filename, '/');
> if (slash == NULL) slash = strrchr(filename, '\\');
>
> if (slash != NULL) strcpy(title, slash + 1);
> else strcpy(title, filename);
> }
>
> if (changed) strcat(title, " (modified)");
>
> window->label(title);
> }
>}
>
The >complete >source >for >our >text >editor >can >be >
found >in >the > test/editor.cxx> source >file. >
Both >the >Makefile >and >Visual >C++ > workspace >
include >the >necessary >rules >to >build >the >editor. >
You >can > also >compile >it >using >a >standard >
compiler >with: >
>CC -o editor editor.cxx -lfltk -lXext -lX11 -lm
>
As >noted >in >Chapter >1>, >
you >may >need >to > include >compiler >and >linker >
options >to >tell >them >where >to >find >the >FLTK >
library. >Also, >the >CC> command >may >also >
be >called >gcc> or >c++> on >your >
system. >
Congratulations, >you've >just >built >your >own >text >
editor! >
The >final >editor >window >should >look >like >the >
image >on >the >next >page. >
Contents
Previous
Next
No comments for this page.
|