You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

919 lines
25KB

  1. /**
  2. \page editor Designing a Simple Text Editor
  3. This chapter takes you through the design of a simple
  4. FLTK-based text editor.
  5. \section editor_goals Determining the Goals of the Text Editor
  6. Since this will be the first big project you'll be doing with FLTK,
  7. lets define what we want our text editor to do:
  8. -# Provide a menubar/menus for all functions.
  9. -# Edit a single text file, possibly with multiple views.
  10. -# Load from a file.
  11. -# Save to a file.
  12. -# Cut/copy/delete/paste functions.
  13. -# Search and replace functions.
  14. -# Keep track of when the file has been changed.
  15. <!-- NEED 4in -->
  16. \section editor_main_window Designing the Main Window
  17. Now that we've outlined the goals for our editor, we can begin with
  18. the design of our GUI. Obviously the first thing that we need is a
  19. window, which we'll place inside a class called \p EditorWindow:
  20. \code
  21. class EditorWindow : public Fl_Double_Window {
  22. public:
  23. EditorWindow(int w, int h, const char* t);
  24. ~EditorWindow();
  25. Fl_Window *replace_dlg;
  26. Fl_Input *replace_find;
  27. Fl_Input *replace_with;
  28. Fl_Button *replace_all;
  29. Fl_Return_Button *replace_next;
  30. Fl_Button *replace_cancel;
  31. Fl_Text_Editor *editor;
  32. char search[256];
  33. };
  34. \endcode
  35. \section editor_variables Variables
  36. Our text editor will need some global variables to keep track of things:
  37. \code
  38. int changed = 0;
  39. char filename[256] = "";
  40. Fl_Text_Buffer *textbuf;
  41. \endcode
  42. The \p textbuf variable is the text editor buffer for
  43. our window class described previously. We'll cover the other
  44. variables as we build the application.
  45. \section editor_menubars Menubars and Menus
  46. The first goal requires us to use a menubar and menus that
  47. define each function the editor needs to perform. The Fl_Menu_Item
  48. structure is used to define the menus and items in a menubar:
  49. \code
  50. Fl_Menu_Item menuitems[] = {
  51. { "&File", 0, 0, 0, FL_SUBMENU },
  52. { "&New File", 0, (Fl_Callback *)new_cb },
  53. { "&Open File...", FL_COMMAND + 'o', (Fl_Callback *)open_cb },
  54. { "&Insert File...", FL_COMMAND + 'i', (Fl_Callback *)insert_cb, 0, FL_MENU_DIVIDER },
  55. { "&Save File", FL_COMMAND + 's', (Fl_Callback *)save_cb },
  56. { "Save File &As...", FL_COMMAND + FL_SHIFT + 's', (Fl_Callback *)saveas_cb, 0, FL_MENU_DIVIDER },
  57. { "New &View", FL_ALT + 'v', (Fl_Callback *)view_cb, 0 },
  58. { "&Close View", FL_COMMAND + 'w', (Fl_Callback *)close_cb, 0, FL_MENU_DIVIDER },
  59. { "E&xit", FL_COMMAND + 'q', (Fl_Callback *)quit_cb, 0 },
  60. { 0 },
  61. { "&Edit", 0, 0, 0, FL_SUBMENU },
  62. { "&Undo", FL_COMMAND + 'z', (Fl_Callback *)undo_cb, 0, FL_MENU_DIVIDER },
  63. { "Cu&t", FL_COMMAND + 'x', (Fl_Callback *)cut_cb },
  64. { "&Copy", FL_COMMAND + 'c', (Fl_Callback *)copy_cb },
  65. { "&Paste", FL_COMMAND + 'v', (Fl_Callback *)paste_cb },
  66. { "&Delete", 0, (Fl_Callback *)delete_cb },
  67. { 0 },
  68. { "&Search", 0, 0, 0, FL_SUBMENU },
  69. { "&Find...", FL_COMMAND + 'f', (Fl_Callback *)find_cb },
  70. { "F&ind Again", FL_COMMAND + 'g', find2_cb },
  71. { "&Replace...", FL_COMMAND + 'r', replace_cb },
  72. { "Re&place Again", FL_COMMAND + 't', replace2_cb },
  73. { 0 },
  74. { 0 }
  75. };
  76. \endcode
  77. Once we have the menus defined we can create the
  78. Fl_Menu_Bar widget and assign the menus to it with:
  79. \code
  80. Fl_Menu_Bar *m = new Fl_Menu_Bar(0, 0, 640, 30);
  81. m->copy(menuitems);
  82. \endcode
  83. We'll define the callback functions later.
  84. \section editor_editing Editing the Text
  85. To keep things simple our text editor will use the
  86. Fl_Text_Editor widget to edit the text:
  87. \code
  88. w->editor = new Fl_Text_Editor(0, 30, 640, 370);
  89. w->editor->buffer(textbuf);
  90. \endcode
  91. So that we can keep track of changes to the file, we also want to add
  92. a "modify" callback:
  93. \code
  94. textbuf->add_modify_callback(changed_cb, w);
  95. textbuf->call_modify_callbacks();
  96. \endcode
  97. Finally, we want to use a mono-spaced font like \p FL_COURIER:
  98. \code
  99. w->editor->textfont(FL_COURIER);
  100. \endcode
  101. \section editor_replace_dialog The Replace Dialog
  102. We can use the FLTK convenience functions for many of the
  103. editor's dialogs, however the replace dialog needs its own
  104. custom window. To keep things simple we will have a
  105. "find" string, a "replace" string, and
  106. "replace all", "replace next", and
  107. "cancel" buttons. The strings are just
  108. Fl_Input widgets, the "replace all" and
  109. "cancel" buttons are Fl_Button widgets, and
  110. the "replace next " button is a
  111. Fl_Return_Button widget:
  112. \image html editor-replace.png "Figure 4-1: The search and replace dialog"
  113. \image latex editor-replace.png "The search and replace dialog" width=10cm
  114. \code
  115. Fl_Window *replace_dlg = new Fl_Window(300, 105, "Replace");
  116. Fl_Input *replace_find = new Fl_Input(70, 10, 200, 25, "Find:");
  117. Fl_Input *replace_with = new Fl_Input(70, 40, 200, 25, "Replace:");
  118. Fl_Button *replace_all = new Fl_Button(10, 70, 90, 25, "Replace All");
  119. Fl_Button *replace_next = new Fl_Button(105, 70, 120, 25, "Replace Next");
  120. Fl_Button *replace_cancel = new Fl_Button(230, 70, 60, 25, "Cancel");
  121. \endcode
  122. \section editor_callbacks Callbacks
  123. Now that we've defined the GUI components of our editor, we
  124. need to define our callback functions.
  125. \subsection editor_changed_cb changed_cb()
  126. This function will be called whenever the user changes any text in the
  127. \p editor widget:
  128. \code
  129. void changed_cb(int, int nInserted, int nDeleted,int, const char*, void* v) {
  130. if ((nInserted || nDeleted) && !loading) changed = 1;
  131. EditorWindow *w = (EditorWindow *)v;
  132. set_title(w);
  133. if (loading) w->editor->show_insert_position();
  134. }
  135. \endcode
  136. The \p set_title() function is one that we will write to set
  137. the changed status on the current file. We're doing it this way
  138. because we want to show the changed status in the window's
  139. title bar.
  140. \subsection editor_copy_cb copy_cb()
  141. This callback function will call Fl_Text_Editor::kf_copy()
  142. to copy the currently selected text to the clipboard:
  143. \code
  144. void copy_cb(Fl_Widget*, void* v) {
  145. EditorWindow* e = (EditorWindow*)v;
  146. Fl_Text_Editor::kf_copy(0, e->editor);
  147. }
  148. \endcode
  149. \subsection editor_cut_cb cut_cb()
  150. This callback function will call Fl_Text_Editor::kf_cut()
  151. to cut the currently selected text to the clipboard:
  152. \code
  153. void cut_cb(Fl_Widget*, void* v) {
  154. EditorWindow* e = (EditorWindow*)v;
  155. Fl_Text_Editor::kf_cut(0, e->editor);
  156. }
  157. \endcode
  158. \subsection editor_delete_cb delete_cb()
  159. This callback function will call Fl_Text_Buffer::remove_selection()
  160. to delete the currently selected text to the clipboard:
  161. \code
  162. void delete_cb(Fl_Widget*, void* v) {
  163. textbuf->remove_selection();
  164. }
  165. \endcode
  166. \subsection editor_find_cb find_cb()
  167. This callback function asks for a search string using the
  168. fl_input() convenience function and then calls the \p find2_cb()
  169. function to find the string:
  170. \code
  171. void find_cb(Fl_Widget* w, void* v) {
  172. EditorWindow* e = (EditorWindow*)v;
  173. const char *val;
  174. val = fl_input("Search String:", e->search);
  175. if (val != NULL) {
  176. // User entered a string - go find it!
  177. strcpy(e->search, val);
  178. find2_cb(w, v);
  179. }
  180. \endcode
  181. \subsection editor_find2_cb find2_cb()
  182. This function will find the next occurrence of the search
  183. string. If the search string is blank then we want to pop up the
  184. search dialog:
  185. \code
  186. void find2_cb(Fl_Widget* w, void* v) {
  187. EditorWindow* e = (EditorWindow*)v;
  188. if (e->search[0] == '\0') {
  189. // Search string is blank; get a new one...
  190. find_cb(w, v);
  191. return;
  192. }
  193. int pos = e->editor->insert_position();
  194. int found = textbuf->search_forward(pos, e->search, &pos);
  195. if (found) {
  196. // Found a match; select and update the position...
  197. textbuf->select(pos, pos+strlen(e->search));
  198. e->editor->insert_position(pos+strlen(e->search));
  199. e->editor->show_insert_position();
  200. }
  201. else fl_alert("No occurrences of \'%s\' found!", e->search);
  202. }
  203. \endcode
  204. If the search string cannot be found we use the fl_alert()
  205. convenience function to display a message to that effect.
  206. \subsection editor_new_cb new_cb()
  207. This callback function will clear the editor widget and current
  208. filename. It also calls the \p check_save() function to give the
  209. user the opportunity to save the current file first as needed:
  210. \code
  211. void new_cb(Fl_Widget*, void*) {
  212. if (!check_save()) return;
  213. filename[0] = '\0';
  214. textbuf->select(0, textbuf->length());
  215. textbuf->remove_selection();
  216. changed = 0;
  217. textbuf->call_modify_callbacks();
  218. }
  219. \endcode
  220. \subsection editor_open_cb open_cb()
  221. This callback function will ask the user for a filename and then load
  222. the specified file into the input widget and current filename. It also
  223. calls the \p check_save() function to give the user the
  224. opportunity to save the current file first as needed:
  225. \code
  226. void open_cb(Fl_Widget*, void*) {
  227. if (!check_save()) return;
  228. char *newfile = fl_file_chooser("Open File?", "*", filename);
  229. if (newfile != NULL) load_file(newfile, -1);
  230. }
  231. \endcode
  232. We call the \p load_file() function to actually load the file.
  233. \subsection editor_paste_cb paste_cb()
  234. This callback function will call Fl_Text_Editor::kf_paste()
  235. to paste the clipboard at the current position:
  236. \code
  237. void paste_cb(Fl_Widget*, void* v) {
  238. EditorWindow* e = (EditorWindow*)v;
  239. Fl_Text_Editor::kf_paste(0, e->editor);
  240. }
  241. \endcode
  242. \subsection editor_quit_cb quit_cb()
  243. The quit callback will first see if the current file has been
  244. modified, and if so give the user a chance to save it. It then exits
  245. from the program:
  246. \code
  247. void quit_cb(Fl_Widget*, void*) {
  248. if (changed && !check_save())
  249. return;
  250. exit(0);
  251. }
  252. \endcode
  253. \subsection editor_replace_cb replace_cb()
  254. The replace callback just shows the replace dialog:
  255. \code
  256. void replace_cb(Fl_Widget*, void* v) {
  257. EditorWindow* e = (EditorWindow*)v;
  258. e->replace_dlg->show();
  259. }
  260. \endcode
  261. \subsection editor_replace2_cb replace2_cb()
  262. This callback will replace the next occurrence of the replacement
  263. string. If nothing has been entered for the replacement string, then
  264. the replace dialog is displayed instead:
  265. \code
  266. void replace2_cb(Fl_Widget*, void* v) {
  267. EditorWindow* e = (EditorWindow*)v;
  268. const char *find = e->replace_find->value();
  269. const char *replace = e->replace_with->value();
  270. if (find[0] == '\0') {
  271. // Search string is blank; get a new one...
  272. e->replace_dlg->show();
  273. return;
  274. }
  275. e->replace_dlg->hide();
  276. int pos = e->editor->insert_position();
  277. int found = textbuf->search_forward(pos, find, &pos);
  278. if (found) {
  279. // Found a match; update the position and replace text...
  280. textbuf->select(pos, pos+strlen(find));
  281. textbuf->remove_selection();
  282. textbuf->insert(pos, replace);
  283. textbuf->select(pos, pos+strlen(replace));
  284. e->editor->insert_position(pos+strlen(replace));
  285. e->editor->show_insert_position();
  286. }
  287. else fl_alert("No occurrences of \'%s\' found!", find);
  288. }
  289. \endcode
  290. \subsection editor_replall_cb replall_cb()
  291. This callback will replace all occurrences of the search
  292. string in the file:
  293. \code
  294. void replall_cb(Fl_Widget*, void* v) {
  295. EditorWindow* e = (EditorWindow*)v;
  296. const char *find = e->replace_find->value();
  297. const char *replace = e->replace_with->value();
  298. find = e->replace_find->value();
  299. if (find[0] == '\0') {
  300. // Search string is blank; get a new one...
  301. e->replace_dlg->show();
  302. return;
  303. }
  304. e->replace_dlg->hide();
  305. e->editor->insert_position(0);
  306. int times = 0;
  307. // Loop through the whole string
  308. for (int found = 1; found;) {
  309. int pos = e->editor->insert_position();
  310. found = textbuf->search_forward(pos, find, &pos);
  311. if (found) {
  312. // Found a match; update the position and replace text...
  313. textbuf->select(pos, pos+strlen(find));
  314. textbuf->remove_selection();
  315. textbuf->insert(pos, replace);
  316. e->editor->insert_position(pos+strlen(replace));
  317. e->editor->show_insert_position();
  318. times++;
  319. }
  320. }
  321. if (times) fl_message("Replaced %d occurrences.", times);
  322. else fl_alert("No occurrences of \'%s\' found!", find);
  323. }
  324. \endcode
  325. \subsection editor_replcan_cb replcan_cb()
  326. This callback just hides the replace dialog:
  327. \code
  328. void replcan_cb(Fl_Widget*, void* v) {
  329. EditorWindow* e = (EditorWindow*)v;
  330. e->replace_dlg->hide();
  331. }
  332. \endcode
  333. \subsection editor_save_cb save_cb()
  334. This callback saves the current file. If the current filename is
  335. blank it calls the "save as" callback:
  336. \code
  337. void save_cb(void) {
  338. if (filename[0] == '\0') {
  339. // No filename - get one!
  340. saveas_cb();
  341. return;
  342. }
  343. else save_file(filename);
  344. }
  345. \endcode
  346. The \p save_file() function saves the current file to the
  347. specified filename.
  348. \subsection editor_saveas_cb saveas_cb()
  349. This callback asks the user for a filename and saves the current file:
  350. \code
  351. void saveas_cb(void) {
  352. char *newfile;
  353. newfile = fl_file_chooser("Save File As?", "*", filename);
  354. if (newfile != NULL) save_file(newfile);
  355. }
  356. \endcode
  357. The \p save_file() function saves the current file to the
  358. specified filename.
  359. \section editor_other_functions Other Functions
  360. Now that we've defined the callback functions, we need our support
  361. functions to make it all work:
  362. \subsection editor_check_save check_save()
  363. This function checks to see if the current file needs to be saved. If
  364. so, it asks the user if they want to save it:
  365. \code
  366. int check_save(void) {
  367. if (!changed) return 1;
  368. int r = fl_choice("The current file has not been saved.\n"
  369. "Would you like to save it now?",
  370. "Cancel", "Save", "Discard");
  371. if (r == 1) {
  372. save_cb(); // Save the file...
  373. return !changed;
  374. }
  375. return (r == 2) ? 1 : 0;
  376. }
  377. \endcode
  378. \subsection editor_load_file load_file()
  379. This function loads the specified file into the \p textbuf variable:
  380. \code
  381. int loading = 0;
  382. void load_file(char *newfile, int ipos) {
  383. loading = 1;
  384. int insert = (ipos != -1);
  385. changed = insert;
  386. if (!insert) strcpy(filename, "");
  387. int r;
  388. if (!insert) r = textbuf->loadfile(newfile);
  389. else r = textbuf->insertfile(newfile, ipos);
  390. if (r)
  391. fl_alert("Error reading from file \'%s\':\n%s.", newfile, strerror(errno));
  392. else
  393. if (!insert) strcpy(filename, newfile);
  394. loading = 0;
  395. textbuf->call_modify_callbacks();
  396. }
  397. \endcode
  398. When loading the file we use the Fl_Text_Buffer::loadfile()
  399. method to "replace" the text in the buffer, or the
  400. Fl_Text_Buffer::insertfile()
  401. method to insert text in the buffer from the named file.
  402. \subsection editor_save_file save_file()
  403. This function saves the current buffer to the specified file:
  404. \code
  405. void save_file(char *newfile) {
  406. if (textbuf->savefile(newfile))
  407. fl_alert("Error writing to file \'%s\':\n%s.", newfile, strerror(errno));
  408. else
  409. strcpy(filename, newfile);
  410. changed = 0;
  411. textbuf->call_modify_callbacks();
  412. }
  413. \endcode
  414. \subsection editor_set_title set_title()
  415. This function checks the \p changed variable and updates the
  416. window label accordingly:
  417. \code
  418. void set_title(Fl_Window* w) {
  419. if (filename[0] == '\0') strcpy(title, "Untitled");
  420. else {
  421. char *slash;
  422. slash = strrchr(filename, '/');
  423. #ifdef WIN32
  424. if (slash == NULL) slash = strrchr(filename, '\\');
  425. #endif
  426. if (slash != NULL) strcpy(title, slash + 1);
  427. else strcpy(title, filename);
  428. }
  429. if (changed) strcat(title, " (modified)");
  430. w->label(title);
  431. }
  432. \endcode
  433. \section editor_main_function The main() Function
  434. Once we've created all of the support functions, the only thing left
  435. is to tie them all together with the \p main() function.
  436. The \p main() function creates a new text buffer, creates a
  437. new view (window) for the text, shows the window, loads the file on
  438. the command-line (if any), and then enters the FLTK event loop:
  439. \code
  440. int main(int argc, char **argv) {
  441. textbuf = new Fl_Text_Buffer;
  442. Fl_Window* window = new_view();
  443. window->show(1, argv);
  444. if (argc > 1) load_file(argv[1], -1);
  445. return Fl::run();
  446. }
  447. \endcode
  448. \section editor_compiling Compiling the Editor
  449. The complete source for our text editor can be found in the
  450. \p test/editor.cxx source file. Both the Makefile and Visual C++
  451. workspace include the necessary rules to build the editor. You can
  452. also compile it using a standard compiler with:
  453. \code
  454. CC -o editor editor.cxx -lfltk -lXext -lX11 -lm
  455. \endcode
  456. or by using the \p fltk-config script with:
  457. \code
  458. fltk-config --compile editor.cxx
  459. \endcode
  460. As noted in \ref basics_standard_compiler, you may need to
  461. include compiler and linker options to tell them where to find the FLTK
  462. library. Also, the \p CC command may also be called \p gcc
  463. or \p c++ on your system.
  464. Congratulations, you've just built your own text editor!
  465. \section editor_final_product The Final Product
  466. The final editor window should look like the image in Figure 4-2.
  467. \image html editor.png "Figure 4-2: The completed editor window"
  468. \image latex editor.png "The completed editor window" width=12cm
  469. \section editor_advanced_features Advanced Features
  470. Now that we've implemented the basic functionality, it is
  471. time to show off some of the advanced features of the
  472. Fl_Text_Editor widget.
  473. \subsection editor_syntax Syntax Highlighting
  474. The Fl_Text_Editor widget supports highlighting
  475. of text with different fonts, colors, and sizes. The
  476. implementation is based on the excellent
  477. <A HREF="http://www.nedit.org/">NEdit</A>
  478. text editor core, from http://www.nedit.org/, which
  479. uses a parallel "style" buffer which tracks the font, color, and
  480. size of the text that is drawn.
  481. Styles are defined using the
  482. Fl_Text_Display::Style_Table_Entry structure
  483. defined in <tt><FL/Fl_Text_Display.H></tt>:
  484. \code
  485. struct Style_Table_Entry {
  486. Fl_Color color;
  487. Fl_Font font;
  488. int size;
  489. unsigned attr;
  490. };
  491. \endcode
  492. The \p color member sets the color for the text,
  493. the \p font member sets the FLTK font index to use,
  494. and the \p size member sets the pixel size of the
  495. text. The \p attr member is currently not used.
  496. For our text editor we'll define 7 styles for plain code,
  497. comments, keywords, and preprocessor directives:
  498. \code
  499. Fl_Text_Display::Style_Table_Entry styletable[] = { // Style table
  500. { FL_BLACK, FL_COURIER, FL_NORMAL_SIZE }, // A - Plain
  501. { FL_DARK_GREEN, FL_COURIER_ITALIC, FL_NORMAL_SIZE }, // B - Line comments
  502. { FL_DARK_GREEN, FL_COURIER_ITALIC, FL_NORMAL_SIZE }, // C - Block comments
  503. { FL_BLUE, FL_COURIER, FL_NORMAL_SIZE }, // D - Strings
  504. { FL_DARK_RED, FL_COURIER, FL_NORMAL_SIZE }, // E - Directives
  505. { FL_DARK_RED, FL_COURIER_BOLD, FL_NORMAL_SIZE }, // F - Types
  506. { FL_BLUE, FL_COURIER_BOLD, FL_NORMAL_SIZE } // G - Keywords
  507. };
  508. \endcode
  509. You'll notice that the comments show a letter next to each
  510. style - each style in the style buffer is referenced using a
  511. character starting with the letter 'A'.
  512. You call the \p highlight_data() method to associate the
  513. style data and buffer with the text editor widget:
  514. \code
  515. Fl_Text_Buffer *stylebuf;
  516. w->editor->highlight_data(stylebuf, styletable,
  517. sizeof(styletable) / sizeof(styletable[0]),
  518. 'A', style_unfinished_cb, 0);
  519. \endcode
  520. Finally, you need to add a callback to the main text buffer so
  521. that changes to the text buffer are mirrored in the style buffer:
  522. \code
  523. textbuf->add_modify_callback(style_update, w->editor);
  524. \endcode
  525. The \p style_update() function, like the \p change_cb()
  526. function described earlier, is called whenever text is added or removed from
  527. the text buffer. It mirrors the changes in the style buffer and then updates
  528. the style data as necessary:
  529. \code
  530. //
  531. // 'style_update()' - Update the style buffer...
  532. //
  533. void
  534. style_update(int pos, // I - Position of update
  535. int nInserted, // I - Number of inserted chars
  536. int nDeleted, // I - Number of deleted chars
  537. int nRestyled, // I - Number of restyled chars
  538. const char *deletedText, // I - Text that was deleted
  539. void *cbArg) { // I - Callback data
  540. int start, // Start of text
  541. end; // End of text
  542. char last, // Last style on line
  543. *style, // Style data
  544. *text; // Text data
  545. // If this is just a selection change, just unselect the style buffer...
  546. if (nInserted == 0 && nDeleted == 0) {
  547. stylebuf->unselect();
  548. return;
  549. }
  550. // Track changes in the text buffer...
  551. if (nInserted > 0) {
  552. // Insert characters into the style buffer...
  553. style = new char[nInserted + 1];
  554. memset(style, 'A', nInserted);
  555. style[nInserted] = '\0';
  556. stylebuf->replace(pos, pos + nDeleted, style);
  557. delete[] style;
  558. } else {
  559. // Just delete characters in the style buffer...
  560. stylebuf->remove(pos, pos + nDeleted);
  561. }
  562. // Select the area that was just updated to avoid unnecessary
  563. // callbacks...
  564. stylebuf->select(pos, pos + nInserted - nDeleted);
  565. // Re-parse the changed region; we do this by parsing from the
  566. // beginning of the line of the changed region to the end of
  567. // the line of the changed region... Then we check the last
  568. // style character and keep updating if we have a multi-line
  569. // comment character...
  570. start = textbuf->line_start(pos);
  571. end = textbuf->line_end(pos + nInserted - nDeleted);
  572. text = textbuf->text_range(start, end);
  573. style = stylebuf->text_range(start, end);
  574. last = style[end - start - 1];
  575. style_parse(text, style, end - start);
  576. stylebuf->replace(start, end, style);
  577. ((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);
  578. if (last != style[end - start - 1]) {
  579. // The last character on the line changed styles, so reparse the
  580. // remainder of the buffer...
  581. free(text);
  582. free(style);
  583. end = textbuf->length();
  584. text = textbuf->text_range(start, end);
  585. style = stylebuf->text_range(start, end);
  586. style_parse(text, style, end - start);
  587. stylebuf->replace(start, end, style);
  588. ((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);
  589. }
  590. free(text);
  591. free(style);
  592. }
  593. \endcode
  594. The \p style_parse() function scans a copy of the
  595. text in the buffer and generates the necessary style characters
  596. for display. It assumes that parsing begins at the start of a line:
  597. \code
  598. //
  599. // 'style_parse()' - Parse text and produce style data.
  600. //
  601. void
  602. style_parse(const char *text,
  603. char *style,
  604. int length) {
  605. char current;
  606. int col;
  607. int last;
  608. char buf[255],
  609. *bufptr;
  610. const char *temp;
  611. for (current = *style, col = 0, last = 0; length > 0; length --, text ++) {
  612. if (current == 'A') {
  613. // Check for directives, comments, strings, and keywords...
  614. if (col == 0 && *text == '#') {
  615. // Set style to directive
  616. current = 'E';
  617. } else if (strncmp(text, "//", 2) == 0) {
  618. current = 'B';
  619. } else if (strncmp(text, "/*", 2) == 0) {
  620. current = 'C';
  621. } else if (strncmp(text, "\\\"", 2) == 0) {
  622. // Quoted quote...
  623. *style++ = current;
  624. *style++ = current;
  625. text ++;
  626. length --;
  627. col += 2;
  628. continue;
  629. } else if (*text == '\"') {
  630. current = 'D';
  631. } else if (!last && islower(*text)) {
  632. // Might be a keyword...
  633. for (temp = text, bufptr = buf;
  634. islower(*temp) && bufptr < (buf + sizeof(buf) - 1);
  635. *bufptr++ = *temp++);
  636. if (!islower(*temp)) {
  637. *bufptr = '\0';
  638. bufptr = buf;
  639. if (bsearch(&bufptr, code_types,
  640. sizeof(code_types) / sizeof(code_types[0]),
  641. sizeof(code_types[0]), compare_keywords)) {
  642. while (text < temp) {
  643. *style++ = 'F';
  644. text ++;
  645. length --;
  646. col ++;
  647. }
  648. text --;
  649. length ++;
  650. last = 1;
  651. continue;
  652. } else if (bsearch(&bufptr, code_keywords,
  653. sizeof(code_keywords) / sizeof(code_keywords[0]),
  654. sizeof(code_keywords[0]), compare_keywords)) {
  655. while (text < temp) {
  656. *style++ = 'G';
  657. text ++;
  658. length --;
  659. col ++;
  660. }
  661. text --;
  662. length ++;
  663. last = 1;
  664. continue;
  665. }
  666. }
  667. }
  668. } else if (current == 'C' && strncmp(text, "*/", 2) == 0) {
  669. // Close a C comment...
  670. *style++ = current;
  671. *style++ = current;
  672. text ++;
  673. length --;
  674. current = 'A';
  675. col += 2;
  676. continue;
  677. } else if (current == 'D') {
  678. // Continuing in string...
  679. if (strncmp(text, "\\\"", 2) == 0) {
  680. // Quoted end quote...
  681. *style++ = current;
  682. *style++ = current;
  683. text ++;
  684. length --;
  685. col += 2;
  686. continue;
  687. } else if (*text == '\"') {
  688. // End quote...
  689. *style++ = current;
  690. col ++;
  691. current = 'A';
  692. continue;
  693. }
  694. }
  695. // Copy style info...
  696. if (current == 'A' && (*text == '{' || *text == '}')) *style++ = 'G';
  697. else *style++ = current;
  698. col ++;
  699. last = isalnum(*text) || *text == '.';
  700. if (*text == '\n') {
  701. // Reset column and possibly reset the style
  702. col = 0;
  703. if (current == 'B' || current == 'E') current = 'A';
  704. }
  705. }
  706. }
  707. \endcode
  708. \htmlonly
  709. <hr>
  710. <table summary="navigation bar" width="100%" border="0">
  711. <tr>
  712. <td width="45%" align="LEFT">
  713. <a class="el" href="common.html">
  714. [Prev]
  715. Common Widgets and Attributes
  716. </a>
  717. </td>
  718. <td width="10%" align="CENTER">
  719. <a class="el" href="index.html">[Index]</a>
  720. </td>
  721. <td width="45%" align="RIGHT">
  722. <a class="el" href="drawing.html">
  723. Drawing Things in FLTK
  724. [Next]
  725. </a>
  726. </td>
  727. </tr>
  728. </table>
  729. \endhtmlonly
  730. */