~ubuntu-branches/ubuntu/edgy/fltk1.1/edgy

« back to all changes in this revision

Viewing changes to documentation/editor.html

  • Committer: Bazaar Package Importer
  • Author(s): Aaron M. Ucko
  • Date: 2004-04-14 21:55:19 UTC
  • Revision ID: james.westby@ubuntu.com-20040414215519-avj0ojjkjni1s4ty
Tags: upstream-1.1.4+1.1.5rc1
ImportĀ upstreamĀ versionĀ 1.1.4+1.1.5rc1

Show diffs side-by-side

added added

removed removed

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