1 /* Copyright (c) 2006 by Arkkra Enterprises */
2 /* All rights reserved */
4 // Code for Edit menu on main toolbar
10 #include <FL/Enumerations.H>
11 #include <FL/fl_ask.H>
17 //-------------------Find_dialog class--------------------------------
18 // This class is for the window that pops up when user does a "Find"
20 // Constructor creates the window and all the widgets inside it
22 Find_dialog::Find_dialog(void)
23 : Fl_Double_Window(430, 170, "Find")
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.");
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();
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.");
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);
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);
50 down_p->tooltip("Search downward from current place.");
51 down_p->callback(change_cb, this);
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);
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();
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();
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);
79 // Arrange for destructor to clean up new-ed widgets
82 // Arrange for window manager closes to do Cancel.
83 callback(Cancel_cb, this);
87 Find_dialog::~Find_dialog()
92 // The class can be used for either Find or Replace.
93 // These next two methods set which of those two personalities
97 Find_dialog::as_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);
106 replace_with_p->hide();
108 replace_all_p->hide();
113 Find_dialog::as_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);
122 replace_with_p->show();
124 replace_all_p->show();
129 // Callback for when user clicks "Find Next" button after filling in
132 CALL_BACK(Find_dialog, FindNext)
134 int start = editor_p->insert_position();
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());
143 found = editor_p->buffer()->search_backward(
144 start - 1, pattern_p->value(),
145 &where, casematch_p->value());
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) {
171 if (strncasecmp(pattern_p->value(),
172 editor_p->buffer()->text()
173 + where, patlength) == 0) {
180 fl_alert("Cannot find \"%s\"", pattern_p->value());
183 // The main editor window should now
184 // be made active, rather than the Find window.
185 editor_p->take_focus();
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();
196 // Callback for when user clicks "Replace"
198 CALL_BACK(Find_dialog, Replace)
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()) {
210 at_pattern = (strncmp(pattern_p->value(),
211 editor_p->buffer()->text() + start,
212 pattern_p->size()) == 0);
215 at_pattern = (strncasecmp(pattern_p->value(),
216 editor_p->buffer()->text() + start,
217 pattern_p->size()) == 0);
223 editor_p->buffer()->unhighlight();
224 editor_p->buffer()->replace(start, end, replace_with_p->value());
231 // Callback for when use clicks "Replace All"
233 CALL_BACK(Find_dialog, ReplaceAll)
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
244 bool replaced_something = false; // if any matches found at all
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()))
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;
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();
263 if (casematch_p->value()) {
264 if (strcmp(pattern_p->value(),
265 altered_buff.text() + where) == 0) {
270 if (strcasecmp(pattern_p->value(),
271 altered_buff.text() + where) == 0) {
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;
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);
289 fl_alert("No instances of pattern to replace.");
291 replace_all_p->deactivate();
295 // Callback for when user clicks "Cancel" in the "Find" window.
298 CALL_BACK(Find_dialog, Cancel)
300 editor_p->buffer()->unhighlight();
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.
312 CALL_BACK(Find_dialog, change)
315 replace_p->activate();
316 replace_all_p->activate();
320 // Callback for when user changes pattern, to know whether to gray or ungray
321 // Find Next button or not.
323 CALL_BACK(Find_dialog, Pattern)
325 if (pattern_p->size() > 0) {
327 replace_p->activate();
328 replace_all_p->activate();
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.
345 Find_dialog::gray_out(void)
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);
356 Find_dialog::cursor_change_check(void * data)
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) {
364 Fl::repeat_timeout(0.5, cursor_change_check, data);
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.
373 Find_dialog::set_editor(Fl_Text_Editor * ed)
379 // Returns the current "Find" pattern entered by user,
380 // or "" if they have not yet entered any such pattern
383 Find_dialog::get_pattern()
385 if (pattern_p->value() == 0) {
389 return(pattern_p->value());
394 //---------------- GoTo class---------------------------------------------
396 GoTo_dialog::GoTo_dialog(void)
397 : Fl_Double_Window(225, 95, "Goto line")
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.");
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);
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);
412 // Arrange for destructor to clean up new-ed widgets
415 // Arrange for window manager closes to do Cancel.
416 callback(Cancel_cb, this);
420 GoTo_dialog::~GoTo_dialog()
425 // Callback for when user clicks "OK" in GoTo dialog
427 CALL_BACK(GoTo_dialog, OK)
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");
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();
448 // Callback if user cancel Go To
450 CALL_BACK(GoTo_dialog, Cancel)
456 // Code that calls constructor should then call this to tell
457 // us which editor instance to act upon.
460 GoTo_dialog::set_editor(Fl_Text_Editor * ed)
466 // Initialize contents on GoTo field to the current line number
469 GoTo_dialog::set_current_line()
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);
479 //------------------Edit class-----------------------------------------------
480 // Implements the items in the Edit menu on the main toolbar
487 wrote_to_clipboard = false;
504 //---Undo menu item---------------
506 CALL_BACK(Edit, Undo)
512 //---Cut menu item---------------
516 Fl_Text_Editor::kf_cut('x', editor_p);
521 //---Copy menu item---------------
523 CALL_BACK(Edit, Copy)
525 Fl_Text_Editor::kf_copy('c', editor_p);
530 //---Paste menu item---------------
532 CALL_BACK(Edit, Paste)
534 Fl_Text_Editor::kf_paste('v', editor_p);
538 //---Delete menu item---------------
540 CALL_BACK(Edit, Delete)
542 buffer_p->remove_selection();
543 buffer_p->unselect();
547 //---Find menu item---------------
549 CALL_BACK(Edit, Find)
552 // First time, create widget
553 find_p = new Find_dialog();
554 find_p->set_editor(editor_p);
561 //---Find Next menu item---------------
563 CALL_BACK(Edit, FindNext)
565 if (find_p == 0 || strlen(find_p->get_pattern()) == 0) {
566 // No pattern specified yet; turn into Find
574 //---Replace menu item---------------
576 CALL_BACK(Edit, Replace)
579 // First time, create widget
580 find_p = new Find_dialog();
581 find_p->set_editor(editor_p);
583 find_p->as_Replace();
588 //---Go To menu item---------------
590 CALL_BACK(Edit, GoTo)
593 // First time, create widget
594 goto_p = new GoTo_dialog();
595 goto_p->set_editor(editor_p);
597 goto_p->set_current_line();
602 //---Select All menu item---------------
604 CALL_BACK(Edit, SelectAll)
606 buffer_p->select(0, buffer_p->length());
610 //---- Callback for when editor window is modified.
611 // Grayed out find/replace should be ungrayed, because the modified
612 // text might now match.
615 Edit::modify_cb(int, int, int, int, const char *, void * data)
617 if ( ((Edit *)data)->find_p != 0) {
618 ((Edit *)data)->find_p->change();
623 // Class needs access to the editor; this lets it know which editor
627 Edit::set_editor(Fl_Text_Editor * ed)
630 buffer_p = editor_p->buffer();
633 // Ungray Paste button
636 Edit::set_can_paste(void)
638 wrote_to_clipboard = true;
639 editor_p->buffer()->call_modify_callbacks();