FLTK Editor

FLTK 是一个GUI库,它提供了跨平台支持。下载源代码在 deepin 系统中,make 即可生成。生成时,根据提示需先安装依赖库。

编译 FLTK 需要的库:

sudo apt-get install libx11-dev
sudo apt-get install libxrender-dev
sudo apt-get install libxft-dev
sudo apt-get install libgl1-mesa-dev
sudo apt-get install libglu1-mesa-dev
sudo apt install mesa-utils
sudo apt install libglew-dev

FLTK Editor 效果图:

FLTK Editor

编译运行:

  1. 在fltk目录中, 新建 Editor.cxx 文件,终端编译:
./fltk-config --compile Editor.cxx`
  • 终端运行:
./Editor

Editor.cxx 源码:

#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/fl_ask.H>
#include <FL/filename.H>
#include <FL/fl_string_functions.h>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/platform.H>
#include <FL/Fl_Flex.H>
#include <FL/Fl_Tile.H>
#include <errno.h>

Fl_Double_Window *window = NULL;
Fl_Menu_Bar *menubar = NULL;
bool text_changed = false;
char app_filename[FL_PATH_MAX + 1] = "";
char last_find_text[1024] = "";
char last_replace_text[1024] = "";
Fl_Text_Editor *text_editor = NULL;
Fl_Text_Editor *split_editor = NULL;
Fl_Text_Buffer *text_buffer = NULL;
Fl_Tile *app_tile = NULL;

void SetChanged(bool);

void TextChanged(int, int inserted, int deleted, int, const char *, void *) {
  SetChanged(true);
}

void MenuNewCallback(Fl_Widget *, void *) {
  text_buffer->text("");
  SetChanged(false);
}

void Build_Editor() {
  window->begin();
  text_buffer = new Fl_Text_Buffer();
  text_buffer->add_modify_callback(TextChanged, NULL);
  text_editor = new Fl_Text_Editor(0, 25, window->w(), window->h() - 25);
  text_editor->buffer(text_buffer);
  text_editor->textfont(FL_COURIER);
  window->resizable(text_editor);
  window->end();
}

void Build_Window() {
  window = new Fl_Double_Window(900, 680, "FLTK Editor");
}

void UpdateTitle() {
  const char *filename = NULL;
  if (app_filename[0])
    filename = fl_filename_name(app_filename);
  if (filename) {
    char buf[FL_PATH_MAX + 3];
    if (text_changed) {
      snprintf(buf, FL_PATH_MAX + 2, "%s *", filename);
    } else {
      snprintf(buf, FL_PATH_MAX + 2, "%s", filename);
    }
    window->copy_label(buf);
  } else {
    window->label("FLTK Editor");
  }
}

void SetChanged(bool v) {
  if (v != text_changed) {
    text_changed = v;
    UpdateTitle();
  }
}

void SetFilename(const char *filename) {
  if (filename) {
    fl_strlcpy(app_filename, filename, FL_PATH_MAX + 1);
  } else {
    app_filename[0] = 0;
  }
  UpdateTitle();
}

void MenuQuitCallback(Fl_Widget *w, void *v) {
  if (text_changed) {
    int c = fl_choice("Save changes?", "Quit", "Cancel", NULL);
    if (c == 1) {
      return;
    }
  }
  Fl::hide_all_windows();
}

void Build_Menu() {
  window->begin();
  menubar = new Fl_Menu_Bar(0, 0, window->w(), 25);
  menubar->add("File/Quit", FL_COMMAND + 'q', MenuQuitCallback);
  int index = menubar->find_index(MenuQuitCallback);
  menubar->insert(index, "New", FL_COMMAND + 'n', MenuNewCallback);
  window->callback(MenuQuitCallback);
  window->end();
}

void MenuSaveAsCallback(Fl_Widget *, void *) {
  Fl_Native_File_Chooser chooser;
  chooser.title("Save As");
  chooser.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);

  if (app_filename[0]) {
    char temp[FL_PATH_MAX + 1];
    fl_strlcpy(temp, app_filename, FL_PATH_MAX + 1);
    const char *name = fl_filename_name(temp);
    if (name) {
      chooser.preset_file(name);
      temp[name - temp] = 0;
      chooser.directory(temp);
    }
  }

  if (chooser.show() == 0) {
    text_buffer->savefile(chooser.filename());
    SetFilename(chooser.filename());
    SetChanged(false);
  }
}

void MenuSaveCallback(Fl_Widget *, void *) {
  if (app_filename[0]) {
    text_buffer->savefile(app_filename);
    SetChanged(false);
  } else {
    MenuSaveAsCallback(NULL, NULL);
  }
}

void LoadFlie(const char *filename) {
  if (filename && filename[0]) {
    text_buffer->loadfile(filename);
    SetFilename(filename);
    SetChanged(false);
  }
}

// TODO: Add Open File (Fl_Native_File_Chooser
void MenuOpenCallback(Fl_Widget *, void *) {
  if (text_changed) {
    int c = fl_choice("Save changes?", "Cancel", "Save", "Don't Save");
    if (c == 2)
      return;
    if (c == 1)
      MenuSaveCallback(NULL, NULL);
  }

  Fl_Native_File_Chooser chooser;
  chooser.title("Open File");
  chooser.type(Fl_Native_File_Chooser::BROWSE_FILE);
  if (!app_filename[0]) {
    char temp[FL_PATH_MAX + 1];
    fl_strlcpy(temp, app_filename, FL_PATH_MAX + 1);
    const char *name = fl_filename_name(temp);
    if (name) {
      chooser.preset_file(name);
      temp[name - temp] = 0;
      chooser.directory(temp);
    }
  }

  if (chooser.show() == 0) {
    LoadFlie(chooser.filename());
  }
}

void AddFileMenu() {
  int i = menubar->find_index(MenuQuitCallback);
  menubar->insert(i, "Open", FL_COMMAND + 'o', MenuOpenCallback, NULL, FL_MENU_DIVIDER);
  menubar->insert(i + 1, "Save", FL_COMMAND + 's', MenuSaveCallback);
  menubar->insert(i + 2, "Save As", FL_COMMAND + 'a', MenuSaveAsCallback, NULL, FL_MENU_DIVIDER);
}

int ArgsHandler(int argc, char **argv, int &i) {
  if (argv && argv[i] && argv[i][0] != '-') {
    LoadFlie(argv[i]);
    i++;
    return 1;
  }
  return 0;
}

int HandleCommandLine(int argc, char **argv) {
  for (int i = 1; i < argc; ++i) {
    if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
      printf("Usage: %s [filename]\n", argv[0]);
    }
  }

  int i = 0;
  Fl::args_to_utf8(argc, argv);
  Fl::args(argc, argv, i, ArgsHandler);
  fl_open_callback(LoadFlie);
  window->show(argc, argv);
  return Fl::run();
}

void MenuCutCallback(Fl_Widget *, void *) {
  Fl_Widget *w = Fl::focus();
  if (w && (w == text_editor || w == split_editor)) {
    Fl_Text_Editor::kf_cut(0, (Fl_Text_Editor *)w);
  }
}

void MenuCopyCallback(Fl_Widget *, void *) {
  Fl_Widget *w = Fl::focus();
  if (w && (w == text_editor || w == split_editor)) {
    Fl_Text_Editor::kf_copy(0, (Fl_Text_Editor *)w);
  }
}

void MenuPasteCallback(Fl_Widget *, void *) {
  Fl_Widget *w = Fl::focus();
  if (w && (w == text_editor || w == split_editor)) {
    Fl_Text_Editor::kf_paste(0, (Fl_Text_Editor *)w);
  }
}

void FindNext(const char *needle) {
  Fl_Text_Editor *editor = text_editor;
  Fl_Widget *w = Fl::focus();
  if (w && w == split_editor) {
    editor = split_editor;
  }
  int pos = editor->insert_position();
  int found = text_buffer->search_forward(pos, needle, &pos);
  if (found) {
    text_buffer->select(pos, pos + (int)strlen(needle));
    editor->insert_position(pos + (int)strlen(needle));
    editor->show_insert_position();
  } else {
    fl_alert("Find: '%s' not found", needle);
  }
}

void MenuFindCallback(Fl_Widget *, void *) {
  const char *find_text = fl_input("Find in text: ", last_find_text);
  if (find_text && find_text[0]) {
    fl_strlcpy(last_find_text, find_text, sizeof(last_find_text));
    FindNext(find_text);
  }
}

void MenuF1indNextCallback(Fl_Widget *, void *) {
  if (last_find_text[0]) {
    FindNext(last_find_text);
  } else {
    MenuFindCallback(NULL, NULL);
  }
}

void ReplaceSelection(const char *new_text) {
  Fl_Text_Editor *editor = text_editor;
  Fl_Widget *w = Fl::focus();
  if (w && w == split_editor) {
    editor = split_editor;
  }
  int start, end;
  if (text_buffer->selection_position(&start, &end)) {
    text_buffer->remove_selection();
    text_buffer->insert(start, new_text);
    text_buffer->select(start, start + (int)strlen(new_text));
    editor->insert_position(start + (int)strlen(new_text));
    editor->show_insert_position();
  }
}

class ReplaceDialog : public Fl_Double_Window {
  Fl_Input *find_text_input, *replace_text_input;
  Fl_Button *find_next_button, *replace_and_find_button, *close_button;

public:
  ReplaceDialog(const char *label);
  void show() FL_OVERRIDE;

private:
  static void find_next_callback(Fl_Widget *, void *);
  static void replace_and_find_callback(Fl_Widget *, void *);
  static void close_callback(Fl_Widget *, void *);
};

ReplaceDialog *replace_dialog = NULL;

ReplaceDialog::ReplaceDialog(const char *label)
  : Fl_Double_Window(430, 100, label) {
  find_text_input = new Fl_Input(100, 10, 320, 25, "Find: ");
  replace_text_input = new Fl_Input(100, 50, 320, 25, "Replace: ");
  Fl_Flex *button_field = new Fl_Flex(100, 70, w() - 100, 40);
  button_field->type(Fl_Flex::HORIZONTAL);
  button_field->margin(0, 5, 10, 10);
  button_field->gap(10);
  find_next_button = new Fl_Button(0, 0, 0, 0, "Next");
  find_next_button->callback(replace_and_find_callback, this);
  close_button = new Fl_Button(0, 0, 0, 0, "Close");
  close_button->callback(close_callback, this);
  button_field->end();
  set_non_modal();
}

void ReplaceDialog::show() {
  find_text_input->value(last_find_text);
  replace_text_input->value(last_replace_text);
  Fl_Double_Window::show();
}

void ReplaceDialog::find_next_callback(Fl_Widget *, void *data) {
  FindNext(static_cast<ReplaceDialog *>(data)->find_text_input->value());
}

void ReplaceDialog::replace_and_find_callback(Fl_Widget *, void *data) {
  ReplaceDialog *dlg = static_cast<ReplaceDialog *>(data);
  ReplaceSelection(dlg->replace_text_input->value());
  find_next_callback(NULL, data);
}

void ReplaceDialog::close_callback(Fl_Widget *, void *data) {
  ReplaceDialog *dlg = static_cast<ReplaceDialog *>(data);
  dlg->hide();
}

void MenuReplaceCallback(Fl_Widget *, void *) {
  if (!replace_dialog) {
    replace_dialog = new ReplaceDialog("Find and Replace");
  }
  replace_dialog->show();
}

void AddEditMenu() {
  menubar->add("Edit/Cut", FL_COMMAND + 'x', MenuCutCallback);
  menubar->add("Edit/Copy", FL_COMMAND + 'c', MenuCopyCallback);
  menubar->add("Edit/Paste", FL_COMMAND + 'v', MenuPasteCallback, NULL, FL_MENU_DIVIDER);
  menubar->add("Edit/Find", FL_COMMAND + 'f', MenuFindCallback);
  menubar->add("Edit/Find Next", FL_COMMAND + 'n', MenuF1indNextCallback);
  menubar->add("Edit/Replace", FL_COMMAND + 'r', MenuReplaceCallback);
}

// Window menu
void MenuLineNumberCallback(Fl_Widget *w, void *) {
  Fl_Menu_Bar *menu = static_cast<Fl_Menu_Bar *>(w);
  const Fl_Menu_Item *item = menu->mvalue();
  if (item->value()) {
    text_editor->linenumber_width(40);
    if (split_editor)
      split_editor->linenumber_width(40);
  } else {
    text_editor->linenumber_width(0);
    if (split_editor)
      split_editor->linenumber_width(0);
  }
  text_editor->redraw();
  if (split_editor)
    split_editor->redraw();
}

void MenuWordWrapCallback(Fl_Widget *w, void *) {
  Fl_Menu_Bar *menu = static_cast<Fl_Menu_Bar *>(w);
  const Fl_Menu_Item *item = menu->mvalue();
  if (item->value()) {
    text_editor->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);
    if (split_editor)
      split_editor->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);
  } else {
    text_editor->wrap_mode(Fl_Text_Display::WRAP_NONE, 0);
    if (split_editor)
      split_editor->wrap_mode(Fl_Text_Display::WRAP_NONE, 0);
  }
  text_editor->redraw();
  if (split_editor)
    split_editor->redraw();
}

void BuildSplitEditor() {
  window->begin();
  app_tile = new Fl_Tile(text_editor->x(), text_editor->y(), text_editor->w(), text_editor->h());
  window->remove(text_editor);
  app_tile->add(text_editor);

  split_editor = new Fl_Text_Editor(app_tile->x(), app_tile->y() + app_tile->h(), app_tile->w(), 0);
  split_editor->buffer(text_buffer);
  split_editor->textfont(FL_COURIER);
  split_editor->hide();
  app_tile->end();
  app_tile->size_range(0, 25, 25);
  app_tile->size_range(1, 25, 25);
  window->end();
  window->resizable(app_tile);
  app_tile->resizable(text_editor);
}

void MenuSplitCallback(Fl_Widget *w, void *) {
  Fl_Menu_Bar *menu = static_cast<Fl_Menu_Bar *>(w);
  const Fl_Menu_Item *item = menu->mvalue();
  if (item->value()) {
    int h_split = app_tile->h() / 2;
    text_editor->size(app_tile->w(), h_split);
    split_editor->resize(app_tile->x(), app_tile->y() + h_split, app_tile->w(),
                         app_tile->h() - h_split);
    split_editor->show();
  } else {
    text_editor->size(app_tile->w(), app_tile->h());
    split_editor->resize(app_tile->x(), app_tile->y() + app_tile->h(), app_tile->w(), 0);
    split_editor->hide();
  }
  app_tile->resizable(text_editor);
  app_tile->init_sizes();
  app_tile->redraw();
}

// Syntax highlighting
#include <ctype.h>
#include <stdlib.h>

Fl_Text_Buffer *app_style_buffer = NULL;

#define TS 14

Fl_Text_Display::Style_Table_Entry styletable[] = {
    {FL_BLACK, FL_COURIER, TS},               // A - Plain
    {FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS}, // B - Line comments
    {FL_DARK_GREEN, FL_HELVETICA_ITALIC, TS}, // C - Block comments
    {FL_BLUE, FL_COURIER, TS},                // D - Strings
    {FL_DARK_RED, FL_COURIER, TS},            // E - Directives
    {FL_DARK_RED, FL_COURIER_BOLD, TS},       // F - Types
    {FL_BLUE, FL_COURIER_BOLD, TS},           // G - Keywords
};

const char *code_keywords[] = {
    "and",      "and_eq",  "asm",    "bitand",   "bitor", "break", "case",   "catch",  "compl",
    "continue", "default", "delete", "do",       "else",  "false", "for",    "goto",   "if",
    "new",      "not",     "not_eq", "operator", "or",    "or_eq", "return", "switch", "template",
    "this",     "throw",   "true",   "try",      "while", "xor",   "xor_eq"};

const char *code_types[] = {
    "auto",     "bool",         "char",        "class",    "const",    "const_cast",
    "double",   "dynamic_cast", "enum",        "explicit", "extern",   "float",
    "friend",   "inline",       "int",         "long",     "mutable",  "namespace",
    "private",  "protected",    "public",      "register", "short",    "signed",
    "sizeof",   "static",       "static_cast", "struct",   "template", "typedef",
    "typename", "union",        "unsigned",    "virtual",  "void",     "volatile"};

extern "C" {
int compare_keywords(const void *a, const void *b) {
  return strcmp(*(const char **)a, *(const char **)b);
}
}

void style_parse(const char *text, char *style, int length) {
  char current;
  int col;
  int last;
  char buf[255], *bufptr;
  const char *temp;

  // Style letters:
  //
  // A - Plain
  // B - Line comments
  // C - Block comments
  // D - Strings
  // E - Directives
  // F - Types
  // G - Keywords

  for (current = *style, col = 0, last = 0; length > 0; length--, text++) {
    if (current == 'B' || current == 'F' || current == 'G')
      current = 'A';
    if (current == 'A') {
      // Check for directives, comments, strings, and keywords...
      if (col == 0 && *text == '#') {
        // Set style to directive
        current = 'E';
      } else if (strncmp(text, "//", 2) == 0) {
        current = 'B';
        for (; length > 0 && *text != '\n'; length--, text++)
          *style++ = 'B';
        if (length == 0)
          break;
      } else if (strncmp(text, "/*", 2) == 0) {
        current = 'C';
      } else if (strncmp(text, "\\\"", 2) == 0) {
        // Quoted quote...
        *style++ = current;
        *style++ = current;
        text++;
        length--;
        col += 2;
        continue;
      } else if (*text == '\"') {
        current = 'D';
      } else if (!last && (islower((*text) & 255) || *text == '_')) {
        // Might be a keyword...
        for (temp = text, bufptr = buf;
             (islower((*temp) & 255) || *temp == '_') && bufptr < (buf + sizeof(buf) - 1);
             *bufptr++ = *temp++) {
          // nothing
        }

        if (!islower((*temp) & 255) && *temp != '_') {
          *bufptr = '\0';

          bufptr = buf;

          if (bsearch(&bufptr, code_types, sizeof(code_types) / sizeof(code_types[0]),
                      sizeof(code_types[0]), compare_keywords)) {
            while (text < temp) {
              *style++ = 'F';
              text++;
              length--;
              col++;
            }

            text--;
            length++;
            last = 1;
            continue;
          } else if (bsearch(&bufptr, code_keywords,
                             sizeof(code_keywords) / sizeof(code_keywords[0]),
                             sizeof(code_keywords[0]), compare_keywords)) {
            while (text < temp) {
              *style++ = 'G';
              text++;
              length--;
              col++;
            }

            text--;
            length++;
            last = 1;
            continue;
          }
        }
      }
    } else if (current == 'C' && strncmp(text, "*/", 2) == 0) {
      // Close a C comment...
      *style++ = current;
      *style++ = current;
      text++;
      length--;
      current = 'A';
      col += 2;
      continue;
    } else if (current == 'D') {
      // Continuing in string...
      if (strncmp(text, "\\\"", 2) == 0) {
        // Quoted end quote...
        *style++ = current;
        *style++ = current;
        text++;
        length--;
        col += 2;
        continue;
      } else if (*text == '\"') {
        // End quote...
        *style++ = current;
        col++;
        current = 'A';
        continue;
      }
    }

    // Copy style info...
    if (current == 'A' && (*text == '{' || *text == '}'))
      *style++ = 'G';
    else
      *style++ = current;
    col++;

    last = isalnum((*text) & 255) || *text == '_' || *text == '.';

    if (*text == '\n') {
      // Reset column and possibly reset the style
      col = 0;
      if (current == 'B' || current == 'E')
        current = 'A';
    }
  }
}

void StyleInit() {
  char *style = new char[text_buffer->length() + 1];
  char *text = text_buffer->text();

  memset(style, 'A', text_buffer->length());
  style[text_buffer->length()] = '\0';

  if (!app_style_buffer)
    app_style_buffer = new Fl_Text_Buffer(text_buffer->length() + 1);

  style_parse(text, style, text_buffer->length());

  app_style_buffer->text(style);
  delete[] style;
  free(text);
}

void StyleUnfinishedCallback(int reason, void *editor) {}

void StyleUpdate(int pos, int nInserted, 
  int nDeleted, int nRestyled, const char *dletedText, void *cbArg) 
{
  int start, end;
  char last, *style, *text;
  if (nInserted == 0 && nDeleted == 0) {
    app_style_buffer->unselect();
    return;
  }

  if (nInserted > 0) {
    style = new char[nInserted + 1];
    memset(style, 'A', nInserted);
    style[nInserted] = '\0';
    app_style_buffer->replace(pos, pos + nDeleted, style);
    delete[] style;
  } else {
    app_style_buffer->remove(pos, pos + nDeleted);
  }

  app_style_buffer->select(pos, pos + nInserted - nDeleted);

  start = text_buffer->line_start(pos);
  end = text_buffer->line_end(pos + nInserted);
  text = text_buffer->text_range(start, end);
  style = app_style_buffer->text_range(start, end);
  if (start == end)
    last = 0;
  else
    last = style[end - start - 1];

  style_parse(text, style, end - start);

  app_style_buffer->replace(start, end, style);
  ((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);

  if (start == end || last != style[end - start - 1]) {
    free(text);
    free(style);

    end = text_buffer->length();
    text = text_buffer->text_range(start, end);
    style = app_style_buffer->text_range(start, end);

    style_parse(text, style, end - start);
    app_style_buffer->replace(start, end, style);
    ((Fl_Text_Editor *)cbArg)->redisplay_range(start, end);
  }

  free(text);
  free(style);
}

void MenuSyntaxHighlightCallback(Fl_Widget *w, void *) {
  Fl_Menu_Bar *menu = static_cast<Fl_Menu_Bar *>(w);
  const Fl_Menu_Item *item = menu->mvalue();
  if (item->value()) {
    StyleInit();
    text_editor->highlight_data(app_style_buffer, styletable,
                                sizeof(styletable) / sizeof(styletable[0]), 'A',
                                StyleUnfinishedCallback, 0);
    text_buffer->add_modify_callback(StyleUpdate, text_editor);
  } else {
    text_buffer->remove_modify_callback(StyleUpdate, text_editor);
    text_editor->highlight_data(NULL, NULL, 0, 'A', NULL, 0);
  }
  text_editor->redraw();
  if (split_editor) {
    if (item->value()) {
      split_editor->highlight_data(app_style_buffer, styletable,
                                   sizeof(styletable) / sizeof(styletable[0]), 'A',
                                   StyleUnfinishedCallback, 0);
    } else {
      split_editor->highlight_data(NULL, NULL, 0, 'A', NULL, 0);
    }
    split_editor->redraw();
  }
}

void AddWindowMenu() {
  menubar->add("Window/Line Numbers", FL_COMMAND + 'l', MenuLineNumberCallback, NULL,
               FL_MENU_TOGGLE);
  menubar->add("Window/Word Wrap", FL_COMMAND + 'w', MenuWordWrapCallback, NULL, FL_MENU_TOGGLE);
  menubar->add("Window/Split", FL_COMMAND + 's', MenuSplitCallback, NULL, FL_MENU_TOGGLE);
  menubar->add("Window/Syntax Highlighting", FL_COMMAND + 'h', MenuSyntaxHighlightCallback, NULL,
               FL_MENU_TOGGLE);
}


int main(int argc, char **argv) {
  Build_Window();
  Build_Menu();
  Build_Editor();
  BuildSplitEditor();

  AddFileMenu();
  AddEditMenu();
  AddWindowMenu();

  return HandleCommandLine(argc, argv);
}

posted on 2024-11-10 14:29  x01  阅读(1)  评论(0编辑  收藏  举报

导航