# MusicDB, a music manager with web-bases UI that focus on music.
# Copyright (C) 2017 Ralf Stemmer <ralf.stemmer@gmx.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import tty
import termios
import sys
import os
[docs]class Text(object):
r"""
This class handles the shell output.
It sets color and cursor settings and handles the screen.
The default color is gray text on black background (``0;37;40``).
Whenever a parameter is a color, the ANSI escape format for colors as strings were expected.
These strings were sent to the terminal as shown in the following code example:
.. code-block:: python
def SetColor(color):
print("\033[%sm" % color, end="")
SetColor("1;32") # bright green text
SetColor("44") # blue background
"""
def __init__(self):
self.fgcolor = "37"
self.bgcolor = "40"
[docs] def SetColor(self, fgcolor=None, bgcolor=None):
"""
This method calls :meth:`~SetFGColor` and :meth:`~SetBGColor`.
"""
self.SetFGColor(fgcolor)
self.SetBGColor(bgcolor)
[docs] def SetFGColor(self, color=None):
"""
This method sets the foreground (text) color.
If the color argument is ``None``, then the default foreground color gets used.
If the color argument is a color, then a new default foreground color gets defined and set.
Next time this method gets called without an argument, this new color will be used as default.
Args:
color (str): New foreground (text) color
Returns:
*Nothing*
Raises:
TypeError: When argument is set and not a string
"""
if color != None and type(color) != str:
raise TypeError("Color must be a string with an ANSI color code.")
if color:
self.fgcolor = color
print("\033[%sm" % (self.fgcolor), end="")
[docs] def SetBGColor(self, color=None):
"""
This method sets the background color.
If the color argument is ``None``, then the default background color gets used.
If the color argument is a color, then a new default backgound color gets defined and set.
Next time this method gets called without an argument, this new color will be used as default.
Args:
color (str): New background color
Returns:
*Nothing*
Raises:
TypeError: When argument is set and not a string
"""
if color != None and type(color) != str:
raise TypeError("Color must be a string with an ANSI color code.")
if color:
self.bgcolor = color
print("\033[%sm" % (self.bgcolor), end="")
[docs] def SetCursor(self, x, y):
"""
This method sets the position of the curser.
The top left corner is at position *x=0; y=0*.
*y* represents the rows and *x* the columns.
For setting the cursor the ANSI escape sequence ``[y;xH`` gets used.
Args:
x (int): Column position of the cursor
y (int): Row position of the cursor
Returns:
*Nothing*
Raises:
TypeError: When *x* or *y* are not of type integer
ValueError: When *x* or *y* are negative
"""
if type(x) != int or type(y) != int:
raise TypeError("The cursor coordinates must be of type int!")
if x < 0 or y < 0:
raise ValueError("No negative values for cursor coordinates allowed!")
print("\033[%d;%dH" % (y+1,x+1), end="") # I start at (0,0), vt100 at (1,1)
[docs] def ShowCursor(self, show=True):
"""
This method makes the cursor visible or invisible.
Therefore the ANSI escape seqneces ``[?25h`` and ``[?25l`` will be used.
Args:
show (bool): ``True``: Make cursor visible, ``False``: Hide cursor
Returns:
*Nothing*
"""
if show:
print("\033[?25h")
else:
print("\033[?25l")
[docs] def ClearScreen(self):
"""
This method clears the screen using the ANSI escape squence ``[2J``.
Returns:
*Nothing*
"""
print("\033[2J", end="")
[docs] def FlushScreen(self):
"""
This method forces the shell to flush the *stdout* buffer.
After calling this method, everything written into the output buffer will be visible on the screen.
Returns:
*Nothing*
"""
sys.stdout.flush()
[docs] def GetScreenSize(self):
"""
This method returns the screen size as columns and rows.
For detecting the screen size, the tool ``stty`` gets called:
.. code-block:: bash
stty size
Returns:
columns, rows (as integer)
"""
# https://stackoverflow.com/questions/566746/how-to-get-linux-console-window-width-in-python
rows, columns = os.popen('stty size', 'r').read().split()
return int(columns), int(rows)
[docs] def PrintText(self, text):
"""
This method simply prints text to the screen.
It does nothing more than:
.. code-block:: python
print(text, end="")
So instead of the "normal" print, there will be no line break at the end.
Returns:
*Nothing*
"""
print(text, end="")
[docs] def GetRawKey(self):
"""
This method reads a raw key input from the input buffer without printing it on the screen.
Usually you want to use the more abstract method :meth:`~GetKey`
Returns:
The key from the input buffer
"""
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
KEY_ESCAPE = "\x1B"
KEY_BACKSPACE = "\x7F"
KEY_CTRL_D = "\x04"
KEY_CTRL_U = "\x15"
KEY_CTRL_W = "\x17"
[docs] def GetKey(self):
"""
This method returns the name of the pressed key.
The key input gets not printed to the screen.
Until now, the following keys will be recognized:
* The usual prinable keys
* ``escape`` (Must be hit twice)
* ``enter``, ``backspace``, ``delete``
* ``backtab`` (shift+tab)
* ``up``, ``down``, ``left``, ``right``, ``end``, ``home``
* ``page down``, ``page up``
* ``Ctrl-D`` (Quit), ``Ctrl-W`` (Delete Word), ``Ctrl-U`` (Delete Line)
In case you want to get other keys, it is easy to add them by editing the code of this method.
I just implemented the one I need for the :class:`~lib.clui.listview.ListView` UI element.
Returns:
The pressed key. If it is a special key, then the name of the key gets returnd as string.
``None`` will be returnd for unknown keys.
"""
key = self.GetRawKey()
if key == "\x0D":
return "enter"
elif key == Text.KEY_BACKSPACE:
return "backspace"
elif key == Text.KEY_CTRL_D:
return "Ctrl-D"
elif key == Text.KEY_CTRL_W:
return "Ctrl-W"
elif key == Text.KEY_CTRL_U:
return "Ctrl-U"
# in not escape, than it is a usual character
elif key != Text.KEY_ESCAPE:
return key
key = self.GetRawKey()
if key == Text.KEY_ESCAPE:
return "escape"
# expecting a sequence opener
elif key != "[":
return None
key = self.GetRawKey()
if key == "A":
return "up"
elif key == "B":
return "down"
elif key == "C":
return "right"
elif key == "D":
return "left"
elif key == "Z":
return "backtab"
# complex codes
name = None
if key == "1":
name = "home"
elif key == "3":
name = "delete"
elif key == "4":
name = "end"
elif key == "6":
name = "page up"
elif key == "6":
name = "page down"
# either end of sequence (~) or continuing (;)
key = self.GetRawKey()
if key == "~":
return name
return None
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4