--- /dev/null
+/* Copyright (c) 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+// Code for Edit menu on main toolbar
+
+
+#include "globals.H"
+#include "Edit.H"
+#include "utils.H"
+#include <FL/Enumerations.H>
+#include <FL/fl_ask.H>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+
+//-------------------Find_dialog class--------------------------------
+// This class is for the window that pops up when user does a "Find"
+
+// Constructor creates the window and all the widgets inside it
+
+Find_dialog::Find_dialog(void)
+ : Fl_Double_Window(430, 170, "Find")
+{
+ pattern_p = new Fl_Input(90, 20, 175, 20, "Find what:");
+ // We have ungray the "Find Next" when user enters a pattern,
+ // so set up callback for that
+ pattern_p->callback(Pattern_cb, this);
+ pattern_p->when(FL_WHEN_CHANGED);
+ pattern_p->tooltip("Enter the pattern you want to\n"
+ "search for in your Mup input.");
+
+ replace_with_p = new Fl_Input(90, 60, 175, 20, "Replace with");
+ replace_with_p->tooltip("Enter the replacement text.");
+ replace_with_p->hide();
+
+ casematch_p = new Fl_Check_Button(10, 85, 100, 20, "Match case");
+ casematch_p->tooltip("If checked, upper/lower case must match.\n"
+ "If not checked, case is ignored.");
+
+ direction_p = new Fl_Box(FL_ENGRAVED_BOX, 120, 55, 125, 35, "");
+ up_p = new Fl_Round_Button(130, 60, 40, 20, "Up");
+ up_p->type(FL_RADIO_BUTTON);
+ up_p->value(0);
+ up_p->tooltip("Search upward from current place.");
+ up_p->callback(change_cb, this);
+ down_p = new Fl_Round_Button(175, 60, 60, 20, "Down");
+ down_p->type(FL_RADIO_BUTTON);
+ down_p->value(1);
+ down_p->tooltip("Search downward from current place.");
+ down_p->callback(change_cb, this);
+
+ next_p = new Fl_Return_Button(285, 10, 125, 30, "Find Next");
+ next_p->when(FL_WHEN_RELEASE);
+ next_p->callback(FindNext_cb, this);
+ next_p->deactivate();
+
+ replace_p = new Fl_Button(285, 45, 125, 30, "Replace");
+ replace_p->tooltip("Replace the current instance of the pattern\n"
+ "with the replacement text.");
+ replace_p->callback(Replace_cb, this);
+ replace_p->when(FL_WHEN_RELEASE);
+ replace_p->deactivate();
+ replace_p->hide();
+
+ replace_all_p = new Fl_Button(285, 80, 125, 30, "Replace All");
+ replace_all_p->tooltip("Replace all instances of the pattern\n"
+ "with the replacement text.");
+ replace_all_p->callback(ReplaceAll_cb, this);
+ replace_all_p->when(FL_WHEN_RELEASE);
+ replace_all_p->deactivate();
+ replace_all_p->hide();
+
+ cancel_p = new Fl_Button(285, 115, 125, 30, "Cancel");
+ cancel_p->shortcut(FL_Escape);
+ cancel_p->when(FL_WHEN_RELEASE);
+ cancel_p->callback(Cancel_cb, this);
+
+ // Arrange for destructor to clean up new-ed widgets
+ end();
+
+ // Arrange for window manager closes to do Cancel.
+ callback(Cancel_cb, this);
+ when(FL_WHEN_NEVER);
+}
+
+Find_dialog::~Find_dialog()
+{
+}
+
+
+// The class can be used for either Find or Replace.
+// These next two methods set which of those two personalities
+// the window has.
+
+void
+Find_dialog::as_Find()
+{
+ label("Find");
+ cancel_p->resize(cancel_p->x(), 55, cancel_p->w(), cancel_p->h());
+ casematch_p->resize(casematch_p->x(), 65, casematch_p->w(), casematch_p->h());
+ resize(x(), y(), w(), 120);
+ direction_p->show();
+ up_p->show();
+ down_p->show();
+ replace_with_p->hide();
+ replace_p->hide();
+ replace_all_p->hide();
+ is_replace = false;
+}
+
+void
+Find_dialog::as_Replace()
+{
+ label("Replace");
+ cancel_p->resize(cancel_p->x(), 115, cancel_p->w(), cancel_p->h());
+ casematch_p->resize(casematch_p->x(), 115, casematch_p->w(), casematch_p->h());
+ resize(x(), y(), w(), 155);
+ direction_p->hide();
+ up_p->hide();
+ down_p->hide();
+ replace_with_p->show();
+ replace_p->show();
+ replace_all_p->show();
+ is_replace = true;
+}
+
+
+// Callback for when user clicks "Find Next" button after filling in
+// the dialog.
+
+CALL_BACK(Find_dialog, FindNext)
+{
+ int start = editor_p->insert_position();
+ bool found;
+ int where;
+ if (down_p->value() == 1 || is_replace) {
+ found = editor_p->buffer()->search_forward(
+ start + 1, pattern_p->value(),
+ &where, casematch_p->value());
+ }
+ else {
+ found = editor_p->buffer()->search_backward(
+ start - 1, pattern_p->value(),
+ &where, casematch_p->value());
+ }
+
+ if ( ! found ) {
+ // It seems fltk does not find a pattern if it is exactly
+ // at the beginning for an upward search or exactly
+ // at the end for a downward search. That surely can't
+ // be right, so add special checks for that. If fltk
+ // fixes that some day, we'll never hit this case,
+ // so this should still be compatible.
+ int patlength = pattern_p->size();
+ int bufflength = editor_p->buffer()->length();
+ where = (down_p->value() ? bufflength - patlength : 0);
+ // If pattern is longer than buffer, then no match possible.
+ // If already at pattern,
+ // we already found the end one last time.
+ if (patlength <= bufflength &&
+ where != editor_p->insert_position() - patlength) {
+ if (casematch_p->value()) {
+ if (strncmp(pattern_p->value(),
+ editor_p->buffer()->text()
+ + where, patlength) == 0) {
+ found = 1;
+ }
+ }
+ else {
+ if (strncasecmp(pattern_p->value(),
+ editor_p->buffer()->text()
+ + where, patlength) == 0) {
+ found = 1;
+ }
+ }
+ }
+
+ if ( ! found ) {
+ fl_alert("Cannot find \"%s\"", pattern_p->value());
+ gray_out();
+
+ // The main editor window should now
+ // be made active, rather than the Find window.
+ editor_p->take_focus();
+ }
+ }
+ if (found) {
+ editor_p->buffer()->highlight(where, where + pattern_p->size());
+ editor_p->insert_position(where + pattern_p->size());
+ editor_p->show_insert_position();
+ }
+}
+
+
+// Callback for when user clicks "Replace"
+
+CALL_BACK(Find_dialog, Replace)
+{
+ // See if we are already at the pattern to replace due to
+ // a previous Replace/Find Next
+ int start, end, isRect, rectStart, rectEnd;
+ bool at_pattern = false;
+ if (editor_p->buffer()->highlight_position(&start, &end, &isRect,
+ &rectStart, &rectEnd)) {
+ int place = editor_p->insert_position();
+ if (place == end && (end - start == pattern_p->size())) {
+ if (casematch_p->value()) {
+
+ at_pattern = (strncmp(pattern_p->value(),
+ editor_p->buffer()->text() + start,
+ pattern_p->size()) == 0);
+ }
+ else {
+ at_pattern = (strncasecmp(pattern_p->value(),
+ editor_p->buffer()->text() + start,
+ pattern_p->size()) == 0);
+ }
+ }
+ }
+
+ if (at_pattern) {
+ editor_p->buffer()->unhighlight();
+ editor_p->buffer()->replace(start, end, replace_with_p->value());
+ }
+
+ FindNext();
+}
+
+
+// Callback for when use clicks "Replace All"
+
+CALL_BACK(Find_dialog, ReplaceAll)
+{
+ // We want to be able to "undo" the entire "Replace All"
+ // so we make a copy of the buffer, make all the changes in the
+ // copy and then replace the original with the altered copy.
+ Fl_Text_Buffer altered_buff;
+ altered_buff.copy(editor_p->buffer(), 0, editor_p->buffer()->length(), 0);
+ int start; // where to begin each search
+ bool found = true; // if matching pattern found on current search
+ int where; // offset into buffer where match occurred
+ int new_cursor_pos;
+ bool replaced_something = false; // if any matches found at all
+
+ new_cursor_pos = editor_p->insert_position();
+ for (start = 0; found; start = where + replace_with_p->size()) {
+ if ((found = altered_buff.search_forward(
+ start, pattern_p->value(),
+ &where, casematch_p->value()))
+ != 0) {
+ altered_buff.replace(where, where + pattern_p->size(),
+ replace_with_p->value());
+ new_cursor_pos = where + replace_with_p->size();
+ replaced_something = true;
+ }
+ }
+ // Kludge because pattern at very end is not found.
+ // See more complete explanation in FindNext().
+ where = altered_buff.length() - pattern_p->size();
+ if (where >= 0) {
+ found = false;
+ if (casematch_p->value()) {
+ if (strcmp(pattern_p->value(),
+ altered_buff.text() + where) == 0) {
+ found = true;
+ }
+ }
+ else {
+ if (strcasecmp(pattern_p->value(),
+ altered_buff.text() + where) == 0) {
+ found = true;
+ }
+ }
+ if (found) {
+ altered_buff.replace(where, where + pattern_p->size(),
+ replace_with_p->value());
+ new_cursor_pos = where + replace_with_p->size();
+ replaced_something = true;
+ }
+ }
+
+ if (replaced_something) {
+ editor_p->buffer()->replace(0, editor_p->buffer()->length(),
+ altered_buff.text());
+ editor_p->insert_position(new_cursor_pos);
+ }
+ else {
+ fl_alert("No instances of pattern to replace.");
+ }
+ replace_all_p->deactivate();
+}
+
+
+// Callback for when user clicks "Cancel" in the "Find" window.
+// Hides the window.
+
+CALL_BACK(Find_dialog, Cancel)
+{
+ editor_p->buffer()->unhighlight();
+ hide();
+}
+
+
+// If user did Find Next until no more instances found,
+// the Find Next and Replace buttons will get grayed out,
+// But if they then change search direction, we need to reactivate them
+// since the pattern might be found in that direction.
+// They must also be ungrayed if the contents of the editor buffer change,
+// since the new text might contain the pattern.
+
+CALL_BACK(Find_dialog, change)
+{
+ next_p->activate();
+ replace_p->activate();
+ replace_all_p->activate();
+}
+
+
+// Callback for when user changes pattern, to know whether to gray or ungray
+// Find Next button or not.
+
+CALL_BACK(Find_dialog, Pattern)
+{
+ if (pattern_p->size() > 0) {
+ next_p->activate();
+ replace_p->activate();
+ replace_all_p->activate();
+ }
+ else {
+ gray_out();
+ }
+}
+
+// Horrible kludge. If the "Find" or "Replace" is grayed out because there
+// are no more instances of the pattern in the current direction,
+// and then user moves the cursor somewhere else, it's possible the pattern
+// may then be findable. But modify_callback does not get called for
+// a change in cursor position. So we poll to see if the cursor position
+// changed since the last check, and if so, ungray.
+// Fortunately, we can limit this to only when the button are grayed,
+// which shouldn't be too often.
+
+void
+Find_dialog::gray_out(void)
+{
+ next_p->deactivate();
+ replace_p->deactivate();
+ replace_all_p->deactivate();
+ // Remember where we are and set up to poll for changes
+ last_cursor_position = editor_p->insert_position();
+ Fl::add_timeout(0.5, cursor_change_check, this);
+}
+
+void
+Find_dialog::cursor_change_check(void * data)
+{
+ Find_dialog * obj_p = (Find_dialog *) data;
+ if (obj_p->editor_p->insert_position() != obj_p->last_cursor_position
+ && obj_p->pattern_p->size() > 0) {
+ obj_p->change();
+ }
+ else {
+ Fl::repeat_timeout(0.5, cursor_change_check, data);
+ }
+}
+
+
+// Class needs access to the editor; this lets it know which editor
+// instance to use, and which main window it is associated with.
+
+void
+Find_dialog::set_editor(Fl_Text_Editor * ed)
+{
+ editor_p = ed;
+}
+
+
+// Returns the current "Find" pattern entered by user,
+// or "" if they have not yet entered any such pattern
+
+const char *
+Find_dialog::get_pattern()
+{
+ if (pattern_p->value() == 0) {
+ return("");
+ }
+ else {
+ return(pattern_p->value());
+ }
+}
+
+
+//---------------- GoTo class---------------------------------------------
+
+GoTo_dialog::GoTo_dialog(void)
+ : Fl_Double_Window(225, 95, "Goto line")
+{
+ linenum_p = new Positive_Int_Input(115, 10, 60, 30, "Line Number:");
+ linenum_p->tooltip("Enter the line number of the line\n"
+ "you want to make the current line.");
+
+ ok_p = new Fl_Return_Button(25, 50, 75, 30, "OK");
+ ok_p->when(FL_WHEN_RELEASE);
+ ok_p->callback(OK_cb, this);
+
+ cancel_p = new Fl_Button(125, 50, 75, 30, "Cancel");
+ cancel_p->shortcut(FL_Escape);
+ cancel_p->when(FL_WHEN_RELEASE);
+ cancel_p->callback(Cancel_cb, this);
+
+ // Arrange for destructor to clean up new-ed widgets
+ end();
+
+ // Arrange for window manager closes to do Cancel.
+ callback(Cancel_cb, this);
+ when(FL_WHEN_NEVER);
+}
+
+GoTo_dialog::~GoTo_dialog()
+{
+}
+
+
+// Callback for when user clicks "OK" in GoTo dialog
+
+CALL_BACK(GoTo_dialog, OK)
+{
+ // Find end of valid range
+ int last_line = editor_p->buffer()->count_lines(0,
+ editor_p->buffer()->length());
+ // FLTK line numbers start at 0, so we have to subtract 1 from
+ // number supplied by user.
+ int desired_line = (int) strtol(linenum_p->value(), 0, 0) - 1;
+ if (desired_line < 0 || desired_line > last_line) {
+ fl_alert("Line number out of range");
+ return;
+ }
+
+ // Find appropriate new cursor position and move cursor there
+ int newposition = editor_p->buffer()->skip_lines(0, desired_line);
+ editor_p->insert_position(newposition);
+ editor_p->show_insert_position();
+ hide();
+}
+
+
+// Callback if user cancel Go To
+
+CALL_BACK(GoTo_dialog, Cancel)
+{
+ hide();
+}
+
+
+// Code that calls constructor should then call this to tell
+// us which editor instance to act upon.
+
+void
+GoTo_dialog::set_editor(Fl_Text_Editor * ed)
+{
+ editor_p = ed;
+}
+
+
+// Initialize contents on GoTo field to the current line number
+
+void
+GoTo_dialog::set_current_line()
+{
+ char num_as_string[16];
+ // fltk numbers lines from 0, so add 1 to get what user expects
+ (void) sprintf(num_as_string, "%d",
+ editor_p->buffer()->count_lines(0, editor_p->insert_position()) + 1);
+ linenum_p->value(num_as_string);
+}
+
+
+//------------------Edit class-----------------------------------------------
+// Implements the items in the Edit menu on the main toolbar
+
+
+Edit::Edit()
+{
+ find_p = 0;
+ goto_p = 0;
+ wrote_to_clipboard = false;
+}
+
+
+Edit::~Edit()
+{
+ if (find_p != 0) {
+ delete find_p;
+ find_p = 0;
+ }
+ if (goto_p != 0) {
+ delete goto_p;
+ goto_p = 0;
+ }
+}
+
+
+//---Undo menu item---------------
+
+CALL_BACK(Edit, Undo)
+{
+ buffer_p->undo();
+}
+
+
+//---Cut menu item---------------
+
+CALL_BACK(Edit, Cut)
+{
+ Fl_Text_Editor::kf_cut('x', editor_p);
+ set_can_paste();
+}
+
+
+//---Copy menu item---------------
+
+CALL_BACK(Edit, Copy)
+{
+ Fl_Text_Editor::kf_copy('c', editor_p);
+ set_can_paste();
+}
+
+
+//---Paste menu item---------------
+
+CALL_BACK(Edit, Paste)
+{
+ Fl_Text_Editor::kf_paste('v', editor_p);
+}
+
+
+//---Delete menu item---------------
+
+CALL_BACK(Edit, Delete)
+{
+ buffer_p->remove_selection();
+ buffer_p->unselect();
+}
+
+
+//---Find menu item---------------
+
+CALL_BACK(Edit, Find)
+{
+ if (find_p == 0) {
+ // First time, create widget
+ find_p = new Find_dialog();
+ find_p->set_editor(editor_p);
+ }
+ find_p->as_Find();
+ find_p->show();
+}
+
+
+//---Find Next menu item---------------
+
+CALL_BACK(Edit, FindNext)
+{
+ if (find_p == 0 || strlen(find_p->get_pattern()) == 0) {
+ // No pattern specified yet; turn into Find
+ Find();
+ return;
+ }
+ find_p->FindNext();
+}
+
+
+//---Replace menu item---------------
+
+CALL_BACK(Edit, Replace)
+{
+ if (find_p == 0) {
+ // First time, create widget
+ find_p = new Find_dialog();
+ find_p->set_editor(editor_p);
+ }
+ find_p->as_Replace();
+ find_p->show();
+}
+
+
+//---Go To menu item---------------
+
+CALL_BACK(Edit, GoTo)
+{
+ if (goto_p == 0) {
+ // First time, create widget
+ goto_p = new GoTo_dialog();
+ goto_p->set_editor(editor_p);
+ }
+ goto_p->set_current_line();
+ goto_p->show();
+}
+
+
+//---Select All menu item---------------
+
+CALL_BACK(Edit, SelectAll)
+{
+ buffer_p->select(0, buffer_p->length());
+}
+
+
+//---- Callback for when editor window is modified.
+// Grayed out find/replace should be ungrayed, because the modified
+// text might now match.
+
+void
+Edit::modify_cb(int, int, int, int, const char *, void * data)
+{
+ if ( ((Edit *)data)->find_p != 0) {
+ ((Edit *)data)->find_p->change();
+ }
+}
+
+
+// Class needs access to the editor; this lets it know which editor
+// instance to use.
+
+void
+Edit::set_editor(Fl_Text_Editor * ed)
+{
+ editor_p = ed;
+ buffer_p = editor_p->buffer();
+}
+
+// Ungray Paste button
+
+void
+Edit::set_can_paste(void)
+{
+ wrote_to_clipboard = true;
+ editor_p->buffer()->call_modify_callbacks();
+}