Сп. н.
Стрелки завтра, конечно.
Вот и настало завтра.
Выкладываю новую версию, там несколько переработано горизонтальное позиционирование.
Всё вынесено в отдельную функцию, которая вызывается при любом перемещении курсора.
Исходник:
/*
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, see <http://www.gnu.org/licenses/>.
Copyright 2013 inkblack.
*/
#include <ncurses.h>
#include <string.h>
/*
Первая модель текстового редактора.
1. Подразумеваем, что терминал имеет размер 24x80.
ЕСЛИ ЗАПУСТИТЬ В МЕНЬШЕМ ТЕРМИНАЛЕ, БУДЕТ РАБОТАТЬ НЕКОРРЕКТНО!
2. Пока с файлом не совсем все налажено.
3. Ниже задается количество строк в файле и максимальное количество
символов в строке.
*/
#define MAX_ROWS 10
#define MAX_COLS 91
/// Размер окна, в котором будем показывать наш файл.
#define V_ROWS 23
#define V_COLS 78
/// Левый верхний угол этого окна. (0,0) - это левый верхний угол всего экрана.
#define UL_ROW 1
#define UL_COL 1
/// UL_COL я сделал 1, а не 0, проверить корректность работы. Курсор не должен
/// перемещаться левее. Точно так же V_COLS равно 78, справа тоже получается
/// "запретная колонка".
/// Тут же формат строки статуса, поскольку ее длина должна равняться V_COLS ;
/// если V_COLS поменяется, то STAT_FMT тоже надо будет изменить.
/// Результат редактирования пока записываем в файл b.out.out
//char* STAT_FMT = "%s c%i,%i o%i,%i L%i C%i "; - это было для отладки.
char* STAT_FMT = "%-66s L%-4i C%-3i>";
char* OUT_NAME = "b.out.out";
/// Самая верхняя строка экрана оставлена для вывода подскзок и т. п.
/// (т. н. строка статуса).
/// Дальше идет функция, которая управляет горизонтальной координатой курсора
/// и горизонтальным сдвигом файла. Логика несколько мутновата, зато в главной
/// программе стало "чисто и аккуратно", надеюсь.
void movecuro(int* c_col, int* o_horz, int* f_symb, int length) {
int delta;
if (*f_symb > length+1) *f_symb = length+1;
*c_col = *f_symb - 1 + UL_COL - *o_horz;
delta = *c_col - UL_COL;
if (delta < 0) { *c_col -= delta; *o_horz += delta; return; }
if (delta >= V_COLS) { *c_col = UL_COL+V_COLS-1; *o_horz = *f_symb-V_COLS; }
}
int main(void) {
FILE* outfile;
int c_row, c_col, /// Координаты курсора на экране.
f_line, f_symb, /// Координаты курсора в файле (строка, столбец).
o_vert, o_horz; /// Смещение файла в окне редактирования.
/* Если в окне редактирования видна первая строка файла, то o_vert == 0,
а если там видна третья строка и следущие (на 2 больше), а вторая строка
оказалась выше края окна, то o_vert == 2.
Аналогично, o_horz == 0, если видна первая колонка в строке. */
int ch; /// Сюда читаем кнопки, нажатые на клавиатуре.
int i; /// Переменная цикла.
/// Это статический массив, в котором и будет храниться текущее состояние файла
char content[MAX_ROWS][MAX_COLS+1]; /// MAX_COLS симв. и \0 в конце
int len_arr[MAX_ROWS]; /// Здесь записаны длины строк.
char lin_buf[MAX_COLS+1]; /// Этот буфер используем для работы со строкой.
WINDOW* ed_buf; /// Здесь храним изображение всего файла
/// Это виртуальное окно, его часть будет показана
/// на физическом экране.
char stat_lin[V_COLS+1]; /// Строка статуса
////////////////////////////////////////////////////////////////////////////////
/// Начинаем. Стартуем curses, задаем режимы.
initscr(); raw(); keypad(stdscr, TRUE); noecho();
c_row = UL_ROW; /// Курсор на экране - в лев. верх. углу
c_col = UL_COL; /// окна редактирования.
f_line = 1; /// курсор в файле - в первой строке,
f_symb = 1; /// первой колонке.
o_vert = o_horz = 0; /// файл не смещен.
/* Создаем буфер. В этом буфере будет находиться изображение нашего
файла. В цикле заполняем буфер всякой всячиной, или пустыми строками,
в старом варианте были числа, которые соответствуют
номерам строк, чтобы было видно, как файл скроллится.
После всего надо сделать refresh()!
ОБРАТИТЕ внимание! Ширина этого буфера на единицу больше, чтобы курсор
можно было поставить на "конец строки", вот так:
The END._ */
ed_buf = newpad(MAX_ROWS, MAX_COLS+1);
lin_buf[0] = 0;
for (i=1; i<=MAX_ROWS; i++) {
mvwaddstr(ed_buf, i-1, 0, lin_buf);
/* Здесь мы копируем созданную строку в массив строк. В этом массиве
хранится состояние нашего файла, а в ed_buf лежит изображение. */
strcpy(content[i-1], lin_buf); len_arr[i-1] = strlen(lin_buf);
}
refresh();
/* Заполняем строку статуса (подсказка как выйти, номер текущей строки и
колонки) выводим её на экран, ДАЛЕЕ: выводим на нужную часть экрана
нужную часть буфера, где лежит изображение нашего файла.
И в конце обновляем положение курсора. */
sprintf(stat_lin, STAT_FMT, "F10 to exit", f_line, f_symb);
mvwaddstr(stdscr, UL_ROW-1, UL_COL, stat_lin);
prefresh(ed_buf,o_vert,o_horz,UL_ROW,UL_COL,UL_ROW+V_ROWS-1,UL_COL+V_COLS-1);
move(c_row, c_col);
/* Основной цикл: получаем кнопку, которую нажал юзер, если F10, то
заканчиваем цикл и переходим к завершающей части программы.
Дальше:
Пока обрабатываются только стрелки (все), home, end, ASCII-символы,
причем ASCII - в некоторых случаях некорректно. Вверх, вниз пофиксил,
ASCII - надо смотреть, вроде тоже OK. */
while (1) {
ch = wgetch(stdscr);
if (ch == KEY_F(10)) break;
/* Обрабатываем кнопки. В кейсах (case) устанавливаем все переменные,
а после switch выводим результат на экран. */
switch (ch) {
/* Вниз.
1. Если курсор в последней строке файла, то куда дальше вниз? Некуда.
Значит, ничего не надо делать:
break;
2. Если курсор НЕ в самой нижней строке окна редактирования, то его просто
надо подвинуть вниз:
c_row++;
3. А если в самой нижней строке окна, то он должен физически остаться
на том же месте, а файл надо подвинуть на строчку вверх:
o_vert++;
4. В случаях 2 и 3 курсор перемещается в файле на одну строку вниз:
f_line++; */
case KEY_DOWN:
if (f_line == MAX_ROWS) break;
if (c_row < UL_ROW+V_ROWS-1) c_row++;
else o_vert++;
f_line++;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/* Вверх - всё аналогично. */
case KEY_UP:
if (f_line == 1) break;
if (c_row > UL_ROW) c_row--;
else o_vert--;
f_line--;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/* Влево. */
case KEY_LEFT:
if (f_symb == 1) break;
// if (c_col > UL_COL) c_col--;
// else o_horz--;
f_symb--;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/* Вправо.
Тут есть один момент. Курсор может находиться правее последнего символа.
Вот так:
Period is the last symbol in this line._
курсор ^
Поэтому: f_symb > MAX_COLS, а не f_symb == MAX_COLS.
А теперь еще изменение: вместо MAX_COLS будет len_arr[f_line-1].
Курсор не может быть правее, чем в позиции сразу за концом строки. */
case KEY_RIGHT:
if (f_symb > len_arr[f_line-1] /*MAX_COLS*/) break;
// if (c_col < UL_COL+V_COLS-1) c_col++;
// else o_horz++;
f_symb++;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/* В начало строки. */
case KEY_HOME:
if (f_symb == 1) break;
// c_col = UL_COL;
// o_horz = 0;
f_symb = 1;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/* В конец строки. Это посложнее, чем home.
Здесь надо немного переделать. Если место после конца строки и так видно
на экране, не надо менять o_horz. */
case KEY_END:
if (f_symb > len_arr[f_line-1]) break; /// Это тоже не совсем понятно.
f_symb = len_arr[f_line-1]+1;
// c_col = UL_COL+f_symb-1;
// if (c_col>=UL_COL+V_COLS) {
// o_horz = f_symb - V_COLS;
// c_col -= o_horz;
// }
// o_horz = 0;
// f_symb = 1;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
break;
/// case ...:
/// break;
/* PG_UP / PG_DN пока делать не буду... */
/* Обрабатываем весь остальной ввод. Пока что игнорируем всё, что меньше
пробела (32) и больше тильды (126) */
default:
if (ch<' ' || ch>'~') break;
/* Вставляем символ в соответствующее место строки. Если строка получается
слишком длинная, то никакие действия не производятся. Другие проверки
пока не делаются. */
if (len_arr[f_line-1] >= MAX_COLS) break;
strcpy(lin_buf+f_symb,content[f_line-1]+f_symb-1);
lin_buf[f_symb-1]=ch;
strcpy(content[f_line-1]+f_symb-1,lin_buf+f_symb-1);
++len_arr[f_line-1];
mvwaddstr(ed_buf, f_line-1, f_symb-1, lin_buf+f_symb-1);
f_symb++;
movecuro(&c_col, &o_horz, &f_symb, len_arr[f_line-1]);
// if (c_col < UL_COL+V_COLS-1) c_col++;
// else o_horz++;
// f_symb++;
break;
}
/* Наконец, после обработок:
- обновляем информацию о видимой части изображения файла, оно могло
подвинуться вверх или вниз, или вправо, или влево,
- создаем строку статуса, там мог измениться номер строки / колонки,
- определяем эту строку на законное место,
- задаем координаты курсора на экране,
- выводим всё на экран:
refresh(); */
prefresh(ed_buf,o_vert,o_horz,UL_ROW,UL_COL,UL_ROW+V_ROWS-1,UL_COL+V_COLS-1);
sprintf(stat_lin, STAT_FMT, "F10 to exit", f_line, f_symb);
mvwaddstr(stdscr, UL_ROW-1, UL_COL, stat_lin);
move(c_row, c_col);
refresh();
/* Цикл while заканчивается, и всё по новой... */
}
/* Завершающая часть программы.
Здесь задаются режимы работы терминала, потом работа curses завершается. */
echo(); keypad(stdscr, FALSE); cbreak(); endwin();
/// Выводим результат редактирования; ошибки при открытии и записи не проверяем:
outfile = fopen(OUT_NAME,"w");
for (i=0; i<MAX_ROWS; i++) {
fprintf(outfile, "%s\n", content[i]);
}
return 0;
}
Чуть позже выложу объяснение, что это за чудо-функция movecuro.
Пользователь решил продолжить мысль 27 Апреля 2013, 18:27:28:
Вот функция movecuro (move cursor & set offset):
void movecuro(int* c_col, int* o_horz, int* f_symb, int length) {
int delta;
if (*f_symb > length+1) *f_symb = length+1;
*c_col = *f_symb - 1 + UL_COL - *o_horz;
delta = *c_col - UL_COL;
if (delta < 0) { *c_col -= delta; *o_horz += delta; return; }
if (delta >= V_COLS) { *c_col = UL_COL+V_COLS-1; *o_horz = *f_symb-V_COLS; }
}
Просто приведу пример ее работы. Предположим:
UL_COL = 3;
V_COLS = 11;
o_horz = 6;
f_symb = 12;
c_col = 8;
А.
Файл такой:
123456789-123456789-1234\n 24 симв.
123456789-12345\n 15 симв.
12\n 2 симв.
Б.
12345678901234567890123456 — колонки в файле.
.................
123...789-1234567...1234 24 симв. | строки в файле.
123...789-12345 ... 15 симв. | курсор в 12 позиции.
12 ... ... 2 симв. | эта строка вообще не видна.
... ...
... ...
... ...
.................
1 2
01234567890123456789012 — колонки на экране.
В.
12345678901234567890123456 — колонки в файле.
.................
...3456789-123...789-1234 24 симв. | строки в файле.
...3456789-123... 15 симв. |
..._ ... 2 симв. | эта строка вообще не видна,
... ... курсор в 3 позиции.
... ...
... ...
.................На рис.
А. показан файл, в первой строке 24 символа, потом 15 и 2.
На рис.
Б. показано, как это может выглядеть в редакторе.
Синим выделено то, что нам реально видно в этот момент. Курсор находится во 2-й строке,
12 колонке.
Если мы нажмем стрелку вниз, должно получиться то, что на рис.
В.Что происходит, когда мы нажимаем стрелку вниз?
f_line становится равным 3.
Вызывается функция movecuro. Ей передаются:
c_col = 8 (да, позиция курсора на экране равна именно 8)
o_horz = 6
f_symb = 12
length = 2 (да, длина третьей строки, той, на которую перешли)
Курсор был в 12 колонке файла. Но наша строка короче, поэтому
курсор переходит в максимально правую позицию, она равна 3.
Теперь смотрим, где должен оказаться курсор на экране.
В позиции UL_COL, но правее на столько, какова его позиция
в файле минус 1. И настолько левее, насколько сдвинут файл
по горизонтали (непонятно, да?) Это как раз и получается
UL_COL + (*f_symb - 1) - *o_horz. Это равно 3 + (3-1) - 6 = -1
А теперь посмотрим, на сколько отличается вычисленная позиция курсора
от самой левой колонки окна редактирования. Это delta = *c_col - UL_COL.
delta = -1 - 3. получается -4.
Если delta<0, значит, курсор оказался левее, чем ему можно находиться.
Поэтому двигаем весь файл вправо, как раз на delta, и курсор на столько же.
Получаем f_symb =
3, c_col = -1 + 4, это
3, o_horz = 6 - 4 = 2.
Итак, в разобранном случае f_symb, c_col и o_horz получили новые правильные
значения.
Теперь рассмотрим другой случай: в положении
Б. нажимаем стрелку вверх.
курсор должен сдвинуться на 1 позицию вверх, f_symb, c_col и o_horz
поменяться не должны.
Смотрим:
f_line становится равным 1.
Вызывается функция movecuro. Ей передаются:
c_col = 8
o_horz = 6
f_symb = 12
length = 24 (длина первой строки, той, на которую перешли)
Курсор был в 12 позиции, а максимально возможная — 25. Поэтому ничего
никуда не двигаем.
Теперь вычисляем c_col.
3 + (12-1) - 6 = 8. Ну, так и было.
delta получается 8 - 3 = 5
Это больше 0, то есть курсор должен быть правее левой границы окна.
Теперь проверим, не слишком ли далеко он сдвинут вправо:
если delta равна ширине окна редактирования или больше, то слишком.
Но в нашем случае это не так. Поэтому c_col и o_horz не меняются (в последней строке функции).
Итого, в данном случае movecuro возвращает назад после проверок то, что ей передали:
c_col = 8, o_horz = 6, f_symb = 12.
А теперь нажмем END. Позиция курсора в файле должна стать равной длине текущей строки
плюс 1. Потом вызывается movecuro с параметрами:
c_col = 8
o_horz = 6
f_symb = 25
length = 24 (длина первой строки, той, на которую перешли)
f_symb сравниваем c length+1. Они равны.
Находим c_col: 3 + 25-1 - 6, получается 21, т. е., курсор должен оказаться в 21 колонке
экрана.
Это действительно так, посмотрите на схеме. Проверяем дальше.
delta получается 21 - 3, это 18. И это
больше, чем V_COLS, (=11).
Тогда выполняются действия в последней строке функции: положение курсора на экране —
правый край окна редактирования, соответственно задается и o_horz.
Пользователь решил продолжить мысль 27 Апреля 2013, 19:01:06:
Сделал Delete и Backspace, но пока немного некорректно работают.
— Всё хорошо, поправил. Но с Enter всё сложнее, сразу сейчас не сделаю.