Make the size modular, remove small-size lock (also seems to fix #2155)
some stuff is now hidden wen the window size gets too small (might need some adjustments). The info buffer in the roster tab, the userlist in mucs, the vertical tab list, the info buffer everywhere, etc…
This commit is contained in:
parent
069283e349
commit
0caf9417b2
11 changed files with 233 additions and 85 deletions
|
@ -41,6 +41,7 @@ from keyboard import Keyboard
|
|||
from logger import logger
|
||||
from plugin_manager import PluginManager
|
||||
from roster import roster
|
||||
from size_manager import SizeManager
|
||||
from text_buffer import TextBuffer
|
||||
from theming import get_theme
|
||||
from windows import g_lock
|
||||
|
@ -105,6 +106,7 @@ class Core(object):
|
|||
self.plugin_manager = PluginManager(self)
|
||||
self.events = events.EventHandler()
|
||||
|
||||
self.size = SizeManager(self, windows.Win)
|
||||
|
||||
# global commands, available from all tabs
|
||||
# a command is tuple of the form:
|
||||
|
@ -1498,20 +1500,25 @@ class Core(object):
|
|||
Resize the global_information_win only once at each resize.
|
||||
"""
|
||||
with g_lock:
|
||||
height = (tabs.Tab.height - 1 - self.information_win_size
|
||||
- tabs.Tab.tab_win_height())
|
||||
self.information_win.resize(self.information_win_size,
|
||||
tabs.Tab.width,
|
||||
height,
|
||||
0)
|
||||
height = min(tabs.Tab.height - 1 - self.information_win_size
|
||||
- tabs.Tab.tab_win_height(),
|
||||
tabs.Tab.height - 5)
|
||||
if not self.size.core_degrade_y:
|
||||
self.information_win.resize(self.information_win_size,
|
||||
tabs.Tab.width,
|
||||
height,
|
||||
0)
|
||||
|
||||
def resize_global_info_bar(self):
|
||||
"""
|
||||
Resize the GlobalInfoBar only once at each resize
|
||||
"""
|
||||
with g_lock:
|
||||
self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0)
|
||||
if config.get('enable_vertical_tab_list', False):
|
||||
height, width = self.stdscr.getmaxyx()
|
||||
if not self.size.core_degrade_y:
|
||||
self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0)
|
||||
if (config.get('enable_vertical_tab_list', False)
|
||||
and not self.size.core_degrade_x):
|
||||
try:
|
||||
height, _ = self.stdscr.getmaxyx()
|
||||
truncated_win = self.stdscr.subwin(height,
|
||||
|
@ -1550,7 +1557,10 @@ class Core(object):
|
|||
# window to each Tab class, so the draw themself in the portion
|
||||
# of the screen that the can occupy, and we draw the tab list
|
||||
# on the left remaining space
|
||||
if config.get('enable_vertical_tab_list', False):
|
||||
with g_lock:
|
||||
height, width = self.stdscr.getmaxyx()
|
||||
if (config.get('enable_vertical_tab_list', False) and
|
||||
not self.size.core_degrade_x):
|
||||
with g_lock:
|
||||
try:
|
||||
scr = self.stdscr.subwin(0,
|
||||
|
|
|
@ -617,7 +617,8 @@ def on_chatstate_groupchat_conversation(self, message, state):
|
|||
self.events.trigger('muc_chatstate', message, tab)
|
||||
tab.get_user_by_name(nick).chatstate = state
|
||||
if tab == self.current_tab():
|
||||
tab.user_win.refresh(tab.users)
|
||||
if not self.size.tab_degrade_x:
|
||||
tab.user_win.refresh(tab.users)
|
||||
tab.input.refresh()
|
||||
self.doupdate()
|
||||
else:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from . basetabs import Tab, ChatTab, GapTab, STATE_PRIORITY
|
||||
from . basetabs import Tab, ChatTab, GapTab
|
||||
from . basetabs import STATE_PRIORITY
|
||||
from . rostertab import RosterInfoTab
|
||||
from . muctab import MucTab, NS_MUC_USER
|
||||
from . privatetab import PrivateTab
|
||||
|
|
|
@ -38,9 +38,6 @@ from theming import get_theme
|
|||
from windows import g_lock
|
||||
|
||||
|
||||
MIN_WIDTH = 42
|
||||
MIN_HEIGHT = 6
|
||||
|
||||
# getters for tab colors (lambdas, so that they are dynamic)
|
||||
STATE_COLORS = {
|
||||
'disconnected': lambda: get_theme().COLOR_TAB_DISCONNECTED,
|
||||
|
@ -88,6 +85,7 @@ STATE_PRIORITY = {
|
|||
|
||||
class Tab(object):
|
||||
tab_core = None
|
||||
size_manager = None
|
||||
|
||||
plugin_commands = {}
|
||||
plugin_keys = {}
|
||||
|
@ -102,6 +100,12 @@ class Tab(object):
|
|||
self.commands = {} # and their own commands
|
||||
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
if not Tab.size_manager:
|
||||
Tab.size_manager = self.core.size
|
||||
return Tab.size_manager
|
||||
|
||||
@property
|
||||
def core(self):
|
||||
if not Tab.tab_core:
|
||||
|
@ -182,11 +186,7 @@ class Tab(object):
|
|||
@staticmethod
|
||||
def resize(scr):
|
||||
with g_lock:
|
||||
Tab.size = (Tab.height, Tab.width) = scr.getmaxyx()
|
||||
if Tab.height < MIN_HEIGHT or Tab.width < MIN_WIDTH:
|
||||
Tab.visible = False
|
||||
else:
|
||||
Tab.visible = True
|
||||
Tab.height, Tab.width = scr.getmaxyx()
|
||||
windows.Win._tab_win = scr
|
||||
|
||||
def register_command(self, name, func, *, desc='', shortdesc='', completion=None, usage=''):
|
||||
|
@ -283,9 +283,9 @@ class Tab(object):
|
|||
return False
|
||||
|
||||
def refresh_tab_win(self):
|
||||
if self.left_tab_win:
|
||||
if self.left_tab_win and not self.size.core_degrade_x:
|
||||
self.left_tab_win.refresh()
|
||||
else:
|
||||
elif not self.size.core_degrade_y:
|
||||
self.tab_win.refresh()
|
||||
|
||||
def refresh(self):
|
||||
|
|
|
@ -260,23 +260,44 @@ class ConversationTab(ChatTab):
|
|||
callback=callback)
|
||||
|
||||
def resize(self):
|
||||
if self.core.information_win_size >= self.height-3 or not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
self.text_win.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width, 1, 0)
|
||||
if self.size.tab_degrade_y:
|
||||
display_bar = False
|
||||
info_win_height = 0
|
||||
tab_win_height = 0
|
||||
bar_height = 0
|
||||
else:
|
||||
display_bar = True
|
||||
info_win_height = self.core.information_win_size
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
bar_height = 1
|
||||
|
||||
self.text_win.resize(self.height - 2 - bar_height - info_win_height
|
||||
- tab_win_height,
|
||||
self.width, bar_height, 0)
|
||||
self.text_win.rebuild_everything(self._text_buffer)
|
||||
self.upper_bar.resize(1, self.width, 0, 0)
|
||||
self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
if display_bar:
|
||||
self.upper_bar.resize(1, self.width, 0, 0)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - bar_height - info_win_height
|
||||
- tab_win_height,
|
||||
0)
|
||||
self.input.resize(1, self.width, self.height - 1, 0)
|
||||
|
||||
def refresh(self):
|
||||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
display_bar = display_info_win = not self.size.tab_degrade_y
|
||||
|
||||
self.text_win.refresh()
|
||||
self.upper_bar.refresh(self.get_dest_jid(), roster[self.get_dest_jid()])
|
||||
|
||||
if display_bar:
|
||||
self.upper_bar.refresh(self.get_dest_jid(), roster[self.get_dest_jid()])
|
||||
self.info_header.refresh(self.get_dest_jid(), roster[self.get_dest_jid()], self.text_win, self.chatstate, ConversationTab.additional_informations)
|
||||
self.info_win.refresh()
|
||||
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
self.refresh_tab_win()
|
||||
self.input.refresh()
|
||||
|
||||
|
@ -439,14 +460,21 @@ class DynamicConversationTab(ConversationTab):
|
|||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
display_bar = display_info_win = not self.size.tab_degrade_y
|
||||
|
||||
self.text_win.refresh()
|
||||
self.upper_bar.refresh(self.get_name(), roster[self.get_name()])
|
||||
if display_bar:
|
||||
self.upper_bar.refresh(self.get_name(), roster[self.get_name()])
|
||||
if self.locked_resource:
|
||||
displayed_jid = "%s/%s" % (self.get_name(), self.locked_resource)
|
||||
else:
|
||||
displayed_jid = self.get_name()
|
||||
self.info_header.refresh(displayed_jid, roster[self.get_name()], self.text_win, self.chatstate, ConversationTab.additional_informations)
|
||||
self.info_win.refresh()
|
||||
self.info_header.refresh(displayed_jid, roster[self.get_name()],
|
||||
self.text_win, self.chatstate,
|
||||
ConversationTab.additional_informations)
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
|
||||
self.refresh_tab_win()
|
||||
self.input.refresh()
|
||||
|
||||
|
|
|
@ -63,26 +63,41 @@ class MucListTab(Tab):
|
|||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
if self.size.tab_degrade_y:
|
||||
display_info_win = False
|
||||
else:
|
||||
display_info_win = True
|
||||
|
||||
self.info_header.refresh(window=self.listview)
|
||||
self.info_win.refresh()
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
self.refresh_tab_win()
|
||||
self.list_header.refresh()
|
||||
self.listview.refresh()
|
||||
self.input.refresh()
|
||||
self.update_commands()
|
||||
|
||||
def resize(self):
|
||||
if self.core.information_win_size >= self.height-3 or not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
|
||||
column_size = {'node-part': int(self.width*2/8),
|
||||
'name': int(self.width*5/8),
|
||||
'users': self.width-int(self.width*2/8)-int(self.width*5/8)}
|
||||
if self.size.tab_degrade_y:
|
||||
info_win_height = 0
|
||||
tab_win_height = 0
|
||||
else:
|
||||
info_win_height = self.core.information_win_size
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - info_win_height
|
||||
- tab_win_height,
|
||||
0)
|
||||
column_size = {'node-part': int(self.width* 2 / 8),
|
||||
'name': int(self.width * 5 / 8),
|
||||
'users': self.width - int(self.width * 2 / 8)
|
||||
- int(self.width * 5 / 8)}
|
||||
self.list_header.resize_columns(column_size)
|
||||
self.list_header.resize(1, self.width, 0, 0)
|
||||
self.listview.resize_columns(column_size)
|
||||
self.listview.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width, 1, 0)
|
||||
self.listview.resize(self.height - 3 - info_win_height - tab_win_height,
|
||||
self.width, 1, 0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
|
||||
def on_slash(self):
|
||||
|
|
|
@ -796,29 +796,42 @@ class MucTab(ChatTab):
|
|||
"""
|
||||
Resize the whole window. i.e. all its sub-windows
|
||||
"""
|
||||
if not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
if config.get("hide_user_list", False):
|
||||
if config.get("hide_user_list", False) or self.size.tab_degrade_x:
|
||||
display_user_list = False
|
||||
text_width = self.width
|
||||
else:
|
||||
text_width = (self.width//10)*9
|
||||
self.user_win.resize(self.height - 3 - self.core.information_win_size
|
||||
- Tab.tab_win_height(),
|
||||
self.width - (self.width // 10) * 9 - 1,
|
||||
1,
|
||||
(self.width // 10) * 9 + 1)
|
||||
display_user_list = True
|
||||
text_width = (self.width // 10) * 9
|
||||
|
||||
if self.size.tab_degrade_y:
|
||||
display_info_win = False
|
||||
tab_win_height = 0
|
||||
info_win_height = 0
|
||||
else:
|
||||
display_info_win = True
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
info_win_height = self.core.information_win_size
|
||||
|
||||
|
||||
if display_user_list:
|
||||
self.user_win.resize(self.height - 3 - info_win_height
|
||||
- tab_win_height,
|
||||
self.width - (self.width // 10) * 9 - 1,
|
||||
1,
|
||||
(self.width // 10) * 9 + 1)
|
||||
self.v_separator.resize(self.height - 2 - tab_win_height,
|
||||
1, 1, 9 * (self.width // 10))
|
||||
|
||||
self.topic_win.resize(1, self.width, 0, 0)
|
||||
self.v_separator.resize(self.height - 2 - Tab.tab_win_height(),
|
||||
1, 1, 9 * (self.width // 10))
|
||||
self.text_win.resize(self.height - 3 - self.core.information_win_size
|
||||
- Tab.tab_win_height(),
|
||||
text_width, 1, 0)
|
||||
|
||||
self.text_win.resize(self.height - 3 - info_win_height
|
||||
- tab_win_height,
|
||||
text_width, 1, 0)
|
||||
self.text_win.rebuild_everything(self._text_buffer)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2
|
||||
- self.core.information_win_size
|
||||
- Tab.tab_win_height(),
|
||||
self.height - 2 - info_win_height
|
||||
- tab_win_height,
|
||||
0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
|
||||
|
@ -826,14 +839,21 @@ class MucTab(ChatTab):
|
|||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
if config.get("hide_user_list", False) or self.size.tab_degrade_x:
|
||||
display_user_list = False
|
||||
else:
|
||||
display_user_list = True
|
||||
display_info_win = not self.size.tab.degrade_y
|
||||
|
||||
self.topic_win.refresh(self.get_single_line_topic())
|
||||
self.text_win.refresh()
|
||||
if not config.get("hide_user_list", False):
|
||||
if display_user_list:
|
||||
self.v_separator.refresh()
|
||||
self.user_win.refresh(self.users)
|
||||
self.info_header.refresh(self, self.text_win)
|
||||
self.refresh_tab_win()
|
||||
self.info_win.refresh()
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
self.input.refresh()
|
||||
|
||||
def on_input(self, key, raw):
|
||||
|
|
|
@ -237,21 +237,36 @@ class PrivateTab(ChatTab):
|
|||
self.parent_muc.command_info(user)
|
||||
|
||||
def resize(self):
|
||||
if self.core.information_win_size >= self.height-3 or not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
self.text_win.resize(self.height-2-self.core.information_win_size - Tab.tab_win_height(), self.width, 0, 0)
|
||||
|
||||
if self.size.tab_degrade_y:
|
||||
info_win_height = 0
|
||||
tab_win_height = 0
|
||||
else:
|
||||
info_win_height = self.core.information_win_size
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
|
||||
self.text_win.resize(self.height - 2 - info_win_height - tab_win_height,
|
||||
self.width, 0, 0)
|
||||
self.text_win.rebuild_everything(self._text_buffer)
|
||||
self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - info_win_height
|
||||
- tab_win_height,
|
||||
0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
|
||||
def refresh(self):
|
||||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
display_info_win = not self.size.tab_degrade_y
|
||||
|
||||
self.text_win.refresh()
|
||||
self.info_header.refresh(self.name, self.text_win, self.chatstate, PrivateTab.additional_informations)
|
||||
self.info_win.refresh()
|
||||
self.info_header.refresh(self.name, self.text_win, self.chatstate,
|
||||
PrivateTab.additional_informations)
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
|
||||
self.refresh_tab_win()
|
||||
self.input.refresh()
|
||||
|
||||
|
|
|
@ -266,15 +266,40 @@ class RosterInfoTab(Tab):
|
|||
self.core.command_last_activity(jid)
|
||||
|
||||
def resize(self):
|
||||
if not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
roster_width = self.width//2
|
||||
info_width = self.width-roster_width-1
|
||||
self.v_separator.resize(self.height-1 - Tab.tab_win_height(), 1, 0, roster_width)
|
||||
self.information_win.resize(self.height-2-4, info_width, 0, roster_width+1, self.core.information_buffer)
|
||||
self.roster_win.resize(self.height-1 - Tab.tab_win_height(), roster_width, 0, 0)
|
||||
self.contact_info_win.resize(5 - Tab.tab_win_height(), info_width, self.height-2-4, roster_width+1)
|
||||
if self.size.tab_degrade_x:
|
||||
display_info = False
|
||||
roster_width = self.width
|
||||
else:
|
||||
display_info = True
|
||||
roster_width = self.width // 2
|
||||
if self.size.tab_degrade_y:
|
||||
display_contact_win = False
|
||||
contact_win_h = 0
|
||||
else:
|
||||
display_contact_win = True
|
||||
contact_win_h = 5
|
||||
if self.size.tab_degrade_y:
|
||||
tab_win_height = 0
|
||||
else:
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
|
||||
info_width = self.width - roster_width - 1
|
||||
if display_info:
|
||||
self.v_separator.resize(self.height - 1 - tab_win_height,
|
||||
1, 0, roster_width)
|
||||
self.information_win.resize(self.height - 1 - tab_win_height
|
||||
- contact_win_h,
|
||||
info_width, 0, roster_width + 1,
|
||||
self.core.information_buffer)
|
||||
if display_contact_win:
|
||||
self.contact_info_win.resize(contact_win_h - tab_win_height,
|
||||
info_width,
|
||||
self.height - tab_win_height
|
||||
- contact_win_h - 1,
|
||||
roster_width + 1)
|
||||
self.roster_win.resize(self.height - 1 - Tab.tab_win_height(),
|
||||
roster_width, 0, 0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
self.default_help_message.resize(1, self.width, self.height-1, 0)
|
||||
|
||||
|
@ -699,10 +724,17 @@ class RosterInfoTab(Tab):
|
|||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
self.v_separator.refresh()
|
||||
|
||||
display_info = not self.size.tab_degrade_x
|
||||
display_contact_win = not self.size.tab_degrade_y
|
||||
|
||||
self.roster_win.refresh(roster)
|
||||
self.contact_info_win.refresh(self.roster_win.get_selected_row())
|
||||
self.information_win.refresh()
|
||||
if display_info:
|
||||
self.v_separator.refresh()
|
||||
self.information_win.refresh()
|
||||
if display_contact_win:
|
||||
self.contact_info_win.refresh(
|
||||
self.roster_win.get_selected_row())
|
||||
self.refresh_tab_win()
|
||||
self.input.refresh()
|
||||
|
||||
|
|
|
@ -163,23 +163,38 @@ class XMLTab(Tab):
|
|||
self.core.close_tab()
|
||||
|
||||
def resize(self):
|
||||
if self.core.information_win_size >= self.height-3 or not self.visible:
|
||||
return
|
||||
self.need_resize = False
|
||||
min = 1 if self.left_tab_win else 2
|
||||
self.text_win.resize(self.height-self.core.information_win_size - Tab.tab_win_height() - 2, self.width, 0, 0)
|
||||
if self.size.tab_degrade_y:
|
||||
info_win_size = 0
|
||||
tab_win_height = 0
|
||||
else:
|
||||
info_win_size = self.core.information_win_size
|
||||
tab_win_height = Tab.tab_win_height()
|
||||
|
||||
self.text_win.resize(self.height - info_win_size - tab_win_height - 2,
|
||||
self.width, 0, 0)
|
||||
self.text_win.rebuild_everything(self.core.xml_buffer)
|
||||
self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - info_win_size
|
||||
- tab_win_height,
|
||||
0)
|
||||
self.input.resize(1, self.width, self.height-1, 0)
|
||||
|
||||
def refresh(self):
|
||||
if self.need_resize:
|
||||
self.resize()
|
||||
log.debug(' TAB Refresh: %s', self.__class__.__name__)
|
||||
|
||||
if self.size.tab_degrade_y:
|
||||
display_info_win = False
|
||||
else:
|
||||
display_info_win = True
|
||||
|
||||
self.text_win.refresh()
|
||||
self.info_header.refresh(self.filter_type, self.filter, self.text_win)
|
||||
self.refresh_tab_win()
|
||||
self.info_win.refresh()
|
||||
if display_info_win:
|
||||
self.info_win.refresh()
|
||||
self.input.refresh()
|
||||
|
||||
def on_lose_focus(self):
|
||||
|
|
|
@ -52,6 +52,16 @@ g_lock = RLock()
|
|||
|
||||
LINES_NB_LIMIT = 4096
|
||||
|
||||
class DummyWin(object):
|
||||
def __getattribute__(self, name):
|
||||
if name != '__bool__':
|
||||
return lambda *args, **kwargs: (0, 0)
|
||||
else:
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def find_first_format_char(text):
|
||||
pos = -1
|
||||
for char in format_chars:
|
||||
|
@ -103,6 +113,7 @@ class Win(object):
|
|||
_tab_win = None
|
||||
def __init__(self):
|
||||
self._win = None
|
||||
self.height, self.width = 0, 0
|
||||
|
||||
def _resize(self, height, width, y, x):
|
||||
if height == 0 or width == 0:
|
||||
|
@ -113,8 +124,8 @@ class Win(object):
|
|||
self._win = Win._tab_win.derwin(height, width, y, x)
|
||||
except:
|
||||
log.debug('DEBUG: mvwin returned ERR. Please investigate')
|
||||
|
||||
# If this ever fail, uncomment that ^
|
||||
if self._win is None:
|
||||
self._win = DummyWin()
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue