chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mupmate / Edit.C
CommitLineData
69695f33
MW
1/* Copyright (c) 2006 by Arkkra Enterprises */
2/* All rights reserved */
3
4// Code for Edit menu on main toolbar
5
6
7#include "globals.H"
8#include "Edit.H"
9#include "utils.H"
10#include <FL/Enumerations.H>
11#include <FL/fl_ask.H>
12#include <stdlib.h>
13#include <string.h>
14#include <stdio.h>
15
16
17//-------------------Find_dialog class--------------------------------
18// This class is for the window that pops up when user does a "Find"
19
20// Constructor creates the window and all the widgets inside it
21
22Find_dialog::Find_dialog(void)
23 : Fl_Double_Window(430, 170, "Find")
24{
25 pattern_p = new Fl_Input(90, 20, 175, 20, "Find what:");
26 // We have ungray the "Find Next" when user enters a pattern,
27 // so set up callback for that
28 pattern_p->callback(Pattern_cb, this);
29 pattern_p->when(FL_WHEN_CHANGED);
30 pattern_p->tooltip("Enter the pattern you want to\n"
31 "search for in your Mup input.");
32
33 replace_with_p = new Fl_Input(90, 60, 175, 20, "Replace with");
34 replace_with_p->tooltip("Enter the replacement text.");
35 replace_with_p->hide();
36
37 casematch_p = new Fl_Check_Button(10, 85, 100, 20, "Match case");
38 casematch_p->tooltip("If checked, upper/lower case must match.\n"
39 "If not checked, case is ignored.");
40
41 direction_p = new Fl_Box(FL_ENGRAVED_BOX, 120, 55, 125, 35, "");
42 up_p = new Fl_Round_Button(130, 60, 40, 20, "Up");
43 up_p->type(FL_RADIO_BUTTON);
44 up_p->value(0);
45 up_p->tooltip("Search upward from current place.");
46 up_p->callback(change_cb, this);
47 down_p = new Fl_Round_Button(175, 60, 60, 20, "Down");
48 down_p->type(FL_RADIO_BUTTON);
49 down_p->value(1);
50 down_p->tooltip("Search downward from current place.");
51 down_p->callback(change_cb, this);
52
53 next_p = new Fl_Return_Button(285, 10, 125, 30, "Find Next");
54 next_p->when(FL_WHEN_RELEASE);
55 next_p->callback(FindNext_cb, this);
56 next_p->deactivate();
57
58 replace_p = new Fl_Button(285, 45, 125, 30, "Replace");
59 replace_p->tooltip("Replace the current instance of the pattern\n"
60 "with the replacement text.");
61 replace_p->callback(Replace_cb, this);
62 replace_p->when(FL_WHEN_RELEASE);
63 replace_p->deactivate();
64 replace_p->hide();
65
66 replace_all_p = new Fl_Button(285, 80, 125, 30, "Replace All");
67 replace_all_p->tooltip("Replace all instances of the pattern\n"
68 "with the replacement text.");
69 replace_all_p->callback(ReplaceAll_cb, this);
70 replace_all_p->when(FL_WHEN_RELEASE);
71 replace_all_p->deactivate();
72 replace_all_p->hide();
73
74 cancel_p = new Fl_Button(285, 115, 125, 30, "Cancel");
75 cancel_p->shortcut(FL_Escape);
76 cancel_p->when(FL_WHEN_RELEASE);
77 cancel_p->callback(Cancel_cb, this);
78
79 // Arrange for destructor to clean up new-ed widgets
80 end();
81
82 // Arrange for window manager closes to do Cancel.
83 callback(Cancel_cb, this);
84 when(FL_WHEN_NEVER);
85}
86
87Find_dialog::~Find_dialog()
88{
89}
90
91
92// The class can be used for either Find or Replace.
93// These next two methods set which of those two personalities
94// the window has.
95
96void
97Find_dialog::as_Find()
98{
99 label("Find");
100 cancel_p->resize(cancel_p->x(), 55, cancel_p->w(), cancel_p->h());
101 casematch_p->resize(casematch_p->x(), 65, casematch_p->w(), casematch_p->h());
102 resize(x(), y(), w(), 120);
103 direction_p->show();
104 up_p->show();
105 down_p->show();
106 replace_with_p->hide();
107 replace_p->hide();
108 replace_all_p->hide();
109 is_replace = false;
110}
111
112void
113Find_dialog::as_Replace()
114{
115 label("Replace");
116 cancel_p->resize(cancel_p->x(), 115, cancel_p->w(), cancel_p->h());
117 casematch_p->resize(casematch_p->x(), 115, casematch_p->w(), casematch_p->h());
118 resize(x(), y(), w(), 155);
119 direction_p->hide();
120 up_p->hide();
121 down_p->hide();
122 replace_with_p->show();
123 replace_p->show();
124 replace_all_p->show();
125 is_replace = true;
126}
127
128
129// Callback for when user clicks "Find Next" button after filling in
130// the dialog.
131
132CALL_BACK(Find_dialog, FindNext)
133{
134 int start = editor_p->insert_position();
135 bool found;
136 int where;
137 if (down_p->value() == 1 || is_replace) {
138 found = editor_p->buffer()->search_forward(
139 start + 1, pattern_p->value(),
140 &where, casematch_p->value());
141 }
142 else {
143 found = editor_p->buffer()->search_backward(
144 start - 1, pattern_p->value(),
145 &where, casematch_p->value());
146 }
147
148 if ( ! found ) {
149 // It seems fltk does not find a pattern if it is exactly
150 // at the beginning for an upward search or exactly
151 // at the end for a downward search. That surely can't
152 // be right, so add special checks for that. If fltk
153 // fixes that some day, we'll never hit this case,
154 // so this should still be compatible.
155 int patlength = pattern_p->size();
156 int bufflength = editor_p->buffer()->length();
157 where = (down_p->value() ? bufflength - patlength : 0);
158 // If pattern is longer than buffer, then no match possible.
159 // If already at pattern,
160 // we already found the end one last time.
161 if (patlength <= bufflength &&
162 where != editor_p->insert_position() - patlength) {
163 if (casematch_p->value()) {
164 if (strncmp(pattern_p->value(),
165 editor_p->buffer()->text()
166 + where, patlength) == 0) {
167 found = 1;
168 }
169 }
170 else {
171 if (strncasecmp(pattern_p->value(),
172 editor_p->buffer()->text()
173 + where, patlength) == 0) {
174 found = 1;
175 }
176 }
177 }
178
179 if ( ! found ) {
180 fl_alert("Cannot find \"%s\"", pattern_p->value());
181 gray_out();
182
183 // The main editor window should now
184 // be made active, rather than the Find window.
185 editor_p->take_focus();
186 }
187 }
188 if (found) {
189 editor_p->buffer()->highlight(where, where + pattern_p->size());
190 editor_p->insert_position(where + pattern_p->size());
191 editor_p->show_insert_position();
192 }
193}
194
195
196// Callback for when user clicks "Replace"
197
198CALL_BACK(Find_dialog, Replace)
199{
200 // See if we are already at the pattern to replace due to
201 // a previous Replace/Find Next
202 int start, end, isRect, rectStart, rectEnd;
203 bool at_pattern = false;
204 if (editor_p->buffer()->highlight_position(&start, &end, &isRect,
205 &rectStart, &rectEnd)) {
206 int place = editor_p->insert_position();
207 if (place == end && (end - start == pattern_p->size())) {
208 if (casematch_p->value()) {
209
210 at_pattern = (strncmp(pattern_p->value(),
211 editor_p->buffer()->text() + start,
212 pattern_p->size()) == 0);
213 }
214 else {
215 at_pattern = (strncasecmp(pattern_p->value(),
216 editor_p->buffer()->text() + start,
217 pattern_p->size()) == 0);
218 }
219 }
220 }
221
222 if (at_pattern) {
223 editor_p->buffer()->unhighlight();
224 editor_p->buffer()->replace(start, end, replace_with_p->value());
225 }
226
227 FindNext();
228}
229
230
231// Callback for when use clicks "Replace All"
232
233CALL_BACK(Find_dialog, ReplaceAll)
234{
235 // We want to be able to "undo" the entire "Replace All"
236 // so we make a copy of the buffer, make all the changes in the
237 // copy and then replace the original with the altered copy.
238 Fl_Text_Buffer altered_buff;
239 altered_buff.copy(editor_p->buffer(), 0, editor_p->buffer()->length(), 0);
240 int start; // where to begin each search
241 bool found = true; // if matching pattern found on current search
242 int where; // offset into buffer where match occurred
243 int new_cursor_pos;
244 bool replaced_something = false; // if any matches found at all
245
246 new_cursor_pos = editor_p->insert_position();
247 for (start = 0; found; start = where + replace_with_p->size()) {
248 if ((found = altered_buff.search_forward(
249 start, pattern_p->value(),
250 &where, casematch_p->value()))
251 != 0) {
252 altered_buff.replace(where, where + pattern_p->size(),
253 replace_with_p->value());
254 new_cursor_pos = where + replace_with_p->size();
255 replaced_something = true;
256 }
257 }
258 // Kludge because pattern at very end is not found.
259 // See more complete explanation in FindNext().
260 where = altered_buff.length() - pattern_p->size();
261 if (where >= 0) {
262 found = false;
263 if (casematch_p->value()) {
264 if (strcmp(pattern_p->value(),
265 altered_buff.text() + where) == 0) {
266 found = true;
267 }
268 }
269 else {
270 if (strcasecmp(pattern_p->value(),
271 altered_buff.text() + where) == 0) {
272 found = true;
273 }
274 }
275 if (found) {
276 altered_buff.replace(where, where + pattern_p->size(),
277 replace_with_p->value());
278 new_cursor_pos = where + replace_with_p->size();
279 replaced_something = true;
280 }
281 }
282
283 if (replaced_something) {
284 editor_p->buffer()->replace(0, editor_p->buffer()->length(),
285 altered_buff.text());
286 editor_p->insert_position(new_cursor_pos);
287 }
288 else {
289 fl_alert("No instances of pattern to replace.");
290 }
291 replace_all_p->deactivate();
292}
293
294
295// Callback for when user clicks "Cancel" in the "Find" window.
296// Hides the window.
297
298CALL_BACK(Find_dialog, Cancel)
299{
300 editor_p->buffer()->unhighlight();
301 hide();
302}
303
304
305// If user did Find Next until no more instances found,
306// the Find Next and Replace buttons will get grayed out,
307// But if they then change search direction, we need to reactivate them
308// since the pattern might be found in that direction.
309// They must also be ungrayed if the contents of the editor buffer change,
310// since the new text might contain the pattern.
311
312CALL_BACK(Find_dialog, change)
313{
314 next_p->activate();
315 replace_p->activate();
316 replace_all_p->activate();
317}
318
319
320// Callback for when user changes pattern, to know whether to gray or ungray
321// Find Next button or not.
322
323CALL_BACK(Find_dialog, Pattern)
324{
325 if (pattern_p->size() > 0) {
326 next_p->activate();
327 replace_p->activate();
328 replace_all_p->activate();
329 }
330 else {
331 gray_out();
332 }
333}
334
335// Horrible kludge. If the "Find" or "Replace" is grayed out because there
336// are no more instances of the pattern in the current direction,
337// and then user moves the cursor somewhere else, it's possible the pattern
338// may then be findable. But modify_callback does not get called for
339// a change in cursor position. So we poll to see if the cursor position
340// changed since the last check, and if so, ungray.
341// Fortunately, we can limit this to only when the button are grayed,
342// which shouldn't be too often.
343
344void
345Find_dialog::gray_out(void)
346{
347 next_p->deactivate();
348 replace_p->deactivate();
349 replace_all_p->deactivate();
350 // Remember where we are and set up to poll for changes
351 last_cursor_position = editor_p->insert_position();
352 Fl::add_timeout(0.5, cursor_change_check, this);
353}
354
355void
356Find_dialog::cursor_change_check(void * data)
357{
358 Find_dialog * obj_p = (Find_dialog *) data;
359 if (obj_p->editor_p->insert_position() != obj_p->last_cursor_position
360 && obj_p->pattern_p->size() > 0) {
361 obj_p->change();
362 }
363 else {
364 Fl::repeat_timeout(0.5, cursor_change_check, data);
365 }
366}
367
368
369// Class needs access to the editor; this lets it know which editor
370// instance to use, and which main window it is associated with.
371
372void
373Find_dialog::set_editor(Fl_Text_Editor * ed)
374{
375 editor_p = ed;
376}
377
378
379// Returns the current "Find" pattern entered by user,
380// or "" if they have not yet entered any such pattern
381
382const char *
383Find_dialog::get_pattern()
384{
385 if (pattern_p->value() == 0) {
386 return("");
387 }
388 else {
389 return(pattern_p->value());
390 }
391}
392
393
394//---------------- GoTo class---------------------------------------------
395
396GoTo_dialog::GoTo_dialog(void)
397 : Fl_Double_Window(225, 95, "Goto line")
398{
399 linenum_p = new Positive_Int_Input(115, 10, 60, 30, "Line Number:");
400 linenum_p->tooltip("Enter the line number of the line\n"
401 "you want to make the current line.");
402
403 ok_p = new Fl_Return_Button(25, 50, 75, 30, "OK");
404 ok_p->when(FL_WHEN_RELEASE);
405 ok_p->callback(OK_cb, this);
406
407 cancel_p = new Fl_Button(125, 50, 75, 30, "Cancel");
408 cancel_p->shortcut(FL_Escape);
409 cancel_p->when(FL_WHEN_RELEASE);
410 cancel_p->callback(Cancel_cb, this);
411
412 // Arrange for destructor to clean up new-ed widgets
413 end();
414
415 // Arrange for window manager closes to do Cancel.
416 callback(Cancel_cb, this);
417 when(FL_WHEN_NEVER);
418}
419
420GoTo_dialog::~GoTo_dialog()
421{
422}
423
424
425// Callback for when user clicks "OK" in GoTo dialog
426
427CALL_BACK(GoTo_dialog, OK)
428{
429 // Find end of valid range
430 int last_line = editor_p->buffer()->count_lines(0,
431 editor_p->buffer()->length());
432 // FLTK line numbers start at 0, so we have to subtract 1 from
433 // number supplied by user.
434 int desired_line = (int) strtol(linenum_p->value(), 0, 0) - 1;
435 if (desired_line < 0 || desired_line > last_line) {
436 fl_alert("Line number out of range");
437 return;
438 }
439
440 // Find appropriate new cursor position and move cursor there
441 int newposition = editor_p->buffer()->skip_lines(0, desired_line);
442 editor_p->insert_position(newposition);
443 editor_p->show_insert_position();
444 hide();
445}
446
447
448// Callback if user cancel Go To
449
450CALL_BACK(GoTo_dialog, Cancel)
451{
452 hide();
453}
454
455
456// Code that calls constructor should then call this to tell
457// us which editor instance to act upon.
458
459void
460GoTo_dialog::set_editor(Fl_Text_Editor * ed)
461{
462 editor_p = ed;
463}
464
465
466// Initialize contents on GoTo field to the current line number
467
468void
469GoTo_dialog::set_current_line()
470{
471 char num_as_string[16];
472 // fltk numbers lines from 0, so add 1 to get what user expects
473 (void) sprintf(num_as_string, "%d",
474 editor_p->buffer()->count_lines(0, editor_p->insert_position()) + 1);
475 linenum_p->value(num_as_string);
476}
477
478
479//------------------Edit class-----------------------------------------------
480// Implements the items in the Edit menu on the main toolbar
481
482
483Edit::Edit()
484{
485 find_p = 0;
486 goto_p = 0;
487 wrote_to_clipboard = false;
488}
489
490
491Edit::~Edit()
492{
493 if (find_p != 0) {
494 delete find_p;
495 find_p = 0;
496 }
497 if (goto_p != 0) {
498 delete goto_p;
499 goto_p = 0;
500 }
501}
502
503
504//---Undo menu item---------------
505
506CALL_BACK(Edit, Undo)
507{
508 buffer_p->undo();
509}
510
511
512//---Cut menu item---------------
513
514CALL_BACK(Edit, Cut)
515{
516 Fl_Text_Editor::kf_cut('x', editor_p);
517 set_can_paste();
518}
519
520
521//---Copy menu item---------------
522
523CALL_BACK(Edit, Copy)
524{
525 Fl_Text_Editor::kf_copy('c', editor_p);
526 set_can_paste();
527}
528
529
530//---Paste menu item---------------
531
532CALL_BACK(Edit, Paste)
533{
534 Fl_Text_Editor::kf_paste('v', editor_p);
535}
536
537
538//---Delete menu item---------------
539
540CALL_BACK(Edit, Delete)
541{
542 buffer_p->remove_selection();
543 buffer_p->unselect();
544}
545
546
547//---Find menu item---------------
548
549CALL_BACK(Edit, Find)
550{
551 if (find_p == 0) {
552 // First time, create widget
553 find_p = new Find_dialog();
554 find_p->set_editor(editor_p);
555 }
556 find_p->as_Find();
557 find_p->show();
558}
559
560
561//---Find Next menu item---------------
562
563CALL_BACK(Edit, FindNext)
564{
565 if (find_p == 0 || strlen(find_p->get_pattern()) == 0) {
566 // No pattern specified yet; turn into Find
567 Find();
568 return;
569 }
570 find_p->FindNext();
571}
572
573
574//---Replace menu item---------------
575
576CALL_BACK(Edit, Replace)
577{
578 if (find_p == 0) {
579 // First time, create widget
580 find_p = new Find_dialog();
581 find_p->set_editor(editor_p);
582 }
583 find_p->as_Replace();
584 find_p->show();
585}
586
587
588//---Go To menu item---------------
589
590CALL_BACK(Edit, GoTo)
591{
592 if (goto_p == 0) {
593 // First time, create widget
594 goto_p = new GoTo_dialog();
595 goto_p->set_editor(editor_p);
596 }
597 goto_p->set_current_line();
598 goto_p->show();
599}
600
601
602//---Select All menu item---------------
603
604CALL_BACK(Edit, SelectAll)
605{
606 buffer_p->select(0, buffer_p->length());
607}
608
609
610//---- Callback for when editor window is modified.
611// Grayed out find/replace should be ungrayed, because the modified
612// text might now match.
613
614void
615Edit::modify_cb(int, int, int, int, const char *, void * data)
616{
617 if ( ((Edit *)data)->find_p != 0) {
618 ((Edit *)data)->find_p->change();
619 }
620}
621
622
623// Class needs access to the editor; this lets it know which editor
624// instance to use.
625
626void
627Edit::set_editor(Fl_Text_Editor * ed)
628{
629 editor_p = ed;
630 buffer_p = editor_p->buffer();
631}
632
633// Ungray Paste button
634
635void
636Edit::set_can_paste(void)
637{
638 wrote_to_clipboard = true;
639 editor_p->buffer()->call_modify_callbacks();
640}