summaryrefslogtreecommitdiff
path: root/handlekey.c
diff options
context:
space:
mode:
Diffstat (limited to 'handlekey.c')
-rw-r--r--handlekey.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/handlekey.c b/handlekey.c
new file mode 100644
index 0000000..0d900a3
--- /dev/null
+++ b/handlekey.c
@@ -0,0 +1,624 @@
+/*
+Copyright (c) 2003 Bruno T. C. de Oliveira
+
+LICENSE INFORMATION:
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public
+License along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+Copyright (c) 2002 Bruno T. C. de Oliveira
+
+INFORMAÇÕES DE LICENÇA:
+Este programa é um software de livre distribuição; você pode
+redistribuí-lo e/ou modificá-lo sob os termos da GNU General
+Public License, conforme publicado pela Free Software Foundation,
+pela versão 2 da licença ou qualquer versão posterior.
+
+Este programa é distribuído na esperança de que ele será útil
+aos seus usuários, porém, SEM QUAISQUER GARANTIAS; sem sequer
+a garantia implícita de COMERCIABILIDADE ou DE ADEQUAÇÃO A
+QUALQUER FINALIDADE ESPECÍFICA. Consulte a GNU General Public
+License para obter mais detalhes (uma cópia acompanha este
+programa, armazenada no arquivo COPYING).
+*/
+
+
+#include <stdbool.h>
+#include "bores/bores.h"
+#include <limits.h>
+#include <stdlib.h>
+#include <ncurses.h>
+#include <string.h>
+#include <errno.h>
+#include <pwd.h> /* For getpwuid() */
+#include <unistd.h>
+
+#include "handlekey.h"
+#include "document.h"
+#include "psd.h"
+#include "ui.h"
+#include "keys.h"
+#include "clipboard.h"
+#include "colordlg.h"
+#include "layerdlg.h"
+#include "helpdlg.h"
+#include "chtr.h"
+#include "editmeta.h"
+#include "keybind.h"
+#include "commands.h"
+#include "menubar.h"
+#include "welcomedlg.h"
+#include "filedlg.h"
+
+#define FAST_MOVE_AMOUNT 8 /* how much the cursor moves each time the fast
+ * movent keys (Ctrl+Z, Ctrl+X) are pressed */
+
+/* Interacts with the user in order to allow him to add a new layer to the
+ * document. If <interactive>, asks user for parameters; else just
+ * adds using the defaults. */
+static void u_add_layer(bool interactive) {
+ int width, height; int nom_width, nom_height;
+ char *name; Layer *lyr;
+
+ document_get_nom_dim(_doc, &nom_width, &nom_height);
+ if (interactive) {
+ width = ui_ask_i("NEW LAYER: Enter width",
+ nom_width, 2, INT_MAX); if (ui_cancel) return;
+ height = ui_ask_i("NEW LAYER: Enter height",
+ nom_height, 2, INT_MAX); if (ui_cancel) return;
+ name = ui_ask_s("NEW LAYER: Name", "unnamed"); if (ui_cancel) return;
+ }
+ else {
+ width = nom_width;
+ height = nom_height;
+ name = strdup("unnamed");
+ }
+
+ lyr = layer_create(name, width, height);
+
+ /* if this is the first layer we are adding, make it opaque;
+ * otherwise make it transparent */
+ if (_doc->layer_count) lyr->transp = true;
+ else lyr->transp = false;
+
+ document_insert_layer(_doc, 0, lyr);
+
+ free(name);
+}
+
+/* Interacts with the user in order to allow him to rename the current
+ * layer */
+static void u_rename_layer(void) {
+ Layer *l = _doc->layers[_lyr];
+ char *new_name;
+
+ new_name = ui_ask_s("Rename layer to", l->name);
+ if (!ui_cancel)
+ dstrset(&l->name, new_name);
+
+ if (new_name) free(new_name);
+}
+
+/* Interacts with the user in order to allow him to resize the current
+ * layer */
+static void u_resize_layer(void) {
+ Layer *l = _doc->layers[_lyr];
+ Layer *new_l;
+ int h, w, x, y;
+
+ w = ui_ask_i("RESIZE LAYER: Enter new width",
+ l->width, 2, INT_MAX); if (ui_cancel) return;
+ h = ui_ask_i("RESIZE LAYER: Enter new height",
+ l->height, 2, INT_MAX); if (ui_cancel) return;
+
+ new_l = layer_create(l->name, w, h);
+
+ w = l->width > new_l->width ? new_l->width : l->width;
+ h = l->height > new_l->height ? new_l->height : l->height;
+
+ for (y = 0; y < h; y++)
+ for (x = 0; x < w; x++) {
+ new_l->cells[x][y].ch = l->cells[x][y].ch;
+ new_l->cells[x][y].attr = l->cells[x][y].attr;
+ }
+
+ layer_destroy(l);
+ _doc->layers[_lyr] = new_l;
+}
+
+/* Duplicates current layer, adding a duplicate to the end of the
+ * layer stack. */
+static void u_dup_layer(void) {
+ char buf[32];
+ Layer *l;
+ sprintf(buf, "copy of %d", _lyr);
+
+ l = _doc->layers[_lyr];
+ document_add_layer(_doc, layer_dup(buf, l));
+ switch_to_layer(_doc->layer_count - 1);
+}
+
+/* Return home directory of specified user name.
+ * If name is omitted, returns current user's home dir. */
+char *get_home_dir(char *name) {
+ char msg[128];
+ char *home_dir;
+ struct passwd *pw;
+
+ if(name) {
+ pw = getpwnam(name);
+ if(!pw)
+ goto doesnt_have;
+ return pw->pw_dir;
+ } else {
+ home_dir = getenv("HOME");
+ if(home_dir) {
+ return home_dir;
+ } else {
+ /* $HOME isn't set. Get directory from /etc/passwd. */
+ pw = getpwuid(getuid());
+ if(!pw)
+ goto doesnt_have;
+ return pw->pw_dir;
+ }
+ }
+
+ /* Only reached with goto. */
+doesnt_have:
+ sprintf(msg, "ERROR: User '%s' doesn't have a home directory.", name);
+ ui_message(msg, UIMSG_ERROR);
+ return NULL;
+}
+
+/* Returns filename with '~' replaced with the user's
+ * home directory. */
+static char *expand_tilde(char *filename) {
+ char *home_dir;
+ char *slash;
+
+ if(filename[1]) {
+ char *username, *filename_dup;
+ char *ret;
+
+ filename_dup = strdup(filename);
+ slash = strchr(filename_dup, '/');
+ username = filename_dup + 1;
+
+ if(!slash) {
+ /* Only "~user" was given, use it as a filename. */
+ free(filename_dup);
+ return filename;
+ }
+
+ *slash = '\0';
+
+ if(!strlen(username))
+ username = NULL;
+ home_dir = get_home_dir(username);
+ if(!home_dir)
+ return NULL;
+
+ slash++; /* Everything after the tilde and possible username. */
+ ret = malloc(strlen(home_dir) + strlen(slash) + 1);
+ sprintf(ret, "%s/%s", home_dir, slash);
+ free(filename_dup);
+ return ret;
+ } else {
+ /* Only a tilde was given, use it as a filename. */
+ return filename;
+ }
+}
+
+/* Interacts with the user in order to allow him to load a different
+ * file into the editor. If the <filename> argument is not NULL,
+ * this routine will not ask the user for the filename but will
+ * rather use the supplied filename as the file to load. */
+void u_load_file(const char *supplied_filename) {
+ char *filename;
+ FILE *f = NULL;
+ char msg[128];
+ Document *newdoc;
+
+ if (!supplied_filename) {
+ filename = filedlg_show("Load File");
+ if (!filename) return;
+ }
+ else filename = strdup(supplied_filename);
+
+ if ( !(newdoc = document_load_from(filename)) ) {
+ sprintf(msg, "ERROR: Failed to load file. -- More --");
+ ui_message(msg, UIMSG_ERROR);
+ ui_message(aeff_get_error(), UIMSG_ERROR);
+ goto cleanup;
+ }
+
+ /* now that we have the new document, do the actual replacement */
+ document_destroy(_doc);
+ _doc = newdoc;
+
+ /* set the filename of the loaded file */
+ dstrset(&_filename, filename);
+
+cleanup:
+ if (f) fclose(f);
+ if (filename) free(filename);
+}
+
+/* Interacts with the user allowing him to save current file */
+static void u_save_file(bool forceAsk) {
+ char *filename, *expanded_filename;
+ FILE *tmpf;
+ AeFile *f = 0;
+ char msg[128];
+
+ if (forceAsk || !_filename || !strlen(_filename)) {
+ filename = filedlg_show("Save File As");
+ if (!filename) return;
+ }
+ else filename = strdup(_filename);
+
+ if (filename[0] == '~') {
+ expanded_filename = expand_tilde(filename);
+ if (!expanded_filename) return;
+
+ /* Comparing pointers. */
+ if (expanded_filename != filename) {
+ free(filename);
+ filename = expanded_filename;
+ }
+ }
+
+ if ( _filename && strcmp(_filename, filename) &&
+ (tmpf = fopen(filename, "r")) ) {
+ /* file exists. Ask if user wants to overwrite */
+ fclose(tmpf); tmpf = 0;
+ if (!ui_ask_yn("FILE EXISTS. Overwrite?", 0) || ui_cancel) {
+ ui_message("File was NOT saved!", UIMSG_INFORM);
+ goto cleanup;
+ }
+ }
+
+ if ( !(f = aeff_open(filename, 'w')) ) {
+ sprintf(msg, "ERROR: Can't write to file. %s.", aeff_get_error());
+ ui_message(msg, UIMSG_ERROR);
+ goto cleanup;
+ }
+
+ document_save(_doc, f);
+
+ dstrset(&_filename, filename);
+
+ sprintf(msg, "File saved: %s.", filename);
+ ui_message(msg, UIMSG_INFORM);
+
+cleanup:
+ if (f) aeff_close(f);
+ if (filename) free(filename);
+}
+
+/* erases current selection */
+static void erase_sel() {
+ int x, y;
+ int x0, x1, y0, y1;
+
+ get_norm_sel(&x0, &y0, &x1, &y1);
+ for (x = x0; x <= x1; x++)
+ for (y = y0; y <= y1; y++)
+ _doc->layers[_lyr]->cells[x][y] = BLANK_CELL;
+}
+
+/* if <fg>, sets foreground color of selection to current foreground
+ * color. Else, sets background color of selection to current background. */
+static void tint_sel(bool fg) {
+ int x0, y0, x1, y1, x, y;
+ get_norm_sel(&x0, &y0, &x1, &y1);
+
+ for (x = x0; x <= x1; x++)
+ for (y = y0; y <= y1; y++)
+ if (fg)
+ _doc->layers[_lyr]->cells[x][y].attr =
+ (_fg << 4) | (_doc->layers[_lyr]->cells[x][y].attr & 0x0F);
+ else
+ _doc->layers[_lyr]->cells[x][y].attr =
+ _bg | (_doc->layers[_lyr]->cells[x][y].attr & 0xF0);
+}
+
+/* Remaps character ch to its lgmode equivalent. When in _lgmode == true,
+ * this function is executed for every character typed. */
+static int lgmode_map_ch(int ch) {
+ switch (ch) {
+ case '7': return AEWAN_CHAR_ULCORNER;
+ case '8': return AEWAN_CHAR_TTEE;
+ case '9': return AEWAN_CHAR_URCORNER;
+ case '4': return AEWAN_CHAR_LTEE;
+ case '5': return AEWAN_CHAR_PLUS;
+ case '6': return AEWAN_CHAR_RTEE;
+ case '1': return AEWAN_CHAR_LLCORNER;
+ case '2': return AEWAN_CHAR_BTEE;
+ case '3': return AEWAN_CHAR_LRCORNER;
+ case '-': return AEWAN_CHAR_HLINE;
+ case '|': return AEWAN_CHAR_VLINE;
+ case '0': return AEWAN_CHAR_CKBOARD;
+ default : return ch;
+ }
+}
+
+/* handle special keys for SM_SELECT mode */
+static void sm_select_handle_key(int ch) {
+ int x0, y0, x1, y1;
+ int tmp;
+ get_norm_sel(&x0, &y0, &x1, &y1);
+
+ /* we are in SM_SELECT mode */
+ switch (ch) {
+ case 'm':
+ _selmode = SM_FLOAT;
+ copy_sel_to_clipboard();
+ erase_sel();
+ _x = x0; _y = y0;
+ break;
+ case 'c':
+ _selmode = SM_FLOAT;
+ copy_sel_to_clipboard();
+ _x = x0; _y = y0;
+ break;
+ case 'e':
+ _selmode = SM_NONE;
+ erase_sel();
+ break;
+ case 'f':
+ tint_sel(true);
+ break;
+ case 'b':
+ tint_sel(false);
+ break;
+ case 'o':
+ /* swap the anchor with the cursor position *
+ * that will enable the user to manipulate the "other edge"
+ * of the rectangle */
+ tmp = _x; _x = _ax; _ax = tmp;
+ tmp = _y; _y = _ay; _ay = tmp;
+ break;
+ }
+}
+
+/* handle special keys for SM_FLOAT mode */
+static void sm_float_handle_key(int ch) {
+ switch (ch) {
+ case 's': /* stamp */
+ paste_clipboard();
+ break;
+ case 'x': case 'X': /* flip x */
+ layer_flip_x(_clipboard, ch == 'x');
+ break;
+ case 'y': case 'Y': /* flip y */
+ layer_flip_y(_clipboard, ch == 'y');
+ break;
+ case 't': case 'T': /* toggle transparency */
+ _clipboard->transp = !_clipboard->transp;
+ break;
+ }
+}
+
+/* Warns the user that he will be taken into an editor to edit the
+ * document's meta-info, then fires up the editor */
+void u_edit_meta(void) {
+ int x0, y0, x, y, ch;
+ kurses_color(7, 0);
+ draw_centered_window(40, 5, "Edit Metainfo", &x0, &y0);
+
+ x = x0, y = y0;
+ kurses_move(x, y++);
+ addstr("You will now be taken into an editor");
+ kurses_move(x, y++);
+ addstr("in which you will be able to edit this");
+ kurses_move(x, y++);
+ addstr("document's metainfo (arbitrary text).");
+
+ ch = getch();
+ if (ch == 7 || ch == 27) return;
+
+ edit_metainfo();
+}
+
+static void correct_coords(void) {
+ Layer *lyr;
+ int scr_width = kurses_width();
+ int scr_height = kurses_height();
+
+ if (!_doc->layer_count) return;
+ lyr = _doc->layers[_lyr];
+
+ /* clamp coordinates to bounds */
+ if (_x < 0) _x = 0;
+ if (_x >= lyr->width) _x = lyr->width - 1;
+ if (_y < 0) _y = 0;
+ if (_y >= lyr->height) _y = lyr->height - 1;
+
+ /* now correct scrolling */
+ if (_x >= _svx + scr_width) _svx = _x - scr_width + 1;
+ else if (_x < _svx) _svx = _x;
+ if (_y >= _svy + scr_height - 1) _svy = _y - scr_height + 2;
+ else if (_y < _svy) _svy = _y;
+}
+
+
+void handle_key(int ch) {
+ int command;
+ Layer *lyr;
+
+ /* ENABLE THIS TO DEBUG AEWL... */
+ #if 0
+ if (ch == KEY_F(10)) {
+ debug_aewl();
+ return;
+ }
+ #endif
+
+ /* first check for function keys (they open the menu) */
+ if (ch > KEY_F0 && ch <= KEY_F(12)) {
+ /* show menu and perform command mandated by it */
+ command = menubar_show(ch - KEY_F0 - 1);
+ if (command != COMMAND_UNDEFINED) handle_command(command);
+ return;
+ }
+
+ if ( (command = keybind_translate(ch)) ) {
+ /* key triggers a command: execute command */
+ handle_command(command);
+ if (ch != 27) return; /* we also want to handle 27 (escape) ourselves
+ * below */
+ }
+
+ /* key does not trigger a command; handle it normally if there are
+ * layers in the document; ignore it if there aren't */
+ if (!_doc->layer_count) return;
+ lyr = _doc->layers[_lyr];
+
+ /* interpret character keys for no-selection mode */
+ if (printable_char(ch) && _selmode == SM_NONE) {
+ int ch_to_put = _lgmode ? lgmode_map_ch(ch) : ch;
+
+ if (_insmode) {
+ int x;
+ for (x = lyr->width - 1; x > _x; x--)
+ lyr->cells[x][_y] = lyr->cells[x-1][_y];
+ }
+
+ lyr->cells[_x][_y].ch = ch_to_put;
+ lyr->cells[_x][_y].attr = _fg << 4 | _bg;
+ _x++;
+ }
+
+ /* interpret character keys for SM_SELECT and SM_FLOAT modes */
+ if (_selmode == SM_SELECT) sm_select_handle_key(ch);
+ else if (_selmode == SM_FLOAT) sm_float_handle_key(ch);
+
+ /* interpret the backspace and delete keys, also for no-sel mode */
+ if (_selmode == SM_NONE &&
+ ((_x > 0 && (ch == 8 || ch == KEY_BACKSPACE || ch == 127))
+ || ch == KEY_DC)) {
+ int x;
+ _selmode = false;
+ if (ch != KEY_DC) _x--; /* we know this can't make _x negative, the
+ * condition on the "if" above guarantees that */
+ for (x = _x; x < lyr->width - 1; x++)
+ lyr->cells[x][_y] = lyr->cells[x+1][_y];
+
+ lyr->cells[lyr->width - 1][_y] = BLANK_CELL;
+ }
+
+ correct_coords();
+};
+
+void handle_command(int command) {
+ int newfg, newbg;
+ int ret;
+ Layer *lyr;
+
+ /* handle commands that work regardless of whether document is empty */
+ switch (command) {
+ case COMMAND_QUIT:
+ ret = ui_ask_yn("Really quit aewan?", 0);
+ if (!ui_cancel && ret) exit(0);
+ break;
+ case COMMAND_ADD_LAYER_DEFAULTS:
+ case COMMAND_ADD_LAYER_SPECIFY:
+ u_add_layer(command == COMMAND_ADD_LAYER_SPECIFY);
+ if (!doc_empty()) _lyr = 0; /* now we have a layer, so correct _lyr
+ * so that it makes sense */
+ return;
+ case COMMAND_SET_FOREGROUND:
+ newfg = ui_ask_color("Foreground color");
+ if (!ui_cancel) _fg = newfg;
+ return;
+ case COMMAND_SET_BACKGROUND:
+ newbg = ui_ask_color("Background color");
+ if (!ui_cancel) _bg = newbg;
+ return;
+ case COMMAND_SHOW_COLOR_DLG: show_color_dlg(); return;
+ case COMMAND_TOGGLE_INSERT: _insmode = !_insmode; return;
+ case COMMAND_SHOW_HELP_DLG: show_help_dlg(); return;
+ case COMMAND_SHOW_ABOUT_DLG: show_welcome_dlg(); return;
+ case COMMAND_LOAD_FILE: u_load_file(NULL); return;
+ case COMMAND_SAVE_FILE: u_save_file(false); return;
+ case COMMAND_SAVE_FILE_AS: u_save_file(true); return;
+ case COMMAND_NEW_FILE:
+ if (_doc->layer_count > 0) {
+ ret = ui_ask_yn("Really start a new document?", 0);
+ if (ui_cancel || !ret) break;
+ }
+ document_destroy(_doc);
+ zero_state();
+ return;
+ case COMMAND_EDIT_META: u_edit_meta(); return;
+ }
+
+ /* from this point on, we handle the keys that only work if the document
+ * is not empty */
+ if (doc_empty()) return;
+ lyr = _doc->layers[_lyr];
+
+ /* if clipboard exists and we are not in SM_FLOAT mode, delete it */
+ if (_clipboard && _selmode != SM_FLOAT) clear_clipboard();
+
+ switch (command) {
+ case COMMAND_MOVE_LEFT: _x--; break;
+ case COMMAND_MOVE_RIGHT: _x++; break;
+ case COMMAND_MOVE_UP: _y--; break;
+ case COMMAND_MOVE_DOWN: _y++; break;
+ case COMMAND_CARRIAGE_RETURN: _y++; _x = 0; break;
+ case COMMAND_PAGE_DOWN: _y += kurses_height() - 1; break;
+ case COMMAND_PAGE_UP: _y -= kurses_height() - 2; break;
+ case COMMAND_START_OF_LINE: _x = 0; break;
+ case COMMAND_END_OF_LINE: _x = lyr->width - 1; break;
+ case COMMAND_FAST_RIGHT: _x += FAST_MOVE_AMOUNT; break;
+ case COMMAND_FAST_LEFT: _x -= FAST_MOVE_AMOUNT; break;
+ case COMMAND_PICK_COLOR: /* pick up color from current position */
+ if (_selmode != SM_NONE) break;
+ _fg = lyr->cells[_x][_y].attr >> 4;
+ _bg = lyr->cells[_x][_y].attr & 0x0F;
+ break;
+ case COMMAND_TINT_CELL: /* tint cell under cursor with current color */
+ if (_selmode != SM_NONE) break;
+ lyr->cells[_x][_y].attr = _fg << 4 | _bg;
+ _x++;
+ break;
+
+ case COMMAND_TOGGLE_SELECTION: /* start or stop selection mode */
+ if (_selmode == SM_NONE) {
+ _selmode = SM_SELECT;
+ _ax = _x;
+ _ay = _y;
+ }
+ else _selmode = SM_NONE;
+ break;
+
+ case COMMAND_CANCEL: _selmode = SM_NONE; break;
+ case COMMAND_SHOW_LAYER_DLG:
+ show_layer_dlg(); /* bring up layer manager dialog */
+ /* make sure current layer is still valid */
+ if (_lyr > _doc->layer_count) _lyr = _doc->layer_count - 1;
+ break;
+ case COMMAND_NEXT_LAYER: switch_to_layer(_lyr+1); break;
+ case COMMAND_PREV_LAYER: switch_to_layer(_lyr-1); break;
+ case COMMAND_DUP_LAYER: u_dup_layer(); break;
+ case COMMAND_RENAME_LAYER: u_rename_layer(); break;
+ case COMMAND_RESIZE_LAYER: u_resize_layer(); break;
+
+ case COMMAND_TOGGLE_LINE_MODE: _lgmode = !_lgmode; break;
+ case COMMAND_TOGGLE_COMPOSITE: _compmode = !_compmode; break;
+ }
+
+ correct_coords();
+}
+