#!/usr/bin/env python
# -*- coding: utf-8 -*-
# It script it publish under GNU GENERAL PUBLIC LICENSE
# http://www.gnu.org/licenses/gpl-3.0.en.html
# Author: the Galaxie Curses Team, all rights reserved
import GLXCurses
import logging
import curses
[docs]class Dialog(GLXCurses.Window, GLXCurses.Movable):
def __init__(self):
self.__buttons_list = None
self.__action_area = None
self.__content_area = None
self.__use_header_bar = None
self.__action_area_border = None
self.__button_spacing = None
self.__content_area_border = None
self.__content_area_spacing = None
self.__sub_sub_win = None
self._action_area = []
self._content_area = GLXCurses.VBox()
# Style Property
self.action_aera_border = 0
self.button_spacing = 6
self.content_area_border = 2
self.content_area_spacing = 0
# Load heritage
GLXCurses.Window.__init__(self)
GLXCurses.Movable.__init__(self)
# It's a GLXCurse Type
self.glxc_type = "GLXCurses.Dialog"
# Widgets can be named, which allows you to refer to them from a GLXCStyle
self.name = "{0}{1}".format(self.__class__.__name__, self.id)
# Default Setting
self.type_hint = GLXCurses.GLXC.WINDOW_TYPE_HINT_DIALOG
self.decorated = True
self.color_normal = (
self.style.color(
bg=self.style.attribute_to_rgb("dark", "STATE_NORMAL"),
fg=self.style.attribute_to_rgb("white", "STATE_NORMAL"),
attributes=False,
)
| GLXCurses.GLXC.A_REVERSE
)
self.color_prelight = self.style.color(
bg=self.style.attribute_to_rgb("base", "STATE_PRELIGHT"),
fg=self.style.attribute_to_rgb("dark", "STATE_NORMAL"),
attributes=False,
)
# Subscription
self.connect(
"BUTTON1_CLICKED", self.__class__._handle_mouse_event
) # Mouse Button
self.connect("BUTTON1_RELEASED", self.__class__._handle_mouse_event)
# Keyboard
self.connect("CURSES", self.__class__._handle_key_event)
def _handle_mouse_event(self, event_signal, event_args=None):
if event_args is None:
event_args = dict()
logging.debug(str(event_args))
logging.debug("i GOT")
for button in self.get_action_area():
if (
event_args["id"]
== self.get_widget_for_response(
self.get_response_for_widget(button.widget)
).id
):
self.emit(
"RESPONSE", {"id": self.get_response_for_widget(button.widget)}
)
return
def _handle_key_event(self, event_signal, *event_args):
key = event_args[0]
logging.debug("HANDLE KEY: " + str(key))
# Touch Escape
if key == GLXCurses.GLXC.KEY_ESC:
self.emit("RESPONSE", {"id": 42})
self.close()
if key == GLXCurses.GLXC.KEY_LF:
if (
GLXCurses.Application().has_default
and GLXCurses.Application().has_default.widget
):
self.emit(
"RESPONSE",
{
"id": self.get_response_for_widget(
GLXCurses.Application().has_default.widget
)
},
)
if key == curses.KEY_RIGHT:
# Found ID/WIDGET/Index Position
# SET + 1 To the position
if self.selected_menu + 1 > len(self.children) - 1:
self.selected_menu = 0
self.selected_menu_item = 0
else:
self.selected_menu += 1
self.selected_menu_item = 0
# count = 0
# for child in self.children:
# if self.selected_menu = count:
self.emit(
"CLAIM_PRELIGHT",
{"id": self.children[self.selected_menu].widget.children[0].widget.id},
)
# GLXCurses.Application().has_prelight = self.children[self.selected_menu].widget.children[0].widget
if key == curses.KEY_LEFT:
if self.selected_menu - 1 < 0:
self.selected_menu = len(self.children) - 1
self.selected_menu_item = 0
else:
self.selected_menu -= 1
self.selected_menu_item = 0
self.emit(
"CLAIM_PRELIGHT",
{"id": self.children[self.selected_menu].widget.children[0].widget.id},
)
@property
def _content_area(self):
"""
The content area is a GLXCurses.VBox, and is where widgets such as a GLXCurses.Label or a GLXCurses.Entry
should be packed.
It GLXCurses.VBox will be display on top of the GLXCurses.Dialog.
:return: The content area
:rtype: GLXCurses.VBox
"""
return self.__content_area
@_content_area.setter
def _content_area(self, vbox=None):
"""
Set the ``_content_area`` property value
:param vbox: a GLXCurses.VBox object
:type vbox: GLXCurses.VBox
:raise TypeError: when ``vbox`` is not a GLXCurses.VBox type
"""
if vbox is None:
vbox = GLXCurses.VBox()
if type(vbox) != GLXCurses.VBox:
raise TypeError('"vbox" must be a Vbox type')
if self._content_area != vbox:
self.__content_area = vbox
@property
def _action_area(self):
"""
Internal property it store the Dialog buttons list
:return: the ``_buttons_list`` property value
:rtype: list
"""
return self.__buttons_list
@_action_area.setter
def _action_area(self, value=None):
"""
set the ``_buttons_list`` property
:param value: a list
:type value: list
:raise TypeError: is ``value`` is not a list type
"""
if value is None:
value = []
if type(value) != list:
raise TypeError('"value" must be a list type')
if self._action_area != value:
self.__buttons_list = value
@property
def action_aera_border(self):
"""
The default border width used around the action area of the dialog, as returned by Dialog.get_action_area(),
unless GLXCurses.Container.set_border_width() was called on that widget directly.
:return: the ``action_aera_border`` property value
:rtype: int
"""
return self.__action_area_border
@action_aera_border.setter
def action_aera_border(self, value=5):
"""
Set the ``action_aera_border`` property value
allowed values: >= 0
:param value: a positive int value
:type value: int
:raise TypeError: when value is not a int type
:raise ValueError: when value is not >= 0
"""
if type(value) != int:
raise TypeError('"value" must be a int type')
if not value >= 0:
raise ValueError('"value" must be >= 0')
if self.action_aera_border != value:
self.__action_area_border = value
@property
def button_spacing(self):
"""
Spacing between buttons in Chars
:return: the ``button_spacing`` property value
:rtype: int
"""
return self.__button_spacing
@button_spacing.setter
def button_spacing(self, value=None):
"""
Set the ``button_spacing`` property value
allowed values: >= 0
:param value: a positive int value in Chars
:type value: int
:raise TypeError: when value is not a int type
:raise ValueError: when value is not >= 0
"""
if type(value) != int:
raise TypeError('"value" must be a int type')
if not value >= 0:
raise ValueError('"value" must be >= 0')
if self.button_spacing != value:
self.__button_spacing = value
@property
def content_area_border(self):
"""
The default border width used around the content area of the dialog, as returned by
dialog_get_content_area(), unless container_set_border_width() was called on that widget directly.
:return: the ``content_area_border`` property value
:rtype: int
"""
return self.__content_area_border
@content_area_border.setter
def content_area_border(self, value=None):
"""
Set the ``content_area_border`` property value
allowed values: >= 0
:param value: a positive int value in Chars
:type value: int
:raise TypeError: when value is not a int type
:raise ValueError: when value is not >= 0
"""
if type(value) != int:
raise TypeError('"value" must be a int type')
if not value >= 0:
raise ValueError('"value" must be >= 0')
if self.content_area_border != value:
self.__content_area_border = value
@property
def content_area_spacing(self):
"""
The default spacing used between elements of the content area of the dialog, as returned by
dialog_get_content_area(), unless box_set_spacing() was called on that widget directly.
:return: the ``content_area_spacing`` property value
:rtype: int
"""
return self.__content_area_spacing
@content_area_spacing.setter
def content_area_spacing(self, value=None):
"""
Set the ``content_area_spacing`` property value
allowed values: >= 0
:param value: a positive int value in Chars
:type value: int
:raise TypeError: when value is not a int type
:raise ValueError: when value is not >= 0
"""
if type(value) != int:
raise TypeError('"value" must be a int type')
if not value >= 0:
raise ValueError('"value" must be >= 0')
if self.content_area_spacing != value:
self.__content_area_spacing = value
[docs] def run(self):
"""
Inform Application about the GLXCurses.Dialog is active.
Cause Application to forward event only inside the GLXCurses.Dialog.
The GLXCurses.Mainloop and GLXCurses.Application will work with the dialog like a normal GLXCurses.Window
because dialog is a subclass of GLXCurses.Window.
"""
self.emit("RUN", {"widget": self, "name": self.name})
GLXCurses.Application().active_window_id = self.id
[docs] def response(self, response_id=None):
"""
Emits the “response” signal with the given response ID.
Used to indicate that the user has responded to the dialog in some way; typically either you or
Dialog().run() will be monitoring the ::response signal and take appropriate action.
:param response_id: response ID
:type response_id: str
:raise TypeError: when ``response_id`` is not a str type
"""
if type(response_id) != str:
raise TypeError('"response_id" must be a str type')
self.emit("CURSES", {"response_id": response_id})
[docs] def set_default_response(self, response_id=None):
"""
Sets the last widget in the dialog’s action area with the given response_id as the
default widget for the dialog.
Pressing “Enter” normally activates the default widget.
:param response_id: a response ID
:type response_id: str
:raise TypeError: when ``response_id`` is not a dtr type
"""
if type(response_id) != str:
raise TypeError('"response_id" must be a int type')
for item in self._action_area:
if item.id == response_id:
self.emit("CLAIM_DEFAULT", {"id": item.widget.id})
self.emit("CLAIM_FOCUS", {"id": item.widget.id})
self.emit("CLAIM_PRELIGHT", {"id": item.widget.id})
[docs] def set_response_sensitive(self, response_id=None, setting=None):
"""
Calls gtk_widget_set_sensitive (widget, @setting) for each widget in the dialog’s action area
with the given response_id .
A convenient way to sensitize/desensitize dialog buttons.
:param response_id: a response ID
:type response_id: str
:param setting: True for sensitive
:type setting: bool
:raise TypeError: when ``response_id`` is not a str type
:raise TypeError: when ``setting`` is not a bool type
"""
if type(response_id) != str:
raise TypeError('"response_id" must be a int type')
if type(setting) != bool:
raise TypeError('"setting" must be a bool type')
if type(self._action_area) == list and len(self._action_area) > 0:
for item in self._action_area:
if item.id == response_id:
item.widget.sensitive = setting
[docs] def get_action_area(self):
"""
has been deprecated since version GTK3.12, GLXCurses return the internal _action_area.
Here the structure:
```
[
{
'widget': button_widget,
'response_id': response_id,
'default_response': False
},
{
'widget': button_widget,
'response_id': response_id,
'default_response': False
}
]
```
Returns the action area of dialog .
:return: the action area.
:rtype: list
"""
return self._action_area
[docs] def get_content_area(self):
"""
Returns the content area of dialog .
:return: the content area GLXCurses Box.
:rtype: GLXCurses.VBox
"""
return self._content_area
# Signals
[docs] def close(self):
"""
Signal emitted when the user uses a keybinding to close the dialog.
"""
if isinstance(GLXCurses.Application().active_window.widget, GLXCurses.Dialog):
if len(GLXCurses.Application().children) > 1:
if GLXCurses.Application().children[1]:
GLXCurses.Application().active_window_id = (
GLXCurses.Application().active_window_id_prev
)
[docs] def update_preferred_sizes(self):
self.preferred_width = self._get_estimated_preferred_width()
self.preferred_height = self._get_estimated_preferred_height()
def _update_preferred_position(self):
self.y_offset = 0
self.y_offset += int(self.height / 2)
self.y_offset -= int(self.preferred_height / 2)
self.x_offset = 0
self.x_offset += int(self.width / 2)
self.x_offset -= int(self.preferred_width / 2)
def _get_estimated_preferred_width(self):
"""
Estimate a preferred width, by consider X Location, allowed width
:return: a estimated preferred width
:rtype: int
"""
estimated_preferred_width = 0
if self.decorated:
estimated_preferred_width += 2
content_area_width = 0
if len(self._content_area.children) > 0:
for child in self._content_area.children:
content_area_width += child.preferred_width
action_area_width = 0
# if len(self._action_area) > 0:
# for button in self._action_area:
# action_area_width += button.widget.preferred_width
if self.title:
estimated_preferred_width += max(
content_area_width, max(action_area_width, len(self.title))
)
else:
estimated_preferred_width += max(
content_area_width, max(action_area_width, 0)
)
if len(self._action_area) > 0:
estimated_preferred_width += len(self._action_area)
estimated_preferred_width += 3
return estimated_preferred_width + 4
def _get_estimated_preferred_height(self):
"""
Estimate a preferred height, by consider Y Location
:return: a estimated preferred height
:rtype: int
"""
estimated_preferred_height = 4
if self._action_area:
# One for the Line Tee , One for the button action area
estimated_preferred_height += 2
if self.decorated:
estimated_preferred_height += 2
if len(self._content_area.children) > 0:
for child in self._content_area.children:
estimated_preferred_height += child.preferred_height
return estimated_preferred_height
def _draw_action_area(self):
# Draw the child.
if self._action_area:
total_size = len(self._action_area) - 1
a_button_have_default = False
for button in self._action_area:
total_size += button.widget.preferred_width
if button.widget.has_default is True:
a_button_have_default = True
if a_button_have_default is False:
self._action_area[-1].widget.has_default = True
cursor_position = int(((self.width - 2) / 2) - total_size / 2)
for button in self._action_area:
# Injection
button.widget.stdscr = GLXCurses.Application().stdscr
if button.widget.has_default is True:
self.emit("CLAIM_DEFAULT", {"id": button.widget.id})
self.emit("CLAIM_PRELIGHT", {"id": button.widget.id})
button.widget.x = cursor_position
button.widget.y = self.y_offset + self.preferred_height - 4
button.widget.width = button.widget.preferred_width + 2
button.widget.height = 2
button.widget.has_prelight = True
GLXCurses.Application().has_prelight = button.widget
cursor_position += button.widget.preferred_width + 1
elif button.widget.has_default is False:
self.emit("RELEASE_PRELIGHT", {"id": button.widget.id})
button.widget.x = cursor_position
button.widget.y = self.y_offset + self.preferred_height - 4
button.widget.width = button.widget.preferred_width + 2
button.widget.height = 2
cursor_position += button.widget.preferred_width + 1
if hasattr(button.widget, 'update_preferred_sizes'):
button.widget.update_preferred_sizes()
button.widget.draw_widget_in_area()
def _draw_background(self):
for y_inc in range(self.y_offset, self.y_offset + self.preferred_height):
for x_inc in range(self.x_offset, self.x_offset + self.preferred_width):
try:
self.subwin.delch(y_inc, x_inc)
self.subwin.insch(
y_inc,
x_inc,
" ",
self.color_normal,
)
except curses.error: # pragma: no cover
pass
def _draw_title(self):
if self.width > 1:
title_with_spacing = GLXCurses.resize_text(
" {0} ".format(self.title), self.width - 2, "~"
)
# pos = int(self.width / 2) - int(len(title_with_spacing) / 2)
self.add_string(
y=self.y_offset + 1,
x=int(self.width / 2) - int(len(title_with_spacing) / 2),
text=title_with_spacing,
color=self.style.color(
bg=self.style.attribute_to_rgb("base", "STATE_NORMAL"),
fg=self.style.attribute_to_rgb("white", "STATE_NORMAL"),
attributes=False,
)
| GLXCurses.GLXC.A_REVERSE,
)
def _draw_box(self):
if self.decorated:
self._draw_box_upper_left_corner()
self._draw_box_top()
self._draw_box_upper_right_corner()
self._draw_box_left_side()
self._draw_box_right_side()
self._draw_box_bottom_left_corner()
self._draw_box_bottom()
self._draw_box_bottom_right_corner()
if len(self._action_area) > 0:
self._draw_box_tee_pointing_right()
self._draw_box_tee_pointing_left()
self._draw_box_bottom_tee()
def _draw_box_bottom(self, char=None):
# Bottom
if char is None:
char = curses.ACS_HLINE
self.add_horizontal_line(
y=self.y_offset + self.preferred_height - 2,
x=self.x_offset + 2,
character=char,
length=self.preferred_width - 3,
color=self.color_normal,
)
def _draw_box_top(self, char=None):
# Top
if char is None:
char = curses.ACS_HLINE
self.add_horizontal_line(
y=self.y_offset + 1,
x=self.x_offset + 2,
character=char,
length=self.preferred_width - 3,
color=self.color_normal,
)
def _draw_box_right_side(self, char=None):
# Right side
if char is None:
char = curses.ACS_VLINE
self.add_vertical_line(
y=self.y_offset + 2,
x=self.x_offset + self.preferred_width - 2,
character=char,
length=self.preferred_height - 4,
color=self.color_normal,
)
def _draw_box_left_side(self, char=None):
# Left side
if char is None:
char = curses.ACS_VLINE
self.add_vertical_line(
y=self.y_offset + 2,
x=self.x_offset + 1,
character=char,
length=self.preferred_height - 4,
color=self.color_normal,
)
def _draw_box_upper_right_corner(self, char=None):
# Upper-right corner
if char is None:
char = curses.ACS_URCORNER
try:
self.add_string(
y=self.y_offset + 1,
x=self.preferred_width + self.x_offset - 2,
text="╮",
color=self.color_normal,
)
except curses.error:
self.add_string(
y=self.y_offset + 1,
x=self.preferred_width + self.x_offset - 2,
text=char,
color=self.color_normal,
)
def _draw_box_upper_left_corner(self, char=None):
# Upper-left corner
if char is None:
char = curses.ACS_ULCORNER
try:
self.add_string(
y=self.y_offset + 1,
x=self.x_offset + 1,
text="╭",
color=self.color_normal,
)
except curses.error:
self.add_string(
y=self.y_offset + 1,
x=self.x_offset + 1,
text=char,
color=self.color_normal,
)
def _draw_box_bottom_right_corner(self, char=None):
# Bottom-right corner
if char is None:
char = curses.ACS_LRCORNER
try:
self.add_string(
y=self.y_offset + self.preferred_height - 2,
x=self.x_offset + self.preferred_width - 2,
text="╯",
color=self.color_normal,
)
except curses.error:
self.add_string(
y=self.y_offset + self.preferred_height - 2,
x=self.x_offset + self.preferred_width - 2,
text=char,
color=self.color_normal,
)
def _draw_box_bottom_left_corner(self, char=None):
# Bottom-left corner
if char is None:
char = curses.ACS_LLCORNER
try:
self.add_string(
y=self.y_offset + self.preferred_height - 2,
x=self.x_offset + 1,
text="╰",
color=self.color_normal,
)
except curses.error:
self.add_string(
y=self.y_offset + self.preferred_height - 2,
x=self.x_offset + 1,
text=char,
color=self.color_normal,
)
def _draw_box_tee_pointing_right(self, char=None):
# Bottom-left tee_pointing_right
if char is None:
char = curses.ACS_LTEE
self.add_character(
y=self.y_offset + self.preferred_height - 4,
x=self.x_offset + 1,
character=char,
color=self.color_normal,
)
def _draw_box_tee_pointing_left(self, char=None):
# Bottom-right corner
if char is None:
char = curses.ACS_RTEE
self.add_character(
y=self.y_offset + self.preferred_height - 4,
x=self.x_offset + self.preferred_width - 2,
character=char,
color=self.color_normal,
)
def _draw_box_bottom_tee(self, char=None):
# Bottom
if char is None:
char = curses.ACS_HLINE
self.add_horizontal_line(
y=self.y_offset + self.preferred_height - 4,
x=self.x_offset + 2,
character=char,
length=self.preferred_width - 4,
color=self.color_normal,
)