Add C++ source
This commit is contained in:
parent
d4adc7df28
commit
7fb94ff145
|
@ -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)
|
|
@ -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
|
|
@ -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();
|
||||
}
|
Loading…
Reference in New Issue