2024-01-22 14:50:33 +00:00
|
|
|
#include <string>
|
|
|
|
#include <sstream>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <FL/Fl.H>
|
|
|
|
#include <FL/fl_ask.H>
|
|
|
|
#include <FL/filename.H>
|
|
|
|
|
|
|
|
#include <FL/Fl_Native_File_Chooser.H>
|
|
|
|
|
|
|
|
#include <FL/Fl_Menu_Item.H>
|
2024-01-23 14:09:39 +00:00
|
|
|
#include <FL/Fl_Text_Buffer.H>
|
|
|
|
#include <FL/Fl_Text_Display.H>
|
2024-01-22 14:50:33 +00:00
|
|
|
|
|
|
|
#include "gui.hpp"
|
|
|
|
|
|
|
|
MainWindow top;
|
|
|
|
Fl_Text_Buffer buffer;
|
|
|
|
Fl_Fontsize base_font_size;
|
|
|
|
bool word_wrap = true;
|
|
|
|
|
|
|
|
std::string file_name;
|
|
|
|
bool modified = false;
|
|
|
|
|
|
|
|
std::string search_term;
|
|
|
|
|
|
|
|
void on_mod(int pos, int nIns, int nDel, int nSt, const char* del, void* d) {
|
|
|
|
if (nIns > 0 || nDel > 0) {
|
|
|
|
modified = true;
|
2024-02-05 08:56:34 +00:00
|
|
|
top.status3->value("(modified)");
|
2024-01-22 14:50:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_new(Fl_Widget *w, void *data) {
|
|
|
|
if (modified) {
|
|
|
|
fl_message_title("Please confirm");
|
|
|
|
if (fl_choice("File isn't saved. Start another?",
|
|
|
|
"New", "Keep", 0) > 0)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
file_name = "";
|
|
|
|
buffer.remove(0, buffer.length());
|
|
|
|
top.status1->value(empty_status);
|
2024-02-05 08:56:34 +00:00
|
|
|
top.status3->value("");
|
2024-01-22 14:50:33 +00:00
|
|
|
modified = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void load_file(const char *name, bool change=true) {
|
|
|
|
fl_message_title(window_title);
|
|
|
|
switch (buffer.loadfile(name)) {
|
|
|
|
case 0:
|
|
|
|
if (change) {
|
|
|
|
file_name = name;
|
|
|
|
top.status2->value(fl_filename_name(name));
|
|
|
|
}
|
|
|
|
top.status1->value("Opened");
|
2024-02-05 08:56:34 +00:00
|
|
|
top.status3->value("");
|
2024-01-22 14:50:33 +00:00
|
|
|
modified = false;
|
|
|
|
break;
|
|
|
|
case 1: fl_alert("Error opening file."); break;
|
|
|
|
case 2: fl_alert("Error reading file."); break;
|
|
|
|
default: fl_alert("Error handling file.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_open(Fl_Widget *w, void *data) {
|
|
|
|
if (modified) {
|
|
|
|
fl_message_title("Please confirm");
|
|
|
|
if (fl_choice("File isn't saved. Start another?",
|
|
|
|
"New", "Keep", 0) > 0)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// fl_file_chooser("Open file", "*.txt,*.md,*.org", "");
|
|
|
|
Fl_Native_File_Chooser choice;
|
|
|
|
|
|
|
|
choice.title("Open file");
|
|
|
|
choice.filter("Text files \t*.{txt,md}\nScript files \t*.{py,tcl,sh}");
|
|
|
|
if (file_name != "")
|
|
|
|
choice.preset_file(file_name.c_str());
|
|
|
|
|
|
|
|
fl_message_title(window_title);
|
|
|
|
switch (choice.show()) {
|
|
|
|
case -1: fl_alert("Error: %s", choice.errmsg()); break;
|
|
|
|
case 1: top.status1->value("Opening canceled"); break;
|
|
|
|
default: load_file(choice.filename());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void save_file(const char *name, bool change=true) {
|
|
|
|
fl_message_title(window_title);
|
|
|
|
switch (buffer.savefile(name)) {
|
|
|
|
case 0:
|
|
|
|
if (change) {
|
|
|
|
file_name = name;
|
|
|
|
top.status2->value(fl_filename_name(name));
|
|
|
|
}
|
|
|
|
top.status1->value("Saved");
|
2024-02-05 08:56:34 +00:00
|
|
|
top.status3->value("");
|
2024-01-22 14:50:33 +00:00
|
|
|
modified = false;
|
|
|
|
break;
|
|
|
|
case 1: fl_alert("Error opening file."); break;
|
|
|
|
case 2: fl_alert("Error writing file."); break;
|
|
|
|
default: fl_alert("Error handling file.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_save_as(Fl_Widget *w, void *data) {
|
|
|
|
// fl_file_chooser("Open file", "*.txt,*.md,*.org", "");
|
|
|
|
Fl_Native_File_Chooser choice;
|
|
|
|
|
|
|
|
choice.title("Save file as");
|
|
|
|
choice.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
|
|
|
|
choice.filter("Text files \t*.{txt,md}"
|
|
|
|
"\nScript files \t*.{py,tcl,sh}"
|
|
|
|
"\nAll files\t*");
|
|
|
|
if (file_name != "")
|
|
|
|
choice.preset_file(file_name.c_str());
|
|
|
|
|
|
|
|
fl_message_title(window_title);
|
|
|
|
switch (choice.show()) {
|
|
|
|
case -1: fl_alert("Error: %s", choice.errmsg()); break;
|
|
|
|
case 1: top.status1->value("Save canceled"); break;
|
|
|
|
default: save_file(choice.filename());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_save(Fl_Widget *w, void *data) {
|
|
|
|
if (file_name == "")
|
|
|
|
do_save_as(w, data);
|
|
|
|
else
|
|
|
|
save_file(file_name.c_str(), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_reload(Fl_Widget *w, void *data) {
|
|
|
|
if (file_name == "") {
|
|
|
|
fl_message_title("Can't reload");
|
|
|
|
fl_alert("The file was never saved.");
|
|
|
|
} else {
|
|
|
|
fl_message_title("Reload file?");
|
|
|
|
if (fl_choice("Reload last save?", "Reload", "Keep", 0) == 0)
|
|
|
|
load_file(file_name.c_str(), false);
|
|
|
|
else
|
|
|
|
top.status1->value("Reloading canceled.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_show_stats(Fl_Widget *w, void *data) {
|
|
|
|
int lines = 0, words = 0, chars = 0;
|
|
|
|
char *t;
|
|
|
|
if (buffer.selected()) {
|
|
|
|
fl_message_title("Selection stats");
|
|
|
|
t = buffer.selection_text();
|
|
|
|
} else {
|
|
|
|
fl_message_title("File statistics");
|
|
|
|
t = buffer.text();
|
|
|
|
}
|
|
|
|
std::stringstream text_buf(t);
|
|
|
|
chars = strlen(t);
|
|
|
|
free(t);
|
|
|
|
std::string each_line;
|
|
|
|
while (std::getline(text_buf, each_line)) {
|
|
|
|
std::stringstream line_buf(each_line);
|
|
|
|
std::string each_word;
|
|
|
|
while (line_buf >> each_word)
|
|
|
|
words++;
|
|
|
|
lines++;
|
|
|
|
}
|
|
|
|
fl_message("Lines: %d\nWords: %d\nCharacters: %d",
|
|
|
|
lines, words, chars);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_quit(Fl_Widget *w, void *data) {
|
|
|
|
if (modified) {
|
|
|
|
fl_message_title("Please confirm");
|
|
|
|
if (fl_choice("Quit ToyEd Native?", "Quit", "Stay", 0) == 0)
|
|
|
|
top.window->hide();
|
|
|
|
} else {
|
|
|
|
top.window->hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_undo(Fl_Widget *w, void *data) {
|
|
|
|
Fl_Text_Editor::kf_undo(0, top.editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_cut(Fl_Widget *w, void *data) {
|
|
|
|
Fl_Text_Editor::kf_cut(0, top.editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_copy(Fl_Widget *w, void *data) {
|
|
|
|
Fl_Text_Editor::kf_copy(0, top.editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_paste(Fl_Widget *w, void *data) {
|
|
|
|
Fl_Text_Editor::kf_paste(0, top.editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_select_all(Fl_Widget *w, void *data) {
|
|
|
|
Fl_Text_Editor::kf_select_all(0, top.editor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void step_search() {
|
|
|
|
const int start = top.editor->insert_position();
|
|
|
|
int found = 0;
|
|
|
|
if (buffer.search_forward(start, search_term.c_str(), &found)) {
|
|
|
|
const int n = found + search_term.length();
|
|
|
|
buffer.select(found, n);
|
|
|
|
top.editor->insert_position(n);
|
|
|
|
top.editor->show_insert_position();
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_find(Fl_Widget *w, void *data) {
|
|
|
|
std::string init;
|
|
|
|
if (buffer.selected()) {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
init = t;
|
|
|
|
free(t);
|
|
|
|
}
|
|
|
|
fl_message_title("Find in file");
|
|
|
|
const char *answer = fl_input("Search term:", init.c_str());
|
|
|
|
if (answer == NULL) {
|
|
|
|
top.status1->value("Search canceled.");
|
|
|
|
} else {
|
|
|
|
search_term = answer;
|
|
|
|
step_search();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_find_next(Fl_Widget *w, void *data) {
|
|
|
|
if (search_term == "")
|
|
|
|
do_find(w, data);
|
|
|
|
else
|
|
|
|
step_search();
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_join_lines(Fl_Widget *w, void *data) {
|
|
|
|
if (buffer.selected()) {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
std::string sel(t);
|
|
|
|
free(t);
|
|
|
|
std::replace(sel.begin(), sel.end(), '\n', ' ');
|
|
|
|
buffer.replace_selection(sel.c_str());
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing selected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_open_line(Fl_Widget *w, void *data) {
|
|
|
|
top.editor->insert("\n");
|
|
|
|
top.editor->move_left();
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_lower_case(Fl_Widget *w, void *data) {
|
|
|
|
if (buffer.selected()) {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
std::string sel(t);
|
|
|
|
free(t);
|
|
|
|
std::transform(sel.cbegin(), sel.cend(), sel.begin(),
|
|
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
buffer.replace_selection(sel.c_str());
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing selected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_title_case(Fl_Widget *w, void *data) {
|
|
|
|
if (buffer.selected()) {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
std::string sel(t);
|
|
|
|
free(t);
|
|
|
|
std::transform(sel.cbegin(), sel.cend(), sel.begin(),
|
|
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
if (sel.length() > 0)
|
|
|
|
sel[0] = std::toupper(sel[0]);
|
|
|
|
buffer.replace_selection(sel.c_str());
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing selected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_upper_case(Fl_Widget *w, void *data) {
|
|
|
|
if (buffer.selected()) {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
std::string sel(t);
|
|
|
|
free(t);
|
|
|
|
std::transform(sel.cbegin(), sel.cend(), sel.begin(),
|
|
|
|
[](unsigned char c) { return std::toupper(c); });
|
|
|
|
buffer.replace_selection(sel.c_str());
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing selected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_prefix_lines(Fl_Widget *w, void *data) {
|
|
|
|
if (buffer.selected()) {
|
|
|
|
fl_message_title(window_title);
|
|
|
|
const char *answer = fl_input("Prefix selected lines with:");
|
|
|
|
if (answer == NULL) {
|
|
|
|
top.status1->value("Prefix canceled");
|
|
|
|
} else {
|
|
|
|
char *t = buffer.selection_text();
|
|
|
|
std::stringstream sel(t);
|
|
|
|
free(t);
|
|
|
|
std::string line;
|
|
|
|
std::string result;
|
|
|
|
while (std::getline(sel, line)) {
|
|
|
|
result += answer;
|
|
|
|
result += line;
|
|
|
|
result += "\n";
|
|
|
|
}
|
|
|
|
buffer.replace_selection(result.c_str());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
top.status1->value("Nothing selected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void toggle_word_wrap(Fl_Widget *w, void *data) {
|
|
|
|
if (word_wrap) {
|
|
|
|
top.editor->wrap_mode(Fl_Text_Editor::WRAP_NONE, 80);
|
|
|
|
word_wrap = false;
|
|
|
|
} else {
|
|
|
|
top.editor->wrap_mode(Fl_Text_Editor::WRAP_AT_BOUNDS, 80);
|
|
|
|
word_wrap = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void toggle_line_numbers(Fl_Widget *w, void *data) {
|
|
|
|
if (top.editor->linenumber_width() > 0)
|
|
|
|
top.editor->linenumber_width(0);
|
|
|
|
else
|
|
|
|
top.editor->linenumber_width(BOX_W/2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void toggle_full_screen(Fl_Widget *w, void *data) {
|
|
|
|
if (top.window->fullscreen_active())
|
|
|
|
top.window->fullscreen_off();
|
|
|
|
else
|
|
|
|
top.window->fullscreen();
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_zoom_in(Fl_Widget *w, void *data) {
|
|
|
|
top.editor->textsize(top.editor->textsize() + 1);
|
|
|
|
top.editor->redisplay_range(0, buffer.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_zoom_out(Fl_Widget *w, void *data) {
|
|
|
|
top.editor->textsize(top.editor->textsize() - 1);
|
|
|
|
top.editor->redisplay_range(0, buffer.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_reset_zoom(Fl_Widget *w, void *data) {
|
|
|
|
top.editor->textsize(base_font_size);
|
|
|
|
top.editor->redisplay_range(0, buffer.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
const Fl_Menu_Item menu_items[] = {
|
|
|
|
{ "&File", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &New", FL_COMMAND + 'n', do_new },
|
|
|
|
{ " &Open...", FL_COMMAND + 'o', do_open },
|
|
|
|
{ " &Save", FL_COMMAND + 's', do_save, 0, FL_MENU_DIVIDER },
|
|
|
|
{ " Save &As... ", 0, do_save_as },
|
|
|
|
{ " &Reload", FL_COMMAND + 'r', do_reload },
|
|
|
|
{ " S&tatistics ", FL_COMMAND + 't', do_show_stats, 0,
|
|
|
|
FL_MENU_DIVIDER },
|
|
|
|
{ " &Quit", FL_COMMAND + 'q', do_quit },
|
|
|
|
{ 0 },
|
|
|
|
{ "&Edit", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &Undo", FL_COMMAND + 'z', do_undo, 0, FL_MENU_DIVIDER },
|
|
|
|
{ " &Cut", FL_COMMAND + 'x', do_cut },
|
|
|
|
{ " C&opy", FL_COMMAND + 'c', do_copy },
|
|
|
|
{ " &Paste", FL_COMMAND + 'v', do_paste, 0, FL_MENU_DIVIDER },
|
|
|
|
{ " Select &all ", FL_COMMAND + 'a', do_select_all },
|
|
|
|
{ " &Find...", FL_COMMAND + 'f', do_find },
|
|
|
|
{ " Find &next ", FL_COMMAND + 'g', do_find_next },
|
|
|
|
{ 0 },
|
|
|
|
{ "For&mat", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &Join lines", FL_ALT + 'j', do_join_lines },
|
|
|
|
{ " &Open line", FL_ALT + 'o', do_open_line, 0,
|
|
|
|
FL_MENU_DIVIDER },
|
|
|
|
{ " &Lower case", FL_ALT + 'l', do_lower_case },
|
|
|
|
{ " &Title case", FL_ALT + 't', do_title_case },
|
|
|
|
{ " &Upper case", FL_ALT + 'u', do_upper_case, 0,
|
|
|
|
FL_MENU_DIVIDER },
|
|
|
|
{ " &Prefix lines... ", FL_ALT + 'p', do_prefix_lines },
|
|
|
|
{ 0 },
|
|
|
|
{ "&View", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &Word wrap", 0, toggle_word_wrap, 0,
|
|
|
|
FL_MENU_TOGGLE | FL_MENU_VALUE },
|
|
|
|
{ " &Line numbers", 0, toggle_line_numbers, 0,
|
|
|
|
FL_MENU_TOGGLE | FL_MENU_DIVIDER },
|
2024-01-23 14:09:39 +00:00
|
|
|
{ " &Bigger font", FL_COMMAND + '=', do_zoom_in },
|
2024-01-22 14:50:33 +00:00
|
|
|
{ " &Smaller font ", FL_COMMAND + '-', do_zoom_out },
|
|
|
|
{ " &Reset font", FL_COMMAND + '0', do_reset_zoom, 0,
|
|
|
|
FL_MENU_DIVIDER },
|
|
|
|
{ " &Full screen", FL_F + 11, toggle_full_screen, 0,
|
|
|
|
FL_MENU_TOGGLE },
|
|
|
|
{ 0 },
|
2024-02-05 08:56:34 +00:00
|
|
|
{ "&Scheme", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &Base", 0, scheme_base, 0, FL_MENU_RADIO },
|
|
|
|
{ " &Gtk+", 0, scheme_gtk, 0, FL_MENU_RADIO },
|
|
|
|
{ " &Plastic", 0, scheme_plastic, 0, FL_MENU_RADIO },
|
|
|
|
{ " Glea&m", 0, scheme_gleam, 0,
|
|
|
|
FL_MENU_RADIO | FL_MENU_VALUE },
|
|
|
|
{ 0 },
|
2024-01-22 14:50:33 +00:00
|
|
|
{ "&Help", 0, 0, 0, FL_SUBMENU },
|
|
|
|
{ " &About", FL_F + 1, show_about },
|
|
|
|
{ " &Credits ", 0, show_credits },
|
|
|
|
{ " &Website ", 0, show_website },
|
|
|
|
{ 0 },
|
|
|
|
{ 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
Fl::scheme("gleam");
|
|
|
|
if (Fl::scrollbar_size() < 18)
|
|
|
|
Fl::scrollbar_size(18);
|
|
|
|
base_font_size = top.editor->textsize();
|
|
|
|
|
|
|
|
top.main_menu->copy(menu_items);
|
|
|
|
|
|
|
|
top.editor->buffer(buffer);
|
|
|
|
buffer.add_modify_callback(on_mod, NULL);
|
|
|
|
|
|
|
|
top.cmd_new->callback(do_new);
|
|
|
|
top.cmd_open->callback(do_open);
|
|
|
|
top.cmd_save->callback(do_save);
|
|
|
|
top.cmd_reload->callback(do_reload);
|
|
|
|
top.cmd_stats->callback(do_show_stats);
|
|
|
|
|
|
|
|
top.cmd_undo->callback(do_undo);
|
|
|
|
top.cmd_find->callback(do_find);
|
|
|
|
top.cmd_next->callback(do_find_next);
|
|
|
|
|
|
|
|
top.window->callback(do_quit);
|
|
|
|
|
|
|
|
if (argc > 1) {
|
|
|
|
if (argv[1][0] == '-') {
|
|
|
|
top.window->show(argc, argv);
|
|
|
|
} else {
|
|
|
|
top.window->show();
|
|
|
|
load_file(argv[1]);
|
|
|
|
}
|
2024-01-22 18:11:27 +00:00
|
|
|
} else {
|
|
|
|
top.window->show();
|
2024-01-22 14:50:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Fl::focus(top.editor);
|
|
|
|
return Fl::run();
|
|
|
|
}
|