Repository: ppizarror/pygame-menu

Issue:
I can't input Latin Extended characters
Hello,
I have this code:

`nick = menu.add_text_input('Name: ', default='John')`

In the game menu, it is impossible to input from keyboard characters like:

> ą, ę, ś, ć, ż, ź, ó, ł, ń

Is it possible to fix by adding some code or is it not supported by pygame-menu?

Discussion:

Patch:
diff --git a/docs/_source/themes.rst b/docs/_source/themes.rst
index f5658ee3..f922bc4f 100644
--- a/docs/_source/themes.rst
+++ b/docs/_source/themes.rst
@@ -69,7 +69,7 @@ Background Color/Images
 -----------------------
 
 Theme background can be both a color or an image. All colors can be defined
-using a tuple or an list of 3 or 4 numbers between 0 and 255. The format of
+using a tuple or a list of 3 or 4 numbers between 0 and 255. The format of
 the numbers are:
 
 .. code-block:: python
diff --git a/pygame_menu/__init__.py b/pygame_menu/__init__.py
index 8804ce2e..910dcf6f 100644
--- a/pygame_menu/__init__.py
+++ b/pygame_menu/__init__.py
@@ -45,7 +45,7 @@ except (ModuleNotFoundError, ImportError):
 # Import modules that require pygame
 if __pygame_version__ is not None:
     """
-    BaseImage: Provides basic image loading an manipulation with pygame
+    BaseImage: Provides basic image loading and manipulation with pygame
     """
     import pygame_menu.baseimage
 
diff --git a/pygame_menu/baseimage.py b/pygame_menu/baseimage.py
index 12ebeaf5..a5b271bd 100644
--- a/pygame_menu/baseimage.py
+++ b/pygame_menu/baseimage.py
@@ -4,7 +4,7 @@ pygame-menu
 https://github.com/ppizarror/pygame-menu
 
 BASEIMAGE
-Provides a class to perform basic image loading an manipulation with pygame.
+Provides a class to perform basic image loading and manipulation with pygame.
 
 NOTE: pygame-menu v3 will not provide new widgets or functionalities, consider
 upgrading to the latest version.
@@ -44,7 +44,7 @@ except ImportError:
     _Path = None
 
 import pygame
-from pygame_menu.utils import assert_vector2
+from pygame_menu.utils import assert_vector2, isinstance_str
 
 # Example image paths
 __images_path__ = path.join(path.dirname(path.abspath(__file__)), 'resources', 'images', '{0}')
@@ -74,7 +74,7 @@ _VALID_IMAGE_FORMATS = ['.jpg', '.png', '.gif', '.bmp', '.pcx', '.tga', '.tif',
 class BaseImage(object):
     """
     Object that loads an image, stores as a surface, transform it and
-    let write the image to an surface.
+    let write the image to a surface.
 
     :param image_path: Path of the image to be loaded. It can be a string or :py:class:`pathlib.Path` on ``Python 3+``
     :type image_path: str, :py:class:`pathlib.Path`
@@ -92,12 +92,10 @@ class BaseImage(object):
                  drawing_offset=(0, 0),
                  load_from_file=True
                  ):
-        if _Path is None:
-            assert isinstance(image_path, str)
-        else:
-            assert isinstance(image_path, (str, _Path))
+        if _Path is not None:
             if isinstance(image_path, _Path):
                 image_path = str(image_path)
+        assert isinstance_str(image_path)
         assert isinstance(load_from_file, bool)
 
         _, file_extension = path.splitext(image_path)
@@ -340,7 +338,7 @@ class BaseImage(object):
         :return: Self reference
         :rtype: BaseImage
         """
-        if isinstance(channels, str):
+        if isinstance_str(channels):
             channels = [channels]
         assert isinstance(channels, (list, tuple))
         assert 1 <= len(channels) <= 3, 'maximum size of channels can be 3'
diff --git a/pygame_menu/examples/game_selector.py b/pygame_menu/examples/game_selector.py
index 40ee0d45..a182c678 100644
--- a/pygame_menu/examples/game_selector.py
+++ b/pygame_menu/examples/game_selector.py
@@ -100,7 +100,6 @@ def play_function(difficulty, font, test=False):
     """
     assert isinstance(difficulty, (tuple, list))
     difficulty = difficulty[0]
-    assert isinstance(difficulty, str)
 
     # Define globals
     global main_menu
diff --git a/pygame_menu/menu.py b/pygame_menu/menu.py
index 4e247063..e371740e 100644
--- a/pygame_menu/menu.py
+++ b/pygame_menu/menu.py
@@ -127,7 +127,7 @@ class Menu(object):
                  ):
         assert isinstance(height, (int, float))
         assert isinstance(width, (int, float))
-        # assert isinstance(title, str)
+        assert _utils.isinstance_str(title)
 
         assert isinstance(center_content, bool)
         assert isinstance(column_force_fit_text, bool)
@@ -135,7 +135,7 @@ class Menu(object):
         assert isinstance(columns, int)
         assert isinstance(enabled, bool)
         assert isinstance(joystick_enabled, bool)
-        assert isinstance(menu_id, str)
+        assert _utils.isinstance_str(menu_id)
         assert isinstance(menu_position, (tuple, list))
         assert isinstance(mouse_enabled, bool)
         assert isinstance(mouse_motion_selection, bool)
@@ -143,7 +143,7 @@ class Menu(object):
         assert isinstance(overflow, (tuple, list, bool))
         assert isinstance(rows, (int, type(None)))
         assert isinstance(screen_dimension, (tuple, list, type(None)))
-        assert isinstance(theme, _themes.Theme), 'theme bust be an pygame_menu.themes.Theme object instance'
+        assert isinstance(theme, _themes.Theme), 'theme bust be a pygame_menu.themes.Theme object instance'
         assert isinstance(touchscreen_enabled, bool)
         assert isinstance(touchscreen_motion_selection, bool)
 
@@ -468,7 +468,7 @@ class Menu(object):
 
         # Get ID
         button_id = kwargs.pop('button_id', '')
-        assert isinstance(button_id, str), 'id must be a string'
+        assert _utils.isinstance_str(button_id), 'id must be a string'
 
         # Change action if certain events
         if action == _events.PYGAME_QUIT or action == _events.PYGAME_WINDOWCLOSE:
@@ -708,7 +708,7 @@ class Menu(object):
         :return: Widget object, or List of widgets if the text overflows
         :rtype: :py:class:`pygame_menu.widgets.Label`, list[:py:class:`pygame_menu.widgets.Label`]
         """
-        assert isinstance(label_id, str)
+        assert _utils.isinstance_str(label_id)
         assert isinstance(max_char, int)
         assert isinstance(selectable, bool)
         assert max_char >= -1
@@ -929,7 +929,9 @@ class Menu(object):
         :return: Widget object
         :rtype: :py:class:`pygame_menu.widgets.TextInput`
         """
-        assert isinstance(default, (str, int, float))
+        if not isinstance(default, (int, float)):
+            assert _utils.isinstance_str(default), \
+                'default value must be a string, an integer or a float value'
 
         # Filter widget attributes to avoid passing them to the callbacks
         attributes = self._filter_widget_attributes(kwargs)
@@ -1042,7 +1044,7 @@ class Menu(object):
         """
         attributes = {}
         align = kwargs.pop('align', self._theme.widget_alignment)
-        assert isinstance(align, str)
+        assert _utils.isinstance_str(align)
         attributes['align'] = align
 
         background_is_color = False
@@ -1077,7 +1079,7 @@ class Menu(object):
         attributes['font_color'] = font_color
 
         font_name = kwargs.pop('font_name', self._theme.widget_font)
-        assert isinstance(font_name, str)
+        assert _utils.isinstance_str(font_name)
         attributes['font_name'] = font_name
 
         font_size = kwargs.pop('font_size', self._theme.widget_font_size)
@@ -1111,7 +1113,7 @@ class Menu(object):
         attributes['shadow_color'] = shadow_color
 
         shadow_position = kwargs.pop('shadow_position', self._theme.widget_shadow_position)
-        assert isinstance(shadow_position, str)
+        assert _utils.isinstance_str(shadow_position)
         attributes['shadow_position'] = shadow_position
 
         shadow_offset = kwargs.pop('shadow_offset', self._theme.widget_shadow_offset)
@@ -1212,7 +1214,7 @@ class Menu(object):
         :type widget: :py:class:`pygame_menu.widgets.core.widget.Widget`, str
         :return: None
         """
-        if isinstance(widget, str):
+        if _utils.isinstance_str(widget):
             widget = self.get_widget(widget_id=widget)
         assert isinstance(widget, _widgets.core.Widget)
         if not widget.is_selectable:
@@ -1234,7 +1236,7 @@ class Menu(object):
         :type widget: :py:class:`pygame_menu.widgets.core.widget.Widget`, str
         :return: None
         """
-        if isinstance(widget, str):
+        if _utils.isinstance_str(widget):
             widget = self.get_widget(widget_id=widget)
         assert isinstance(widget, _widgets.core.Widget)
         try:
@@ -1475,7 +1477,7 @@ class Menu(object):
         sx = self._scroll.get_scrollbar_thickness(_locals.ORIENTATION_HORIZONTAL, real=True)
         sy = self._scroll.get_scrollbar_thickness(_locals.ORIENTATION_VERTICAL, real=True)
 
-        # Remove the thick of the scrollbar to avoid displaying an horizontal one
+        # Remove the thick of the scrollbar to avoid displaying a horizontal one
         # If overflow in both axis
         if max_x > self._width and max_y > self._height - menubar_height:
             width, height = max_x + sy * 0.5, max_y + sx * 0.25
@@ -1524,7 +1526,7 @@ class Menu(object):
         :type widget_id: str
         :return: None
         """
-        assert isinstance(widget_id, str)
+        assert _utils.isinstance_str(widget_id)
         for widget in self._widgets:  # type: _widgets.core.Widget
             if widget.get_id() == widget_id:
                 raise IndexError('widget ID="{0}" already exists on the menu'.format(widget_id))
@@ -2541,7 +2543,7 @@ class Menu(object):
         :return: Widget object
         :rtype: :py:class:`pygame_menu.widgets.core.widget.Widget`, None
         """
-        assert isinstance(widget_id, str)
+        assert _utils.isinstance_str(widget_id)
         assert isinstance(recursive, bool)
         for widget in self._widgets:  # type: _widgets.core.Widget
             if widget.get_id() == widget_id:
@@ -2651,7 +2653,7 @@ class Menu(object):
         :type value: any
         :return: None
         """
-        assert isinstance(key, str)
+        assert _utils.isinstance_str(key)
         self._attributes[key] = value
 
     def get_attribute(self, key, default=None):
@@ -2665,7 +2667,7 @@ class Menu(object):
         :return: Attribute data
         :rtype: any
         """
-        assert isinstance(key, str)
+        assert _utils.isinstance_str(key)
         if not self.has_attribute(key):
             return default
         return self._attributes[key]
@@ -2679,7 +2681,7 @@ class Menu(object):
         :return: True if exists
         :rtype: bool
         """
-        assert isinstance(key, str)
+        assert _utils.isinstance_str(key)
         return key in self._attributes.keys()
 
     def remove_attribute(self, key):
diff --git a/pygame_menu/sound.py b/pygame_menu/sound.py
index 47b03906..a4e16159 100644
--- a/pygame_menu/sound.py
+++ b/pygame_menu/sound.py
@@ -48,6 +48,8 @@ except ImportError:
     AUDIO_ALLOW_CHANNELS_CHANGE = False
     AUDIO_ALLOW_FREQUENCY_CHANGE = False
 
+from pygame_menu.utils import isinstance_str
+
 # Sound types
 SOUND_TYPE_CLICK_MOUSE = '__pygame_menu_sound_click_mouse__'
 SOUND_TYPE_CLOSE_MENU = '__pygame_menu_sound_close_menu__'
@@ -118,7 +120,7 @@ class Sound(object):
         assert isinstance(size, int)
         assert isinstance(channels, int)
         assert isinstance(buffer, int)
-        assert isinstance(devicename, str)
+        assert isinstance_str(devicename)
         assert isinstance(allowedchanges, int)
         assert isinstance(force_init, bool)
         assert frequency > 0, 'frequency must be greater than zero'
@@ -227,8 +229,9 @@ class Sound(object):
         :return: The status of the sound load, *True* if the sound was loaded
         :rtype: bool
         """
-        assert isinstance(sound_type, str)
-        assert isinstance(sound_file, (str, type(None)))
+        assert isinstance_str(sound_type)
+        if sound_file is not None:
+            assert isinstance_str(sound_file)
         assert isinstance(volume, float)
         assert isinstance(loops, int)
         assert isinstance(maxtime, (int, float))
diff --git a/pygame_menu/themes.py b/pygame_menu/themes.py
index 5119bf36..eda4ff9f 100644
--- a/pygame_menu/themes.py
+++ b/pygame_menu/themes.py
@@ -71,7 +71,7 @@ class Theme(object):
     :type focus_background_color: tuple, list
     :param menubar_close_button: Draw a back-box button on header to close the menu, if user moves through nested submenus this buttons turns to a back-arrow
     :type menubar_close_button: bool
-    :param scrollarea_outer_margin: Outer scroll area margin (px), the tuple is added to computed scroll area width/height, it can add an margin to bottom/right scrolls after widgets. If value less than 1 use percentage of width/height. Default *(0,0)*. It cannot be negative values
+    :param scrollarea_outer_margin: Outer scroll area margin (px), the tuple is added to computed scroll area width/height, it can add a margin to bottom/right scrolls after widgets. If value less than 1 use percentage of width/height. Default *(0,0)*. It cannot be negative values
     :type scrollarea_outer_margin: tuple, list
     :param scrollbar_color: Scrollbars color
     :type scrollbar_color: tuple, list
@@ -399,54 +399,89 @@ class Theme(object):
         """
         Return a value from a dictionary.
 
-        :param params: parameters dictionary
+        Custom types (str)
+            -   alignment           pygame-menu alignment (locals)
+            -   callable            Is callable type, same as ``"function"``
+            -   color               Check color
+            -   color_image         Color or :py:class:`pygame_menu.baseimage.BaseImage`
+            -   color_image_none    Color, :py:class:`pygame_menu.baseimage.BaseImage`, or None
+            -   color_none          Color or None
+            -   cursor              Cursor object (pygame)
+            -   font                Font type
+            -   image               Value must be ``BaseImage``
+            -   none                None only
+            -   position            pygame-menu position (locals)}
+            -   tuple2              Only valid numeric tuples ``(x, y)`` or ``[x, y]``
+            -   type                Type-class (bool, str, etc...)
+
+        :param params: Parameters dictionary
         :type params: dict
-        :param key: key to look for
+        :param key: Key to look for
         :type key: str
-        :param allowed_types: list of allowed types
+        :param allowed_types: List of allowed types
         :type allowed_types: any
-        :param default: default value to return
+        :param default: Default value to return
         :type default: any
         :return: The value associated to the key
         :rtype: any
         """
-        if key not in params:
-            return default
-
-        value = params.pop(key)
-        if allowed_types:
+        value = params.pop(key, default)
+        if allowed_types is not None:
+            other_types = []  # Contain other types to check from
             if not isinstance(allowed_types, (tuple, list)):
                 allowed_types = (allowed_types,)
             for val_type in allowed_types:
-                if val_type == 'color':
-                    _utils.assert_color(value)
-                elif val_type == 'color_none':
-                    if value is None:
-                        return value
+
+                if val_type == 'alignment':
+                    _utils.assert_alignment(value)
+
+                elif val_type == callable or val_type == 'function' or val_type == 'callable':
+                    assert _utils.is_callable(value), \
+                        'value must be callable type'
+
+                elif val_type == 'color':
                     _utils.assert_color(value)
+
                 elif val_type == 'color_image':
-                    if isinstance(value, BaseImage):
-                        return value
-                    _utils.assert_color(value)
+                    if not isinstance(value, BaseImage):
+                        _utils.assert_color(value)
+
                 elif val_type == 'color_image_none':
-                    if value is None:
-                        return value
-                    elif isinstance(value, BaseImage):
-                        return value
-                    _utils.assert_color(value)
+                    if not (value is None or isinstance(value, BaseImage)):
+                        _utils.assert_color(value)
+
+                elif val_type == 'color_none':
+                    if value is not None:
+                        _utils.assert_color(value)
+
+                elif val_type == 'image':
+                    assert isinstance(value, BaseImage), \
+                        'value must be BaseImage type'
+
+                elif val_type == 'none':
+                    assert value is None
+
                 elif val_type == 'position':
                     _utils.assert_position(value)
-                elif val_type == 'alignment':
-                    _utils.assert_alignment(value)
+
+                elif val_type == 'type':
+                    assert isinstance(value, type), \
+                        'value is not type-class'
+
                 elif val_type == 'tuple2':
                     _utils.assert_vector2(value)
 
-            all_types = ('color', 'color_none', 'color_image', 'color_image_none',
-                         'position', 'alignment', 'tuple2')
-            others = tuple(t for t in allowed_types if t not in all_types)
-            if others:
+                else:  # Unknown type
+                    assert isinstance(val_type, type), \
+                        'allowed type "{0}" is not a type-class'.format(val_type)
+                    other_types.append(val_type)
+
+            # Check other types
+            if len(other_types) > 0:
+                others = tuple(other_types)
                 msg = 'Theme.{} type shall be in {} types (got {})'.format(key, others, type(value))
                 assert isinstance(value, others), msg
+
         return value
 
 
diff --git a/pygame_menu/utils.py b/pygame_menu/utils.py
index 5fa7b7fc..3eafbef9 100644
--- a/pygame_menu/utils.py
+++ b/pygame_menu/utils.py
@@ -204,6 +204,20 @@ def is_callable(func):
     return isinstance(func, (types.FunctionType, types.BuiltinFunctionType, types.MethodType, functools.partial))
 
 
+def isinstance_str(s):
+    """
+    Return ``True`` if the given object is a string (also unicode).
+
+    :param s: String
+    :type s: any
+    :return: Bool
+    :rtype: bool
+    """
+    if sys.version_info < (3, 0) and str(type(s)) == "<type 'unicode'>":
+        return True
+    return isinstance(s, (str, bytes))
+
+
 # noinspection SpellCheckingInspection
 def to_string(s, strict=False):
     """
diff --git a/pygame_menu/version.py b/pygame_menu/version.py
index dea8dc92..cba33562 100644
--- a/pygame_menu/version.py
+++ b/pygame_menu/version.py
@@ -57,6 +57,6 @@ class Version(tuple):
     patch = property(lambda self: self[2])
 
 
-vernum = Version(3, 5, 7)
+vernum = Version(3, 5, 8)
 ver = str(vernum)
 rev = ''
diff --git a/pygame_menu/widgets/core/widget.py b/pygame_menu/widgets/core/widget.py
index af269da6..c673bd9a 100644
--- a/pygame_menu/widgets/core/widget.py
+++ b/pygame_menu/widgets/core/widget.py
@@ -40,7 +40,7 @@ import pygame_menu.locals as _locals
 from pygame_menu.widgets.core.selection import Selection
 from pygame_menu.sound import Sound
 from pygame_menu.utils import make_surface, assert_alignment, assert_color, assert_position, assert_vector2, \
-    to_string, is_callable
+    to_string, is_callable, isinstance_str
 
 from uuid import uuid4
 import time
@@ -73,7 +73,6 @@ class Widget(object):
                  args=None,
                  kwargs=None
                  ):
-        assert isinstance(widget_id, str), 'widget id must be a string'
         if onchange:
             assert is_callable(onchange), 'onchange must be callable or None'
         if onreturn:
@@ -81,14 +80,16 @@ class Widget(object):
 
         # Store id, if None or empty create new ID based on UUID
         if widget_id is None or len(widget_id) == 0:
-            widget_id = uuid4()
+            widget_id = str(uuid4())
+        assert isinstance_str(widget_id), \
+            'widget id must be a string, but received {0}'.format(widget_id)
 
         self._alignment = _locals.ALIGN_CENTER
         self._attributes = {}  # Stores widget attributes
         self._background_color = None
         self._background_inflate = (0, 0)
         self._events = []  # type: list
-        self._id = str(widget_id)
+        self._id = widget_id
         self._margin = (0.0, 0.0)  # type: tuple
         self._max_width = None  # type: (int,float)
         self._padding = (0, 0, 0, 0)  # top, right, bottom, left
@@ -174,7 +175,7 @@ class Widget(object):
         :type value: any
         :return: None
         """
-        assert isinstance(key, str)
+        assert isinstance_str(key)
         self._attributes[key] = value
 
     def get_attribute(self, key, default=None):
@@ -188,7 +189,7 @@ class Widget(object):
         :return: Attribute data
         :rtype: any
         """
-        assert isinstance(key, str)
+        assert isinstance_str(key)
         if not self.has_attribute(key):
             return default
         return self._attributes[key]
@@ -202,7 +203,7 @@ class Widget(object):
         :return: True if exists
         :rtype: bool
         """
-        assert isinstance(key, str)
+        assert isinstance_str(key)
         return key in self._attributes.keys()
 
     def remove_attribute(self, key):
@@ -577,7 +578,7 @@ class Widget(object):
         :type widget_id: str
         :return: None
         """
-        assert isinstance(widget_id, str)
+        assert isinstance_str(widget_id)
         if self._menu is not None:
             # noinspection PyProtectedMember
             self._menu._check_id_duplicated(widget_id)
@@ -608,7 +609,8 @@ class Widget(object):
         :return: Text surface
         :rtype: :py:class:`pygame.Surface`
         """
-        # assert isinstance(text, str)
+        if text is not None:
+            assert isinstance_str(text)
         assert isinstance(color, tuple), 'invalid color'
         assert isinstance(use_background_color, bool), 'use_background_color must be boolean'
         bgcolor = self._font_background_color
@@ -717,7 +719,7 @@ class Widget(object):
         :type antialias: bool
         :return: None
         """
-        assert isinstance(font, str)
+        assert isinstance_str(font)
         assert isinstance(font_size, (int, float))
         assert isinstance(antialias, bool)
         assert_color(color)
@@ -1290,7 +1292,7 @@ class Widget(object):
         :type callback_id: str
         :return: None
         """
-        assert isinstance(callback_id, str)
+        assert isinstance_str(callback_id)
         if callback_id not in self._draw_callbacks.keys():
             raise IndexError('callback ID "{0}" does not exist'.format(callback_id))
         del self._draw_callbacks[callback_id]
@@ -1338,7 +1340,7 @@ class Widget(object):
         :type callback_id: str
         :return: None
         """
-        assert isinstance(callback_id, str)
+        assert isinstance_str(callback_id)
         if callback_id not in self._update_callbacks.keys():
             raise IndexError('callback ID "{0}" does not exist'.format(callback_id))
         del self._update_callbacks[callback_id]
diff --git a/pygame_menu/widgets/widget/colorinput.py b/pygame_menu/widgets/widget/colorinput.py
index 7a394a43..1466b435 100644
--- a/pygame_menu/widgets/widget/colorinput.py
+++ b/pygame_menu/widgets/widget/colorinput.py
@@ -36,7 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 import pygame
 import pygame_menu.locals as _locals
-from pygame_menu.utils import check_key_pressed_valid, make_surface, to_string
+from pygame_menu.utils import check_key_pressed_valid, make_surface, to_string, isinstance_str
 from pygame_menu.widgets.widget.textinput import TextInput
 
 TYPE_HEX = 'hex'
@@ -95,10 +95,9 @@ class ColorInput(TextInput):  # lgtm [py/missing-call-to-init]
                  *args,
                  **kwargs
                  ):
-        assert isinstance(colorinput_id, str)
-        assert isinstance(color_type, str)
-        assert isinstance(input_separator, str)
-        assert isinstance(input_underline, str)
+        assert isinstance_str(color_type)
+        assert isinstance_str(input_separator)
+        assert isinstance_str(input_underline)
         assert isinstance(cursor_color, tuple)
         assert isinstance(repeat_keys_initial_ms, (int, float))
         assert isinstance(repeat_keys_interval_ms, (int, float))
diff --git a/pygame_menu/widgets/widget/image.py b/pygame_menu/widgets/widget/image.py
index 071f092d..9fe9d39f 100644
--- a/pygame_menu/widgets/widget/image.py
+++ b/pygame_menu/widgets/widget/image.py
@@ -61,7 +61,6 @@ class Image(Widget):
                  scale=(1, 1),
                  scale_smooth=True
                  ):
-        assert isinstance(image_id, str)
         assert isinstance(angle, (int, float))
         assert isinstance(scale, (tuple, list))
         assert isinstance(scale_smooth, bool)
diff --git a/pygame_menu/widgets/widget/selector.py b/pygame_menu/widgets/widget/selector.py
index 2df8d820..a2e497e5 100644
--- a/pygame_menu/widgets/widget/selector.py
+++ b/pygame_menu/widgets/widget/selector.py
@@ -35,7 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 import pygame
 import pygame_menu.controls as _controls
-from pygame_menu.utils import check_key_pressed_valid, to_string
+from pygame_menu.utils import check_key_pressed_valid, to_string, isinstance_str
 from pygame_menu.widgets.core import Widget
 
 
@@ -102,14 +102,12 @@ class Selector(Widget):
                  **kwargs
                  ):
         assert isinstance(elements, list)
-        assert isinstance(selector_id, str)
         assert isinstance(default, int)
 
         # Check element list
         _check_elements(elements)
         assert default >= 0, 'default position must be equal or greater than zero'
         assert default < len(elements), 'default position should be lower than number of values'
-        assert isinstance(selector_id, str), 'id must be a string'
         assert isinstance(default, int), 'default must be an integer'
 
         super(Selector, self).__init__(
@@ -195,8 +193,7 @@ class Selector(Widget):
         :type item: str, int
         :return: None
         """
-        assert isinstance(item, (str, int)), 'item must be an string or an integer'
-        if isinstance(item, str):
+        if isinstance_str(item):
             for element in self._elements:
                 if element[0] == item:
                     self._index = self._elements.index(element)
@@ -206,6 +203,8 @@ class Selector(Widget):
             assert 0 <= item < len(self._elements), \
                 'item index must be greater than zero and lower than the number of elements on the selector'
             self._index = item
+        else:
+            raise AssertionError('item must be a string or an integer, but received {0}'.format(item))
 
     def update_elements(self, elements):
         """
diff --git a/pygame_menu/widgets/widget/textinput.py b/pygame_menu/widgets/widget/textinput.py
index edf24172..364b5929 100644
--- a/pygame_menu/widgets/widget/textinput.py
+++ b/pygame_menu/widgets/widget/textinput.py
@@ -38,7 +38,7 @@ import math
 import pygame
 import pygame_menu.controls as _controls
 import pygame_menu.locals as _locals
-from pygame_menu.utils import check_key_pressed_valid, make_surface, assert_color, to_string
+from pygame_menu.utils import check_key_pressed_valid, make_surface, assert_color, to_string, isinstance_str
 from pygame_menu.widgets.core import Widget
 
 try:
@@ -164,9 +164,8 @@ class TextInput(Widget):
                  *args,
                  **kwargs
                  ):
-        assert isinstance(textinput_id, str)
-        assert isinstance(input_type, str)
-        assert isinstance(input_underline, str)
+        assert isinstance_str(input_type)
+        assert isinstance_str(input_underline)
         assert isinstance(input_underline_len, int)
         assert isinstance(cursor_color, tuple)
         assert isinstance(copy_paste_enable, bool)
@@ -176,13 +175,13 @@ class TextInput(Widget):
         assert isinstance(maxchar, int)
         assert isinstance(maxwidth, int)
         assert isinstance(password, bool)
-        assert isinstance(password_char, str)
+        assert isinstance_str(password_char)
         assert isinstance(repeat_keys_initial_ms, (int, float))
         assert isinstance(repeat_keys_interval_ms, (int, float))
         assert isinstance(repeat_mouse_interval_ms, (int, float))
         assert isinstance(repeat_touch_interval_ms, (int, float))
         assert isinstance(tab_size, int)
-        assert isinstance(text_ellipsis, str)
+        assert isinstance_str(text_ellipsis)
 
         assert history >= 0, 'history must be equal or greater than zero'
         assert maxchar >= 0, 'maxchar must be equal or greater than zero'
@@ -281,7 +280,7 @@ class TextInput(Widget):
             for ch in range(len(valid_chars)):
                 _char = to_string(valid_chars[ch])
                 valid_chars[ch] = _char
-                assert isinstance(_char, str), 'element "{0}" of valid_chars must be a string'.format(_char)
+                assert isinstance_str(_char), 'element "{0}" of valid_chars must be a string'.format(_char)
                 assert len(_char) == 1, 'element "{0}" of valid_chars must be character'.format(_char)
             assert len(valid_chars) > 0, 'valid_chars list must contain at least 1 element'
         self._valid_chars = valid_chars
@@ -987,7 +986,9 @@ class TextInput(Widget):
         """
         if self._password and text != '':
             raise ValueError('value cannot be set in password type')
-        assert isinstance(text, (str, int, float))
+        if not isinstance(text, (int, float)):
+            assert isinstance_str(text)
+            text = to_string(text)
         if self._check_input_type(text):
             _default = to_string(text)
 
@@ -1128,7 +1129,7 @@ class TextInput(Widget):
         :type update_history: bool
         :return: None
         """
-        assert isinstance(new_string, str)
+        assert isinstance_str(new_string)
         assert isinstance(update_history, bool)
 
         l_history = len(self._history)
@@ -1491,7 +1492,7 @@ class TextInput(Widget):
                     self._keyrepeat_counters[event.key] = [0, event.unicode]
 
                 # User press ctrl+something
-                if pygame.key.get_mods() & pygame.KMOD_CTRL:
+                if pygame.key.get_mods() in (pygame.KMOD_CTRL, pygame.KMOD_RCTRL, pygame.KMOD_LCTRL):
 
                     # If test, disable CTRL
                     if 'test' in event.dict and event.dict['test']:


Test patch:
diff --git a/test/test_themes.py b/test/test_themes.py
index 3316c76b..4cf120c0 100644
--- a/test/test_themes.py
+++ b/test/test_themes.py
@@ -87,3 +87,72 @@ class ThemeTest(unittest.TestCase):
         theme.background_color = image
 
         self.assertRaises(AssertionError, lambda: theme.set_background_color_opacity(0.5))
+
+    def test_get(self):
+        """
+        Test custom types get.
+        """
+        t = pygame_menu.themes.THEME_DEFAULT
+        img = pygame_menu.baseimage.BaseImage(pygame_menu.baseimage.IMAGE_EXAMPLE_GRAY_LINES)
+
+        class Test(object):
+            """
+            Class to test.
+            """
+            pass
+
+        def dummy():
+            """
+            Callable.
+            """
+            return True
+
+        # Test correct
+        t._get({}, '', 'tuple2', (1, 1))
+        t._get({}, '', str, 'hi')
+        t._get({}, '', int, 4)
+        t._get({}, '', bool, True)
+        t._get({}, '', 'color', (1, 1, 1))
+        t._get({}, '', 'color', [11, 1, 0])
+        t._get({}, '', 'color', [11, 1, 0, 55])
+        t._get({}, '', 'color_none', (1, 1, 1))
+        t._get({}, '', 'color_none', [11, 1, 0])
+        t._get({}, '', 'color_none', [11, 1, 0, 55])
+        t._get({}, '', 'color_none', None)
+        t._get({}, '', Test, Test())
+        t._get({}, '', callable, dummy)
+        t._get({}, '', 'callable', dummy)
+        t._get({}, '', 'callable', dummy)
+        t._get({}, '', 'image', img)
+        t._get({}, '', 'tuple2', (1, -1))
+        t._get({}, '', 'tuple2', [1, -1])
+        t._get({}, '', 'color_image', (1, 1, 1))
+        t._get({}, '', 'color_image', [11, 1, 0])
+        t._get({}, '', 'color_image', [11, 1, 0, 55])
+        t._get({}, '', 'color_image', img)
+        t._get({}, '', 'alignment', pygame_menu.locals.ALIGN_LEFT)
+        t._get({}, '', 'position', pygame_menu.locals.POSITION_SOUTHWEST)
+        t._get({}, '', 'none', None)
+        t._get({}, '', 'color_image_none', [11, 1, 0, 55])
+        t._get({}, '', 'color_image_none', None)
+        t._get({}, '', 'color_image_none', img)
+
+        self.assertTrue(t._get({}, '', 'callable', dummy)())
+
+        # Test raises
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'none', 1))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'none', bool))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'image', bool))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'callable', bool))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'unknown', Test()))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', callable, Test()))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'color', None))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'color', [11, 1, -1]))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'color', [11, 1, -1]))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'color', [11, 1]))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'color', [1, 1, 1, 256]))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', int, 4.4))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', bool, 4.4))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'tuple2.1', (1, 1)))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'tuple2', (1, 1, 1)))
+        self.assertRaises(AssertionError, lambda: t._get({}, '', 'cursor', 'hi'))
diff --git a/test/test_widgets.py b/test/test_widgets.py
index bd72f681..d83e6956 100644
--- a/test/test_widgets.py
+++ b/test/test_widgets.py
@@ -35,6 +35,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 import sys
 
+from pygame import K_l
+
 from test._utils import *
 from pygame_menu import locals as _locals
 from pygame_menu.widgets import ScrollBar, Label, Button, MenuBar
@@ -63,13 +65,18 @@ class WidgetsTest(unittest.TestCase):
         self.menu.add_button('Test', pygame_menu.events.NONE)
         self.menu.add_button(u'Menú', pygame_menu.events.NONE)
         self.menu.add_color_input(u'Cólor', 'rgb')
-        self.menu.add_text_input(u'Téxt')
+        text = self.menu.add_text_input(u'Téxt')
         self.menu.add_label(u'Téxt')
         if sys.version_info < (3, 0):
             self.assertRaises(Exception, lambda: self.menu.add_selector(u'Sélect', [('a', 'a')]))  # Strict
         self.menu.add_selector(u'Sélect'.encode('latin1'), [('a', 'a')])
         self.menu.enable()
         self.menu.draw(surface)
+        text.set_value(u'ą, ę, ś, ć, ż, ź, ó, ł, ń')
+        self.assertEqual(text.get_value(), u'ą, ę, ś, ć, ż, ź, ó, ł, ń')
+        text.set_value('')
+        text.update(PygameUtils.key(K_l, char=u'ł', keydown=True))
+        self.assertEqual(text.get_value(), u'ł')
 
     def test_background(self):
         """