big ui update, messages sending added
This commit is contained in:
parent
4957d7ce09
commit
30aa254d39
6 changed files with 196 additions and 31 deletions
10
src/main.py
10
src/main.py
|
@ -1,7 +1,7 @@
|
||||||
from loginscreen import LoginScreen
|
from loginscreen import LoginScreen
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
from mainscreen import MainWindow
|
from mainscreen import MainWindow
|
||||||
from profile import Profile, tox_factory
|
from profile import ProfileHelper, tox_factory
|
||||||
import sys
|
import sys
|
||||||
from PySide import QtCore, QtGui
|
from PySide import QtCore, QtGui
|
||||||
from callbacks import init_callbacks
|
from callbacks import init_callbacks
|
||||||
|
@ -42,7 +42,7 @@ def main():
|
||||||
# show login screen if default profile not found
|
# show login screen if default profile not found
|
||||||
ls = LoginScreen()
|
ls = LoginScreen()
|
||||||
ls.setWindowIconText("Toxygen")
|
ls.setWindowIconText("Toxygen")
|
||||||
profiles = Profile.find_profiles()
|
profiles = ProfileHelper.find_profiles()
|
||||||
ls.update_select(map(lambda x: x[1], profiles))
|
ls.update_select(map(lambda x: x[1], profiles))
|
||||||
_login = login(profiles)
|
_login = login(profiles)
|
||||||
ls.update_on_close(_login.login_screen_close)
|
ls.update_on_close(_login.login_screen_close)
|
||||||
|
@ -57,17 +57,17 @@ def main():
|
||||||
tox = tox_factory()
|
tox = tox_factory()
|
||||||
tox.self_set_name('Toxygen User')
|
tox.self_set_name('Toxygen User')
|
||||||
tox.self_set_status('Toxing on Toxygen')
|
tox.self_set_status('Toxing on Toxygen')
|
||||||
Profile.save_profile(tox.get_savedata(), name)
|
ProfileHelper.save_profile(tox.get_savedata(), name)
|
||||||
else: # load existing profile
|
else: # load existing profile
|
||||||
path, name = _login.get_data()
|
path, name = _login.get_data()
|
||||||
if _login.default:
|
if _login.default:
|
||||||
settings['auto_profile'] = (path, name)
|
settings['auto_profile'] = (path, name)
|
||||||
settings.save()
|
settings.save()
|
||||||
data = Profile.open_profile(path, name)
|
data = ProfileHelper.open_profile(path, name)
|
||||||
tox = tox_factory(data, settings)
|
tox = tox_factory(data, settings)
|
||||||
else:
|
else:
|
||||||
path, name = settings['auto_profile']
|
path, name = settings['auto_profile']
|
||||||
data = Profile.open_profile(path, name)
|
data = ProfileHelper.open_profile(path, name)
|
||||||
tox = tox_factory(data, settings)
|
tox = tox_factory(data, settings)
|
||||||
|
|
||||||
ms = MainWindow(tox)
|
ms = MainWindow(tox)
|
||||||
|
|
|
@ -3,9 +3,35 @@
|
||||||
import sys
|
import sys
|
||||||
from PySide import QtGui, QtCore
|
from PySide import QtGui, QtCore
|
||||||
from menu import *
|
from menu import *
|
||||||
|
from profile import Profile
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
|
|
||||||
|
|
||||||
|
class ContactItem(QtGui.QListWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QtGui.QListWidget.__init__(self, parent)
|
||||||
|
# self.setMinimumSize(QtCore.QSize(250, 50))
|
||||||
|
# self.setMaximumSize(QtCore.QSize(250, 50))
|
||||||
|
self.setBaseSize(QtCore.QSize(250, 50))
|
||||||
|
self.name = QtGui.QLabel(self)
|
||||||
|
self.name.setGeometry(QtCore.QRect(80, 10, 191, 25))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily("Times New Roman")
|
||||||
|
font.setPointSize(12)
|
||||||
|
font.setBold(True)
|
||||||
|
self.name.setFont(font)
|
||||||
|
self.name.setObjectName("name")
|
||||||
|
self.status_message = QtGui.QLabel(self)
|
||||||
|
self.status_message.setGeometry(QtCore.QRect(80, 30, 191, 17))
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily("Times New Roman")
|
||||||
|
font.setPointSize(10)
|
||||||
|
font.setBold(False)
|
||||||
|
self.status_message.setFont(font)
|
||||||
|
self.status_message.setObjectName("status_message")
|
||||||
|
|
||||||
|
|
||||||
class StatusCircle(QtGui.QWidget):
|
class StatusCircle(QtGui.QWidget):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
|
@ -126,6 +152,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
self.sendMessageButton = QtGui.QPushButton(Form)
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(440, 10, 51, 131))
|
self.sendMessageButton.setGeometry(QtCore.QRect(440, 10, 51, 131))
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
self.sendMessageButton.setObjectName("sendMessageButton")
|
||||||
|
self.sendMessageButton.clicked.connect(self.send_message)
|
||||||
self.screenshotButton.setText(QtGui.QApplication.translate("Form", "Screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
self.screenshotButton.setText(QtGui.QApplication.translate("Form", "Screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.fileTransferButton.setText(QtGui.QApplication.translate("Form", "File transfer", None, QtGui.QApplication.UnicodeUTF8))
|
self.fileTransferButton.setText(QtGui.QApplication.translate("Form", "File transfer", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.sendMessageButton.setText(QtGui.QApplication.translate("Form", "Send", None, QtGui.QApplication.UnicodeUTF8))
|
self.sendMessageButton.setText(QtGui.QApplication.translate("Form", "Send", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
@ -148,7 +175,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
Form.resize(500, 300)
|
Form.resize(500, 300)
|
||||||
Form.setMinimumSize(QtCore.QSize(250, 100))
|
Form.setMinimumSize(QtCore.QSize(250, 100))
|
||||||
Form.setMaximumSize(QtCore.QSize(250, 100))
|
Form.setMaximumSize(QtCore.QSize(250, 100))
|
||||||
Form.setBaseSize(QtCore.QSize(2500, 100))
|
Form.setBaseSize(QtCore.QSize(250, 100))
|
||||||
self.graphicsView = QtGui.QGraphicsView(Form)
|
self.graphicsView = QtGui.QGraphicsView(Form)
|
||||||
self.graphicsView.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
self.graphicsView.setGeometry(QtCore.QRect(10, 20, 64, 64))
|
||||||
self.graphicsView.setMinimumSize(QtCore.QSize(64, 64))
|
self.graphicsView.setMinimumSize(QtCore.QSize(64, 64))
|
||||||
|
@ -201,16 +228,36 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self.callButton = QtGui.QPushButton(Form)
|
self.callButton = QtGui.QPushButton(Form)
|
||||||
self.callButton.setGeometry(QtCore.QRect(380, 30, 98, 27))
|
self.callButton.setGeometry(QtCore.QRect(380, 30, 98, 27))
|
||||||
self.callButton.setObjectName("callButton")
|
self.callButton.setObjectName("callButton")
|
||||||
self.account_name.setText(QtGui.QApplication.translate("Form", "TextLabel", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.account_status.setText(QtGui.QApplication.translate("Form", "TextLabel", None, QtGui.QApplication.UnicodeUTF8))
|
|
||||||
self.callButton.setText(QtGui.QApplication.translate("Form", "Start call", None, QtGui.QApplication.UnicodeUTF8))
|
self.callButton.setText(QtGui.QApplication.translate("Form", "Start call", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
|
def setup_left_bottom(self, widget):
|
||||||
|
# widget.setFixedWidth(250)
|
||||||
|
# widget.setMinimumSize(QtCore.QSize(250, 500))
|
||||||
|
# widget.setMaximumSize(QtCore.QSize(250, 500))
|
||||||
|
# widget.setBaseSize(QtCore.QSize(250, 500))
|
||||||
|
self.friends_list = QtGui.QListWidget(widget)
|
||||||
|
self.friends_list.setGeometry(0, 0, 250, 300)
|
||||||
|
count = self.tox.self_get_friend_list_size()
|
||||||
|
widgets = []
|
||||||
|
for i in xrange(count):
|
||||||
|
item = ContactItem()
|
||||||
|
elem = QtGui.QListWidgetItem(self.friends_list)
|
||||||
|
print item.sizeHint()
|
||||||
|
elem.setSizeHint(QtCore.QSize(250, 50))
|
||||||
|
self.friends_list.addItem(elem)
|
||||||
|
self.friends_list.setItemWidget(elem, item)
|
||||||
|
widgets.append(item)
|
||||||
|
self.profile = Profile(self.tox, widgets, None)
|
||||||
|
self.friends_list.clicked.connect(self.friend_click)
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
|
self.setMinimumSize(800, 400)
|
||||||
|
self.setGeometry(400, 400, 800, 400)
|
||||||
|
self.setWindowTitle('Toxygen')
|
||||||
main = QtGui.QWidget()
|
main = QtGui.QWidget()
|
||||||
grid = QtGui.QGridLayout()
|
grid = QtGui.QGridLayout()
|
||||||
search = QtGui.QWidget()
|
search = QtGui.QWidget()
|
||||||
grid.setColumnStretch(1, 1)
|
|
||||||
self.setup_left_center(search)
|
self.setup_left_center(search)
|
||||||
grid.addWidget(search, 1, 0)
|
grid.addWidget(search, 1, 0)
|
||||||
name = QtGui.QWidget()
|
name = QtGui.QWidget()
|
||||||
|
@ -222,12 +269,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
message = QtGui.QWidget()
|
message = QtGui.QWidget()
|
||||||
self.setup_right_bottom(message)
|
self.setup_right_bottom(message)
|
||||||
grid.addWidget(message, 2, 1)
|
grid.addWidget(message, 2, 1)
|
||||||
|
main_list = QtGui.QWidget()
|
||||||
|
self.setup_left_bottom(main_list)
|
||||||
|
grid.addWidget(main_list, 2, 0)
|
||||||
|
grid.setColumnMinimumWidth(1, 500)
|
||||||
|
grid.setColumnMinimumWidth(0, 250)
|
||||||
main.setLayout(grid)
|
main.setLayout(grid)
|
||||||
self.setCentralWidget(main)
|
self.setCentralWidget(main)
|
||||||
self.setup_menu(self)
|
self.setup_menu(self)
|
||||||
self.setMinimumSize(800, 400)
|
|
||||||
self.setGeometry(400, 400, 800, 400)
|
|
||||||
self.setWindowTitle('Toxygen')
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, event):
|
def mouseReleaseEvent(self, event):
|
||||||
pass
|
pass
|
||||||
|
@ -238,6 +287,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
# self.connection_status.repaint()
|
# self.connection_status.repaint()
|
||||||
|
|
||||||
def setup_info_from_tox(self):
|
def setup_info_from_tox(self):
|
||||||
|
# TODO: remove - use Profile()
|
||||||
self.name.setText(self.tox.self_get_name())
|
self.name.setText(self.tox.self_get_name())
|
||||||
self.status_message.setText(self.tox.self_get_status_message())
|
self.status_message.setText(self.tox.self_get_status_message())
|
||||||
|
|
||||||
|
@ -276,6 +326,28 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self.int_s = InterfaceSettings()
|
self.int_s = InterfaceSettings()
|
||||||
self.int_s.show()
|
self.int_s.show()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Messages
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def send_message(self):
|
||||||
|
text = self.messageEdit.toPlainText()
|
||||||
|
if self.profile.isActiveOnline() and text:
|
||||||
|
num = self.profile.getActiveNumber()
|
||||||
|
self.tox.friend_send_message(num, TOX_MESSAGE_TYPE['NORMAL'], text)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Functions which called when user click somewhere else
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def friend_click(self, index):
|
||||||
|
print 'row:', index.row()
|
||||||
|
num = index.row()
|
||||||
|
self.profile.setActive(num)
|
||||||
|
friend = self.profile.friends[num]
|
||||||
|
self.account_name.setText(friend.name)
|
||||||
|
self.account_status.setText(friend.status_message)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = QtGui.QApplication(sys.argv)
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
|
@ -10,5 +10,5 @@ def tray_notification(title, text):
|
||||||
tray.setContextMenu(QtGui.QMenu())
|
tray.setContextMenu(QtGui.QMenu())
|
||||||
tray.show()
|
tray.show()
|
||||||
if len(text) > 30:
|
if len(text) > 30:
|
||||||
text = text[:30] + '...'
|
text = text[:27] + '...'
|
||||||
tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
|
tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000)
|
||||||
|
|
107
src/profile.py
107
src/profile.py
|
@ -1,11 +1,12 @@
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
import os
|
import os
|
||||||
from tox import Tox
|
from tox import Tox
|
||||||
|
from util import Singleton
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
|
|
||||||
|
|
||||||
class Profile(object):
|
class ProfileHelper(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_profiles():
|
def find_profiles():
|
||||||
|
@ -26,22 +27,112 @@ class Profile(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open_profile(path, name):
|
def open_profile(path, name):
|
||||||
Profile._path = path + name + '.tox'
|
ProfileHelper._path = path + name + '.tox'
|
||||||
with open(Profile._path, 'rb') as fl:
|
with open(ProfileHelper._path, 'rb') as fl:
|
||||||
data = fl.read()
|
data = fl.read()
|
||||||
if data:
|
if data:
|
||||||
print 'Data loaded from: {}'.format(Profile._path)
|
print 'Data loaded from: {}'.format(ProfileHelper._path)
|
||||||
return data
|
return data
|
||||||
else:
|
else:
|
||||||
raise IOError('Save file not found. Path: {}'.format(Profile._path))
|
raise IOError('Save file not found. Path: {}'.format(ProfileHelper._path))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_profile(data, name=None):
|
def save_profile(data, name=None):
|
||||||
if name is not None:
|
if name is not None:
|
||||||
Profile._path = Settings.get_default_path() + name + '.tox'
|
ProfileHelper._path = Settings.get_default_path() + name + '.tox'
|
||||||
with open(Profile._path, 'wb') as fl:
|
with open(ProfileHelper._path, 'wb') as fl:
|
||||||
fl.write(data)
|
fl.write(data)
|
||||||
print 'Data saved to: {}'.format(Profile._path)
|
print 'Data saved to: {}'.format(ProfileHelper._path)
|
||||||
|
|
||||||
|
|
||||||
|
class Contact(object):
|
||||||
|
|
||||||
|
def __init__(self, name, status_message, number, widget):
|
||||||
|
self._name, self._status_message, self._number = name, status_message, number
|
||||||
|
self._status, self._widget = None, widget
|
||||||
|
widget.name.setText(name)
|
||||||
|
widget.status_message.setText(status_message)
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def setName(self, value):
|
||||||
|
self._name = value
|
||||||
|
self._widget.name.setText(value)
|
||||||
|
|
||||||
|
name = property(getName, setName)
|
||||||
|
|
||||||
|
def getStatusMessage(self):
|
||||||
|
return self._status_message
|
||||||
|
|
||||||
|
def setStatusMessage(self, value):
|
||||||
|
self._status_message = value
|
||||||
|
self._widget.status.setText(value)
|
||||||
|
|
||||||
|
status_message = property(getStatusMessage, setStatusMessage)
|
||||||
|
|
||||||
|
def getStatus(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
def setStatus(self, value):
|
||||||
|
self._status = value
|
||||||
|
|
||||||
|
status = property(getStatus, setStatus)
|
||||||
|
|
||||||
|
|
||||||
|
class Friend(Contact):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
super(Friend, self).__init__(*args)
|
||||||
|
self._new_messages = False
|
||||||
|
|
||||||
|
def setVisibility(self, value):
|
||||||
|
self._widget.setVisibility(value)
|
||||||
|
|
||||||
|
def setMessages(self, value):
|
||||||
|
self._new_messages = value
|
||||||
|
|
||||||
|
messages = property(None, setMessages)
|
||||||
|
|
||||||
|
def getNumber(self):
|
||||||
|
return self._number
|
||||||
|
|
||||||
|
number = property(getNumber)
|
||||||
|
|
||||||
|
|
||||||
|
class Profile(Contact, Singleton):
|
||||||
|
|
||||||
|
def __init__(self, tox, widgets, widget):
|
||||||
|
self._widget = widget
|
||||||
|
self.tox = tox
|
||||||
|
data = tox.self_get_friend_list()
|
||||||
|
self.friends, num, self._active_friend = [], 0, -1
|
||||||
|
for i in data:
|
||||||
|
name = tox.friend_get_name(i)
|
||||||
|
status_message = tox.friend_get_status_message(i)
|
||||||
|
self.friends.append(Friend(name, status_message, i, widgets[num]))
|
||||||
|
num += 1
|
||||||
|
|
||||||
|
def getActive(self):
|
||||||
|
return self._active_friend
|
||||||
|
|
||||||
|
def setActive(self, value):
|
||||||
|
if 0 <= value < self.tox.self_get_friend_list_size():
|
||||||
|
self._active_friend = value
|
||||||
|
|
||||||
|
active_friend = property(getActive, setActive)
|
||||||
|
|
||||||
|
def getActiveNumber(self):
|
||||||
|
return self.friends[self._active_friend].getNumber()
|
||||||
|
|
||||||
|
def isActiveOnline(self):
|
||||||
|
if not self._active_friend + 1: # no active friend
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# TODO: callbacks!
|
||||||
|
return True
|
||||||
|
status = self.friends[self._active_friend].getStatus()
|
||||||
|
return status is not None
|
||||||
|
|
||||||
|
|
||||||
def tox_factory(data=None, settings=None):
|
def tox_factory(data=None, settings=None):
|
||||||
|
|
|
@ -23,7 +23,7 @@ def curr_directory():
|
||||||
|
|
||||||
class Singleton(object):
|
class Singleton(object):
|
||||||
|
|
||||||
def __new__(cls):
|
def __new__(cls, *args):
|
||||||
if not hasattr(cls, 'instance'):
|
if not hasattr(cls, 'instance'):
|
||||||
cls.instance = super(Singleton, cls).__new__(cls)
|
cls.instance = super(Singleton, cls,).__new__(cls, *args)
|
||||||
return cls.instance
|
return cls.instance
|
||||||
|
|
|
@ -2,7 +2,7 @@ from src.settings import Settings
|
||||||
from src.util import bin_to_string, string_to_bin
|
from src.util import bin_to_string, string_to_bin
|
||||||
import sys
|
import sys
|
||||||
from src.bootstrap import node_generator
|
from src.bootstrap import node_generator
|
||||||
from src.profile import Profile, tox_factory
|
from src.profile import ProfileHelper, tox_factory
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,17 +23,17 @@ class TestSettings():
|
||||||
class TestProfile():
|
class TestProfile():
|
||||||
|
|
||||||
def test_search(self):
|
def test_search(self):
|
||||||
arr = Profile.find_profiles()
|
arr = ProfileHelper.find_profiles()
|
||||||
assert arr
|
assert arr
|
||||||
|
|
||||||
def test_open(self):
|
def test_open(self):
|
||||||
data = Profile.open_profile(Settings.get_default_path(), 'tox_save')
|
data = ProfileHelper.open_profile(Settings.get_default_path(), 'tox_save')
|
||||||
assert data
|
assert data
|
||||||
|
|
||||||
def test_open_save(self):
|
def test_open_save(self):
|
||||||
data = Profile.open_profile(Settings.get_default_path(), 'tox_save')
|
data = ProfileHelper.open_profile(Settings.get_default_path(), 'tox_save')
|
||||||
Profile.save_profile(data)
|
ProfileHelper.save_profile(data)
|
||||||
new_data = Profile.open_profile(Settings.get_default_path(), 'tox_save')
|
new_data = ProfileHelper.open_profile(Settings.get_default_path(), 'tox_save')
|
||||||
assert new_data == data
|
assert new_data == data
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class TestNodeGen():
|
||||||
class TestTox():
|
class TestTox():
|
||||||
|
|
||||||
def test_loading(self):
|
def test_loading(self):
|
||||||
data = Profile.open_profile(Settings.get_default_path(), 'tox_save')
|
data = ProfileHelper.open_profile(Settings.get_default_path(), 'tox_save')
|
||||||
settings = Settings.get_default_settings()
|
settings = Settings.get_default_settings()
|
||||||
tox = tox_factory(data, settings)
|
tox = tox_factory(data, settings)
|
||||||
for data in node_generator():
|
for data in node_generator():
|
||||||
|
@ -68,9 +68,11 @@ class TestTox():
|
||||||
del tox
|
del tox
|
||||||
|
|
||||||
def test_friend_list(self):
|
def test_friend_list(self):
|
||||||
data = Profile.open_profile(Settings.get_default_path(), 'tox_save')
|
data = ProfileHelper.open_profile(Settings.get_default_path(), 'tox_save')
|
||||||
settings = Settings.get_default_settings()
|
settings = Settings.get_default_settings()
|
||||||
tox = tox_factory(data, settings)
|
tox = tox_factory(data, settings)
|
||||||
s = tox.self_get_friend_list()
|
s = tox.self_get_friend_list()
|
||||||
|
size = tox.self_get_friend_list_size()
|
||||||
|
assert size
|
||||||
assert s
|
assert s
|
||||||
del tox
|
del tox
|
||||||
|
|
Loading…
Reference in a new issue