Compare commits
47 Commits
file_trans
...
v0.2.6
Author | SHA1 | Date | |
---|---|---|---|
dc6ec7a6e8 | |||
6ae419441b | |||
1bdccf6f40 | |||
e854516183 | |||
5477a7d548 | |||
9f87f3dc3e | |||
137195c8f2 | |||
202c5a14a5 | |||
e598d027eb | |||
52a5d248c7 | |||
b0389537a1 | |||
34dd74ad48 | |||
e5a228906d | |||
3a90865fd0 | |||
b807daa3ff | |||
a83cd65f79 | |||
476f074d6a | |||
821dce5f28 | |||
67e9c92c09 | |||
9f745d9795 | |||
c4843148e4 | |||
56d8fa1cad | |||
1e6201b3fa | |||
ecf045182a | |||
5367764fdc | |||
417729d666 | |||
f782b99402 | |||
4c6205cc39 | |||
fd722f4628 | |||
dfab0491a5 | |||
8025c6a638 | |||
006b3cd197 | |||
9fe9ba4743 | |||
97ce2b9ceb | |||
337601f2a1 | |||
42e0ec005b | |||
fb1caa244a | |||
0fd75c5517 | |||
d81e3e781b | |||
43d9a41dae | |||
1caf7cd63c | |||
14816588f1 | |||
47b710acdd | |||
3668088f3e | |||
9f702afcb8 | |||
18775ff4b2 | |||
a7431cadd1 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,9 +15,12 @@ toxygen/libs
|
||||
toxygen/build
|
||||
toxygen/dist
|
||||
*.spec
|
||||
dist/
|
||||
dist
|
||||
toxygen/avatars
|
||||
toxygen/__pycache__
|
||||
/*.egg-info
|
||||
/*.egg
|
||||
html
|
||||
Toxygen.egg-info
|
||||
*.tox
|
||||
|
||||
|
13
.travis.yml
13
.travis.yml
@ -4,11 +4,20 @@ python:
|
||||
before_install:
|
||||
- sudo apt-get install -y checkinstall build-essential
|
||||
- sudo apt-get install portaudio19-dev
|
||||
- sudo apt-get install libconfig-dev libvpx-dev check -qq
|
||||
install:
|
||||
- pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/;
|
||||
- python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install
|
||||
- pip install pyaudio
|
||||
before_script:
|
||||
# OPUS
|
||||
- wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
|
||||
- tar xzf opus-1.0.3.tar.gz
|
||||
- cd opus-1.0.3
|
||||
- ./configure
|
||||
- make -j3
|
||||
- sudo make install
|
||||
- cd ..
|
||||
# Libsodium
|
||||
- git clone git://github.com/jedisct1/libsodium.git
|
||||
- cd libsodium
|
||||
@ -28,4 +37,6 @@ before_script:
|
||||
- echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
||||
- sudo ldconfig
|
||||
- cd ..
|
||||
script: py.test tests/travis.py
|
||||
script:
|
||||
- py.test tests/travis.py
|
||||
- py.test tests/tests.py
|
||||
|
13
README.md
13
README.md
@ -1,21 +1,20 @@
|
||||
# Toxygen
|
||||
|
||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python3
|
||||
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
|
||||
|
||||
[](https://github.com/toxygen-project/toxygen/releases/latest)
|
||||
[](https://github.com/toxygen-project/toxygen/stargazers)
|
||||
[](https://github.com/toxygen-project/toxygen/issues)
|
||||
[](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
|
||||
[](https://travis-ci.org/toxygen-project/toxygen)
|
||||
|
||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
|
||||
|
||||
### Supported OS:
|
||||
|
||||
- Windows
|
||||
- Linux
|
||||
- OS X
|
||||

|
||||
|
||||
### Features
|
||||
### Features:
|
||||
|
||||
- [x] 1v1 messages
|
||||
- [x] File transfers
|
||||
@ -61,3 +60,7 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python
|
||||
|
||||
### Docs
|
||||
[Check /docs/ for more info](/docs/)
|
||||
|
||||
Also visit [pythonhosted.org/Toxygen/](http://pythonhosted.org/Toxygen/)
|
||||
|
||||
[Wiki](https://wiki.tox.chat/clients/toxygen)
|
||||
|
@ -27,12 +27,14 @@ Run app using ``toxygen`` command.
|
||||
2. Install PortAudio:
|
||||
``brew install portaudio``
|
||||
3. Install toxygen:
|
||||
``pip3 install toxygen``
|
||||
``pip3.4 install toxygen``
|
||||
4. Run toxygen using ``toxygen`` command.
|
||||
|
||||
## Packages
|
||||
|
||||
Coming soon.
|
||||
Arch Linux: [AUR](https://aur.archlinux.org/packages/toxygen-git/)
|
||||
|
||||
Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux)
|
||||
|
||||
## From source code (recommended for developers)
|
||||
|
||||
|
BIN
docs/os.png
Executable file
BIN
docs/os.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
4
setup.py
4
setup.py
@ -48,7 +48,7 @@ setup(name='Toxygen',
|
||||
version=version,
|
||||
description='Toxygen - Tox client',
|
||||
long_description='Toxygen is powerful Tox client written in Python3',
|
||||
url='https://github.com/xveduk/toxygen/',
|
||||
url='https://github.com/toxygen-project/toxygen/',
|
||||
keywords='toxygen tox messenger',
|
||||
author='Ingvar',
|
||||
maintainer='Ingvar',
|
||||
@ -58,8 +58,6 @@ setup(name='Toxygen',
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
],
|
||||
entry_points={
|
||||
|
117
tests/tests.py
117
tests/tests.py
@ -1,31 +1,12 @@
|
||||
from toxygen.bootstrap import node_generator
|
||||
from toxygen.profile import *
|
||||
from toxygen.settings import ProfileHelper
|
||||
from toxygen.tox_dns import tox_dns
|
||||
import toxygen.toxencryptsave as encr
|
||||
|
||||
|
||||
class TestProfile:
|
||||
|
||||
def test_search(self):
|
||||
arr = ProfileHelper.find_profiles()
|
||||
assert len(arr) >= 2
|
||||
|
||||
def test_open(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
|
||||
assert data
|
||||
import toxygen.messages as m
|
||||
import time
|
||||
|
||||
|
||||
class TestTox:
|
||||
|
||||
def test_loading(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'alice').open_profile()
|
||||
settings = Settings.get_default_settings()
|
||||
tox = tox_factory(data, settings)
|
||||
for data in node_generator():
|
||||
tox.bootstrap(*data)
|
||||
del tox
|
||||
|
||||
def test_creation(self):
|
||||
name = b'Toxygen User'
|
||||
status_message = b'Toxing on Toxygen'
|
||||
@ -38,33 +19,95 @@ class TestTox:
|
||||
assert tox.self_get_name() == str(name, 'utf-8')
|
||||
assert tox.self_get_status_message() == str(status_message, 'utf-8')
|
||||
|
||||
def test_friend_list(self):
|
||||
data = ProfileHelper(Settings.get_default_path(), 'bob').open_profile()
|
||||
settings = Settings.get_default_settings()
|
||||
tox = tox_factory(data, settings)
|
||||
s = tox.self_get_friend_list()
|
||||
size = tox.self_get_friend_list_size()
|
||||
assert size <= 2
|
||||
assert len(s) <= 2
|
||||
del tox
|
||||
|
||||
class TestProfileHelper:
|
||||
|
||||
def test_creation(self):
|
||||
file_name, path = 'test.tox', os.path.dirname(os.path.realpath(__file__)) + '/'
|
||||
data = b'test'
|
||||
with open(path + file_name, 'wb') as fl:
|
||||
fl.write(data)
|
||||
ph = ProfileHelper(path, file_name[:4])
|
||||
assert ProfileHelper.get_path() == path
|
||||
assert ph.open_profile() == data
|
||||
assert os.path.exists(path + 'avatars/')
|
||||
|
||||
|
||||
class TestDNS:
|
||||
|
||||
def test_dns(self):
|
||||
Settings._instance = Settings.get_default_settings()
|
||||
bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5'
|
||||
tox_id = tox_dns('groupbot@toxme.io')
|
||||
assert tox_id == bot_id
|
||||
|
||||
def test_dns2(self):
|
||||
Settings._instance = Settings.get_default_settings()
|
||||
bot_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||
tox_id = tox_dns('echobot@toxme.io')
|
||||
assert tox_id == bot_id
|
||||
|
||||
|
||||
class TestEncryption:
|
||||
|
||||
def test_encr_decr(self):
|
||||
with open(settings.Settings.get_default_path() + '/alice.tox', 'rb') as fl:
|
||||
data = fl.read()
|
||||
tox = tox_factory()
|
||||
data = tox.get_savedata()
|
||||
lib = encr.ToxEncryptSave()
|
||||
lib.set_password('easypassword')
|
||||
copy_data = data[:]
|
||||
data = lib.pass_encrypt(data)
|
||||
data = lib.pass_decrypt(data)
|
||||
assert copy_data == data
|
||||
for password in ('easypassword', 'njvnjfnvaGGV6', 'toxygen'):
|
||||
lib.set_password(password)
|
||||
copy_data = data[:]
|
||||
new_data = lib.pass_encrypt(data)
|
||||
new_data = lib.pass_decrypt(new_data)
|
||||
assert copy_data == new_data
|
||||
|
||||
|
||||
class TestFriend:
|
||||
|
||||
def create_singletons(self):
|
||||
Settings._instance = Settings.get_default_settings()
|
||||
ProfileHelper('abc', 'test')
|
||||
|
||||
def create_friend(self, name, status_message, number, tox_id):
|
||||
friend = Friend(None, number, name, status_message, None, tox_id)
|
||||
return friend
|
||||
|
||||
def test_friend_creation(self):
|
||||
self.create_singletons()
|
||||
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||
friend = self.create_friend(name, status_message, number, tox_id)
|
||||
assert friend.name == name
|
||||
assert friend.tox_id == tox_id
|
||||
assert friend.status_message == status_message
|
||||
assert friend.number == number
|
||||
|
||||
def test_friend_corr(self):
|
||||
self.create_singletons()
|
||||
name, status_message, number = 'Friend', 'I am friend!', 0
|
||||
tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6'
|
||||
friend = self.create_friend(name, status_message, number, tox_id)
|
||||
t = time.time()
|
||||
friend.append_message(m.InfoMessage('Info message', t))
|
||||
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0))
|
||||
friend.append_message(m.TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0))
|
||||
assert friend.get_last_message_text() == 'Hello! It is test!'
|
||||
assert len(friend.get_corr()) == 3
|
||||
assert len(friend.get_corr_for_saving()) == 2
|
||||
friend.append_message(m.TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0))
|
||||
arr = friend.get_unsent_messages_for_saving()
|
||||
assert len(arr) == 1
|
||||
assert arr[0][0] == 'Not sent'
|
||||
tm = m.TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
||||
100, 'file_name', friend.number, 0)
|
||||
friend.append_message(tm)
|
||||
friend.clear_corr()
|
||||
assert len(friend.get_corr()) == 1
|
||||
assert len(friend.get_corr_for_saving()) == 0
|
||||
friend.append_message(m.TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0))
|
||||
assert len(friend.get_corr()) == 2
|
||||
assert len(friend.get_corr_for_saving()) == 1
|
||||
|
||||
# TODO: more friend tests and history test
|
||||
|
@ -1,4 +1,4 @@
|
||||
class TestToxygen:
|
||||
|
||||
def test_main(self):
|
||||
import toxygen.main
|
||||
import toxygen.main # check for syntax errors
|
||||
|
@ -23,7 +23,7 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
||||
self.name = widgets.DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(90, 20, 300, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(settings.Settings.get_instance['font'])
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(16)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
|
118
toxygen/basecontact.py
Normal file
118
toxygen/basecontact.py
Normal file
@ -0,0 +1,118 @@
|
||||
from settings import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
|
||||
|
||||
class BaseContact:
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update, tox id (or public key)
|
||||
Base class for all contacts.
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._tox_id = tox_id
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = str(value, 'utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = str(value, 'utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
|
||||
def init_widget(self):
|
||||
if self._widget is not None:
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.connection_status.update(self._status)
|
||||
self.load_avatar()
|
@ -2,6 +2,7 @@ import random
|
||||
|
||||
|
||||
class Node:
|
||||
|
||||
def __init__(self, ip, port, tox_key, rand):
|
||||
self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand
|
||||
|
||||
|
@ -274,16 +274,18 @@ def lossless_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossless packet
|
||||
"""
|
||||
data = data[:length]
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data, length)
|
||||
invoke_in_main_thread(plugin.callback_lossless, friend_number, data)
|
||||
|
||||
|
||||
def lossy_packet(tox, friend_number, data, length, user_data):
|
||||
"""
|
||||
Incoming lossy packet
|
||||
"""
|
||||
data = data[:length]
|
||||
plugin = PluginLoader.get_instance()
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data, length)
|
||||
invoke_in_main_thread(plugin.callback_lossy, friend_number, data)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
@ -1,113 +1,233 @@
|
||||
import os
|
||||
from settings import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
from history import *
|
||||
import basecontact
|
||||
import util
|
||||
from messages import *
|
||||
import file_transfers as ft
|
||||
|
||||
|
||||
class Contact:
|
||||
class Contact(basecontact.BaseContact):
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update
|
||||
Properties: number, message getter, history etc. Base class for friend and gc classes
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._widget.name.setText(name)
|
||||
self._widget.status_message.setText(status_message)
|
||||
self._tox_id = tox_id
|
||||
self.load_avatar()
|
||||
super().__init__(name, status_message, widget, tox_id)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
self._receipts = 0
|
||||
self._curr_text = ''
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# name - current name or alias of user
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
data = list(self._message_getter.get(PAGE_SIZE))
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def load_all_corr(self):
|
||||
"""
|
||||
Get all chat history from db for current friend
|
||||
"""
|
||||
data = list(self._message_getter.get_all())
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(messages)
|
||||
|
||||
def get_unsent_messages_for_saving(self):
|
||||
"""
|
||||
:return list of unsent messages for saving
|
||||
"""
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(map(lambda x: x.get_data(), messages))
|
||||
|
||||
def delete_message(self, time):
|
||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
|
||||
self._unsaved_messages -= 1
|
||||
self._corr.remove(elem)
|
||||
self._message_getter.delete_one()
|
||||
|
||||
def delete_old_messages(self):
|
||||
"""
|
||||
Delete old messages (reduces RAM if messages saving is not enabled)
|
||||
"""
|
||||
old = filter(lambda x: x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None),
|
||||
self._corr[:-SAVE_MESSAGES])
|
||||
old = list(old)
|
||||
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
|
||||
self._unsaved_messages -= l
|
||||
self._corr = old + self._corr[-SAVE_MESSAGES:]
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
def clear_corr(self, save_unsent=False):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
# don't delete data about active file transfer
|
||||
if not save_unsent:
|
||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||
self._unsaved_messages = 0
|
||||
else:
|
||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = str(value, 'utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = str(value, 'utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
if not self._alias:
|
||||
super().set_name(value)
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
def set_widget(self, widget):
|
||||
self._widget = widget
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages and other actions from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
||||
|
@ -340,3 +340,8 @@ class ReceiveAvatar(ReceiveTransfer):
|
||||
chdir(dirname(avatar_path))
|
||||
remove(avatar_path)
|
||||
rename(self._path, avatar_path)
|
||||
self.finished(True)
|
||||
|
||||
def finished(self, emit=False):
|
||||
if emit:
|
||||
super().finished()
|
||||
|
@ -1,156 +1,15 @@
|
||||
import contact
|
||||
from messages import *
|
||||
from history import *
|
||||
import util
|
||||
import file_transfers as ft
|
||||
|
||||
|
||||
class Friend(contact.Contact):
|
||||
"""
|
||||
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
|
||||
Friend in list of friends.
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, *args):
|
||||
"""
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
super(Friend, self).__init__(*args)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
super().__init__(message_getter, number, name, status_message, widget, tox_id)
|
||||
self._receipts = 0
|
||||
self._curr_text = ''
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
data = list(self._message_getter.get(PAGE_SIZE))
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def load_all_corr(self):
|
||||
data = list(self._message_getter.get_all())
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(messages)
|
||||
|
||||
def get_unsent_messages_for_saving(self):
|
||||
"""
|
||||
:return list of unsent messages for saving
|
||||
"""
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(map(lambda x: x.get_data(), messages))
|
||||
|
||||
def delete_message(self, time):
|
||||
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
|
||||
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
if elem in tmp[-self._unsaved_messages:]:
|
||||
self._unsaved_messages -= 1
|
||||
self._corr.remove(elem)
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
def clear_corr(self, save_unsent=False):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
# don't delete data about active file transfer
|
||||
if not save_unsent:
|
||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||
self._unsaved_messages = 0
|
||||
else:
|
||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
@ -162,7 +21,7 @@ class Friend(contact.Contact):
|
||||
"""
|
||||
try:
|
||||
tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
|
||||
self._corr))[0]
|
||||
self._corr))[0]
|
||||
tr.set_status(status)
|
||||
i = self._corr.index(tr)
|
||||
if inline: # inline was loaded
|
||||
@ -182,70 +41,18 @@ class Friend(contact.Contact):
|
||||
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
if not self._alias:
|
||||
super(Friend, self).set_name(value)
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
@ -1,4 +1,3 @@
|
||||
# coding=utf-8
|
||||
from sqlite3 import connect
|
||||
import settings
|
||||
from os import chdir
|
||||
@ -8,6 +7,10 @@ from toxencryptsave import ToxEncryptSave
|
||||
|
||||
PAGE_SIZE = 42
|
||||
|
||||
TIMEOUT = 11
|
||||
|
||||
SAVE_MESSAGES = 150
|
||||
|
||||
MESSAGE_OWNER = {
|
||||
'ME': 0,
|
||||
'FRIEND': 1,
|
||||
@ -32,7 +35,7 @@ class History:
|
||||
fout.write(data)
|
||||
except:
|
||||
os.remove(path)
|
||||
db = connect(name + '.hstr')
|
||||
db = connect(name + '.hstr', timeout=TIMEOUT)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
||||
' tox_id TEXT PRIMARY KEY'
|
||||
@ -62,7 +65,7 @@ class History:
|
||||
|
||||
def add_friend_to_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, ))
|
||||
@ -75,28 +78,28 @@ class History:
|
||||
')')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def delete_friend_from_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, ))
|
||||
cursor.execute('DROP TABLE id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def friend_exists_in_db(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, ))
|
||||
result = cursor.fetchone()
|
||||
@ -105,56 +108,56 @@ class History:
|
||||
|
||||
def save_messages_to_db(self, tox_id, messages_iter):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) '
|
||||
'VALUES (?, ?, ?, ?);', messages_iter)
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def update_messages(self, tox_id, unsent_time):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
|
||||
'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
pass
|
||||
|
||||
def delete_message(self, tox_id, time):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time = ' + str(time) + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def delete_messages(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -162,21 +165,50 @@ class History:
|
||||
return History.MessageGetter(self._name, tox_id)
|
||||
|
||||
class MessageGetter:
|
||||
|
||||
def __init__(self, name, tox_id):
|
||||
self._count = 0
|
||||
self._name = name
|
||||
self._tox_id = tox_id
|
||||
self._db = self._cursor = None
|
||||
|
||||
def connect(self):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
self._db = connect(name + '.hstr')
|
||||
self._db = connect(self._name + '.hstr', timeout=TIMEOUT)
|
||||
self._cursor = self._db.cursor()
|
||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + tox_id +
|
||||
self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id +
|
||||
' ORDER BY unix_time DESC;')
|
||||
|
||||
def disconnect(self):
|
||||
self._db.close()
|
||||
|
||||
def get_one(self):
|
||||
return self._cursor.fetchone()
|
||||
self.connect()
|
||||
self.skip()
|
||||
data = self._cursor.fetchone()
|
||||
self._count += 1
|
||||
self.disconnect()
|
||||
return data
|
||||
|
||||
def get_all(self):
|
||||
return self._cursor.fetchall()
|
||||
self.connect()
|
||||
data = self._cursor.fetchall()
|
||||
self.disconnect()
|
||||
self._count = len(data)
|
||||
return data
|
||||
|
||||
def get(self, count):
|
||||
return self._cursor.fetchmany(count)
|
||||
self.connect()
|
||||
self.skip()
|
||||
data = self._cursor.fetchmany(count)
|
||||
self.disconnect()
|
||||
self._count += len(data)
|
||||
return data
|
||||
|
||||
def __del__(self):
|
||||
self._db.close()
|
||||
def skip(self):
|
||||
if self._count:
|
||||
self._cursor.fetchmany(self._count)
|
||||
|
||||
def delete_one(self):
|
||||
if self._count:
|
||||
self._count -= 1
|
||||
|
71
toxygen/items_factory.py
Normal file
71
toxygen/items_factory.py
Normal file
@ -0,0 +1,71 @@
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from list_items import *
|
||||
|
||||
|
||||
class ItemsFactory:
|
||||
|
||||
def __init__(self, friends_list, messages):
|
||||
self._friends = friends_list
|
||||
self._messages = messages
|
||||
|
||||
def friend_item(self):
|
||||
item = ContactItem()
|
||||
elem = QtGui.QListWidgetItem(self._friends)
|
||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||
self._friends.addItem(elem)
|
||||
self._friends.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def message_item(self, text, time, name, sent, message_type, append, pixmap):
|
||||
item = MessageItem(text, time, name, sent, message_type, self._messages)
|
||||
if pixmap is not None:
|
||||
item.set_avatar(pixmap)
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def inline_item(self, data, append):
|
||||
elem = QtGui.QListWidgetItem()
|
||||
item = InlineImageItem(data, self._messages.width(), elem)
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def unsent_file_item(self, file_name, size, name, time, append):
|
||||
item = UnsentFileItem(file_name,
|
||||
size,
|
||||
name,
|
||||
time,
|
||||
self._messages.width())
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def file_transfer_item(self, data, append):
|
||||
data.append(self._messages.width())
|
||||
item = FileTransferItem(*data)
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
@ -8,6 +8,8 @@ class LibToxCore:
|
||||
def __init__(self):
|
||||
if system() == 'Windows':
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._libtoxcore = CDLL('libtoxcore.dylib')
|
||||
else:
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
try:
|
||||
@ -25,6 +27,8 @@ class LibToxAV:
|
||||
if system() == 'Windows':
|
||||
# on Windows av api is in libtox.dll
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._libtoxav = CDLL('libtoxav.dylib')
|
||||
else:
|
||||
# /usr/lib/libtoxav.so must exists
|
||||
try:
|
||||
@ -42,6 +46,8 @@ class LibToxEncryptSave:
|
||||
if system() == 'Windows':
|
||||
# on Windows profile encryption api is in libtox.dll
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
elif system() == 'Darwin':
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.dylib')
|
||||
else:
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
try:
|
||||
|
@ -25,7 +25,9 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
self.setOpenExternalLinks(True)
|
||||
self.setAcceptRichText(True)
|
||||
self.setOpenLinks(False)
|
||||
self.setSearchPaths([smileys.SmileyLoader.get_instance().get_smileys_path()])
|
||||
path = smileys.SmileyLoader.get_instance().get_smileys_path()
|
||||
if path is not None:
|
||||
self.setSearchPaths([path])
|
||||
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
||||
text = self.decoratedText(text)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
@ -127,7 +129,7 @@ class MessageItem(QtGui.QWidget):
|
||||
def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
|
||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 23))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
@ -137,7 +139,7 @@ class MessageItem(QtGui.QWidget):
|
||||
self.name.setText(user)
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25))
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.time.setFont(font)
|
||||
@ -151,12 +153,12 @@ class MessageItem(QtGui.QWidget):
|
||||
self.time.setText(convert_time(time))
|
||||
self.t = False
|
||||
|
||||
self.message = MessageEdit(text, parent.width() - 150, message_type, self)
|
||||
self.message = MessageEdit(text, parent.width() - 160, message_type, self)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height()))
|
||||
self.setFixedHeight(self.message.height())
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
@ -295,7 +297,7 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.state = state
|
||||
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
|
||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 25))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
@ -305,14 +307,14 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.name.setText(user)
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(width - 53, 7, 50, 20))
|
||||
self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25))
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.time.setFont(font)
|
||||
self.time.setText(convert_time(time))
|
||||
|
||||
self.cancel = QtGui.QPushButton(self)
|
||||
self.cancel.setGeometry(QtCore.QRect(width - 120, 2, 30, 30))
|
||||
self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30))
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.cancel.setIcon(icon)
|
||||
@ -360,7 +362,7 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.file_name.setToolTip(file_name)
|
||||
self.saved_name = file_name
|
||||
self.time_left = QtGui.QLabel(self)
|
||||
self.time_left.setGeometry(QtCore.QRect(width - 87, 7, 30, 20))
|
||||
self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20))
|
||||
font.setPointSize(10)
|
||||
self.time_left.setFont(font)
|
||||
self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||
|
@ -9,19 +9,20 @@ except ImportError:
|
||||
from bootstrap import node_generator
|
||||
from mainscreen import MainWindow
|
||||
from callbacks import init_callbacks, stop, start
|
||||
from util import curr_directory, program_version
|
||||
from util import curr_directory, program_version, remove, is_64_bit
|
||||
import styles.style
|
||||
import platform
|
||||
import toxencryptsave
|
||||
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
||||
from plugin_support import PluginLoader
|
||||
import updater
|
||||
|
||||
|
||||
class Toxygen:
|
||||
|
||||
def __init__(self, path_or_uri=None):
|
||||
super(Toxygen, self).__init__()
|
||||
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
||||
self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None
|
||||
if path_or_uri is None:
|
||||
self.uri = self.path = None
|
||||
elif path_or_uri.startswith('tox:'):
|
||||
@ -136,8 +137,19 @@ class Toxygen:
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
path = Settings.get_default_path()
|
||||
else:
|
||||
path = curr_directory()
|
||||
ProfileHelper(path, name).save_profile(self.tox.get_savedata())
|
||||
path = curr_directory() + '/'
|
||||
try:
|
||||
ProfileHelper(path, name).save_profile(self.tox.get_savedata())
|
||||
except Exception as ex:
|
||||
print(str(ex))
|
||||
log('Profile creation exception: ' + str(ex))
|
||||
msgBox = QtGui.QMessageBox()
|
||||
msgBox.setText(QtGui.QApplication.translate("login",
|
||||
'Profile saving error! Does Toxygen have permission to write to this directory?',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.exec_()
|
||||
return
|
||||
path = Settings.get_default_path()
|
||||
settings = Settings(name)
|
||||
if curr_lang in langs:
|
||||
@ -182,6 +194,7 @@ class Toxygen:
|
||||
self.tray.setObjectName('tray')
|
||||
|
||||
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
||||
app.aboutToQuit.connect(self.ms.close_window)
|
||||
|
||||
class Menu(QtGui.QMenu):
|
||||
|
||||
@ -259,6 +272,33 @@ class Toxygen:
|
||||
|
||||
self.ms.show()
|
||||
|
||||
updating = False
|
||||
if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update
|
||||
version = updater.check_for_updates()
|
||||
if version is not None:
|
||||
if settings['update'] == 2:
|
||||
updater.download(version)
|
||||
updating = True
|
||||
else:
|
||||
reply = QtGui.QMessageBox.question(None,
|
||||
'Toxygen',
|
||||
QtGui.QApplication.translate("login",
|
||||
'Update for Toxygen was found. Download and install it?',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
updater.download(version)
|
||||
updating = True
|
||||
|
||||
if updating:
|
||||
data = self.tox.get_savedata()
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
settings.close()
|
||||
del self.tox
|
||||
return
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
|
||||
@ -278,6 +318,7 @@ class Toxygen:
|
||||
|
||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
||||
app.exec_()
|
||||
|
||||
self.init.stop = True
|
||||
self.mainloop.stop = True
|
||||
self.avloop.stop = True
|
||||
@ -410,15 +451,13 @@ class Toxygen:
|
||||
def clean():
|
||||
"""Removes all windows libs from libs folder"""
|
||||
d = curr_directory() + '/libs/'
|
||||
for fl in ('libtox64.dll', 'libtox.dll', 'libsodium64.a', 'libsodium.a'):
|
||||
if os.path.exists(d + fl):
|
||||
os.remove(d + fl)
|
||||
remove(d)
|
||||
|
||||
|
||||
def configure():
|
||||
"""Removes unused libs"""
|
||||
d = curr_directory() + '/libs/'
|
||||
is_64bits = sys.maxsize > 2 ** 32
|
||||
is_64bits = is_64_bit()
|
||||
if not is_64bits:
|
||||
if os.path.exists(d + 'libtox64.dll'):
|
||||
os.remove(d + 'libtox64.dll')
|
||||
|
@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from menu import *
|
||||
from profile import *
|
||||
from list_items import *
|
||||
from widgets import MultilineEdit, LineEdit
|
||||
from widgets import MultilineEdit, LineEdit, ComboBox
|
||||
import plugin_support
|
||||
from mainscreen_widgets import *
|
||||
import settings
|
||||
@ -18,46 +16,54 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.tray = tray
|
||||
self.setAcceptDrops(True)
|
||||
self.initUI(tox)
|
||||
self._saved = False
|
||||
if settings.Settings.get_instance()['show_welcome_screen']:
|
||||
self.ws = WelcomeScreen()
|
||||
|
||||
def setup_menu(self, MainWindow):
|
||||
self.menubar = QtGui.QMenuBar(MainWindow)
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.menubar.setNativeMenuBar(False)
|
||||
self.menubar.setMinimumSize(self.width(), 25)
|
||||
self.menubar.setMaximumSize(self.width(), 25)
|
||||
self.menubar.setBaseSize(self.width(), 25)
|
||||
def setup_menu(self, Form):
|
||||
box = QtGui.QHBoxLayout()
|
||||
box.setContentsMargins(0, 0, 0, 0)
|
||||
box.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.profile_button = MainMenuButton(Form)
|
||||
box.addWidget(self.profile_button)
|
||||
self.settings_button = MainMenuButton(Form)
|
||||
box.addWidget(self.settings_button)
|
||||
self.plugins_button = MainMenuButton(Form)
|
||||
box.addWidget(self.plugins_button)
|
||||
self.about_button = MainMenuButton(Form)
|
||||
box.addWidget(self.about_button)
|
||||
box.setSpacing(0)
|
||||
|
||||
self.menuProfile = QtGui.QMenu(self.menubar)
|
||||
self.menuProfile = QtGui.QMenu()
|
||||
self.menuProfile.setObjectName("menuProfile")
|
||||
self.menuSettings = QtGui.QMenu(self.menubar)
|
||||
self.menuSettings = QtGui.QMenu()
|
||||
self.menuSettings.setObjectName("menuSettings")
|
||||
self.menuPlugins = QtGui.QMenu(self.menubar)
|
||||
self.menuPlugins = QtGui.QMenu()
|
||||
self.menuPlugins.setObjectName("menuPlugins")
|
||||
self.menuAbout = QtGui.QMenu(self.menubar)
|
||||
self.menuAbout = QtGui.QMenu()
|
||||
self.menuAbout.setObjectName("menuAbout")
|
||||
|
||||
self.actionAdd_friend = QtGui.QAction(MainWindow)
|
||||
self.actionAdd_friend = QtGui.QAction(Form)
|
||||
self.actionAdd_friend.setObjectName("actionAdd_friend")
|
||||
self.actionprofilesettings = QtGui.QAction(MainWindow)
|
||||
self.actionprofilesettings = QtGui.QAction(Form)
|
||||
self.actionprofilesettings.setObjectName("actionprofilesettings")
|
||||
self.actionPrivacy_settings = QtGui.QAction(MainWindow)
|
||||
self.actionPrivacy_settings = QtGui.QAction(Form)
|
||||
self.actionPrivacy_settings.setObjectName("actionPrivacy_settings")
|
||||
self.actionInterface_settings = QtGui.QAction(MainWindow)
|
||||
self.actionInterface_settings = QtGui.QAction(Form)
|
||||
self.actionInterface_settings.setObjectName("actionInterface_settings")
|
||||
self.actionNotifications = QtGui.QAction(MainWindow)
|
||||
self.actionNotifications = QtGui.QAction(Form)
|
||||
self.actionNotifications.setObjectName("actionNotifications")
|
||||
self.actionNetwork = QtGui.QAction(MainWindow)
|
||||
self.actionNetwork = QtGui.QAction(Form)
|
||||
self.actionNetwork.setObjectName("actionNetwork")
|
||||
self.actionAbout_program = QtGui.QAction(MainWindow)
|
||||
self.actionAbout_program = QtGui.QAction(Form)
|
||||
self.actionAbout_program.setObjectName("actionAbout_program")
|
||||
self.actionSettings = QtGui.QAction(MainWindow)
|
||||
self.updateSettings = QtGui.QAction(Form)
|
||||
self.actionSettings = QtGui.QAction(Form)
|
||||
self.actionSettings.setObjectName("actionSettings")
|
||||
self.audioSettings = QtGui.QAction(MainWindow)
|
||||
self.pluginData = QtGui.QAction(MainWindow)
|
||||
self.importPlugin = QtGui.QAction(MainWindow)
|
||||
self.lockApp = QtGui.QAction(MainWindow)
|
||||
self.audioSettings = QtGui.QAction(Form)
|
||||
self.pluginData = QtGui.QAction(Form)
|
||||
self.importPlugin = QtGui.QAction(Form)
|
||||
self.lockApp = QtGui.QAction(Form)
|
||||
self.menuProfile.addAction(self.actionAdd_friend)
|
||||
self.menuProfile.addAction(self.actionSettings)
|
||||
self.menuProfile.addAction(self.lockApp)
|
||||
@ -66,13 +72,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.menuSettings.addAction(self.actionNotifications)
|
||||
self.menuSettings.addAction(self.actionNetwork)
|
||||
self.menuSettings.addAction(self.audioSettings)
|
||||
self.menuSettings.addAction(self.updateSettings)
|
||||
self.menuPlugins.addAction(self.pluginData)
|
||||
self.menuPlugins.addAction(self.importPlugin)
|
||||
self.menuAbout.addAction(self.actionAbout_program)
|
||||
self.menubar.addAction(self.menuProfile.menuAction())
|
||||
self.menubar.addAction(self.menuSettings.menuAction())
|
||||
self.menubar.addAction(self.menuPlugins.menuAction())
|
||||
self.menubar.addAction(self.menuAbout.menuAction())
|
||||
|
||||
self.profile_button.setMenu(self.menuProfile)
|
||||
self.settings_button.setMenu(self.menuSettings)
|
||||
self.plugins_button.setMenu(self.menuPlugins)
|
||||
self.about_button.setMenu(self.menuAbout)
|
||||
|
||||
self.actionAbout_program.triggered.connect(self.about_program)
|
||||
self.actionNetwork.triggered.connect(self.network_settings)
|
||||
@ -82,10 +90,13 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||
self.audioSettings.triggered.connect(self.audio_settings)
|
||||
self.updateSettings.triggered.connect(self.update_settings)
|
||||
self.pluginData.triggered.connect(self.plugins_menu)
|
||||
self.lockApp.triggered.connect(self.lock_app)
|
||||
self.importPlugin.triggered.connect(self.import_plugin)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
Form.setLayout(box)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def languageChange(self, *args, **kwargs):
|
||||
self.retranslateUi()
|
||||
@ -98,11 +109,11 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
|
||||
def retranslateUi(self):
|
||||
self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.plugins_button.setText(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuSettings.setTitle(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.menuAbout.setTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.profile_button.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.settings_button.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.about_button.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actionprofilesettings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -112,13 +123,20 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.updateSettings.setText(QtGui.QApplication.translate("MainWindow", "Updates", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.clear()
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online and by name", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first and by name", None, QtGui.QApplication.UnicodeUTF8))
|
||||
ind = Settings.get_instance()['sorting']
|
||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5}
|
||||
self.online_contacts.setCurrentIndex(d[ind])
|
||||
self.importPlugin.setText(QtGui.QApplication.translate("MainWindow", "Import plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def setup_right_bottom(self, Form):
|
||||
@ -127,7 +145,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||
self.messageEdit.setObjectName("messageEdit")
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
font.setPointSize(11)
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
self.messageEdit.setFont(font)
|
||||
|
||||
@ -166,7 +184,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.contact_name.setObjectName("contact_name")
|
||||
self.contact_name.textChanged.connect(self.filtering)
|
||||
|
||||
self.online_contacts = QtGui.QComboBox(Form)
|
||||
self.online_contacts = ComboBox(Form)
|
||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
||||
self.search_label.raise_()
|
||||
@ -175,15 +193,15 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
|
||||
def setup_left_top(self, Form):
|
||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
Form.setMinimumSize(QtCore.QSize(270, 100))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 100))
|
||||
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||
Form.setMinimumSize(QtCore.QSize(270, 75))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 75))
|
||||
Form.setBaseSize(QtCore.QSize(270, 75))
|
||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64))
|
||||
self.avatar_label.setScaledContents(False)
|
||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.name = Form.name = DataLabel(Form)
|
||||
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
|
||||
Form.name.setGeometry(QtCore.QRect(75, 15, 150, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(14)
|
||||
@ -191,13 +209,13 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
Form.name.setFont(font)
|
||||
Form.name.setObjectName("name")
|
||||
self.status_message = Form.status_message = DataLabel(Form)
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 60, 170, 25))
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25))
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
Form.status_message.setFont(font)
|
||||
Form.status_message.setObjectName("status_message")
|
||||
self.connection_status = Form.connection_status = StatusCircle(Form)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||
self.name.mouseReleaseEvent = self.profile_settings
|
||||
@ -205,12 +223,12 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
Form.connection_status.setObjectName("connection_status")
|
||||
|
||||
def setup_right_top(self, Form):
|
||||
Form.resize(650, 100)
|
||||
Form.resize(650, 75)
|
||||
self.account_avatar = QtGui.QLabel(Form)
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64))
|
||||
self.account_avatar.setScaledContents(False)
|
||||
self.account_name = DataLabel(Form)
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25))
|
||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
@ -219,23 +237,23 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.account_name.setFont(font)
|
||||
self.account_name.setObjectName("account_name")
|
||||
self.account_status = DataLabel(Form)
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 45, 400, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25))
|
||||
self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
self.account_status.setFont(font)
|
||||
self.account_status.setObjectName("account_status")
|
||||
self.callButton = QtGui.QPushButton(Form)
|
||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||
self.callButton.setObjectName("callButton")
|
||||
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
||||
self.videocallButton = QtGui.QPushButton(Form)
|
||||
self.videocallButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50))
|
||||
self.videocallButton.setObjectName("videocallButton")
|
||||
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
||||
self.update_call_state('call')
|
||||
self.typing = QtGui.QLabel(Form)
|
||||
self.typing.setGeometry(QtCore.QRect(500, 50, 50, 30))
|
||||
self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30))
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
||||
pixmap.load(curr_directory() + '/images/typing.png')
|
||||
self.typing.setScaledContents(False)
|
||||
@ -252,6 +270,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
|
||||
self.friend_right_click)
|
||||
self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
||||
self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||
|
||||
def setup_right_center(self, widget):
|
||||
self.messages = QtGui.QListWidget(widget)
|
||||
@ -260,7 +281,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.messages.setSpacing(1)
|
||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
# self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.messages.focusOutEvent = lambda event: self.messages.clearSelection()
|
||||
self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu)
|
||||
|
||||
def load(pos):
|
||||
if not pos:
|
||||
@ -276,6 +298,7 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||
self.setWindowTitle('Toxygen')
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
menu = QtGui.QWidget()
|
||||
main = QtGui.QWidget()
|
||||
grid = QtGui.QGridLayout()
|
||||
search = QtGui.QWidget()
|
||||
@ -290,35 +313,38 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.setup_right_top(info)
|
||||
self.setup_right_bottom(message_buttons)
|
||||
self.setup_left_center(main_list)
|
||||
self.setup_menu(menu)
|
||||
if not Settings.get_instance()['mirror_mode']:
|
||||
grid.addWidget(search, 1, 0)
|
||||
grid.addWidget(name, 0, 0)
|
||||
grid.addWidget(messages, 1, 1, 2, 1)
|
||||
grid.addWidget(info, 0, 1)
|
||||
grid.addWidget(message_buttons, 3, 1)
|
||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
||||
grid.addWidget(search, 2, 0)
|
||||
grid.addWidget(name, 1, 0)
|
||||
grid.addWidget(messages, 2, 1, 2, 1)
|
||||
grid.addWidget(info, 1, 1)
|
||||
grid.addWidget(message_buttons, 4, 1)
|
||||
grid.addWidget(main_list, 3, 0, 2, 1)
|
||||
grid.setColumnMinimumWidth(1, 500)
|
||||
grid.setColumnMinimumWidth(0, 270)
|
||||
else:
|
||||
grid.addWidget(search, 1, 1)
|
||||
grid.addWidget(name, 0, 1)
|
||||
grid.addWidget(messages, 1, 0, 2, 1)
|
||||
grid.addWidget(info, 0, 0)
|
||||
grid.addWidget(message_buttons, 3, 0)
|
||||
grid.addWidget(main_list, 2, 1, 2, 1)
|
||||
grid.addWidget(search, 2, 1)
|
||||
grid.addWidget(name, 1, 1)
|
||||
grid.addWidget(messages, 2, 0, 2, 1)
|
||||
grid.addWidget(info, 1, 0)
|
||||
grid.addWidget(message_buttons, 4, 0)
|
||||
grid.addWidget(main_list, 3, 1, 2, 1)
|
||||
grid.setColumnMinimumWidth(0, 500)
|
||||
grid.setColumnMinimumWidth(1, 270)
|
||||
|
||||
grid.addWidget(menu, 0, 0, 1, 2)
|
||||
grid.setSpacing(0)
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
grid.setRowMinimumHeight(0, 100)
|
||||
grid.setRowMinimumHeight(1, 25)
|
||||
grid.setRowMinimumHeight(2, 320)
|
||||
grid.setRowMinimumHeight(3, 55)
|
||||
grid.setRowMinimumHeight(0, 25)
|
||||
grid.setRowMinimumHeight(1, 75)
|
||||
grid.setRowMinimumHeight(2, 25)
|
||||
grid.setRowMinimumHeight(3, 320)
|
||||
grid.setRowMinimumHeight(4, 55)
|
||||
grid.setColumnStretch(1, 1)
|
||||
grid.setRowStretch(2, 1)
|
||||
grid.setRowStretch(3, 1)
|
||||
main.setLayout(grid)
|
||||
self.setCentralWidget(main)
|
||||
self.setup_menu(self)
|
||||
self.messageEdit.setFocus()
|
||||
self.user_info = name
|
||||
self.friend_info = info
|
||||
@ -326,40 +352,46 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.profile = Profile(tox, self)
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.profile.save_history()
|
||||
self.profile.close()
|
||||
s = Settings.get_instance()
|
||||
if not s['close_to_tray'] or s.closing:
|
||||
s['x'] = self.geometry().x()
|
||||
s['y'] = self.geometry().y()
|
||||
s['width'] = self.width()
|
||||
s['height'] = self.height()
|
||||
s.save()
|
||||
QtGui.QApplication.closeAllWindows()
|
||||
event.accept()
|
||||
else:
|
||||
if not self._saved:
|
||||
self._saved = True
|
||||
self.profile.save_history()
|
||||
self.profile.close()
|
||||
s['x'] = self.geometry().x()
|
||||
s['y'] = self.geometry().y()
|
||||
s['width'] = self.width()
|
||||
s['height'] = self.height()
|
||||
s.save()
|
||||
QtGui.QApplication.closeAllWindows()
|
||||
event.accept()
|
||||
elif QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
||||
event.ignore()
|
||||
self.hide()
|
||||
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
||||
def close_window(self):
|
||||
Settings.get_instance().closing = True
|
||||
self.close()
|
||||
|
||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 129)
|
||||
|
||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30))
|
||||
|
||||
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
||||
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25))
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25))
|
||||
self.messageEdit.setFocus()
|
||||
self.profile.update()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
if event.key() == QtCore.Qt.Key_Escape and QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
||||
self.hide()
|
||||
elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||
rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems()))
|
||||
@ -416,6 +448,10 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
self.audio_s = AudioSettings()
|
||||
self.audio_s.show()
|
||||
|
||||
def update_settings(self):
|
||||
self.update_s = UpdateSettings()
|
||||
self.update_s.show()
|
||||
|
||||
def import_plugin(self):
|
||||
import util
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||
@ -497,9 +533,9 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
if self.profile.active_friend + 1:
|
||||
self.sticker = StickerWindow(self)
|
||||
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||
self.y() + self.height() - 200,
|
||||
self.sticker.width(),
|
||||
self.sticker.height()))
|
||||
self.y() + self.height() - 200,
|
||||
self.sticker.width(),
|
||||
self.sticker.height()))
|
||||
self.sticker.show()
|
||||
|
||||
def active_call(self):
|
||||
@ -531,6 +567,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
item = self.friends_list.itemAt(pos)
|
||||
num = self.friends_list.indexFromItem(item).row()
|
||||
friend = Profile.get_instance().get_friend(num)
|
||||
if friend is None:
|
||||
return
|
||||
settings = Settings.get_instance()
|
||||
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
||||
auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8)
|
||||
@ -648,4 +686,6 @@ class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
super(MainWindow, self).mouseReleaseEvent(event)
|
||||
|
||||
def filtering(self):
|
||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||
ind = self.online_contacts.currentIndex()
|
||||
d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
|
||||
self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
|
||||
|
@ -365,7 +365,7 @@ class WelcomeScreen(CenteredWidget):
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 6:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'New in Toxygen v0.2.3:<br>TCS compliance<br>Plugins, smileys and stickers import<br>Bug fixes',
|
||||
'New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 7:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
@ -392,3 +392,15 @@ class WelcomeScreen(CenteredWidget):
|
||||
s = settings.Settings.get_instance()
|
||||
s['show_welcome_screen'] = False
|
||||
s.save()
|
||||
|
||||
|
||||
class MainMenuButton(QtGui.QPushButton):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.setObjectName("mainmenubutton")
|
||||
|
||||
def setText(self, text):
|
||||
metrics = QtGui.QFontMetrics(self.font())
|
||||
self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20)
|
||||
super().setText(text)
|
||||
|
@ -9,6 +9,7 @@ from widgets import CenteredWidget, DataLabel, LineEdit
|
||||
import pyaudio
|
||||
import toxencryptsave
|
||||
import plugin_support
|
||||
import updater
|
||||
|
||||
|
||||
class AddContact(CenteredWidget):
|
||||
@ -714,10 +715,10 @@ class InterfaceSettings(CenteredWidget):
|
||||
msgBox.exec_()
|
||||
|
||||
def select_color(self):
|
||||
col = QtGui.QColorDialog.getColor()
|
||||
settings = Settings.get_instance()
|
||||
col = QtGui.QColorDialog.getColor(settings['unread_color'])
|
||||
|
||||
if col.isValid():
|
||||
settings = Settings.get_instance()
|
||||
name = col.name()
|
||||
settings['unread_color'] = name
|
||||
settings.save()
|
||||
@ -899,3 +900,85 @@ class PluginsSettings(CenteredWidget):
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
|
||||
class UpdateSettings(CenteredWidget):
|
||||
"""
|
||||
Updates settings form
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(UpdateSettings, self).__init__()
|
||||
self.initUI()
|
||||
self.center()
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("updateSettingsForm")
|
||||
self.resize(400, 150)
|
||||
self.setMinimumSize(QtCore.QSize(400, 120))
|
||||
self.setMaximumSize(QtCore.QSize(400, 120))
|
||||
self.in_label = QtGui.QLabel(self)
|
||||
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
|
||||
settings = Settings.get_instance()
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(16)
|
||||
font.setBold(True)
|
||||
font.setFamily(settings['font'])
|
||||
self.in_label.setFont(font)
|
||||
self.autoupdate = QtGui.QComboBox(self)
|
||||
self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
||||
self.button = QtGui.QPushButton(self)
|
||||
self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
|
||||
self.button.setEnabled(settings['update'])
|
||||
self.button.clicked.connect(self.update_client)
|
||||
|
||||
self.retranslateUi()
|
||||
self.autoupdate.setCurrentIndex(settings['update'])
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.setWindowTitle(QtGui.QApplication.translate("updateSettingsForm", "Update settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.in_label.setText(QtGui.QApplication.translate("updateSettingsForm", "Select update mode:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.button.setText(QtGui.QApplication.translate("updateSettingsForm", "Update Toxygen", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Disabled", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Manual", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Auto", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def closeEvent(self, event):
|
||||
settings = Settings.get_instance()
|
||||
settings['update'] = self.autoupdate.currentIndex()
|
||||
settings.save()
|
||||
|
||||
def update_client(self):
|
||||
if not updater.connection_available():
|
||||
msgBox = QtGui.QMessageBox()
|
||||
msgBox.setWindowTitle(
|
||||
QtGui.QApplication.translate("updateSettingsForm", "Error", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
text = (QtGui.QApplication.translate("updateSettingsForm", 'Problems with internet connection', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
return
|
||||
if not updater.updater_available():
|
||||
msgBox = QtGui.QMessageBox()
|
||||
msgBox.setWindowTitle(
|
||||
QtGui.QApplication.translate("updateSettingsForm", "Error", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
text = (QtGui.QApplication.translate("updateSettingsForm", 'Updater not found', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
return
|
||||
version = updater.check_for_updates()
|
||||
if version is not None:
|
||||
updater.download(version)
|
||||
QtGui.QApplication.closeAllWindows()
|
||||
else:
|
||||
msgBox = QtGui.QMessageBox()
|
||||
msgBox.setWindowTitle(
|
||||
QtGui.QApplication.translate("updateSettingsForm", "No updates found", None, QtGui.QApplication.UnicodeUTF8))
|
||||
text = (QtGui.QApplication.translate("updateSettingsForm", 'Toxygen is up to date', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
|
@ -45,8 +45,7 @@ class AudioFile:
|
||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||
channels=self.wf.getnchannels(),
|
||||
rate=self.wf.getframerate(),
|
||||
output=True
|
||||
)
|
||||
output=True)
|
||||
|
||||
def play(self):
|
||||
data = self.wf.readframes(self.chunk)
|
||||
|
@ -64,23 +64,23 @@ class PluginLoader(util.Singleton):
|
||||
self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active)
|
||||
break
|
||||
|
||||
def callback_lossless(self, friend_number, data, length):
|
||||
def callback_lossless(self, friend_number, data):
|
||||
"""
|
||||
New incoming custom lossless packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSLESS_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||
|
||||
def callback_lossy(self, friend_number, data, length):
|
||||
def callback_lossy(self, friend_number, data):
|
||||
"""
|
||||
New incoming custom lossy packet (callback)
|
||||
"""
|
||||
l = data[0] - pl.LOSSY_FIRST_BYTE
|
||||
name = ''.join(chr(x) for x in data[1:l + 1])
|
||||
if name in self._plugins and self._plugins[name][1]:
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number)
|
||||
|
||||
def friend_online(self, friend_number):
|
||||
"""
|
||||
|
@ -26,7 +26,7 @@ def log(name, data):
|
||||
:param data: data for saving in log
|
||||
"""
|
||||
with open(path_to_data(name) + 'logs.txt', 'a') as fl:
|
||||
fl.write(bytes(data, 'utf-8') + b'\n')
|
||||
fl.write(str(data) + '\n')
|
||||
|
||||
|
||||
class PluginSuperClass:
|
||||
@ -129,7 +129,7 @@ class PluginSuperClass:
|
||||
"""
|
||||
App is closing
|
||||
"""
|
||||
pass
|
||||
self.stop()
|
||||
|
||||
def command(self, command):
|
||||
"""
|
||||
@ -169,6 +169,7 @@ class PluginSuperClass:
|
||||
def load_settings(self):
|
||||
"""
|
||||
This method loads settings of plugin and returns raw data
|
||||
If file doesn't this method raises exception
|
||||
"""
|
||||
with open(path_to_data(self._short_name) + 'settings.json', 'rb') as fl:
|
||||
data = fl.read()
|
||||
|
@ -15,9 +15,11 @@ import time
|
||||
import calls
|
||||
import avwidgets
|
||||
import plugin_support
|
||||
import basecontact
|
||||
import items_factory
|
||||
|
||||
|
||||
class Profile(contact.Contact, Singleton):
|
||||
class Profile(basecontact.BaseContact, Singleton):
|
||||
"""
|
||||
Profile of current toxygen user. Contains friends list, tox instance
|
||||
"""
|
||||
@ -26,11 +28,11 @@ class Profile(contact.Contact, Singleton):
|
||||
:param tox: tox instance
|
||||
:param screen: ref to main screen
|
||||
"""
|
||||
contact.Contact.__init__(self,
|
||||
tox.self_get_name(),
|
||||
tox.self_get_status_message(),
|
||||
screen.user_info,
|
||||
tox.self_get_address())
|
||||
basecontact.BaseContact.__init__(self,
|
||||
tox.self_get_name(),
|
||||
tox.self_get_status_message(),
|
||||
screen.user_info,
|
||||
tox.self_get_address())
|
||||
Singleton.__init__(self)
|
||||
self._screen = screen
|
||||
self._messages = screen.messages
|
||||
@ -39,17 +41,19 @@ class Profile(contact.Contact, Singleton):
|
||||
self._call = calls.AV(tox.AV) # object with data about calls
|
||||
self._incoming_calls = set()
|
||||
self._load_history = True
|
||||
self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages)
|
||||
settings = Settings.get_instance()
|
||||
self._show_online = settings['show_online_friends']
|
||||
self._sorting = settings['sorting']
|
||||
self._show_avatars = settings['show_avatars']
|
||||
self._filter_string = ''
|
||||
self._friend_item_height = 40 if settings['compact_mode'] else 70
|
||||
self._paused_file_transfers = dict(settings['paused_file_transfers'])
|
||||
# key - file id, value: [path, friend number, is incoming, start position]
|
||||
screen.online_contacts.setCurrentIndex(int(self._show_online))
|
||||
screen.online_contacts.setCurrentIndex(int(self._sorting))
|
||||
aliases = settings['friends_aliases']
|
||||
data = tox.self_get_friend_list()
|
||||
self._history = History(tox.self_get_public_key()) # connection to db
|
||||
self._friends, self._active_friend = [], -1
|
||||
self._contacts, self._active_friend = [], -1
|
||||
for i in data: # creates list of friends
|
||||
tox_id = tox.friend_get_public_key(i)
|
||||
try:
|
||||
@ -64,8 +68,8 @@ class Profile(contact.Contact, Singleton):
|
||||
message_getter = self._history.messages_getter(tox_id)
|
||||
friend = Friend(message_getter, i, name, status_message, item, tox_id)
|
||||
friend.set_alias(alias)
|
||||
self._friends.append(friend)
|
||||
self.filtration(self._show_online)
|
||||
self._contacts.append(friend)
|
||||
self.filtration_and_sorting(self._sorting)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Edit current user's data
|
||||
@ -83,7 +87,7 @@ class Profile(contact.Contact, Singleton):
|
||||
if status is not None:
|
||||
self._tox.self_set_status(status)
|
||||
else:
|
||||
QtCore.QTimer.singleShot(30000, self.reconnect)
|
||||
QtCore.QTimer.singleShot(45000, self.reconnect)
|
||||
|
||||
def set_name(self, value):
|
||||
if self.name == value:
|
||||
@ -94,7 +98,7 @@ class Profile(contact.Contact, Singleton):
|
||||
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
message = message.format(tmp, value)
|
||||
for friend in self._friends:
|
||||
for friend in self._contacts:
|
||||
friend.append_message(InfoMessage(message, time.time()))
|
||||
if self._active_friend + 1:
|
||||
self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
@ -115,37 +119,67 @@ class Profile(contact.Contact, Singleton):
|
||||
# Filtration
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def filtration(self, show_online=True, filter_str=''):
|
||||
def filtration_and_sorting(self, sorting=0, filter_str=''):
|
||||
"""
|
||||
Filtration of friends list
|
||||
:param show_online: show online only contacts
|
||||
:param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name
|
||||
:param filter_str: show contacts which name contains this substring
|
||||
"""
|
||||
filter_str = filter_str.lower()
|
||||
settings = Settings.get_instance()
|
||||
for index, friend in enumerate(self._friends):
|
||||
friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower())
|
||||
if sorting > 1:
|
||||
if sorting & 2:
|
||||
self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True)
|
||||
if sorting & 4:
|
||||
if not sorting & 2:
|
||||
self._contacts = sorted(self._contacts, key=lambda x: x.name.lower())
|
||||
else: # save results of prev sorting
|
||||
online_friends = filter(lambda x: x.status is not None, self._contacts)
|
||||
count = len(list(online_friends))
|
||||
part1 = self._contacts[:count]
|
||||
part2 = self._contacts[count:]
|
||||
part1 = sorted(part1, key=lambda x: x.name.lower())
|
||||
part2 = sorted(part2, key=lambda x: x.name.lower())
|
||||
self._contacts = part1 + part2
|
||||
else: # sort by number
|
||||
online_friends = filter(lambda x: x.status is not None, self._contacts)
|
||||
count = len(list(online_friends))
|
||||
part1 = self._contacts[:count]
|
||||
part2 = self._contacts[count:]
|
||||
part1 = sorted(part1, key=lambda x: x.number)
|
||||
part2 = sorted(part2, key=lambda x: x.number)
|
||||
self._contacts = part1 + part2
|
||||
self._screen.friends_list.clear()
|
||||
for contact in self._contacts:
|
||||
contact.set_widget(self.create_friend_item())
|
||||
for index, friend in enumerate(self._contacts):
|
||||
friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower())
|
||||
friend.visibility = friend.visibility or friend.messages or friend.actions
|
||||
if friend.visibility:
|
||||
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250,
|
||||
self._friend_item_height))
|
||||
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height))
|
||||
else:
|
||||
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0))
|
||||
self._show_online, self._filter_string = show_online, filter_str
|
||||
settings['show_online_friends'] = self._show_online
|
||||
self._sorting, self._filter_string = sorting, filter_str
|
||||
settings['sorting'] = self._sorting
|
||||
settings.save()
|
||||
|
||||
def update_filtration(self):
|
||||
"""
|
||||
Update list of contacts when 1 of friends change connection status
|
||||
"""
|
||||
self.filtration(self._show_online, self._filter_string)
|
||||
self.filtration_and_sorting(self._sorting, self._filter_string)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend getters
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_friend_by_number(self, num):
|
||||
return list(filter(lambda x: x.number == num, self._friends))[0]
|
||||
return list(filter(lambda x: x.number == num, self._contacts))[0]
|
||||
|
||||
def get_friend(self, num):
|
||||
return self._friends[num]
|
||||
if num < 0 or num >= len(self._contacts):
|
||||
return None
|
||||
return self._contacts[num]
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Work with active friend
|
||||
@ -175,14 +209,16 @@ class Profile(contact.Contact, Singleton):
|
||||
if value is not None:
|
||||
if self._active_friend + 1 and self._active_friend != value:
|
||||
try:
|
||||
self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
|
||||
self._contacts[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
|
||||
except:
|
||||
pass
|
||||
friend = self._friends[value]
|
||||
friend = self._contacts[value]
|
||||
if self._active_friend != value:
|
||||
self._screen.messageEdit.setPlainText(friend.curr_text)
|
||||
self._active_friend = value
|
||||
self._friends[value].reset_messages()
|
||||
friend.reset_messages()
|
||||
if not Settings.get_instance()['save_history']:
|
||||
friend.delete_old_messages()
|
||||
self._messages.clear()
|
||||
friend.load_corr()
|
||||
messages = friend.get_corr()[-PAGE_SIZE:]
|
||||
@ -223,7 +259,7 @@ class Profile(contact.Contact, Singleton):
|
||||
else:
|
||||
self._screen.call_finished()
|
||||
else:
|
||||
friend = self._friends[self._active_friend]
|
||||
friend = self._contacts[self._active_friend]
|
||||
|
||||
self._screen.account_name.setText(friend.name)
|
||||
self._screen.account_status.setText(friend.status_message)
|
||||
@ -237,22 +273,25 @@ class Profile(contact.Contact, Singleton):
|
||||
self.update_filtration()
|
||||
except Exception as ex: # no friend found. ignore
|
||||
log('Friend value: ' + str(value))
|
||||
log('Error: ' + str(ex))
|
||||
log('Error in set active: ' + str(ex))
|
||||
raise
|
||||
|
||||
active_friend = property(get_active, set_active)
|
||||
|
||||
def get_last_message(self):
|
||||
return self._friends[self._active_friend].get_last_message_text()
|
||||
if self._active_friend + 1:
|
||||
return self._contacts[self._active_friend].get_last_message_text()
|
||||
else:
|
||||
return ''
|
||||
|
||||
def get_active_number(self):
|
||||
return self._friends[self._active_friend].number if self._active_friend + 1 else -1
|
||||
return self._contacts[self._active_friend].number if self._active_friend + 1 else -1
|
||||
|
||||
def get_active_name(self):
|
||||
return self._friends[self._active_friend].name if self._active_friend + 1 else ''
|
||||
return self._contacts[self._active_friend].name if self._active_friend + 1 else ''
|
||||
|
||||
def is_active_online(self):
|
||||
return self._active_friend + 1 and self._friends[self._active_friend].status is not None
|
||||
return self._active_friend + 1 and self._contacts[self._active_friend].status is not None
|
||||
|
||||
def new_name(self, number, name):
|
||||
friend = self.get_friend_by_number(number)
|
||||
@ -313,7 +352,7 @@ class Profile(contact.Contact, Singleton):
|
||||
ft = self._file_transfers[(friend_num, file_num)]
|
||||
if type(ft) is SendTransfer:
|
||||
self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1]
|
||||
elif type(ft) is ReceiveTransfer:
|
||||
elif type(ft) is ReceiveTransfer and ft.state != TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||
self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()]
|
||||
self.cancel_transfer(friend_num, file_num, True)
|
||||
|
||||
@ -327,7 +366,7 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
|
||||
try:
|
||||
friend = self._friends[self._active_friend]
|
||||
friend = self._contacts[self._active_friend]
|
||||
if friend.status is not None:
|
||||
self._tox.self_set_typing(friend.number, typing)
|
||||
except:
|
||||
@ -397,7 +436,7 @@ class Profile(contact.Contact, Singleton):
|
||||
t = time.time()
|
||||
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
|
||||
self._messages.scrollToBottom()
|
||||
self._friends[self._active_friend].append_message(
|
||||
self._contacts[self._active_friend].append_message(
|
||||
TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type))
|
||||
else:
|
||||
friend = self.get_friend_by_number(friend_num)
|
||||
@ -436,7 +475,7 @@ class Profile(contact.Contact, Singleton):
|
||||
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
|
||||
|
||||
def delete_message(self, time):
|
||||
friend = self._friends[self._active_friend]
|
||||
friend = self._contacts[self._active_friend]
|
||||
friend.delete_message(time)
|
||||
self._history.delete_message(friend.tox_id, time)
|
||||
self.update()
|
||||
@ -452,7 +491,7 @@ class Profile(contact.Contact, Singleton):
|
||||
s = Settings.get_instance()
|
||||
if hasattr(self, '_history'):
|
||||
if s['save_history']:
|
||||
for friend in self._friends:
|
||||
for friend in self._contacts:
|
||||
if not self._history.friend_exists_in_db(friend.tox_id):
|
||||
self._history.add_friend_to_db(friend.tox_id)
|
||||
if not s['save_unsent_only']:
|
||||
@ -472,13 +511,13 @@ class Profile(contact.Contact, Singleton):
|
||||
Clear chat history
|
||||
"""
|
||||
if num is not None:
|
||||
friend = self._friends[num]
|
||||
friend = self._contacts[num]
|
||||
friend.clear_corr(save_unsent)
|
||||
if self._history.friend_exists_in_db(friend.tox_id):
|
||||
self._history.delete_messages(friend.tox_id)
|
||||
self._history.delete_friend_from_db(friend.tox_id)
|
||||
else: # clear all history
|
||||
for number in range(len(self._friends)):
|
||||
for number in range(len(self._contacts)):
|
||||
self.clear_history(number, save_unsent)
|
||||
if num is None or num == self.get_active_number():
|
||||
self.update()
|
||||
@ -490,7 +529,7 @@ class Profile(contact.Contact, Singleton):
|
||||
if not self._load_history:
|
||||
return
|
||||
self._load_history = False
|
||||
friend = self._friends[self._active_friend]
|
||||
friend = self._contacts[self._active_friend]
|
||||
friend.load_corr(False)
|
||||
data = friend.get_corr()
|
||||
if not data:
|
||||
@ -532,10 +571,9 @@ class Profile(contact.Contact, Singleton):
|
||||
self._history.export(directory)
|
||||
|
||||
def export_history(self, num, as_text=True, _range=None):
|
||||
friend = self._friends[num]
|
||||
friend = self._contacts[num]
|
||||
if _range is None:
|
||||
friend.load_all_corr()
|
||||
if _range is None:
|
||||
corr = friend.get_corr()
|
||||
elif _range[1] + 1:
|
||||
corr = friend.get_corr()[_range[0]:_range[1] + 1]
|
||||
@ -554,10 +592,12 @@ class Profile(contact.Contact, Singleton):
|
||||
friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name,
|
||||
data[0]))
|
||||
s = new_line.join(arr)
|
||||
if not as_text:
|
||||
s = '<html><head><meta charset="UTF-8"><title>{}</title></head><body>{}</body></html>'.format(friend.name, s)
|
||||
return s
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Factories for friend, message and file transfer items
|
||||
# Friend, message and file transfer items creation
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def create_friend_item(self):
|
||||
@ -565,12 +605,7 @@ class Profile(contact.Contact, Singleton):
|
||||
Method-factory
|
||||
:return: new widget for friend instance
|
||||
"""
|
||||
item = ContactItem()
|
||||
elem = QtGui.QListWidgetItem(self._screen.friends_list)
|
||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||
self._screen.friends_list.addItem(elem)
|
||||
self._screen.friends_list.setItemWidget(elem, item)
|
||||
return item
|
||||
return self._factory.friend_item()
|
||||
|
||||
def create_message_item(self, text, time, owner, message_type, append=True):
|
||||
if message_type == MESSAGE_TYPE['INFO_MESSAGE']:
|
||||
@ -579,56 +614,30 @@ class Profile(contact.Contact, Singleton):
|
||||
name = self.get_active_name()
|
||||
else:
|
||||
name = self._name
|
||||
item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages)
|
||||
pixmap = None
|
||||
if self._show_avatars:
|
||||
item.set_avatar(self._friends[self._active_friend].get_pixmap() if owner == MESSAGE_OWNER[
|
||||
'FRIEND'] else self.get_pixmap())
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
if owner == MESSAGE_OWNER['FRIEND']:
|
||||
pixmap = self._contacts[self._active_friend].get_pixmap()
|
||||
else:
|
||||
pixmap = self.get_pixmap()
|
||||
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
|
||||
message_type, append, pixmap)
|
||||
|
||||
def create_file_transfer_item(self, tm, append=True):
|
||||
data = list(tm.get_data())
|
||||
data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name
|
||||
data.append(self._messages.width())
|
||||
item = FileTransferItem(*data)
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
return self._factory.file_transfer_item(data, append)
|
||||
|
||||
def create_unsent_file_item(self, message, append=True):
|
||||
data = message.get_data()
|
||||
item = UnsentFileItem(os.path.basename(data[0]),
|
||||
os.path.getsize(data[0]) if data[1] is None else len(data[1]),
|
||||
self.name,
|
||||
data[2],
|
||||
self._messages.width())
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return self._factory.unsent_file_item(os.path.basename(data[0]),
|
||||
os.path.getsize(data[0]) if data[1] is None else len(data[1]),
|
||||
self.name,
|
||||
data[2],
|
||||
append)
|
||||
|
||||
def create_inline_item(self, data, append=True):
|
||||
elem = QtGui.QListWidgetItem()
|
||||
item = InlineImageItem(data, self._messages.width(), elem)
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return self._factory.inline_item(data, append)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Work with friends (remove, block, set alias, get public key)
|
||||
@ -638,7 +647,7 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
Set new alias for friend
|
||||
"""
|
||||
friend = self._friends[num]
|
||||
friend = self._contacts[num]
|
||||
name = friend.name
|
||||
dialog = QtGui.QApplication.translate('MainWindow',
|
||||
"Enter new alias for friend {} or leave empty to use friend's name:",
|
||||
@ -676,14 +685,14 @@ class Profile(contact.Contact, Singleton):
|
||||
self.update()
|
||||
|
||||
def friend_public_key(self, num):
|
||||
return self._friends[num].tox_id
|
||||
return self._contacts[num].tox_id
|
||||
|
||||
def delete_friend(self, num):
|
||||
"""
|
||||
Removes friend from contact list
|
||||
:param num: number of friend in list
|
||||
"""
|
||||
friend = self._friends[num]
|
||||
friend = self._contacts[num]
|
||||
settings = Settings.get_instance()
|
||||
try:
|
||||
index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id)
|
||||
@ -697,10 +706,10 @@ class Profile(contact.Contact, Singleton):
|
||||
if self._history.friend_exists_in_db(friend.tox_id):
|
||||
self._history.delete_friend_from_db(friend.tox_id)
|
||||
self._tox.friend_delete(friend.number)
|
||||
del self._friends[num]
|
||||
del self._contacts[num]
|
||||
self._screen.friends_list.takeItem(num)
|
||||
if num == self._active_friend: # active friend was deleted
|
||||
if not len(self._friends): # last friend was deleted
|
||||
if not len(self._contacts): # last friend was deleted
|
||||
self.set_active(-1)
|
||||
else:
|
||||
self.set_active(0)
|
||||
@ -721,7 +730,7 @@ class Profile(contact.Contact, Singleton):
|
||||
log('Accept friend request failed! ' + str(ex))
|
||||
message_getter = None
|
||||
friend = Friend(message_getter, num, tox_id, '', item, tox_id)
|
||||
self._friends.append(friend)
|
||||
self._contacts.append(friend)
|
||||
|
||||
def block_user(self, tox_id):
|
||||
"""
|
||||
@ -788,7 +797,7 @@ class Profile(contact.Contact, Singleton):
|
||||
self._history.add_friend_to_db(tox_id)
|
||||
message_getter = self._history.messages_getter(tox_id)
|
||||
friend = Friend(message_getter, result, tox_id, '', item, tox_id)
|
||||
self._friends.append(friend)
|
||||
self._contacts.append(friend)
|
||||
data = self._tox.get_savedata()
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
return True
|
||||
@ -823,7 +832,7 @@ class Profile(contact.Contact, Singleton):
|
||||
Recreate tox instance
|
||||
:param restart: method which calls restart and returns new tox instance
|
||||
"""
|
||||
for friend in self._friends:
|
||||
for friend in self._contacts:
|
||||
self.friend_exit(friend.number)
|
||||
self._call.stop()
|
||||
del self._call
|
||||
@ -831,20 +840,20 @@ class Profile(contact.Contact, Singleton):
|
||||
self._tox = restart()
|
||||
self._call = calls.AV(self._tox.AV)
|
||||
self.status = None
|
||||
for friend in self._friends:
|
||||
for friend in self._contacts:
|
||||
friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update
|
||||
self.update_filtration()
|
||||
|
||||
def reconnect(self):
|
||||
if self.status is None or all(list(map(lambda x: x.status is None, self._friends))):
|
||||
if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts):
|
||||
self.reset(self._screen.reset)
|
||||
QtCore.QTimer.singleShot(30000, self.reconnect)
|
||||
QtCore.QTimer.singleShot(45000, self.reconnect)
|
||||
|
||||
def close(self):
|
||||
for friend in self._friends:
|
||||
for friend in self._contacts:
|
||||
self.friend_exit(friend.number)
|
||||
for i in range(len(self._friends)):
|
||||
del self._friends[0]
|
||||
for i in range(len(self._contacts)):
|
||||
del self._contacts[0]
|
||||
if hasattr(self, '_call'):
|
||||
self._call.stop()
|
||||
del self._call
|
||||
@ -952,7 +961,7 @@ class Profile(contact.Contact, Singleton):
|
||||
0, -1)
|
||||
|
||||
def cancel_not_started_transfer(self, time):
|
||||
self._friends[self._active_friend].delete_one_unsent_file(time)
|
||||
self._contacts[self._active_friend].delete_one_unsent_file(time)
|
||||
self.update()
|
||||
|
||||
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
||||
@ -1080,7 +1089,7 @@ class Profile(contact.Contact, Singleton):
|
||||
item = self.create_file_transfer_item(tm)
|
||||
st.set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
self._friends[friend_number].append_message(tm)
|
||||
self._contacts[friend_number].append_message(tm)
|
||||
|
||||
def incoming_chunk(self, friend_number, file_number, position, data):
|
||||
"""
|
||||
@ -1100,7 +1109,8 @@ class Profile(contact.Contact, Singleton):
|
||||
t = type(transfer)
|
||||
if t is ReceiveAvatar:
|
||||
self.get_friend_by_number(friend_number).load_avatar()
|
||||
self.set_active(None)
|
||||
if friend_number == self.get_active_number():
|
||||
self.set_active(None)
|
||||
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
|
||||
print('inline')
|
||||
inline = InlineImage(transfer.get_data())
|
||||
@ -1115,6 +1125,7 @@ class Profile(contact.Contact, Singleton):
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
self._messages.insertItem(count + i + 1, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
self._messages.scrollToBottom()
|
||||
elif t is not SendAvatar:
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE['FINISHED'])
|
||||
@ -1133,6 +1144,7 @@ class Profile(contact.Contact, Singleton):
|
||||
if not os.path.isfile(avatar_path): # reset image
|
||||
avatar_path = None
|
||||
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
||||
sa.set_transfer_finished_handler(self.transfer_finished)
|
||||
self._file_transfers[(friend_number, sa.get_file_number())] = sa
|
||||
|
||||
def incoming_avatar(self, friend_number, file_number, size):
|
||||
@ -1152,12 +1164,12 @@ class Profile(contact.Contact, Singleton):
|
||||
|
||||
def reset_avatar(self):
|
||||
super(Profile, self).reset_avatar()
|
||||
for friend in filter(lambda x: x.status is not None, self._friends):
|
||||
for friend in filter(lambda x: x.status is not None, self._contacts):
|
||||
self.send_avatar(friend.number)
|
||||
|
||||
def set_avatar(self, data):
|
||||
super(Profile, self).set_avatar(data)
|
||||
for friend in filter(lambda x: x.status is not None, self._friends):
|
||||
for friend in filter(lambda x: x.status is not None, self._contacts):
|
||||
self.send_avatar(friend.number)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -1183,7 +1195,7 @@ class Profile(contact.Contact, Singleton):
|
||||
else:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
self._friends[self._active_friend].append_message(InfoMessage(text, time.time()))
|
||||
self._contacts[self._active_friend].append_message(InfoMessage(text, time.time()))
|
||||
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
elif num in self._call: # finish or cancel call if you call with active friend
|
||||
@ -1221,7 +1233,8 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
self._call.accept_call(friend_number, audio, video)
|
||||
self._screen.active_call()
|
||||
self._incoming_calls.remove(friend_number)
|
||||
if friend_number in self._incoming_calls:
|
||||
self._incoming_calls.remove(friend_number)
|
||||
if hasattr(self, '_call_widget'):
|
||||
del self._call_widget
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from platform import system
|
||||
import json
|
||||
import os
|
||||
from util import Singleton, curr_directory, log, copy
|
||||
from util import Singleton, curr_directory, log, copy, append_slash
|
||||
import pyaudio
|
||||
from toxencryptsave import ToxEncryptSave
|
||||
import smileys
|
||||
@ -49,9 +49,9 @@ class Settings(dict, Singleton):
|
||||
|
||||
@staticmethod
|
||||
def get_auto_profile():
|
||||
path = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(path):
|
||||
with open(path) as fl:
|
||||
p = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(p):
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
auto = json.loads(data)
|
||||
if 'path' in auto and 'name' in auto:
|
||||
@ -61,9 +61,12 @@ class Settings(dict, Singleton):
|
||||
@staticmethod
|
||||
def set_auto_profile(path, name):
|
||||
p = Settings.get_default_path() + 'toxygen.json'
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
if os.path.isfile(p):
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
else:
|
||||
data = {}
|
||||
data['path'] = str(path)
|
||||
data['name'] = str(name)
|
||||
with open(p, 'w') as fl:
|
||||
@ -72,9 +75,12 @@ class Settings(dict, Singleton):
|
||||
@staticmethod
|
||||
def reset_auto_profile():
|
||||
p = Settings.get_default_path() + 'toxygen.json'
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
if os.path.isfile(p):
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
else:
|
||||
data = {}
|
||||
if 'path' in data:
|
||||
del data['path']
|
||||
del data['name']
|
||||
@ -115,7 +121,7 @@ class Settings(dict, Singleton):
|
||||
'allow_inline': True,
|
||||
'allow_auto_accept': True,
|
||||
'auto_accept_path': None,
|
||||
'show_online_friends': False,
|
||||
'sorting': 0,
|
||||
'auto_accept_from_friends': [],
|
||||
'paused_file_transfers': {},
|
||||
'resend_files': True,
|
||||
@ -139,7 +145,8 @@ class Settings(dict, Singleton):
|
||||
'compact_mode': False,
|
||||
'show_welcome_screen': True,
|
||||
'close_to_tray': False,
|
||||
'font': 'Times New Roman'
|
||||
'font': 'Times New Roman',
|
||||
'update': 1
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@ -195,8 +202,8 @@ class Settings(dict, Singleton):
|
||||
app_settings = {}
|
||||
if 'active_profile' not in app_settings:
|
||||
app_settings['active_profile'] = []
|
||||
profilepath = ProfileHelper.get_path()
|
||||
app_settings['active_profile'].append(str(profilepath + str(self.name) + '.tox'))
|
||||
profile_path = ProfileHelper.get_path()
|
||||
app_settings['active_profile'].append(str(profile_path + str(self.name) + '.tox'))
|
||||
data = json.dumps(app_settings)
|
||||
with open(path, 'w') as fl:
|
||||
fl.write(data)
|
||||
@ -225,6 +232,7 @@ class ProfileHelper(Singleton):
|
||||
"""
|
||||
def __init__(self, path, name):
|
||||
Singleton.__init__(self)
|
||||
path = append_slash(path)
|
||||
self._path = path + name + '.tox'
|
||||
self._directory = path
|
||||
# create /avatars if not exists:
|
||||
|
@ -48,7 +48,7 @@ class SmileyLoader(util.Singleton):
|
||||
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||
|
||||
def get_smileys_path(self):
|
||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
|
||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None
|
||||
|
||||
def get_packs_list(self):
|
||||
d = util.curr_directory() + '/smileys/'
|
||||
|
@ -1070,12 +1070,6 @@ QToolButton::menu-arrow:open {
|
||||
border: 1px solid #3A3939;
|
||||
}
|
||||
|
||||
QPushButton::menu-indicator {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: bottom right;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
QTableView
|
||||
{
|
||||
border: 1px solid #444;
|
||||
@ -1287,3 +1281,32 @@ QListWidget > QLabel
|
||||
{
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
#mainmenubutton
|
||||
{
|
||||
border: 1px solid #3A3939;
|
||||
color: silver;
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#mainmenubutton:hover
|
||||
{
|
||||
background: transparent;
|
||||
border: 1px solid #A9A9A9;
|
||||
background-color: #302F2F;
|
||||
}
|
||||
|
||||
#mainmenubutton:pressed
|
||||
{
|
||||
background: transparent;
|
||||
border: 1px solid #A9A9A9;
|
||||
background-color: #302F2F;
|
||||
}
|
||||
|
||||
#mainmenubutton::menu-indicator
|
||||
{
|
||||
image: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py
|
||||
SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py
|
||||
TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
109
toxygen/updater.py
Normal file
109
toxygen/updater.py
Normal file
@ -0,0 +1,109 @@
|
||||
import util
|
||||
import os
|
||||
import settings
|
||||
import platform
|
||||
import urllib
|
||||
try:
|
||||
from PySide import QtNetwork, QtCore
|
||||
except ImportError:
|
||||
from PyQt4 import QtNetwork, QtCore
|
||||
import subprocess
|
||||
|
||||
|
||||
def connection_available():
|
||||
try:
|
||||
urllib.request.urlopen('http://216.58.192.142', timeout=1) # google.com
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def updater_available():
|
||||
if is_from_sources():
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater.py')
|
||||
elif platform.system() == 'Windows':
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater.exe')
|
||||
else:
|
||||
return os.path.exists(util.curr_directory() + '/toxygen_updater')
|
||||
|
||||
|
||||
def check_for_updates():
|
||||
current_version = util.program_version
|
||||
major, minor, patch = list(map(lambda x: int(x), current_version.split('.')))
|
||||
versions = generate_versions(major, minor, patch)
|
||||
for version in versions:
|
||||
if send_request(version):
|
||||
return version
|
||||
return None # no new version was found
|
||||
|
||||
|
||||
def is_from_sources():
|
||||
return __file__.endswith('.py')
|
||||
|
||||
|
||||
def test_url(version):
|
||||
return 'https://github.com/toxygen-project/toxygen/releases/tag/v' + version
|
||||
|
||||
|
||||
def get_url(version):
|
||||
if is_from_sources():
|
||||
return 'https://github.com/toxygen-project/toxygen/archive/v' + version + '.zip'
|
||||
else:
|
||||
if platform.system() == 'Windows':
|
||||
name = 'toxygen_windows.zip'
|
||||
elif util.is_64_bit():
|
||||
name = 'toxygen_linux_64.tar.gz'
|
||||
else:
|
||||
name = 'toxygen_linux.tar.gz'
|
||||
return 'https://github.com/toxygen-project/toxygen/releases/download/v{}/{}'.format(version, name)
|
||||
|
||||
|
||||
def get_params(url, version):
|
||||
if is_from_sources():
|
||||
return ['python3', 'toxygen_updater.py', url, version]
|
||||
elif platform.system() == 'Windows':
|
||||
return [util.curr_directory() + '/toxygen_updater.exe', url, version]
|
||||
else:
|
||||
return ['./toxygen_updater', url, version]
|
||||
|
||||
|
||||
def download(version):
|
||||
os.chdir(util.curr_directory())
|
||||
url = get_url(version)
|
||||
params = get_params(url, version)
|
||||
print('Updating Toxygen')
|
||||
util.log('Updating Toxygen')
|
||||
try:
|
||||
subprocess.Popen(params)
|
||||
except Exception as ex:
|
||||
util.log('Exception: running updater failed with ' + str(ex))
|
||||
|
||||
|
||||
def send_request(version):
|
||||
s = settings.Settings.get_instance()
|
||||
netman = QtNetwork.QNetworkAccessManager()
|
||||
proxy = QtNetwork.QNetworkProxy()
|
||||
if s['proxy_type']:
|
||||
proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
||||
proxy.setHostName(s['proxy_host'])
|
||||
proxy.setPort(s['proxy_port'])
|
||||
netman.setProxy(proxy)
|
||||
url = test_url(version)
|
||||
try:
|
||||
request = QtNetwork.QNetworkRequest(url)
|
||||
reply = netman.get(request)
|
||||
while not reply.isFinished():
|
||||
QtCore.QThread.msleep(1)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
attr = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute)
|
||||
return attr is not None and 200 <= attr < 300
|
||||
except Exception as ex:
|
||||
util.log('TOXYGEN UPDATER ERROR: ' + str(ex))
|
||||
return False
|
||||
|
||||
|
||||
def generate_versions(major, minor, patch):
|
||||
new_major = '.'.join([str(major + 1), '0', '0'])
|
||||
new_minor = '.'.join([str(major), str(minor + 1), '0'])
|
||||
new_patch = '.'.join([str(major), str(minor), str(patch + 1)])
|
||||
return new_major, new_minor, new_patch
|
@ -1,8 +1,9 @@
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
program_version = '0.2.3'
|
||||
program_version = '0.2.6'
|
||||
|
||||
|
||||
def log(data):
|
||||
@ -30,14 +31,30 @@ def copy(src, dest):
|
||||
copy(full_file_name, os.path.join(dest, file_name))
|
||||
|
||||
|
||||
def remove(folder):
|
||||
if os.path.isdir(folder):
|
||||
shutil.rmtree(folder)
|
||||
|
||||
|
||||
def convert_time(t):
|
||||
sec = int(t) - time.timezone
|
||||
offset = time.timezone - time.daylight * 3600
|
||||
sec = int(t) - offset
|
||||
m, s = divmod(sec, 60)
|
||||
h, m = divmod(m, 60)
|
||||
d, h = divmod(h, 24)
|
||||
return '%02d:%02d' % (h, m)
|
||||
|
||||
|
||||
def append_slash(s):
|
||||
if len(s) and s[-1] not in ('\\', '/'):
|
||||
s += '/'
|
||||
return s
|
||||
|
||||
|
||||
def is_64_bit():
|
||||
return sys.maxsize > 2 ** 32
|
||||
|
||||
|
||||
class Singleton:
|
||||
_instance = None
|
||||
|
||||
|
@ -15,6 +15,13 @@ class DataLabel(QtGui.QLabel):
|
||||
super().setText(text)
|
||||
|
||||
|
||||
class ComboBox(QtGui.QComboBox):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.view().setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding)
|
||||
|
||||
|
||||
class CenteredWidget(QtGui.QWidget):
|
||||
|
||||
def __init__(self):
|
||||
|
Reference in New Issue
Block a user