Add C++ source

This commit is contained in:
No Time To Play 2024-01-22 14:50:33 +00:00
parent d4adc7df28
commit 7fb94ff145
3 changed files with 562 additions and 0 deletions

17
native/src/Makefile Normal file
View File

@ -0,0 +1,17 @@
CC=g++
CFLAGS=-std=c++11 -O2
LDFLAGS=-lfltk
SRC=main.cpp gui.hpp
OBJ=toyed
all: $(OBJ)
toyed: $(SRC)
$(CC) $(CFLAGS) $(LDFLAGS) -o toyed main.cpp
release: $(OBJ)
strip $(OBJ)
mv $(OBJ) ..
clean:
rm $(OBJ)

103
native/src/gui.hpp Normal file
View File

@ -0,0 +1,103 @@
#ifndef GUI_HPP
#define GUI_HPP
#include <FL/fl_ask.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Pack.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Text_Editor.H>
const int WIN_W = 800;
const int WIN_H = 600;
const int ROW_H = 40;
const int BOX_W = 96;
const int BOX_H = 32;
const char *window_title = "ToyEd text editor";
const char *empty_status = "Built " __DATE__ " " __TIME__;
struct MainWindow {
Fl_Window *window;
Fl_Menu_Bar *main_menu;
Fl_Pack *toolbar;
Fl_Button *cmd_new;
Fl_Button *cmd_open;
Fl_Button *cmd_save;
Fl_Button *cmd_reload;
Fl_Button *cmd_stats;
Fl_Box *spacer;
Fl_Button *cmd_undo;
Fl_Button *cmd_find;
Fl_Button *cmd_next;
Fl_Text_Editor *editor;
Fl_Output *status1;
Fl_Output *status2;
MainWindow() {
window = new Fl_Window(WIN_W, WIN_H, window_title);
main_menu = new Fl_Menu_Bar(4, 4, WIN_W-8, BOX_H);
toolbar = new Fl_Pack(4, ROW_H+4, WIN_W-8, BOX_H);
toolbar->type(Fl_Pack::HORIZONTAL);
cmd_new = new Fl_Button(
0, 0, BOX_W, BOX_H, "@filenew New");
cmd_open = new Fl_Button(
0, 0, BOX_W, BOX_H, "@fileopen Open");
cmd_save = new Fl_Button(
0, 0, BOX_W, BOX_H, "@filesave Save");
cmd_reload = new Fl_Button(
0, 0, BOX_W, BOX_H, "@reload Reload");
cmd_stats = new Fl_Button(
0, 0, BOX_W, BOX_H, "@menu Stats");
spacer = new Fl_Box(0, 0, BOX_W/6+6, BOX_H);
cmd_undo = new Fl_Button(0, 0, BOX_W, BOX_H, "@undo Undo");
cmd_find = new Fl_Button(0, 0, BOX_W, BOX_H, "@search Find");
cmd_next = new Fl_Button(0, 0, BOX_W, BOX_H, "@>> Next");
toolbar->resizable(spacer);
toolbar->end();
editor = new Fl_Text_Editor(
4, ROW_H*2, WIN_W-8, WIN_H - ROW_H * 3);
status1 = new Fl_Output(
4, WIN_H-ROW_H+4, WIN_W/2-8, BOX_H, "");
status1->value(empty_status);
status2 = new Fl_Output(
WIN_W/2+4, WIN_H-ROW_H+4, WIN_W/2-8, BOX_H, "");
window->resizable(editor);
window->end();
editor->wrap_mode(Fl_Text_Editor::WRAP_AT_BOUNDS, 80);
editor->textfont(FL_COURIER);
}
~MainWindow() {
delete window;
}
};
inline void show_about(Fl_Widget *w, void *data) {
fl_message_title("About ToyEd Native");
fl_message("A toy text editor\nv1.0 (22 Jan 2024)\nBoost License");
}
inline void show_credits(Fl_Widget *w, void *data) {
fl_message_title("ToyEd Native credits");
fl_message("Made by No Time To Play\nWith the FLTK library");
}
inline void show_website(Fl_Widget *w, void *data) {
fl_open_uri("https://ctrl-c.club/~nttp/toys/toyed/");
}
#endif

442
native/src/main.cpp Normal file
View File

@ -0,0 +1,442 @@
#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.status1->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("");
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");
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");
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 },
{ " &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 },
{ "&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]);
}
}
Fl::focus(top.editor);
return Fl::run();
}