toyed/native/src/main.cpp

477 lines
12 KiB
C++

#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>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Text_Display.H>
#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;
top.status3->value("(modified)");
}
}
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);
top.status2->value("");
top.status3->value("");
modified = false;
}
void load_file(const char *name, bool change=true) {
fl_message_title(window_title);
switch (buffer.loadfile(name)) {
case 0:
top.status1->value("Opened");
if (change) {
file_name = name;
top.status2->value(fl_filename_name(name));
}
top.status3->value("");
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:
top.status1->value("Saved");
if (change) {
file_name = name;
top.status2->value(fl_filename_name(name));
}
top.status3->value("");
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 do_number_lines(Fl_Widget *w, void *data) {
if (buffer.selected()) {
char *t = buffer.selection_text();
std::stringstream sel(t);
free(t);
std::string line;
std::string result;
int n = 0;
while (std::getline(sel, line)) {
n++;
result += std::to_string(n);
result += ". ";
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 },
{ " &Number lines ", FL_ALT + 'n', do_number_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 },
{ " &Bigger font", FL_COMMAND + '=', do_zoom_in },
{ " &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 },
{ "&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 },
{ "&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]);
}
} else {
top.window->show();
}
Fl::focus(top.editor);
return Fl::run();
}