Fl_Window *window; window = new Fl_Window(640, 480, "Text Editor");
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.
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.
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);
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");
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.
void copy_cb(void) { input->copy(); }
void cut_cb(void) { input->copy(); input->cut(); }
void delete_cb(void) { input->cut(); }
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(); } }
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.
void new_cb(void) { if (changed) if (!check_save()) return; filename[0] = '\0'; input->value(""); set_changed(0); }
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.
void paste_cb(void) { Fl::paste(*input); }
void quit_cb(void) { if (changed) if (!check_save()) return; window->hide(); }
void replace_cb(void) { replace_dlg->show(); }
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); }
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); }
void replcan_cb() { replace_dlg->hide(); }
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.
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.
void undo_cb(void) { input->undo(); }
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); }
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.
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!"); } }
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); } }
CC -o editor editor.cxx -lfltk -lXext -lX11 -lmAs 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!