540 lines
11 KiB
C++
540 lines
11 KiB
C++
/*
|
|
* A textbase class implementation
|
|
* Nana C++ Library(http://www.nanapro.org)
|
|
* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com)
|
|
*
|
|
* Distributed under the Boost Software License, Version 1.0.
|
|
* (See accompanying file LICENSE_1_0.txt or copy at
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* @file: nana/gui/widgets/skeletons/textbase.hpp
|
|
* @description: This class manages the multi-line text and provides some operation on text
|
|
*/
|
|
|
|
#ifndef NANA_GUI_WIDGET_DETAIL_TEXTBASE_HPP
|
|
#define NANA_GUI_WIDGET_DETAIL_TEXTBASE_HPP
|
|
|
|
#include <nana/charset.hpp>
|
|
#include <nana/basic_types.hpp>
|
|
#include <nana/traits.hpp>
|
|
#include "textbase_export_interface.hpp"
|
|
|
|
#include <deque>
|
|
#include <memory>
|
|
#include <fstream>
|
|
#include <stdexcept>
|
|
|
|
namespace nana
|
|
{
|
|
namespace widgets
|
|
{
|
|
namespace skeletons
|
|
{
|
|
template<typename CharT>
|
|
class textbase
|
|
: public ::nana::noncopyable
|
|
{
|
|
public:
|
|
typedef CharT char_type;
|
|
typedef std::basic_string<CharT> string_type;
|
|
typedef typename string_type::size_type size_type;
|
|
|
|
textbase()
|
|
{
|
|
attr_max_.reset();
|
|
//Insert an empty string for the first line of empty text.
|
|
text_cont_.emplace_back();
|
|
}
|
|
|
|
void set_event_agent(textbase_event_agent_interface * evt)
|
|
{
|
|
evt_agent_ = evt;
|
|
}
|
|
|
|
bool empty() const
|
|
{
|
|
return (text_cont_.empty() ||
|
|
((text_cont_.size() == 1) && (text_cont_[0].empty())));
|
|
}
|
|
|
|
bool load(const char* file_utf8)
|
|
{
|
|
if (!file_utf8)
|
|
return false;
|
|
|
|
std::ifstream ifs(to_osmbstr(file_utf8));
|
|
if (!ifs)
|
|
return false;
|
|
|
|
ifs.seekg(0, std::ios::end);
|
|
std::size_t bytes = static_cast<std::size_t>(ifs.tellg());
|
|
ifs.seekg(0, std::ios::beg);
|
|
|
|
if(bytes >= 2)
|
|
{
|
|
int ch = ifs.get();
|
|
if(0xEF == ch && bytes >= 3)
|
|
{
|
|
//UTF8
|
|
ch = ifs.get();
|
|
if(0xBB == ch && 0xBF == ifs.get())
|
|
{
|
|
ifs.close();
|
|
return load(file_utf8, nana::unicode::utf8);
|
|
}
|
|
}
|
|
else if(0xFF == ch)
|
|
{
|
|
if(0xFE == ifs.get())
|
|
{
|
|
//UTF16,UTF32
|
|
if(bytes >= 4)
|
|
{
|
|
if(ifs.get() == 0 && ifs.get() == 0)
|
|
{
|
|
ifs.close();
|
|
return load(file_utf8, nana::unicode::utf32);
|
|
}
|
|
}
|
|
ifs.close();
|
|
return load(file_utf8, nana::unicode::utf16);
|
|
}
|
|
}
|
|
else if(0xFE == ch)
|
|
{
|
|
if(ifs.get() == 0xFF)
|
|
{
|
|
//UTF16(big-endian)
|
|
ifs.close();
|
|
return load(file_utf8, nana::unicode::utf16);
|
|
}
|
|
}
|
|
else if(0 == ch)
|
|
{
|
|
if(bytes >= 4 && ifs.get() == 0)
|
|
{
|
|
ch = ifs.get();
|
|
if(0xFE == ch && ifs.get() == 0xFF)
|
|
{
|
|
//UTF32(big_endian)
|
|
ifs.close();
|
|
return load(file_utf8, nana::unicode::utf32);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ifs.clear();
|
|
ifs.seekg(0, std::ios::beg);
|
|
|
|
text_cont_.clear(); //Clear only if the file can be opened.
|
|
attr_max_.reset();
|
|
|
|
std::string str_mbs;
|
|
while(ifs.good())
|
|
{
|
|
std::getline(ifs, str_mbs);
|
|
text_cont_.emplace_back(nana::charset(str_mbs));
|
|
if(text_cont_.back().size() > attr_max_.size)
|
|
{
|
|
attr_max_.size = text_cont_.back().size();
|
|
attr_max_.line = text_cont_.size() - 1;
|
|
}
|
|
}
|
|
|
|
_m_saved(file_utf8);
|
|
return true;
|
|
}
|
|
|
|
static void byte_order_translate_2bytes(std::string& str)
|
|
{
|
|
char * s = const_cast<char*>(str.c_str());
|
|
char * end = s + str.size();
|
|
for(; s < end; s += 2)
|
|
{
|
|
char c = *s;
|
|
*s = *(s + 1);
|
|
*(s + 1) = c;
|
|
}
|
|
}
|
|
|
|
static void byte_order_translate_4bytes(std::string& str)
|
|
{
|
|
char * s = const_cast<char*>(str.c_str());
|
|
char * end = s + str.size();
|
|
for(; s < end; s += 4)
|
|
{
|
|
char c = *s;
|
|
*s = *(s + 3);
|
|
*(s + 3) = c;
|
|
|
|
c = *(s + 1);
|
|
*(s + 1) = *(s + 2);
|
|
*(s + 2) = c;
|
|
}
|
|
}
|
|
|
|
bool load(const char* file_utf8, nana::unicode encoding)
|
|
{
|
|
if (!file_utf8)
|
|
return false;
|
|
|
|
std::ifstream ifs(to_osmbstr(file_utf8));
|
|
|
|
if (!ifs)
|
|
return false;
|
|
|
|
std::string str;
|
|
bool big_endian = true;
|
|
|
|
if(ifs.good())
|
|
{
|
|
text_cont_.clear(); //Clear only if the file can be opened.
|
|
attr_max_.reset();
|
|
|
|
std::getline(ifs, str);
|
|
|
|
std::size_t len_of_BOM = 0;
|
|
switch(encoding)
|
|
{
|
|
case nana::unicode::utf8:
|
|
len_of_BOM = 3; break;
|
|
case nana::unicode::utf16:
|
|
len_of_BOM = 2; break;
|
|
case nana::unicode::utf32:
|
|
len_of_BOM = 4; break;
|
|
default:
|
|
throw std::runtime_error("Specified a wrong UTF");
|
|
}
|
|
|
|
big_endian = (str[0] == 0x00 || str[0] == char(0xFE));
|
|
str.erase(0, len_of_BOM);
|
|
if(big_endian)
|
|
{
|
|
if(nana::unicode::utf16 == encoding)
|
|
byte_order_translate_2bytes(str);
|
|
else
|
|
byte_order_translate_4bytes(str);
|
|
}
|
|
|
|
text_cont_.emplace_back(nana::charset(str, encoding));
|
|
|
|
attr_max_.size = text_cont_.back().size();
|
|
attr_max_.line = 0;
|
|
}
|
|
|
|
while(ifs.good())
|
|
{
|
|
std::getline(ifs, str);
|
|
|
|
if(big_endian)
|
|
{
|
|
if(nana::unicode::utf16 == encoding)
|
|
byte_order_translate_2bytes(str);
|
|
else
|
|
byte_order_translate_4bytes(str);
|
|
}
|
|
|
|
text_cont_.emplace_back(nana::charset(str, encoding));
|
|
if(text_cont_.back().size() > attr_max_.size)
|
|
{
|
|
attr_max_.size = text_cont_.back().size();
|
|
attr_max_.line = text_cont_.size() - 1;
|
|
}
|
|
}
|
|
|
|
_m_saved(file_utf8);
|
|
return true;
|
|
}
|
|
|
|
void store(std::string fs, bool is_unicode, ::nana::unicode encoding) const
|
|
{
|
|
std::ofstream ofs(to_osmbstr(fs), std::ios::binary);
|
|
if(ofs && text_cont_.size())
|
|
{
|
|
std::string last_mbs;
|
|
|
|
if (is_unicode)
|
|
{
|
|
const char * le_boms[] = { "\xEF\xBB\xBF", "\xFF\xFE", "\xFF\xFE\x0\x0" }; //BOM for little-endian
|
|
int bytes = 0;
|
|
switch (encoding)
|
|
{
|
|
case nana::unicode::utf8:
|
|
bytes = 3; break;
|
|
case nana::unicode::utf16:
|
|
bytes = 2; break;
|
|
case nana::unicode::utf32:
|
|
bytes = 4; break;
|
|
}
|
|
|
|
if (bytes)
|
|
ofs.write(le_boms[static_cast<int>(encoding)], bytes);
|
|
|
|
if (text_cont_.size() > 1)
|
|
{
|
|
std::string mbs;
|
|
for (auto i = text_cont_.cbegin(), end = text_cont_.cend() - 1; i != end; ++i)
|
|
{
|
|
std::string(nana::charset(*i).to_bytes(encoding)).swap(mbs);
|
|
mbs += "\r\n";
|
|
ofs.write(mbs.c_str(), static_cast<std::streamsize>(mbs.size()));
|
|
}
|
|
}
|
|
|
|
last_mbs = nana::charset(text_cont_.back()).to_bytes(encoding);
|
|
}
|
|
else
|
|
{
|
|
if (text_cont_.size() > 1)
|
|
{
|
|
for (auto i = text_cont_.cbegin(), end = text_cont_.cend() - 1; i != end; ++i)
|
|
{
|
|
std::string mbs = nana::charset(*i);
|
|
ofs.write(mbs.c_str(), mbs.size());
|
|
ofs.write("\r\n", 2);
|
|
}
|
|
}
|
|
last_mbs = nana::charset(text_cont_.back());
|
|
}
|
|
ofs.write(last_mbs.c_str(), static_cast<std::streamsize>(last_mbs.size()));
|
|
_m_saved(std::move(fs));
|
|
}
|
|
}
|
|
|
|
size_type lines() const
|
|
{
|
|
return text_cont_.size();
|
|
}
|
|
|
|
const string_type& getline(size_type pos) const
|
|
{
|
|
if(pos < text_cont_.size())
|
|
return text_cont_[pos];
|
|
|
|
return nullstr_;
|
|
}
|
|
|
|
std::pair<size_t, size_t> max_line() const
|
|
{
|
|
return std::make_pair(attr_max_.line, attr_max_.size);
|
|
}
|
|
public:
|
|
void replace(size_type pos, string_type && text)
|
|
{
|
|
if(text_cont_.size() <= pos)
|
|
{
|
|
text_cont_.emplace_back(std::move(text));
|
|
pos = text_cont_.size() - 1;
|
|
}
|
|
else
|
|
text_cont_[pos].swap(text);
|
|
|
|
_m_make_max(pos);
|
|
_m_edited();
|
|
}
|
|
|
|
void insert(upoint pos, string_type && str)
|
|
{
|
|
if(pos.y < text_cont_.size())
|
|
{
|
|
string_type& lnstr = text_cont_[pos.y];
|
|
|
|
if(pos.x < lnstr.size())
|
|
lnstr.insert(pos.x, str);
|
|
else
|
|
lnstr += str;
|
|
}
|
|
else
|
|
{
|
|
text_cont_.emplace_back(std::move(str));
|
|
pos.y = static_cast<unsigned>(text_cont_.size() - 1);
|
|
}
|
|
|
|
_m_make_max(pos.y);
|
|
_m_edited();
|
|
}
|
|
|
|
void insertln(size_type pos, string_type&& str)
|
|
{
|
|
if(pos < text_cont_.size())
|
|
text_cont_.emplace(text_cont_.begin() + pos, std::move(str));
|
|
else
|
|
text_cont_.emplace_back(std::move(str));
|
|
|
|
_m_make_max(pos);
|
|
_m_edited();
|
|
}
|
|
|
|
void erase(size_type line, size_type pos, size_type count)
|
|
{
|
|
if (line < text_cont_.size())
|
|
{
|
|
string_type& lnstr = text_cont_[line];
|
|
if ((pos == 0) && (count >= lnstr.size()))
|
|
lnstr.clear();
|
|
else
|
|
lnstr.erase(pos, count);
|
|
|
|
if (attr_max_.line == line)
|
|
_m_scan_for_max();
|
|
|
|
_m_edited();
|
|
}
|
|
}
|
|
|
|
bool erase(size_type pos, std::size_t n)
|
|
{
|
|
//Bounds checking
|
|
if ((pos >= text_cont_.size()) || (0 == n))
|
|
return false;
|
|
|
|
if (pos + n > text_cont_.size())
|
|
n = text_cont_.size() - pos;
|
|
|
|
text_cont_.erase(text_cont_.begin() + pos, text_cont_.begin() + (pos + n));
|
|
|
|
if (pos <= attr_max_.line && attr_max_.line < pos + n)
|
|
_m_scan_for_max();
|
|
else if (pos < attr_max_.line)
|
|
attr_max_.line -= n;
|
|
|
|
_m_edited();
|
|
return true;
|
|
}
|
|
|
|
void erase_all()
|
|
{
|
|
std::deque<string_type>().swap(text_cont_);
|
|
attr_max_.reset();
|
|
text_cont_.emplace_back(); //text_cont_ must not be empty
|
|
|
|
_m_saved(std::string());
|
|
}
|
|
|
|
void merge(size_type pos)
|
|
{
|
|
if(pos + 1 < text_cont_.size())
|
|
{
|
|
text_cont_[pos] += text_cont_[pos + 1];
|
|
text_cont_.erase(text_cont_.begin() + (pos + 1));
|
|
_m_make_max(pos);
|
|
if(pos < attr_max_.line)
|
|
--attr_max_.line;
|
|
|
|
_m_edited();
|
|
}
|
|
}
|
|
|
|
const std::string& filename() const
|
|
{
|
|
return filename_;
|
|
}
|
|
|
|
bool edited() const
|
|
{
|
|
return changed_;
|
|
}
|
|
|
|
void edited_reset()
|
|
{
|
|
changed_ = false;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
filename_.clear();
|
|
changed_ = false;
|
|
}
|
|
|
|
bool saved() const
|
|
{
|
|
return ! not_saved();
|
|
}
|
|
|
|
bool not_saved() const
|
|
{
|
|
return edited() || filename_.empty();
|
|
}
|
|
private:
|
|
void _m_make_max(std::size_t pos)
|
|
{
|
|
const string_type& str = text_cont_[pos];
|
|
if(str.size() > attr_max_.size)
|
|
{
|
|
attr_max_.size = str.size();
|
|
attr_max_.line = pos;
|
|
}
|
|
}
|
|
|
|
void _m_scan_for_max()
|
|
{
|
|
attr_max_.size = 0;
|
|
std::size_t n = 0;
|
|
for(auto & s : text_cont_)
|
|
{
|
|
if(s.size() > attr_max_.size)
|
|
{
|
|
attr_max_.size = s.size();
|
|
attr_max_.line = n;
|
|
}
|
|
++n;
|
|
}
|
|
}
|
|
|
|
void _m_first_change() const
|
|
{
|
|
if (evt_agent_)
|
|
evt_agent_->first_change();
|
|
}
|
|
|
|
void _m_saved(std::string && filename) const
|
|
{
|
|
if(filename_ != filename)
|
|
{
|
|
filename_ = std::move(filename);
|
|
_m_first_change();
|
|
}
|
|
else if(changed_)
|
|
_m_first_change();
|
|
|
|
changed_ = false;
|
|
}
|
|
|
|
void _m_edited()
|
|
{
|
|
if(!changed_)
|
|
{
|
|
_m_first_change();
|
|
changed_ = true;
|
|
}
|
|
|
|
if (evt_agent_)
|
|
evt_agent_->text_changed();
|
|
}
|
|
private:
|
|
std::deque<string_type> text_cont_;
|
|
textbase_event_agent_interface* evt_agent_{ nullptr };
|
|
|
|
mutable bool changed_{ false };
|
|
mutable std::string filename_; //A string for the saved filename.
|
|
const string_type nullstr_;
|
|
|
|
struct attr_max
|
|
{
|
|
std::size_t line;
|
|
std::size_t size;
|
|
|
|
void reset()
|
|
{
|
|
line = 0;
|
|
size = 0;
|
|
}
|
|
}attr_max_;
|
|
};
|
|
|
|
}//end namespace detail
|
|
}//end namespace widgets
|
|
}//end namespace nana
|
|
#endif
|