Найти в Дзене

Рукоблудим на С++! Ч.2

И так, в серии этих статей мы занимаемся созданием несложных приложений на простом языке. С предыдущей частью вы можете ознакомиться по ссылке, а сейчас я бы хотел немного подкорректировать формат статей. Главным в любом деле является, в первую очередь, наличие знаний. И хоть я объясняю все свои действия на довольно простом языке, вы не сможете реализовать всё тоже, не зная азов. Поэтому в этой и последующих статьях я буду вводить некоторые пояснения, используя некоторые профессиональные названия, например(Тип данных) и буду подобным образом сразу прикреплять ссылку на видеоролик, в котором компетентные люди объясняют значение этих терминов. Также я бы хотел обратить ваше внимание на то, что вы не должны точь в точь повторять мой стиль написания, ибо, во-первых, любой lead меня съест и не подавится за такое корявое оформление(всё-таки я ещё учусь и далёк от той самой истинны), а, во-вторых, не следует в начале пути задумываться об оформлении. Стоит больше внимания уделять именно языку и реализации. Поэтому вам я не просто советую писать всё вместе, не вынося всё в разные классы и функции, я это рекомендую. В дальнейшем, вы сами заметите, как меняется ваше оформление. В моём случае единственным и главным классом является - MainObj, в нём хранится весь текст, написанный нами, а также другие нужные параметры.

Давайте же приступим к искусству, в общем понимание, разумеется! После некоторого времени зашёл я обратно в свой проект и увидел вот это:

Плохой программист: крадётся.
Плохой программист: крадётся.

Куча условий, постоянная лапша при обращение к переменным, в общем, помогите. Поэтому я принял решение стереть эту часть кода, а она отвечала за вывод текста в консольку, обработку enter и backspace. Но увы, в дальнейшем, у меня могут возникнуть проблемы с беглым чтением своих сочинений на языке C++, поэтому я решил доработать некоторые моменты. Поработал с методами классов, написал функции отдельные. В общем, привёл всё в полагающий вид. Вам же рекомендую не реализовывать ни первой, ни второй подход. И реализовать всё самостоятельно. И да, забыл про некоторую штуку. Для того, чтоб консоль не ждала ввода я использовал _kbhit(), данная вещь возвращает истину, когда произведён ввод, в противном случае ложь. То есть через обычный if(_kbhit()) - мы уже можем отлавливать нажатия и консоль не будет ждать ввода, достаточно лишь поставить это условие в бесконечный цикл, в котором мы будем делать что-то, пока не будет произведён ввод с клавиатуры. В самом же условии мы будем использовать не стандартный ввод данных, а _getch(), данная штука не нуждается в том, чтоб мы после ввода нажали enter, а сразу считывает первую же нажатую клавишу, это то нам и нужно. И так, разобрались с отловом. Далее находим в интернете коды ascii для кнопок escape, enter, backspace, стрелочек: вправо, влево, вниз, вверх. Чтоб в дальнейшем, замечая их код, не вводить их код(да, да, код, код, код, код, но я всё-таки программист, а не лингвист), а обрабатывать действие, например, переход вверх по тексту, когда нажимается стрелочка вверх. Реализуем обычный вывод текста, с запоминанием. Запоминать будем с помощью массива строк. Далее организуем лёгкий перенос строки(то есть нажатие enter только в конце самой последней строки).

Кусок кода для тех, кто шарит. Новички, не сильно зацикливайтесь на этом примере, стройте обработку нажатий по-своему.
Кусок кода для тех, кто шарит. Новички, не сильно зацикливайтесь на этом примере, стройте обработку нажатий по-своему.

Согласитесь, сейчас код стал выглядеть намного лаконичнее и красивее. За кадром я реализовал функцию, которая рисует... М... Палочка или курсор это, кому как удобнее, в общем.

Приблизительно такой результат мы получили. И, записав, данный фрагмент, я действительно нашёл ошибку в коде. Проблема заключается в том, что, оказывается, у стрелочек код двойной. То есть сначала на ввод подаётся один код, а затем второй. И всё бы хорошо, отследить первый код и на его основе решать: ждать ввод второго или это всё-таки это не стрелочки и следует дальше обрабатывать нажатие. Но вот незадача) Оказалось, что первый код стрелочек: -32 - соответствует коду маленькой русской букве "а", поэтому так просто код не отловишь. Да и второй код для каждой буквы соответствовал некоторым заглавным английским. Например, стрелочка вниз имела второй код - 80, такой же код имеет вот такая буква "P"(английская). Так что нужно было применять какой-то действительно хитрый способ отлова. Эта проблема заставила меня встать, сделать чаёк и хорошо подумать. Решение пришло незамедлительно. На самом деле, вводя что-либо, мы делаем большие временные пробелы между первым символом и вторым, поэтому в функции, проверяющей что было нажато, мы можем поставить ещё один if(_kbhit()), то есть если было последовательно быстро введены 2 символа, то это означает в точности то, что был введён двойной код стрелочек, а значит можно просто соединить 2 кода в один. При чём, может показаться, что это довольно костыльный метод решения, ибо мы забыли о том, что человек может зажать букву, например. Тогда по логике половина этих букв будет уничтожена. На самом деле да, но это не критично, ибо человек, зажимающий букву, не будет высчитывать количество этих букв. А так как код обработки нажатий выполняется довольно шустро(раз в одну тысячную секунды), то и задержка между появлением зажатой буквы будет настолько мала, что вы даже под пристальным наблюдением ничего не поймёте. Такие вот дела:) И так, мы реализовали считывание кнопок "управления", организовали ввод и его запоминание, а также смастерили лёгкий переход строки. Сейчас же хочется сделать 2 вещи:

  1. Создать функцию, которая будет неким образом изменять строку, когда мы будем делать перенос строки не в конце текущей, а где-нибудь посередине. То есть в этом случае часть строки после курсора перенесётся на следующую строчку вместе с курсором, а все строки, находящиеся под текущей, должны быть смещены на одну строку вниз.
  2. Создать функцию, которая будет обновлять консоль, чтоб изменение , произошедшие со строками - отобразились и в консоли тоже.

Начнём с первого: в массив строк в нужную нам часть(в позицию после текущей строки) вставим пустую строку. После вырежем содержимое в текущей строке после курсора и поместим его в эту пустую строку. Всё, более ничего тута делать не нужно.

В pos хранится координата нашего курсора. Y - строка, X - позиция элемента в строке(столбец)
В pos хранится координата нашего курсора. Y - строка, X - позиция элемента в строке(столбец)

Опять же выше показанный код лишь демонстрирует пример. Всё это можно делать без функций, правда получится не очень симпатично, но работать будет)

Честно говоря, написал функцию изменение строк я быстро и с первой попытки всё заработало без ошибок. А вот с обновлением консоли пришлось попотеть. Ибо моей задачей было создать не костыльную функцию для конкретной задачи, а функциональность, которая по заданным координатам обновляет нужную часть пространства.

Тем самым я упрощу следующий пункт создания блокнота, о котором поговорим позже. И так, всё довольно просто. Берём координаты начала, координаты конца, сначала обновляем и закидываем символы из уже готовой строки, после дочищаем ненужные символы.

Как-то так.
Как-то так.

Так как строчка "Файл", "Справка" занимают позицию по Y = 0, то в случае, если поступает такая стартовая координата, мы обновляем всё верхнее поле. За это отвечает второе условие. Это нам может понадобиться тогда, когда мы решим сделать меню не просто статичной графикой, а рабочим. Тогда у нас оно будет разворачиваться примерно так:

Это из оригинального блокнота Windows.
Это из оригинального блокнота Windows.

То есть по идее наше окно будет перекрывать записи в консоли, а также менять цвет надписи, как на картинке. А значит после того, как мы это окно закроем, мы помимо текста должны будем также восстановить исходное состояние панели с надписями: убрать цвет, заново отрисовать разделяющую линию и так далее, думаю, механизм понятен.

За кадром реализовали стрелочки, которые лишь перемещают текущую координату, сложного ничего нет.

Вроде бы очень даже легко.
Вроде бы очень даже легко.

Но не совсем похоже на то, что в настоящем блокноте и вот почему - если в оригинальном блокноте мы не можем перемещаться по тем полям, где нет текста, то в нашем случае очень даже можем:

Нужно править...
Нужно править...

То есть мы свободно уехали туда, где быть не желательно - в пустые поля и при попытке вписать туда, не зная куда, ловим исключение(ошибку). Но такое поведение ожидаемо, ибо функция ввода вставляет символ либо в начало новой строки, либо в конец, начало или середину существующей. А тут мы пытаемся в пустоту символ воткнуть. Но это легко исправить! Для этого нам потребуется лишь ограничить передвижение. Мы должны сказать программе, что она не может шагать туда, где нет строки, за исключением конца существующей. Для стрелки вверх можем ничего не делать, ибо она кроме как в существующую, никуда не попадёт. Разве что не дать ей на меню улететь.

Вот таким образом.
Вот таким образом.

Также код на обозрения только тех, кто хорошо знает язык С++. Если вы новичок, то просто поглощайте способ решения ошибок и придумывайте решение самостоятельно на основе знаний основ языка. Без знания основ языка ни один гайд, ни один пример вам не поможет написать проект. Будьте терпеливы и вы обязательно сможете разрабатывать всё, что хотите.

Так, идём дальше. Теперь реализуем ещё одну фичу, как в блокноте. А именно возврат к концу строки. То есть если вы находитесь в строке, размер который больше той, к которой вы перемещаетесь(стрелкой вверх или вниз), то курсор должен вернуться к концу это меньшей строки. Даже не то, что делаем фичу, скорее убираем баг:

Опять исключение.
Опять исключение.

Всё потому, что вроде разрешено переместиться вниз, ибо нижняя строка - не пустая, но она пустая в том месте, куда мы спустились, поэтому ловим ошибку. Нужно, как я и говорил, вернуться к концу маленькой строки и тогда ошибка растает.

Вот так выглядит финальная правка стрелочек.
Вот так выглядит финальная правка стрелочек.

Несколько строчек дополнительного кода, как показано выше и:

Собственно мы закончили со стрелочками.

Вернёмся на несколько шагов назад, к реализации сложных сносок строк. Мы можем грамотно перемещаться по строкам, а значит нужно добавить возможность дописывать к строке что-либо.

Теперь мы не выводим символ напрямую. Мы вставляем его в нужно часть строки, а после запускаем ранее созданную функцию (refresh) где координатой начала будет позиция курсора до записи символа, а конечной координатой будет конец обновлённой строки, то есть строки, в которой уже присутствует введённый символ. Теперь мы проверим результат:

Ура, всё работает! Перенос строки и добавления символа в строку работают как надо. Я доволен.

Практически полностью сделав пространство для текста, я понял, что забыл реализовать backspace... Нужно это исправить. Для этого будем брать нужную строку, убирать из неё элемент, написанный до курсора(то, что хотим стереть). И запустим функцию refresh. А если курсор в начале строки, то переместим его в конец строки, которая находится выше текущей.

После создания методов для моего класса, стало очень легко и быстро создавать новые фичи.
После создания методов для моего класса, стало очень легко и быстро создавать новые фичи.

Собственно теперь я могу сказать, что основные инструменты для манипуляции с текстом созданы! Из неосновных осталось:

  1. Сделать возможность копировать и вставлять текст(самая сложная часть)
  2. Реализовать возможность переходить по гиперссылкам(что на самом деле делается в пару кликов, но моё сознание уже сопротивляется написанию кода).
  3. Возможность выделения текста через зажатый ctrl.

И так, на данный момент мы имеем практически готовый блокнот, общее количество строк кода: 284. Я думал, что уже на этом этапе будет больше, но нет, всё довольно лаконично получилось. Если у вас, дорогой читатель, получится больше - не переживайте! Главное, чтоб всё работало)

В следующей части статьи я доделаю весь неосновной инструментарий своего блокнота(подумаю также над тем, чего нет в оригинальном блокноте, но что хочется видеть в моём, например, гиперссылки). Также планирую сделать окошко для разделов "Файл", "Справка". А вот сам обозреватель я, думаю, буду разделять на несколько частей статьи, ибо его сделать почти также тяжело, как весь блокнот, что не удивительно. Да и я даже сейчас не знаю, как он будет выглядеть - не думал над макетом, так сказать. А на данный момент я заканчиваю вторую часть статьи, желаю вам хорошего настроения, крепкого здоровья и железного терпения, если вы уж решились повторить мой проект)

В заключении текущий результат:

Если вам понравилась статья, оцените её лайком и подпиской на канал. Также не забывайте оставлять комментарии с вопросами и пожеланиями: все они будут читаться и на каждый я буду давать ответ.