工程传送门
用codeblock写的
https://download.csdn.net/download/renzemingcsdn/19421843
效果
题目
很长一堆英文,题目图片放到文末,主要是大小写字母输入,常规的回车、换行、上下左右、光标移动到行首、行尾、删除整行、删除一个字符,还有撤销重做。
设计思路——命令模式
命令模式的资料一艘一大堆,在此不赘述,涉及撤销重做的,很明显用命令模式。
https://www.jianshu.com/p/1bf9c2c907e8
所有涉及的文件如图所示。
简述程序思路:
初始化图型显示什么的老师已经做好,每次执行命令后,刷新界面,学生需要完成对内容的维护。
NcursesEditorView是对ncurses的二次封装,每次执行刷新命令,调用modle类函数获取光标位置,每行文字等信息,而后执行刷新操作。KeypressInteractionReader是NcursesKeypressReader的上层接口,用于接收用户命令并实例化一个commond类。
Command.hpp是个虚基类,需要继承并补上command_my.cpp。
EditorModel类用于管理文字、错误信息和光标信息,并提供接口用于对文字操作,并返回光标位置和文字信息。
EditorModel中存在一个大循环,用于处理各个命令并保存command实例,通过维护两个栈,实现撤销重做功能。通过异常语法,返回错误信息。
BooEditLog是打印输出的,由于终端被界面占用了,所以向文件打印调试信息,linux里面用tail命令,在另一个终端输出显示。
代码
只列出与命令模式有关的代码。
EditorModel
EditorModel.hpp,维护并返回光标位置、错误信息和文本内容。其中,光标位置为整形,并从1开始;错误信息是一个string,文本信息是个vector,其中每个string代表真实文本的一行。
#ifndef EDITORMODEL_HPP
#define EDITORMODEL_HPP
#include <string>
#include <vector>
class Command;
class EditorModel
{
public:
// Initializes the editor model. Initially, the cursor is on
// line 1 column 1 and there should be one empty line of text
// in the editor.
EditorModel();
// Returns the line number where the cursor currently should reside.
int cursorLine() const;
// Returns the column number where the cursor currently should reside.
int cursorColumn() const;
void cursor_set(int x,int y);
// Returns the number of lines of text currently in the editor.
int lineCount() const;
// Returns the text on the given line number.
const std::string& line(int lineNumber) const;
// Returns the error message that should be displayed currently.
const std::string& currentErrorMessage() const;
// Sets the error message that should be displayed currently.
void setErrorMessage(const std::string& errorMessage);
// Clears the error message that should be displayed currently.
void clearErrorMessage();
// add one letter in current cursor
void add(char c);
void add(std::string s);
// huan hang
void _MJ();
void _K();
void _L();
void _U();
void _O();
void _Y();
void _P();
std::string _D();
char _H();
struct xy now_xy();
private:
// Write declarations for any private member variables here.
// Additionally, you can add any private member functions you'd like.
std::vector<std::string> data;
int cursor_x=1,cursor_y=1;
std::string err_info;
};
#endif
EditorModel.cpp
#include <string>
#include "EditorModel.hpp"
#include "EditorException.hpp"
#include "BooEditLog.hpp"
EditorModel::EditorModel()
{
data.clear();
data.push_back(std::string());
}
int EditorModel::cursorLine() const
{
return cursor_x;
}
int EditorModel::cursorColumn() const
{
return cursor_y;
}
void EditorModel::cursor_set(int x,int y){
cursor_x=x;
cursor_y=y;
}
int EditorModel::lineCount() const
{
return data.size();
}
const std::string& EditorModel::line(int lineNumber) const
{
return data[lineNumber-1];
}
const std::string& EditorModel::currentErrorMessage() const
{
return err_info;
}
void EditorModel::setErrorMessage(const std::string& errorMessage)
{
err_info=errorMessage;
}
void EditorModel::clearErrorMessage()
{
err_info="";
}
void EditorModel::add(char c)
{
data[cursor_x-1].insert(cursor_y-1,std::string(1,c));
cursor_y++;
}
void EditorModel::add(std::string s){
data.insert(data.begin()+cursor_x-1,s);
}
void EditorModel::_MJ(){
std::string tmp;
if(cursor_y==data[cursor_x-1].size()+1)tmp="";
else tmp=data[cursor_x-1].substr(cursor_y-1);
data[cursor_x-1]=data[cursor_x-1].substr(0,cursor_y-1);
data.insert(data.begin()+cursor_x,tmp);
cursor_x++;
cursor_y=1;
}
void EditorModel::_L(){
cursor_x--;
if(cursor_x==0) {
cursor_x=1;
throw(EditorException("up to top"));
}
if(cursor_y>data[cursor_x-1].size()) cursor_y=data[cursor_x-1].size()+1;
}
void EditorModel::_K(){
cursor_x++;
if(cursor_x>data.size()){
cursor_x=data.size();
throw(EditorException("down to button"));
}
if(cursor_y>data[cursor_x-1].size()) cursor_y=data[cursor_x-1].size()+1;
}
void EditorModel::_U(){
if(cursor_y==1&&cursor_x==1){
throw(EditorException("head"));
}else{
cursor_y--;
if(cursor_y==0){
cursor_x--;
cursor_y=data[cursor_x-1].size()+1;
}
}
}
void EditorModel::_O(){
if(cursor_x==data.size() && cursor_y==data[cursor_x-1].size()+1){
throw(EditorException("tail"));
}else{
cursor_y++;
if(cursor_y>data[cursor_x-1].size()+1) {
cursor_y=1;
cursor_x++;
}
}
}
void EditorModel::_Y(){
cursor_y=1;
}
void EditorModel::_P(){
cursor_y=data[cursor_x-1].size()+1;
}
char EditorModel::_H(){
cursor_y--;
if(cursor_y==0){
cursor_x--;
if(cursor_x<=0){
cursor_y=1;
cursor_x=1;
throw(EditorException("no letter"));
return 0;
}else{
cursor_y=data[cursor_x-1].size()+1;
data[cursor_x-1]+=data[cursor_x];
data.erase(data.begin()+cursor_x);
return 1;
}
}else{
char tmp=data[cursor_x-1][cursor_y-1];
data[cursor_x-1].erase(data[cursor_x-1].begin()+cursor_y-1);
return tmp;
}
}
std::string EditorModel::_D(){
std::string tmp;
if(data.size()==1){
tmp=data[0];
data[0]="";
cursor_x=1;
cursor_y=1;
return tmp;
}else{
tmp=data[cursor_x-1];
data.erase(data.begin()+cursor_x-1);
if(cursor_x>data.size())cursor_x=data.size();
if(cursor_y>data[cursor_x-1].size()) cursor_y=data[cursor_x-1].size()+1;
return tmp;
}
}
KeypressInteractionReader
这里接收指令,并生成comman实例,返回给 ,区分输入的是ctrl+?还是纯字母。
KeypressInteractionReader.hpp
#ifndef KEYPRESSINTERACTIONREADER_HPP
#define KEYPRESSINTERACTIONREADER_HPP
#include "InteractionReader.hpp"
#include "KeypressReader.hpp"
class KeypressInteractionReader : public InteractionReader
{
public:
explicit KeypressInteractionReader(KeypressReader& keypressReader)
: keypressReader{keypressReader}
{
}
Interaction nextInteraction() override;
private:
KeypressReader& keypressReader;
};
#endif
KeypressInteractionReader.cpp,节省篇幅,只贴部分,字母输入的部分只写几个。
#include "KeypressInteractionReader.hpp"
#include "BooEditLog.hpp"
#include "commond_my.hpp"
#include "commond_my_1.hpp"
#include <string>
Interaction KeypressInteractionReader::nextInteraction()
{
while (true)
{
Keypress keypress = keypressReader.nextKeypress();
Command* cmd;
if (keypress.ctrl())
{
// The user pressed a Ctrl key (e.g., Ctrl+X); react accordingly
switch (keypress.code())
{
case 'L':
cmd=new Command__L;
return Interaction::command(cmd);
case 'K':
cmd=new Command__K;
return Interaction::command(cmd);
}
}
else
{
// The user pressed a normal key (e.g., 'h') without holding
// down Ctrl; react accordingly
char tmp=keypress.code();
switch (tmp)
{
case 'a':
cmd=new Command_a;
return Interaction::command(cmd);
case 'b':
cmd=new Command_b;
return Interaction::command(cmd);
}
}
}
}
Command
command类的实例调用EditorModel中的函数进行操作,如有错误则抛出异常,在InteractionProcessor中被接收。
Command.hpp
#ifndef COMMAND_HPP
#define COMMAND_HPP
#include "EditorModel.hpp"
class Command
{
public:
virtual ~Command() = default;
virtual void execute(EditorModel& model) = 0;
virtual void undo(EditorModel& model) = 0;
};
#endif
command_my.hpp节约篇幅,只写两个。
#ifndef COMMOND_MY_H_INCLUDED
#define COMMOND_MY_H_INCLUDED
#include <string>
#include "Command.hpp"
#include "EditorModel.hpp"
#include "BooEditLog.hpp"
class Command_a : public Command{
public:
void execute(EditorModel& model) override;
void undo(EditorModel& model) override;
};
class Command_b : public Command{
public:
void execute(EditorModel& model) override;
void undo(EditorModel& model) override;
};
command_my.cpp
#include "commond_my.hpp"
#include "EditorModel.hpp"
void Command_a ::execute(EditorModel& model)
{
model.add('a');
}
void Command_a ::undo(EditorModel& model)
{
model._H();
}
InteractionProcessor
一个大循环,维护两个栈,st_1中记录所有已经命令,用户按下键盘后,返回命令实例,执行execute函数,当发生撤销指令时,st_1栈顶元素弹出,执行undo,并压入st_2中。当发生重做指令时,st_2栈顶弹出,执行excute,并压入st_1。当有新命令来到时,清空st_2。
InteractionProcessor.hpp
#ifndef INTERACTIONPROCESSOR_HPP
#define INTERACTIONPROCESSOR_HPP
#include <stack>
#include "EditorModel.hpp"
#include "EditorView.hpp"
#include "InteractionReader.hpp"
class InteractionProcessor
{
public:
InteractionProcessor(
EditorModel& model, EditorView& view,
InteractionReader& interactionReader)
: model{model}, view{view}, interactionReader{interactionReader}
{
}
void run();
private:
EditorModel& model;
EditorView& view;
InteractionReader& interactionReader;
std::stack<Command*> st_1;
std::stack<Command*> st_2;
};
#endif
InteractionProcessor.cpp
#include <string>
#include "InteractionProcessor.hpp"
#include "EditorException.hpp"
#include "Interaction.hpp"
#include "BooEditLog.hpp"
// This function implements command execution, but does not handle "undo"
// and "redo"; you'll need to add that handling.
void InteractionProcessor::run()
{
view.refresh();
Command* command;
while (true)
{
Interaction interaction = interactionReader.nextInteraction();
if (interaction.type() == InteractionType::quit)
{
booEditLog("InteractionProcessor:do quit");
break;
}
else if (interaction.type() == InteractionType::undo)
{
model.clearErrorMessage();
if(!st_1.empty()){
command=st_1.top();
st_1.pop();
booEditLog("InteractionProcessor:do undo");
command->undo(model);
st_2.push(command);
}
view.refresh();
}
else if (interaction.type() == InteractionType::redo)
{
booEditLog("InteractionProcessor:do redo");
if(!st_2.empty()){
command=st_2.top();
st_2.pop();
command->execute(model);
st_1.push(command);
}
view.refresh();
}
else if (interaction.type() == InteractionType::command)
{
command = interaction.command();
try
{
command->execute(model);
st_1.push(command);
while(!st_2.empty()) st_2.pop();
model.clearErrorMessage();
}
catch (EditorException& e)
{
model.setErrorMessage(e.getReason());
}
view.refresh();
// Note that you'll want to be more careful about when you delete
// commands once you implement undo and redo. For now, since
// neither is implemented, I've just deleted it immediately
// after executing it. You'll need to wait to delete it until
// you don't need it anymore.
}
}
}