Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Request for comment about this console window interface.

31 views
Skip to first unread message

Alf P. Steinbach

unread,
May 13, 2016, 8:50:57 PM5/13/16
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 text_screen_decl.hpp>
#pragma once
// Copyright © 2016 Alf P. Steinbach
// Screen output – a minimal interface to console windows, like ncurses.

#include "../cppx/basics.hpp"

#include <assert.h> // assert
#include <algorithm> // std::(min, max)

namespace console {
using cppx::Char;
using cppx::Non_copyable;
using cppx::Non_movable;
using cppx::operator*;
using cppx::Ref_;
using cppx::String;
using cppx::string_from;
using std::max;
using std::min;

inline namespace colors {
struct Colorbit { enum Enum: unsigned { blue, green, red,
intensity }; };

struct Color
{
unsigned value;

friend
auto operator|( const Color a, const Color b )
-> Color
{ return Color( a.value | b.value ); }

void operator|=( const Color other )
{ value |= other.value; }

Color()
: value( 0 )
{}

explicit Color( const unsigned bits )
: value( bits )
{}

Color( const Colorbit::Enum bitnum ); // System
specific mapping.
};

const Color bit_b = Colorbit::blue; // System specific.
const Color bit_g = Colorbit::green; // System specific.
const Color bit_r = Colorbit::red; // System specific.
const Color bit_i = Colorbit::intensity; // System specific.

const Color black = {};
const Color dark_blue = bit_b;
const Color green = bit_g;
const Color teal = bit_b | bit_g;
const Color dark_red = bit_r;
const Color purple = bit_b | bit_r;
const Color olive = bit_g | bit_r;
const Color light_gray = bit_b | bit_g | bit_r;
const Color dark_gray = bit_i;
const Color blue = bit_b | bit_i;
const Color light_green = bit_g | bit_i;
const Color light_cyan = bit_b | bit_g | bit_i;
const Color red = bit_r | bit_i;
const Color light_purple = bit_b | bit_r | bit_i;
const Color yellow = bit_g | bit_r | bit_i;
const Color white = bit_b | bit_g | bit_r | bit_i;
} // namespace colors

inline namespace geometry {
struct Point { int x; int y; };

inline auto operator+( Ref_<const Point> a, Ref_<const Point> b )
-> Point
{ return Point{ a.x + b.x, a.y + b.y }; }

inline auto operator-( Ref_<const Point> a, Ref_<const Point> b )
-> Point
{ return Point{ a.x - b.x, a.y - b.y }; }

struct Rect
{
Point top_left;
Point past_bottom_right;

auto x1() const -> int { return top_left.x; }
auto y1() const -> int { return top_left.y; }
auto x2() const -> int { return past_bottom_right.x; }
auto y2() const -> int { return past_bottom_right.y; }

auto width() const -> int { return x2() - x1(); }
auto height() const -> int { return y2() - y1(); }
auto is_empty() const -> bool { return width() <= 0 or
height() <= 0; }
};

inline auto union_of( Ref_<const Rect> a, Ref_<const Rect> b )
-> Rect
{
return Rect
{
{ min( a.x1(), b.x1() ), min( a.y1(), b.y1() ) },
{ max( a.x2(), b.x2() ), max( a.y2(), b.y2() ) }
};
}

inline auto intersection_of( Ref_<const Rect> a, Ref_<const
Rect> b )
-> Rect
{
// A result with ≤ 0 width or height indicates the
intersection’s empty.
return Rect
{
{ max( a.x1(), b.x1() ), max( a.y1(), b.y1() ) },
{ min( a.x2(), b.x2() ), min( a.y2(), b.y2() ) }
};
}
} // namespace geometry

template< class Derived >
class One_at_a_time
: public Non_copyable
, public Non_movable
{
private:
static auto instance_exists()
-> Ref_<bool>
{
static bool whether_it_does;
return whether_it_does;
}

One_at_a_time( Ref_<const One_at_a_time> ) = delete;
auto operator=( Ref_<const One_at_a_time> ) ->
Ref_<One_at_a_time> = delete;

public:
~One_at_a_time()
{ instance_exists() = false; }

One_at_a_time()
{
assert( not instance_exists() );
if( instance_exists() ) { throw 0; }
instance_exists() = true;
}
};

// System specific:
class Display
: public One_at_a_time< Display >
{
private:
class Impl;
Impl* p_impl_;

Display( Ref_<const Display> ) = delete;
auto operator=( Ref_<const Display> ) = delete;

void clear_by_writing_spaces()
{
const Point sz = size();
const auto spaces = sz.x*string_from( L' ' );
for( int y = 0; y < sz.y; ++y )
{
write_at( {0, y}, spaces );
}
gotoxy( 0, 0 );
}

public:
auto size() const -> Point;

// Equivalent to clear_by_writing_spaces(), but can be faster:
void clear();

auto position() const -> Point;
void gotoxy( const Point pos );

auto textcolor() const -> Color;
void set_textcolor( const Color c );

auto bgcolor() const -> Color;
void set_bgcolor( const Color c );

void write( Ref_<const String> s );

void gotoxy( const int x, const int y )
{
gotoxy( {x, y} );
}

void write_at( const Point pos, Ref_<const String> s )
{
gotoxy( pos );
write( s );
}

~Display();
Display(
Ref_<const String> title,
const Point size = Point{ 120, 30 }
);
};

} // namespace console
</file>

<file 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>

Kalle Olavi Niemitalo

unread,
May 14, 2016, 5:32:17 AM5/14/16
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> 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?

This kind of question might be better asked in comp.terminals.

I don't think line wrapping or scrolling will be a problem.
In ncurses/tty/tty_update.c, the PutCharLR function has three
different ways to place a character in the lower right corner
without scrolling, depending on the capabilities of the terminal.
In some (presumably old) terminals, the function just gives up
and does not display the character.

In software-only terminals like xterm, I think you generally have
a palette of 8 or 16 colors that you can use as foreground or
background colors in character cells. In addition, there may be
a default background color (perhaps mapped to a background
picture rather than a solid color) and a default foreground
color. The bold, italic, underline, and crossed-out attributes
may be displayed as is or mapped to colors. xterm also supports
RGB foreground and background colors in each character cell but I
don't think the terminfo database and the ncurses library support
that. Your _Color::operator|= does not seem reasonable with
default colors and RGB colors, but perhaps you can just not use
those features.

Do not assume you can ask the terminal what the current
foreground and background colors are. Anyway, that ability
doesn't seem important for a full-screen program that sets its
own colors.

ncurses can get the size of the terminal from static information
in the terminfo database, from environment variables, or from a
nonstandard ioctl call. This last method also correctly updates
the size if the user resizes an xterm window while the program is
running. In contrast, if there is a hardware terminal connected
to a serial port and the user switches it from 80-column mode to
132-column mode, the program is unlikely to understand what
happened.

I don't think I've ever used a hardware text terminal that
supports colors or Unicode. Instead, there may be one character
set for Latin-1 and an alternative character set for line-drawing
characters, and the program needs to switch between them. The
ncurses library supports the A_ALTCHARSET attribute for this.

> struct Point { int x; int y; };
>
> inline auto operator+( Ref_<const Point> a, Ref_<const Point> b )
> -> Point
> { return Point{ a.x + b.x, a.y + b.y }; }

Some other libraries use different types for points and sizes,
so that one can add point+size but not point+point. I don't
know whether that actually helps prevent any bugs or is useful
for overloading.

The Ref_ stuff is just confusing. At first, I assumed it was a
separate type like std::reference_wrapper, or perhaps you defined
it differently in debug vs. release configurations. But then you
use it in places like

> One_at_a_time( Ref_<const One_at_a_time> ) = delete;

where it has to mean an ordinary reference; otherwise it wouldn't
delete the copy constructor as intended. Ref_ just makes the
code look strange and thus harder to read, as if you were using
<% and %> instead of braces.

> inline auto union_of( Ref_<const Rect> a, Ref_<const Rect> b )
> -> Rect
> {
> return Rect
> {
> { min( a.x1(), b.x1() ), min( a.y1(), b.y1() ) },
> { max( a.x2(), b.x2() ), max( a.y2(), b.y2() ) }
> };
> }

Perhaps that should check for empty rectangles and not let their
coordinates affect the result.

Jens Kallup

unread,
May 14, 2016, 2:25:09 PM5/14/16
to
Hi,

I don't know, but the solution is "TurboVision C++" for Linux.
A framework like the feeling of TurboPascal's TurboVision.
It comes with C++ code.

Hope this Helps

Jens

0 new messages