/*
* ted.c
*
* Tiny Editor in C/GTK3
*
* 2024 asdala.de
*
* Das Programm kann kostenlos kopiert, benutzt und geaendert werden.
* Keine Gewaehr fuer Verwendbarkeit oder Korrektheit, keine Haftung.
*
*/
#include <gtk/gtk.h>
#include <locale.h>
#include <libgen.h>
static const char appdes[] = "Tiny Editor in C/GTK3";
static struct ted { GtkApplication *app; GtkWidget *topwin; GtkWidget *view; char *fname; } ed;
/** Nachrichten **/
static void out(const gchar s[], int msg_type)
{
const gchar *es = (msg_type >= GTK_MESSAGE_WARNING && errno > 0) ? g_strerror(errno) : "";
GtkWidget *dlg = gtk_message_dialog_new(GTK_WINDOW(ed.topwin), GTK_DIALOG_DESTROY_WITH_PARENT,
msg_type, GTK_BUTTONS_CLOSE, "%s\n%s", s, es);
gtk_dialog_run(GTK_DIALOG(dlg));
gtk_widget_destroy(dlg);
}
static void msg(const gchar s[])
{
out(s, GTK_MESSAGE_INFO);
}
static void wrn(const gchar s[])
{
out(s, GTK_MESSAGE_WARNING);
}
static int cnf(const gchar title[])
{
GtkWidget *dlg = gtk_dialog_new_with_buttons(title, GTK_WINDOW(ed.topwin), GTK_DIALOG_DESTROY_WITH_PARENT,
"_Yes", GTK_RESPONSE_YES, "_No", GTK_RESPONSE_NO, "_Cancel", GTK_RESPONSE_CANCEL, NULL);
gint ret = gtk_dialog_run(GTK_DIALOG(dlg));
gtk_widget_destroy(dlg);
return ret;
}
/** Dateidialoge **/
static char *dlg_open(void)
{
char *fname = NULL;
GtkWidget *dlg;
gint ret;
dlg = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(ed.topwin), GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL);
ret = gtk_dialog_run(GTK_DIALOG(dlg));
if (ret == GTK_RESPONSE_ACCEPT) {
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dlg);
fname = gtk_file_chooser_get_filename(chooser);
}
gtk_widget_destroy(dlg);
return fname;
}
static char *dlg_save(void)
{
char *fname = NULL;
GtkWidget *dlg;
gint ret;
dlg = gtk_file_chooser_dialog_new("Save As", GTK_WINDOW(ed.topwin), GTK_FILE_CHOOSER_ACTION_SAVE,
"_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL);
ret = gtk_dialog_run(GTK_DIALOG(dlg));
if (ret == GTK_RESPONSE_ACCEPT) {
GtkFileChooser *chooser = GTK_FILE_CHOOSER(dlg);
fname = gtk_file_chooser_get_filename(chooser);
}
gtk_widget_destroy(dlg);
return fname;
}
/** Dateioperationen **/
static int file_save(void)
{
GtkTextBuffer *tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ed.view));
if (gtk_text_buffer_get_modified(tbuf) == FALSE)
return 1;
GtkTextIter start, end;
if (ed.fname == NULL)
ed.fname = dlg_save();
if (ed.fname == NULL)
return 0;
gtk_text_buffer_get_bounds(tbuf, &start, &end);
char *fbuf = gtk_text_buffer_get_text(tbuf, &start, &end, FALSE);
if (g_file_set_contents(ed.fname, fbuf, -1, NULL) == 0)
wrn(ed.fname);
else
gtk_text_buffer_set_modified(tbuf, FALSE);
g_free(fbuf);
return 1;
}
static int file_close(void)
{
GtkTextBuffer *tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ed.view));
if (gtk_text_buffer_get_modified(tbuf)) {
int ret = cnf("Save changes?");
switch (ret) {
case GTK_RESPONSE_YES: if (file_save() == 0) return 0; break;
case GTK_RESPONSE_NO: break;
default: return 0;
}
}
if (ed.fname) {
g_free(ed.fname);
ed.fname = NULL;
}
return 1;
}
static int file_open(char fname[])
{
char *fbuf = NULL;
gsize flen = 0;
if (file_close() == 0)
return 0;
if (fname) {
if (g_file_get_contents(fname, &fbuf, &flen, NULL) == 0) {
wrn(fname);
return 0;
}
}
if (!g_utf8_validate(fbuf, flen, NULL)) {
errno = 0;
wrn("No valid UTF8 file!");
return 0;
}
gtk_window_set_title(GTK_WINDOW(ed.topwin), fname ? basename(fname) : "New");
ed.fname = g_strdup(fname);
GtkTextBuffer *tbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ed.view));
gtk_text_buffer_set_text(tbuf, fbuf ? fbuf : "", flen);
gtk_text_buffer_set_modified(tbuf, FALSE);
g_free(fbuf);
return 1;
}
/** Menue-Funktionen **/
static void cb_new(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
file_open(NULL);
}
static void cb_save(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
file_save();
}
static void cb_open(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
char *fname = dlg_open();
if (fname == NULL)
return;
file_open(fname);
g_free(fname);
}
static void cb_help(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
msg(appdes);
}
static void cb_about(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
gtk_show_about_dialog(GTK_WINDOW(ed.topwin), "program-name", "ted", "version", "0.1", "logo-icon-name",
"gtk-edit", "website", "http://asdala.de/prg", "comments", appdes, NULL);
}
static void activate_toggle(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
GVariant *state;
state = g_action_get_state(G_ACTION(action));
g_action_change_state(G_ACTION(action), g_variant_new_boolean(!g_variant_get_boolean(state)));
g_variant_unref(state);
}
static void cb_toggle_fullscreen(GSimpleAction *action, GVariant *state, gpointer user_data)
{
gboolean full = g_variant_get_boolean(state);
gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(ed.topwin), !full);
if (full)
gtk_window_fullscreen(user_data);
else
gtk_window_unfullscreen(user_data);
g_simple_action_set_state(action, state);
}
static void cb_quit(GSimpleAction *action, GVariant *parameter, gpointer user_data)
{
gtk_window_close(GTK_WINDOW(ed.topwin));
}
/** Signal-Handler **/
static void on_drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
GtkSelectionData *gtkseldata, guint info, guint time, gpointer data)
{
gchar **uris = gtk_selection_data_get_uris(gtkseldata);
char *fname = g_filename_from_uri(uris[0], NULL, NULL);
if (fname) {
file_open(fname);
g_free(fname);
}
g_strfreev(uris);
gtk_drag_finish(context, TRUE, FALSE, time);
}
static gboolean on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
return file_close() == 0;
}
static void on_activate(GApplication *gapp, gpointer user_data)
{
GtkApplication *app = GTK_APPLICATION(gapp);
GtkWindow *win = gtk_application_get_active_window(app);
gtk_window_present(win);
}
static void on_open(GApplication *gapp, GFile **files, gint n_files, const gchar *hint)
{
GtkApplication *app = GTK_APPLICATION(gapp);
GtkWindow *win = gtk_application_get_active_window(app);
gtk_window_present(win);
gchar *fname = g_file_get_parse_name(files[0]);
file_open(fname);
g_free(fname);
}
static void on_startup(GApplication *gapp, gpointer user_data)
{
GtkApplication *app = GTK_APPLICATION(gapp);
/* Tastaturkuerzel */
struct tacc { const char *action_and_target; const char *accelerators[2]; };
struct tacc acc[] = {
{ "app.new", { "<Control>n", NULL } },
{ "app.open", { "<Control>o", NULL } },
{ "app.save", { "<Control>s", NULL } },
{ "app.quit", { "<Control>q", NULL } },
{ "app.help", { "F1", NULL } },
{ "win.fullscreen", { "F11", NULL } },
};
for (int i = 0; i < G_N_ELEMENTS(acc); ++i)
gtk_application_set_accels_for_action(app, acc[i].action_and_target, acc[i].accelerators);
/* App-Initialisierung */
static const GActionEntry app_actions[] = {
{ "new", cb_new },
{ "open", cb_open },
{ "save", cb_save },
{ "quit", cb_quit },
{ "help", cb_help },
{ "about", cb_about },
};
g_action_map_add_action_entries(G_ACTION_MAP(app), app_actions, G_N_ELEMENTS(app_actions), app);
/* Menue */
GMenu *menu_bar, *menu;
menu_bar = g_menu_new();
menu = g_menu_new();
g_menu_append(menu, "_New", "app.new");
g_menu_append(menu, "_Open", "app.open");
g_menu_append(menu, "_Save", "app.save");
g_menu_append(menu, "_Quit", "app.quit");
g_menu_append_submenu(menu_bar, "_File", G_MENU_MODEL(menu));
g_object_unref(menu);
menu = g_menu_new();
g_menu_append(menu, "_Full Screen", "win.fullscreen");
g_menu_append_submenu(menu_bar, "_View", G_MENU_MODEL(menu));
g_object_unref(menu);
menu = g_menu_new();
g_menu_append(menu, "_Contents", "app.help");
g_menu_append(menu, "_About", "app.about");
g_menu_append_submenu(menu_bar, "_Help", G_MENU_MODEL(menu));
g_object_unref(menu);
gtk_application_set_menubar(app, G_MENU_MODEL(menu_bar));
g_object_unref(menu_bar);
/* Win-Initialisierung */
GtkWidget *win, *view;
win = gtk_application_window_new(app);
g_signal_connect(win, "delete-event", G_CALLBACK(on_delete_event), NULL);
static GActionEntry win_actions[] = {
{ "fullscreen", activate_toggle, NULL, "false", cb_toggle_fullscreen }
};
g_action_map_add_action_entries(G_ACTION_MAP(win), win_actions, G_N_ELEMENTS(win_actions), win);
gtk_window_set_title(GTK_WINDOW(win), "ted");
gtk_window_set_default_icon_name("gtk-edit");
gtk_window_set_default_size(GTK_WINDOW(win), 640, 480);
view = gtk_text_view_new();
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
GtkWidget *scrollwin = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(scrollwin), view);
gtk_container_set_border_width(GTK_CONTAINER(scrollwin), 5);
gtk_container_add(GTK_CONTAINER(win), scrollwin);
gtk_drag_dest_set_target_list(view, NULL);
gtk_drag_dest_add_uri_targets(view);
g_signal_connect(view, "drag-data-received", G_CALLBACK(on_drag_data_received), NULL);
gtk_widget_show_all(GTK_WIDGET(win));
ed.topwin = win;
ed.view = view;
}
int main(int argc, char **argv)
{
int ret;
setlocale(LC_ALL, "");
ed.app = gtk_application_new(NULL, G_APPLICATION_HANDLES_OPEN);
g_signal_connect(ed.app, "startup", G_CALLBACK(on_startup), NULL);
g_signal_connect(ed.app, "activate", G_CALLBACK(on_activate), NULL);
g_signal_connect(ed.app, "open", G_CALLBACK(on_open), NULL);
ret = g_application_run(G_APPLICATION(ed.app), argc, argv);
g_object_unref(ed.app);
return ret;
}