# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from PySide6.QtCore import QEasingCurve
from PySide6.QtCore import QParallelAnimationGroup
from PySide6.QtCore import QPropertyAnimation
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QFrame
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QSizePolicy
from PySide6.QtWidgets import QSpacerItem
from PySide6.QtWidgets import QWidgetItem
from ansys.aedt.toolkits.common.ui.models import general_settings
from ansys.aedt.toolkits.common.ui.utils.widgets import PyComboBox
from ansys.aedt.toolkits.common.ui.utils.widgets import PyIconButton
from ansys.aedt.toolkits.common.ui.utils.widgets import PyLabel
from ansys.aedt.toolkits.common.ui.utils.widgets import PyLineEdit
from ansys.aedt.toolkits.common.ui.utils.widgets import PyPushButton
from ansys.aedt.toolkits.common.ui.utils.widgets import PyToggle
[docs]
class CommonWindowUtils(object):
"""
Class representing a common window with various UI functionalities.
"""
def __init__(self):
self.load_pages = None
self.left_column = None
self.left_column_frame = None
self.right_column_frame = None
self.right_column = None
self.title_bar_frame = None
self.left_menu = None
self.themes = None
self.group = None
self.progress_frame = None
self.__resize = 1
[docs]
def set_page(self, page):
"""
Set the current page in the load_pages widget.
Parameters
----------
page : QWidget
The page widget to be displayed as the current page.
"""
self.load_pages.pages.setCurrentWidget(page)
self.window_refresh()
[docs]
def set_left_column_menu(self, menu, title, icon_path):
"""
Configures the left column of the CommonWindow UI by setting the current widget as the provided `menu`,
the title in the left column's title label as the provided `title`,
and the icon in the left column as the icon specified by `icon_path`.
Parameters
----------
menu : QWidget
The menu widget to be set as the current widget in the left column.
title : str
The title to be set in the left column's title label.
icon_path : str
The path to the icon image file to be set in the left column's icon.
"""
self.left_column.menus.menus.setCurrentWidget(menu)
self.left_column.title_label.setText(title)
self.left_column.icon.set_icon(icon_path)
[docs]
def is_left_column_visible(self):
"""
Check if the left column is visible.
Returns
-------
bool
``True`` if the left column is visible, ``False`` otherwise.
"""
return self.left_column_frame.width() != 0
[docs]
def is_right_column_visible(self):
"""
Checks if the right column is visible.
Returns
-------
bool
``True`` if the right column is visible, ``False`` otherwise.
"""
return self.right_column_frame.width() != 0
[docs]
def is_progress_visible(self):
"""
Checks if the progress bar is visible.
Returns
-------
bool
``True`` if the progress bar is visible, ``False`` otherwise.
"""
return self.progress_frame.height() != 0
[docs]
def set_right_column_menu(self, title):
"""
Sets the title of the right column menu.
Parameters
----------
title: str
The title to be set.
"""
self.right_column.title_label.setText(title)
[docs]
def get_title_bar(self, object_name):
"""
Get title.
Parameters
----------
object_name: str
The name of the QPushButton object.
"""
return self.title_bar_frame.findChild(QPushButton, object_name)
[docs]
def add_combobox(self, layout, height=40, width=None, label="label1", combobox_list=None, font_size=12):
"""
Adds a label and combobox to a layout.
Parameters
----------
layout: QLayout
The layout object to which the label and combobox will be added.
height: int, optional
The height of the label and combobox widgets. Default is 40.
width: list, optional
The width of the label and combobox widgets. If not provided, a default width of [100, 100] will be used.
label: str, optional
The text to be displayed on the label widget. Default is 'label1'.
combobox_list: list, optional
A list of items to be displayed in the combobox. If not provided, a default list of ['1', '2'] will be used.
font_size: int, optional
The font size of the label widget. Default is 12.
Returns
-------
list
A list containing the layout row object, label object, and combobox object.
"""
combobox_list = combobox_list or []
width = width or [100, 100]
app_color = self.themes["app_color"]
text_foreground = app_color["text_foreground"]
combo_color = app_color["combo_color"]
combo_hover = app_color["combo_hover"]
layout_row = QHBoxLayout()
layout.addLayout(layout_row)
label_widget = PyLabel(text=label, font_size=font_size, color=text_foreground)
label_widget.setMinimumHeight(height)
label_widget.setFixedWidth(width[0])
layout_row.addWidget(label_widget)
combobox_widget = PyComboBox(
text_list=combobox_list,
radius=8,
bg_color=combo_color,
bg_color_hover=combo_hover,
text_color=text_foreground,
font_size=font_size,
)
combobox_widget.setMinimumHeight(height)
combobox_widget.setFixedWidth(width[1])
layout_row.addWidget(combobox_widget)
return [layout_row, label_widget, combobox_widget]
[docs]
def add_textbox(self, layout, height=40, width=None, label="label1", initial_text=None, font_size=12):
"""
Adds a label and textbox to a layout.
Parameters
----------
layout: QLayout
The layout object to which the label and combobox will be added.
height: int, optional
The height of the label and combobox widgets. Default is 40.
width: list, optional
The width of the label and combobox widgets. If not provided, a default width of [100, 100] will be used.
label: str, optional
The text to be displayed on the label widget. Default is '"label1"'.
initial_text: str, optional
Text to be displayed in the textbox.
font_size: int, optional
The font size of the label widget. Default is 12.
Returns
-------
list
A list containing the layout row object, label object, and combobox object.
"""
initial_text = initial_text or " "
width = width or [100, 100]
app_color = self.themes["app_color"]
text_foreground = app_color["text_foreground"]
bg_color = app_color["combo_color"]
layout_row = QHBoxLayout()
layout.addLayout(layout_row)
label_widget = PyLabel(text=label, font_size=font_size, color=text_foreground)
label_widget.setMinimumHeight(height)
label_widget.setFixedWidth(width[0])
layout_row.addWidget(label_widget)
linebox_widget = PyLineEdit(
text=initial_text,
radius=8,
bg_color=bg_color,
color=text_foreground,
selection_color=app_color["white"],
bg_color_active=app_color["dark_three"],
context_color=app_color["context_color"],
font_size=font_size,
)
linebox_widget.setMinimumHeight(height)
linebox_widget.setFixedWidth(width[1])
layout_row.addWidget(linebox_widget)
return [layout_row, label_widget, linebox_widget]
[docs]
def add_toggle(self, layout, height=40, width=None, label=None, font_size=12):
"""
Add a label and a toggle button to a specified layout.
Parameters
----------
layout: QLayout
Layout object to add the label and toggle button to.
height: int, optional
Height of the label and toggle. Default is 40.
width: list, optional
Width of the label and toggle. Default is [50, 100, 50] if None.
label: list of str, optional
Label text. Default is ['label1', 'label2'] if None.
font_size: int, optional
Font size for the label text. Default is 12.
Returns
-------
tuple
A tuple containing the layout row, label object, toggle object, and second label object
"""
label = label or ["label1", "label2"]
width = width or [50, 100, 50]
layout_row = QHBoxLayout()
layout.addLayout(layout_row)
label1 = self._create_label(label[0], font_size, height, width[0])
layout_row.addWidget(label1)
spacer1 = QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Fixed)
layout_row.addItem(spacer1)
toggle = self._create_toggle(width[1], height)
layout_row.addWidget(toggle)
spacer2 = QSpacerItem(15, 0, QSizePolicy.Fixed, QSizePolicy.Fixed)
layout_row.addItem(spacer2)
label2 = self._create_label(label[1], font_size, height, width[2])
layout_row.addWidget(label2)
return layout_row, label1, toggle, label2
[docs]
def add_vertical_line(self, layout, top_spacer=None, bot_spacer=None):
"""
Add a vertical line.
Parameters
----------
layout: QLayout
Layout object to add the label and toggle button to.
top_spacer: list, optional
Top spacer. Default is [0, 10].
bot_spacer: list, optional
Bottom, spacer. Default is [0, 10].
"""
top_spacer = top_spacer or [0, 10]
bot_spacer = bot_spacer or [0, 10]
spacer = QSpacerItem(top_spacer[0], top_spacer[1], QSizePolicy.Minimum, QSizePolicy.Minimum)
layout.addItem(spacer)
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
line.setStyleSheet("background-color: {};".format(self.themes["app_color"]["dark_two"]))
layout.addWidget(line)
spacer = QSpacerItem(bot_spacer[0], bot_spacer[1], QSizePolicy.Minimum, QSizePolicy.Minimum)
layout.addItem(spacer)
return line
[docs]
def toggle_left_column(self):
"""
Toggles the left column of the CommonWindow by starting a box animation.
"""
self.start_box_animation("left")
[docs]
def toggle_right_column(self):
"""
Toggles the display of the right column in a common window.
"""
self.start_box_animation("right")
[docs]
def window_refresh(self):
"""Window refresh"""
# Store the original size
original_size = self.app.size()
if self.__resize == 1:
self.__resize = -1
else:
self.__resize = 1
# Change the size slightly to trigger a repaint
self.app.resize(original_size.width() + self.__resize, original_size.height())
# Restore the original size
self.app.resize(original_size)
[docs]
def start_box_animation(self, direction):
"""
Starts a box animation in the specified direction.
Parameters
----------
direction: str
The direction in which the box animation should be performed.
Possible values are "left" and "right".
"""
minimum_left = general_settings.left_column_size["minimum"]
maximum_left = general_settings.left_column_size["maximum"]
minimum_right = general_settings.right_column_size["minimum"]
maximum_right = general_settings.right_column_size["maximum"]
left_box_width = self.left_column_frame.width()
right_box_width = self.right_column_frame.width()
left_width = maximum_left if left_box_width == minimum_left and direction == "left" else minimum_left
right_width = maximum_right if right_box_width == minimum_right and direction == "right" else minimum_right
self.setup_animation(left_box_width, right_box_width, left_width, right_width)
[docs]
def setup_animation(self, left_start, right_start, left_end, right_end):
"""
Sets up an animation for the left and right columns of the UI.
Parameters
----------
left_start: int
The starting value for the left box animation.
right_start: int
The starting value for the right box animation.
left_end: int
The ending value for the left box animation.
right_end: int
The ending value for the right box animation.
"""
# ANIMATION LEFT BOX
left_box = self.create_animation(self.left_column_frame, b"minimumWidth", left_start, left_end)
# ANIMATION RIGHT BOX
right_box = self.create_animation(self.right_column_frame, b"minimumWidth", right_start, right_end)
# GROUP ANIMATION
self.group = QParallelAnimationGroup()
self.group.stop()
self.group.addAnimation(left_box)
self.group.addAnimation(right_box)
self.group.start()
[docs]
def toggle_progress(self):
"""
Toggles the progress row.
"""
minimum_progress = general_settings.progress_size["minimum"]
maximum_progress = general_settings.progress_size["maximum"]
progress_box_height = self.progress_frame.height()
progress_width = maximum_progress if progress_box_height == minimum_progress else minimum_progress
self.progress_frame.setMaximumHeight(progress_width)
[docs]
def clear_layout(self, layout):
"""Clear all layout."""
for i in reversed(range(layout.count())):
item = layout.itemAt(i)
if isinstance(item, QWidgetItem):
item.widget().close()
elif isinstance(item, QSpacerItem):
pass
# no need to do extra stuff
else:
self.clear_layout(item.layout())
# remove the item from layout
layout.removeItem(item)
[docs]
def update_progress(self, progress_value):
"""Clear all layout."""
self.progress.progress = progress_value
[docs]
def update_logger(self, text):
"""Clear all layout."""
self.logger.log(text)
[docs]
@staticmethod
def item_index(layout, item):
"""
Item index.
"""
try:
for i in range(layout.count()):
if layout.itemAt(i) == item:
return i
elif layout.itemAt(i).widget() == item:
return i
return -1
except:
return -1
[docs]
@staticmethod
def remove_item(layout, index):
"""
Remove item by index.
"""
item = layout.itemAt(index)
if item:
widget = item.widget()
if widget:
layout.removeWidget(widget)
widget.deleteLater()
else:
layout.removeItem(item)
layout.update()
[docs]
@staticmethod
def create_animation(obj, property_name, start_val, end_val):
"""
Creates an animation with specified parameters.
Parameters
----------
obj: QObject
The object on which the animation will be applied.
property_name: str
The name of the property to animate.
start_val: Any
The initial value of the property.
end_val: Any
The final value of the property.
Returns
-------
QPropertyAnimation
The created animation.
"""
animation = QPropertyAnimation(obj, property_name)
animation.setStartValue(start_val)
animation.setEndValue(end_val)
animation.setEasingCurve(QEasingCurve.InOutQuart)
return animation
def _create_toggle(self, width, height):
toggle = PyToggle(
width=width,
bg_color=self.themes["app_color"]["dark_one"],
circle_color=self.themes["app_color"]["icon_color"],
active_color=self.themes["app_color"]["dark_one"],
)
toggle.setMaximumHeight(height)
return toggle
def _create_label(self, text, font_size, height, width):
label = PyLabel(text=text, font_size=font_size, color=self.themes["app_color"]["text_foreground"])
label.setMinimumHeight(height)
label.setAlignment(Qt.AlignLeft)
label.setFixedWidth(width)
return label
def _get_color_theme(self, color_key):
return self.themes["app_color"][color_key]
def _add_button(self, text="button", font_size=10):
button_obj = PyPushButton(
text=text,
radius=8,
color=self.themes["app_color"]["text_foreground"],
bg_color=self.themes["app_color"]["dark_one"],
bg_color_hover=self.themes["app_color"]["dark_three"],
bg_color_pressed=self.themes["app_color"]["dark_four"],
font_size=font_size,
)
return button_obj