Alf P. Steinbach
unread,May 13, 2016, 8:46:43 PM5/13/16You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to
I've only implemented this for Windows, not for Unixland, and it's been
some time since I used e.g. ncurses. The intent is to support the simple
kind of stuff that Indian students now use Turbo C++ in DOSBox for. E.g.
snake game, that kind of thing, what we once did on IBM PC.
So, how practical/impractical is it wrt. implementation for Unix-land,
e.g. in terms of ncurses? I'm thinking, maybe ncurses can't turn off
automatic line wrapping or automatic scrolling, or maybe all necessary
info isn't available. Or maybe (I seem to remember) different colors?
Other comments?
Cheers!,
- Alf
PS: Sorry for the pasted code rather than more easily extracted
attachments, but the Eternal September server didn't like the
attachments. It mumbled something about "binary". Maybe Thunderbird
encoded as base64, I couldn't find anything about it in options.
<file reusable/console/text_screen.impl.windows.hpp>
#pragma once
// Copyright © 2016 Alf P. Steinbach
#include "text_screen.decl.hpp"
#ifndef _WIN32
# error This header is only for Windows.
#endif
#include "text_screen.impl.windows/winapi.hpp"
#include <assert.h> // assert
#include <bitset> // std::bitset
#include <stdlib.h> // abort, atexit, at_quick_exit
#include <io.h> // _open_osfhandle, _dup2
#include <fcntl.h> // _O_WRONLY
namespace console {
using cppx::Contiguous_enumeration;
using cppx::hopefully;
using cppx::fail;
using cppx::invoke;
using cppx::length;
using cppx::operator<<;
using cppx::Ptr_;
using cppx::String;
using cppx::trace;
using std::bitset;
namespace impl{
inline auto screen_buffer_to_restore()
-> Ref_<winapi::Console_handle>
{
static winapi::Console_handle the_buffer_handle = 0;
return the_buffer_handle;
}
inline void set_default_screen_buffer_to_restore()
{
assert( screen_buffer_to_restore() == 0 );
screen_buffer_to_restore() = winapi::original_screen_buffer();
}
inline void restore_screen_buffer()
{
if( screen_buffer_to_restore() != 0 )
{
winapi::SetConsoleActiveScreenBuffer(
screen_buffer_to_restore() )
|| fail( "restore_screen_buffer:
SetConsoleActiveScreenBuffer failed" );
screen_buffer_to_restore() = 0;
}
}
inline void set_automatic_buffer_restore_on_some_bad_happenings()
{
// This is just a best effort to restore the console on
program termination.
// A crash will bypass all these measures, probably leaving
this particular
// console window unusable (it can still be closed, but
that's all).
static const bool dummy = invoke( []() -> bool
{
// console signal handler:
static const auto ctrl_handler = [](
const winapi::Console_signal::Enum signal
) -> winapi::Bool
{
switch( signal )
{
case winapi::Console_signal::ctrl_c:
case winapi::Console_signal::ctrl_break:
restore_screen_buffer();
break;
default: {}
}
return false; // Invoke next or default handler.
};
winapi::SetConsoleCtrlHandler( ctrl_handler, true );
// exit handler:
atexit( &restore_screen_buffer );
// quick exit handler:
#if CPPX_STDLIB_HAS_QUICK_EXIT
at_quick_exit( &restore_screen_buffer );
#endif
// terminate handler:
static const std::terminate_handler old_handler =
std::get_terminate();
std::set_terminate( []()
{
restore_screen_buffer();
const auto handler = old_handler;
handler? handler() : abort();
} );
return false;
} );
(void) dummy;
}
const bool
call_set_automatic_buffer_restore_on_some_bad_happenings =
(set_automatic_buffer_restore_on_some_bad_happenings(), true);
} // namespace impl
inline Color::Color( const Colorbit::Enum bitnum )
: value( 1 << bitnum )
{}
class Display::Impl
: public Non_copyable
, public Non_movable
{
private:
using Api_info = impl::winapi::Console_screen_buffer_info;
using Api_info_ex = impl::winapi::Console_screen_buffer_info_ex;
using Api_handle = impl::winapi::Console_handle;
struct Text_buffer
: Non_copyable
, Non_movable
{
Api_handle handle;
~Text_buffer()
{ impl::winapi::CloseHandle( handle ); }
Text_buffer()
: handle( impl::winapi::make_screen_buffer() )
{}
};
class Settings
: public Non_copyable
, public Non_movable
{
public:
struct Item_id_values
{
enum Enum
{
size, position, attributes, window_rect,
max_window_size, _
};
};
using Item_id = Contiguous_enumeration<Item_id_values>;
private:
const Api_handle text_buffer_handle_;
bitset<Item_id::n_values()> stale_items_;
Api_info_ex api_settings_ex_ = { sizeof(
Api_info_ex ) };
CPPX_STATIC_ASSERT( offsetof( Api_info_ex, struct_size ) ==
0 );
// Coord dwSize;
// Coord dwCursorPosition;
// Word wAttributes;
// Small_rect srWindow;
// Coord dwMaximumWindowSize;
public:
void update_api_info()
{
trace( L"Settings::update_api_info" );
impl::winapi::GetConsoleScreenBufferInfo(
text_buffer_handle_, &api_settings_ex_.data
)
|| fail( "GetConsoleScreenBufferInfo failed" );
stale_items_.reset();
}
void update_stale_api_info_items()
{
if( stale_items_.count() > 0 )
{
// TODO: optimize?
update_api_info();
}
}
auto api_info()
-> Ref_<const Api_info>
{
update_stale_api_info_items();
return api_settings_ex_.data;
}
auto mutable_api_info()
-> Ref_<Api_info>
{
update_stale_api_info_items();
return api_settings_ex_.data;
}
void invalidate( const Item_id::Enum which )
{ stale_items_.set( which ); }
void validate( const Item_id::Enum which )
{ stale_items_.reset( which ); }
void make_real( Ref_<const Api_info> info )
{
Api_info_ex new_info = api_settings_ex_;
new_info.data = info;
//trace( String() << L"1: " << info.srWindow.right <<
L", " << info.srWindow.bottom );
impl::winapi::SetConsoleScreenBufferInfoEx(
text_buffer_handle_, &new_info
)
|| fail( "SetConsoleScreenBufferInfoEx failed" );
update_api_info();
// auto& info2 = api_settings_ex_.data;
// trace( String() << L"2: " << info2.srWindow.right <<
L", " << info2.srWindow.bottom );
}
Settings( const Api_handle h )
: text_buffer_handle_( h )
{
impl::winapi::GetConsoleScreenBufferInfoEx(
text_buffer_handle_, &api_settings_ex_
)
|| fail( "GetConsoleScreenBufferInfoEx failed" );
stale_items_.reset();
}
};
struct Text_buffer_activation
: Non_copyable
, Non_movable
{
~Text_buffer_activation()
{ impl::restore_screen_buffer(); }
Text_buffer_activation(
const impl::winapi::Console_handle text_buffer_handle
)
{
impl::set_default_screen_buffer_to_restore();
const bool activated =
!!impl::winapi::SetConsoleActiveScreenBuffer(
text_buffer_handle
);
if( not activated )
{
impl::screen_buffer_to_restore() = 0;
fail( "Display::<init>:
SetActiveConsoleScreenBuffer failed" );
}
}
};
// State:
Text_buffer text_buffer_;
mutable Settings settings_;
Text_buffer_activation text_buffer_activation_;
public:
auto size() const
-> Point
{
auto const pt = settings_.api_info().dwSize;
return Point{ pt.x, pt.y };
}
auto window_size() const
-> Point
{
const auto r = settings_.api_info().srWindow;
return Point{ 1 + r.right - r.left, 1 + r.bottom - r.top };
}
void gotoxy( const Point pos )
{
// TODO: optimize by deferring?
namespace api = impl::winapi;
const api::Coord coord = {short( pos.x ), short( pos.y )};
impl::winapi::SetConsoleCursorPosition(
text_buffer_.handle, coord )
|| fail( "gotoxy: SetConsoleCursorPosition failed" );
settings_.validate( Settings::Item_id::position ); //
Avoid update.
settings_.mutable_api_info().dwCursorPosition = coord;
}
auto textcolor() const
-> Color
{ return Color( settings_.api_info().wAttributes & 0x0F ); }
void set_textcolor( const Color c )
{
auto colorbits = settings_.api_info().wAttributes;
colorbits = static_cast<impl::winapi::Word>( colorbits &
0xF0 | c.value );
impl::winapi::SetConsoleTextAttribute( text_buffer_.handle,
colorbits )
|| fail( "set_textcolor: SetConsoleTextAttribute failed" );
settings_.mutable_api_info().wAttributes = colorbits;
settings_.validate( Settings::Item_id::attributes );
}
auto bgcolor() const
-> Color
{ return Color( settings_.api_info().wAttributes >> 4 ); }
void set_bgcolor( const Color c )
{
auto colorbits = settings_.api_info().wAttributes;
colorbits = static_cast<impl::winapi::Word>( colorbits &
0x0F | (c.value << 4) );
impl::winapi::SetConsoleTextAttribute( text_buffer_.handle,
colorbits )
|| fail( "set_textcolor: SetConsoleTextAttribute failed" );
settings_.mutable_api_info().wAttributes = colorbits;
settings_.validate( Settings::Item_id::attributes );
}
void write( Ref_<const String> s )
{
namespace api = impl::winapi;
const int n = length( s );
if( n == 0 ) { return; }
settings_.invalidate( Settings::Item_id::position );
api::Double_word n_written = 0;
api::WriteConsoleW( text_buffer_.handle, s.data(), n,
&n_written, nullptr )
|| fail( "Display::write: WriteConsoleW failed" );
assert( int( n_written ) == n );
}
~Impl()
{}
Impl( const Point size )
: text_buffer_()
, settings_( text_buffer_.handle )
, text_buffer_activation_( text_buffer_.handle )
{
{ // with
Api_info params = settings_.api_info();
params.dwSize = {short( size.x ), short( size.y )};
params.dwCursorPosition = {};
//screen.wAttributes = bitsForBgFg( // TODO:
// The window size specified here is 1 too large, to
avoid scrollbars
// appearing when the original window is smaller in any
dimension. It
// works in Windows 7. Just a workaround based on
undocumented behavior.
params.srWindow = {0, 0, short( size.x ), short( size.y )};
params.dwMaximumWindowSize = params.dwSize;
settings_.make_real( params );
}
namespace api = impl::winapi;
using Flag = api::Output_flags;
api::set_output_mode(
text_buffer_.handle, Flag::processed_output &
~Flag::wrap_at_eol
);
{
api::Cursor_info params = { 1, false };
api::SetConsoleCursorInfo( text_buffer_.handle, ¶ms )
|| fail( "Display::<init>: SetConsoleCursorInfo
failed" );
}
// Using the following code (it's not called here) causes
delayed
// bluescreen in Windows 7. Possibly a guaranteed cleanup
may fix it.
const auto text_buffer_handle = text_buffer_.handle;
(void) [text_buffer_handle]() -> void
{
const int fd = _open_osfhandle(
reinterpret_cast<intptr_t>( text_buffer_handle ),
_O_WRONLY
);
hopefully( fd != -1 )
|| fail( "console::Display::<init>: _open_osfhandle
failed" );
const int dup_result = _dup2( fd, _fileno( stdout ) );
// _close( fd ); // ! DON'T CLOSE this. Or, possible
bluescreen.
hopefully( dup_result != -1 )
|| fail( "console::Display::<init>: _dup2 failed" );
};
}
};
inline auto Display::size() const -> Point
{ return p_impl_->size(); }
inline auto Display::position() const -> Point
{ return {}; } // TODO:
inline void Display::gotoxy( const Point pos )
{ return p_impl_->gotoxy( pos ); }
inline auto Display::textcolor() const -> Color
{ return p_impl_->textcolor(); }
inline void Display::set_textcolor( const Color c )
{ p_impl_->set_textcolor( c ); }
inline auto Display::bgcolor() const -> Color
{ return p_impl_->bgcolor(); }
inline void Display::set_bgcolor( const Color c )
{ p_impl_->set_bgcolor( c ); }
inline void Display::write( Ref_<const String> s )
{ p_impl_->write( s ); }
void Display::clear()
{
// TODO: optimize?
clear_by_writing_spaces();
}
inline Display::~Display()
{ delete p_impl_; }
inline Display::Display(
Ref_<const String> title,
const Point size
)
: p_impl_( new Impl( size ) )
{
impl::winapi::SetConsoleTitleW( title.c_str() )
|| fail( "SetConsoleTitle failed" );
}
} // namespace console
</file>
<file reusable/cppx/basics.hpp>
#pragma once
// Copyright © 2016 Alf P. Steinbach
//
// Configuration macro symbols:
// • CPPX_NO_IOSTREAMS Don't drag in the iostreams header,
please.
// • CPPX_STDLIB_HAS_QUICK_EXIT Force assumption that
std::quick_exit exists.
// • NDEBUG Standard meaning (here: suppress
tracing).
#include <assert.h> // assert
#ifndef CPPX_NO_IOSTREAMS
# include <iostream> // std::(wistream, wostream, wcin, wcout,
wcerr, endl)
#endif
#include <locale.h> // setlocale, LC_ALL
#include <stddef.h> // EXIT_FAILURE, EXIT_SUCCESS, ptrdiff_t
#include <stdexcept> // std::(exception, runtime_error)
#include <string> // std::(string, wstring)
#include <type_traits> // std::underlying_type
#include <wctype.h> // towupper
#if !defined( CPPX_STDLIB_HAS_QUICK_EXIT )
# if defined( __GNUC__ )
# if defined( _GLIBCXX_HAVE_AT_QUICK_EXIT )
# define CPPX_STDLIB_HAS_QUICK_EXIT _GLIBCXX_HAVE_AT_QUICK_EXIT
# else
# define CPPX_STDLIB_HAS_QUICK_EXIT 0
# endif
# else
# define CPPX_STDLIB_HAS_QUICK_EXIT 1
# endif
#endif
#define CPPX_STATIC_ASSERT( e ) static_assert( e, #e " // <- Is required!" )
#define CPX_WITH_BITLEVEL_OPS \
using U = std::underlying_type_t< Enum >; \
\
friend auto operator|( const Enum a, const Enum b ) \
-> Enum \
{ return Enum( U( a ) | U( b ) ); } \
\
friend auto operator&( const Enum a, const Enum b ) \
-> Enum \
{ return Enum( U( a ) & U( b ) ); } \
\
friend auto operator~( const Enum a ) \
-> Enum \
{ return Enum( ~U( a ) ); }
// General reusable support:
namespace cppx {
using std::endl;
using std::runtime_error;
using Size = ptrdiff_t;
template< class Type >
using Ref_ = Type&; // To write `Ref_<Sometype>` instead of
`Sometype&`.
template< class Type >
using Rvref_ = Type&&; // To write `Rvref_<Sometype>` instead of
`Sometype&&`.
template< class Type >
using Ptr_ = Type*; // To write `Ptr_<Sometype>` instead of
`Sometype*`.
template< class Values_wrapper >
struct Contiguous_enumeration: Values_wrapper
{
static constexpr auto n_values() -> int { return
Values_wrapper::_; }
};
class Non_copyable
{
private:
Non_copyable( Ref_<const Non_copyable> ) = delete;
auto operator=( Ref_<const Non_copyable> ) ->
Ref_<Non_copyable> = delete;
public:
auto operator=( Rvref_<Non_copyable> )
-> Ref_<Non_copyable>
{ return *this; }
Non_copyable() {}
Non_copyable( Rvref_<Non_copyable> ) {}
};
class Non_movable
{
private:
Non_movable( Rvref_<Non_movable> ) = delete;
auto operator=( Rvref_<Non_movable> ) -> Ref_<Non_movable> =
delete;
public:
auto operator=( Ref_<const Non_movable> )
-> Ref_<Non_movable>
{ return *this; }
Non_movable() {}
Non_movable( Ref_<const Non_movable> ) {}
};
template< class Func >
inline auto invoke( Ref_<const Func> f )
-> decltype( f() )
{ return f(); }
inline auto hopefully( const bool condition )
-> bool
{ return condition; }
inline auto fail( Ref_<const std::string> s )
-> bool
{ throw runtime_error( s ); }
struct Sys_text
{
using Char = wchar_t;
using String = std::wstring;
};
using Char = Sys_text::Char;
using String = Sys_text::String;
# ifndef CPPX_NO_IOSTREAMS
struct Sys
: Sys_text
{
using In_stream = std::wistream;
using Out_stream = std::wostream;
Ref_<In_stream> in = std::wcin;
Ref_<Out_stream> out = std::wcout;
Ref_<Out_stream> err = std::wcerr;
};
const Sys sys{};
inline auto line_from( Ref_<Sys::In_stream> stream )
-> String
{
String result;
getline( stream, result )
|| fail( "cppx::line_from: std::getline failed" );
return result;
}
# endif
inline void trace( Ref_<const Sys::String> s )
{
# if !(defined( CPPX_NO_IOSTREAMS ) || defined( NDEBUG ))
sys.err << "(trace) " << s << endl;
# endif
}
inline auto length( Ref_<const String> s )
-> Size
{ return s.length(); }
inline auto string_from( const wchar_t ch )
-> String
{ return std::wstring( 1, ch ); }
inline auto string_from( Ptr_<const wchar_t> s )
-> String
{ return s; }
template< class Type >
inline auto string_from( Ref_<const Type> value )
-> String
{ return std::to_wstring( value ); }
inline auto operator*( const int n, Ref_<const String> s )
-> String
{
assert( n >= 0 );
switch( length( s ) )
{
case 0: return L"";
case 1: return String( n, s.front() );
}
String result;
for( int i = 1; i <= n; ++i ) { result += s; }
return result;
}
template< class Type >
inline auto operator<<( Ref_<String> s, Ref_<const Type> value )
-> Ref_<String>
{
s += string_from( value );
return s;
}
template< class Type >
inline auto operator<<( Rvref_<String> s, Ref_<const Type> value )
-> Rvref_<String>
{
s += string_from( value );
return move( s );
}
inline auto uppercase( const wchar_t c )
-> wchar_t
{ return towupper( c ); }
inline auto uppercase( Ref_<const String> s )
-> String
{
String result;
for( const wchar_t ch : s )
{
result += uppercase( ch );
}
return result;
}
inline auto mainfunc( Ref_<void()> cpp_mainfunc )
-> int
{
setlocale( LC_ALL, "" ); // User's default locale.
using namespace std;
try
{
cpp_mainfunc();
return EXIT_SUCCESS;
}
catch( exception const& x )
{
// Can be abstracted as general exception handling function.
# ifndef CPPX_NO_IOSTREAMS
sys.err << "!" << x.what() << endl;
# else
(void) x; throw;
# endif
}
return EXIT_FAILURE;
}
} // namespace cppx
</file>