Compare commits
37 Commits
groupchats
...
v0.2.4.1
Author | SHA1 | Date | |
---|---|---|---|
18775ff4b2 | |||
a7431cadd1 | |||
adcc32fc49 | |||
61e7aad847 | |||
742d853b11 | |||
39fe859fe5 | |||
2a0895018a | |||
d1437b3445 | |||
59154d081f | |||
99e8691f0b | |||
b0e82dfd08 | |||
3a64121d72 | |||
99f31cc302 | |||
19de605b79 | |||
9e410254bf | |||
e970fbed80 | |||
9ed62d4414 | |||
9516723c7f | |||
52e6ace847 | |||
c7f50af25c | |||
7d8646b432 | |||
883a30f806 | |||
3bd7655203 | |||
fdfc74521b | |||
3db10ead6a | |||
546eb9f042 | |||
08ef8294df | |||
af5db43248 | |||
697a9efb51 | |||
6297da1c69 | |||
e78ba3942b | |||
28cedae342 | |||
3f9a35e164 | |||
babeeb969c | |||
01e6d45232 | |||
c865ae4df6 | |||
59452aa525 |
31
.travis.yml
Normal file
@ -0,0 +1,31 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.4"
|
||||
before_install:
|
||||
- sudo apt-get install -y checkinstall build-essential
|
||||
- sudo apt-get install portaudio19-dev
|
||||
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:
|
||||
# Libsodium
|
||||
- git clone git://github.com/jedisct1/libsodium.git
|
||||
- cd libsodium
|
||||
- git checkout tags/1.0.3
|
||||
- ./autogen.sh
|
||||
- ./configure && make -j$(nproc)
|
||||
- sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.0 --nodoc -y
|
||||
- sudo ldconfig
|
||||
- cd ..
|
||||
# Toxcore
|
||||
- git clone https://github.com/irungentoo/toxcore.git
|
||||
- cd toxcore
|
||||
- autoreconf -if
|
||||
- ./configure
|
||||
- make -j$(nproc)
|
||||
- sudo make install
|
||||
- echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
|
||||
- sudo ldconfig
|
||||
- cd ..
|
||||
script: py.test tests/travis.py
|
@ -10,6 +10,8 @@ include toxygen/smileys/animated/config.json
|
||||
include toxygen/smileys/starwars/*.gif
|
||||
include toxygen/smileys/starwars/*.png
|
||||
include toxygen/smileys/starwars/config.json
|
||||
include toxygen/smileys/ksk/*.png
|
||||
include toxygen/smileys/ksk/config.json
|
||||
include toxygen/styles/style.qss
|
||||
include toxygen/translations/*.qm
|
||||
include toxygen/libs/libtox.dll
|
||||
|
31
README.md
@ -1,17 +1,22 @@
|
||||
# Toxygen
|
||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
|
||||
|
||||
[](https://github.com/xveduk/toxygen/releases/latest)
|
||||
[](https://github.com/xveduk/toxygen/issues)
|
||||
[](https://raw.githubusercontent.com/xveduk/toxygen/master/LICENSE.md)
|
||||
Toxygen is cross-platform [Tox](https://tox.chat/) client written in pure Python3
|
||||
|
||||
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md)
|
||||
[](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)
|
||||
|
||||
### [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
|
||||
- [x] Audio
|
||||
@ -42,19 +47,17 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written in Python3
|
||||
- [ ] Desktop sharing
|
||||
- [ ] Group chats
|
||||
|
||||
###Downloads
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
### Downloads
|
||||
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
||||
|
||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||
|
||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
||||
[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip)
|
||||
|
||||
###Screenshots
|
||||
### Screenshots
|
||||
*Toxygen on Ubuntu and Windows*
|
||||

|
||||

|
||||
|
||||
|
||||
###Docs
|
||||
### Docs
|
||||
[Check /docs/ for more info](/docs/)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#Compile Toxygen
|
||||
# Compile Toxygen
|
||||
|
||||
You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/)
|
||||
|
||||
@ -7,4 +7,4 @@ Install PyInstaller:
|
||||
|
||||
``pyinstaller --windowed --icon images/icon.ico main.py``
|
||||
|
||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/ (and /libs/libtox.dll on Windows) to /dist/main/
|
||||
Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/
|
||||
|
5
docs/contact.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Contact us:
|
||||
|
||||
1) Using GitHub - open issue
|
||||
|
||||
2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB)
|
@ -3,33 +3,50 @@
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
##Using pip3
|
||||
## Using pip3
|
||||
|
||||
### Windows (32-bit interpreter)
|
||||
### Windows
|
||||
|
||||
``pip3.4 install toxygen``
|
||||
|
||||
Run app using ``toxygen`` command.
|
||||
|
||||
##Linux
|
||||
### Linux
|
||||
|
||||
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
||||
2. Install PortAudio:
|
||||
``sudo apt-get install portaudio19-dev``
|
||||
3. Install toxygen:
|
||||
3. Install PySide: ``sudo apt-get install python3-pyside``
|
||||
4. Install toxygen:
|
||||
``sudo pip3.4 install toxygen``
|
||||
4 Run toxygen using ``toxygen`` command.
|
||||
5. Run toxygen using ``toxygen`` command.
|
||||
|
||||
### OS X
|
||||
|
||||
1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
|
||||
2. Install PortAudio:
|
||||
``brew install portaudio``
|
||||
3. Install toxygen:
|
||||
``pip3 install toxygen``
|
||||
4. Run toxygen using ``toxygen`` command.
|
||||
|
||||
## Packages
|
||||
|
||||
Coming soon.
|
||||
|
||||
## From source code (recommended for developers)
|
||||
|
||||
### Windows
|
||||
|
||||
1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
3. Install PyAudio: ``pip3.4 install pyaudio``
|
||||
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
5. Unpack archive
|
||||
6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
||||
7. Run \src\main.py.
|
||||
7. Run \toxygen\main.py.
|
||||
|
||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
||||
|
||||
[libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip)
|
||||
|
||||
@ -39,21 +56,31 @@ Run app using ``toxygen`` command.
|
||||
|
||||
[libsodium.a for 64-bit Python](https://build.tox.chat/view/libsodium/job/libsodium_build_windows_x86-64_static_release/lastSuccessfulBuild/artifact/libsodium_build_windows_x86-64_static_release.zip)
|
||||
|
||||
|
||||
### Linux
|
||||
|
||||
Dependencies:
|
||||
|
||||
1. Install latest Python3.4:
|
||||
1. Install latest Python3:
|
||||
``sudo apt-get install python3``
|
||||
2. [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) (recommended), using terminal - ``sudo apt-get install python3-pyside``, or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download).
|
||||
2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``).
|
||||
3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/)
|
||||
4. Install PyAudio:
|
||||
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio``
|
||||
``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``)
|
||||
5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
6. Unpack archive
|
||||
7. Run app:
|
||||
``python3.4 main.py``
|
||||
|
||||
## Compile Toxygen
|
||||
Check [compile.md](/docs/compile.md) for more info
|
||||
Optional: install toxygen using setup.py: ``python3.4 setup.py install``
|
||||
|
||||
### OS X
|
||||
|
||||
1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
3. Install PortAudio:
|
||||
``brew install portaudio``
|
||||
4. Install PyAudio: ``pip3 install pyaudio``
|
||||
5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system
|
||||
6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
7. Unpack archive
|
||||
8. Run \toxygen\main.py.
|
||||
|
||||
Optional: install toxygen using setup.py: ``python3 setup.py install``
|
||||
|
@ -12,6 +12,7 @@ All plugin's data should be stored in following structure:
|
||||
|---plugin_short_name.py
|
||||
|---/plugin_short_name/
|
||||
|---settings.json
|
||||
|---readme.txt
|
||||
|---logs.txt
|
||||
|---other_files
|
||||
```
|
||||
|
32
setup.py
@ -3,14 +3,20 @@ from setuptools.command.install import install
|
||||
from platform import system
|
||||
from subprocess import call
|
||||
from toxygen.util import program_version
|
||||
import sys
|
||||
|
||||
|
||||
version = program_version + '.0'
|
||||
|
||||
MODULES = ['PyAudio']
|
||||
MODULES = []
|
||||
|
||||
if system() == 'Windows':
|
||||
MODULES.append('PySide')
|
||||
if system() in ('Windows', 'Darwin'):
|
||||
MODULES = ['PyAudio', 'PySide']
|
||||
else:
|
||||
try:
|
||||
import pyaudio
|
||||
except ImportError:
|
||||
MODULES = ['PyAudio']
|
||||
|
||||
|
||||
class InstallScript(install):
|
||||
@ -18,11 +24,25 @@ class InstallScript(install):
|
||||
|
||||
def run(self):
|
||||
install.run(self)
|
||||
OS = system()
|
||||
if OS == 'Windows':
|
||||
try:
|
||||
if system() == 'Windows':
|
||||
call(["toxygen", "--configure"])
|
||||
elif OS == 'Linux':
|
||||
else:
|
||||
call(["toxygen", "--clean"])
|
||||
except:
|
||||
try:
|
||||
params = list(filter(lambda x: x.startswith('--prefix='), sys.argv))
|
||||
if params:
|
||||
path = params[0][len('--prefix='):]
|
||||
if path[-1] not in ('/', '\\'):
|
||||
path += '/'
|
||||
path += 'bin/toxygen'
|
||||
if system() == 'Windows':
|
||||
call([path, "--configure"])
|
||||
else:
|
||||
call([path, "--clean"])
|
||||
except:
|
||||
pass
|
||||
|
||||
setup(name='Toxygen',
|
||||
version=version,
|
||||
|
4
tests/travis.py
Normal file
@ -0,0 +1,4 @@
|
||||
class TestToxygen:
|
||||
|
||||
def test_main(self):
|
||||
import toxygen.main
|
@ -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("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(16)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
|
@ -9,6 +9,14 @@ from toxcore_enums_and_consts import *
|
||||
from toxav_enums import *
|
||||
from tox import bin_to_string
|
||||
from plugin_support import PluginLoader
|
||||
import queue
|
||||
import threading
|
||||
import util
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class InvokeEvent(QtCore.QEvent):
|
||||
@ -33,6 +41,44 @@ _invoker = Invoker()
|
||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||
|
||||
|
||||
class FileTransfersThread(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
self._queue = queue.Queue()
|
||||
self._timeout = 0.01
|
||||
self._continue = True
|
||||
super().__init__()
|
||||
|
||||
def execute(self, function, *args, **kwargs):
|
||||
self._queue.put((function, args, kwargs))
|
||||
|
||||
def stop(self):
|
||||
self._continue = False
|
||||
|
||||
def run(self):
|
||||
while self._continue:
|
||||
try:
|
||||
function, args, kwargs = self._queue.get(timeout=self._timeout)
|
||||
function(*args, **kwargs)
|
||||
except queue.Empty:
|
||||
pass
|
||||
except queue.Full:
|
||||
util.log('Queue is Full in _thread')
|
||||
except Exception as ex:
|
||||
util.log('Exception in _thread: ' + str(ex))
|
||||
|
||||
_thread = FileTransfersThread()
|
||||
|
||||
|
||||
def start():
|
||||
_thread.start()
|
||||
|
||||
|
||||
def stop():
|
||||
_thread.stop()
|
||||
_thread.join()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - current user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -68,7 +114,7 @@ def friend_status(tox, friend_num, new_status, user_data):
|
||||
if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
invoke_in_main_thread(friend.set_status, new_status)
|
||||
invoke_in_main_thread(profile.send_files, friend_num)
|
||||
invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num))
|
||||
invoke_in_main_thread(profile.update_filtration)
|
||||
|
||||
|
||||
@ -197,28 +243,15 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
|
||||
"""
|
||||
Incoming chunk
|
||||
"""
|
||||
if not length:
|
||||
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
|
||||
friend_number,
|
||||
file_number,
|
||||
position,
|
||||
None)
|
||||
else:
|
||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
|
||||
_thread.execute(Profile.get_instance().incoming_chunk, friend_number, file_number, position,
|
||||
chunk[:length] if length else None)
|
||||
|
||||
|
||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
||||
"""
|
||||
Outgoing chunk
|
||||
"""
|
||||
if size:
|
||||
Profile.get_instance().outgoing_chunk(friend_number, file_number, position, size)
|
||||
else:
|
||||
invoke_in_main_thread(Profile.get_instance().outgoing_chunk,
|
||||
friend_number,
|
||||
file_number,
|
||||
position,
|
||||
size)
|
||||
|
||||
|
||||
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||
@ -280,7 +313,6 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
|
||||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
# print(audio_samples_per_channel, audio_channels_count, rate)
|
||||
Profile.get_instance().call.chunk(
|
||||
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||
audio_channels_count,
|
||||
|
@ -92,10 +92,9 @@ class Contact:
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(width, width))
|
||||
pixmap.load(avatar_path)
|
||||
self._widget.avatar_label.setScaledContents(False)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio))
|
||||
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):
|
||||
|
@ -8,8 +8,7 @@ try:
|
||||
from PySide import QtCore
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore
|
||||
|
||||
# TODO: threads!
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
|
||||
|
||||
TOX_FILE_TRANSFER_STATE = {
|
||||
@ -34,10 +33,13 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
||||
|
||||
|
||||
class StateSignal(QtCore.QObject):
|
||||
try:
|
||||
signal = QtCore.Signal(int, float, int) # state and progress
|
||||
except:
|
||||
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
|
||||
|
||||
signal = QtCore.Signal(int, float, int) # state, progress, time in sec
|
||||
|
||||
|
||||
class TransferFinishedSignal(QtCore.QObject):
|
||||
|
||||
signal = QtCore.Signal(int, int) # friend number, file number
|
||||
|
||||
|
||||
class FileTransfer(QtCore.QObject):
|
||||
@ -56,6 +58,8 @@ class FileTransfer(QtCore.QObject):
|
||||
self._size = float(size)
|
||||
self._done = 0
|
||||
self._state_changed = StateSignal()
|
||||
self._finished = TransferFinishedSignal()
|
||||
self._file_id = None
|
||||
|
||||
def set_tox(self, tox):
|
||||
self._tox = tox
|
||||
@ -63,6 +67,9 @@ class FileTransfer(QtCore.QObject):
|
||||
def set_state_changed_handler(self, handler):
|
||||
self._state_changed.signal.connect(handler)
|
||||
|
||||
def set_transfer_finished_handler(self, handler):
|
||||
self._finished.signal.connect(handler)
|
||||
|
||||
def signal(self):
|
||||
percentage = self._done / self._size if self._size else 0
|
||||
if self._creation_time is None or not percentage:
|
||||
@ -71,12 +78,21 @@ class FileTransfer(QtCore.QObject):
|
||||
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
||||
self._state_changed.signal.emit(self.state, percentage, int(t))
|
||||
|
||||
def finished(self):
|
||||
self._finished.signal.emit(self._friend_number, self._file_number)
|
||||
|
||||
def get_file_number(self):
|
||||
return self._file_number
|
||||
|
||||
def get_friend_number(self):
|
||||
return self._friend_number
|
||||
|
||||
def get_id(self):
|
||||
return self._file_id
|
||||
|
||||
def get_path(self):
|
||||
return self._path
|
||||
|
||||
def cancel(self):
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
if hasattr(self, '_file'):
|
||||
@ -122,6 +138,7 @@ class SendTransfer(FileTransfer):
|
||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||
bytes(basename(path), 'utf-8') if path else b'')
|
||||
self._file_id = self.get_file_id()
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
"""
|
||||
@ -136,11 +153,11 @@ class SendTransfer(FileTransfer):
|
||||
data = self._file.read(size)
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self.signal()
|
||||
else:
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
self.signal()
|
||||
|
||||
|
||||
@ -180,9 +197,9 @@ class SendFromBuffer(FileTransfer):
|
||||
data = self._data[position:position + size]
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self.signal()
|
||||
else:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
self.signal()
|
||||
|
||||
|
||||
@ -204,16 +221,23 @@ class SendFromFileBuffer(SendTransfer):
|
||||
|
||||
class ReceiveTransfer(FileTransfer):
|
||||
|
||||
def __init__(self, path, tox, friend_number, size, file_number):
|
||||
def __init__(self, path, tox, friend_number, size, file_number, position=0):
|
||||
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
|
||||
self._file = open(self._path, 'wb')
|
||||
self._file.truncate(0)
|
||||
self._file_size = 0
|
||||
self._file_size = position
|
||||
self._file.truncate(position)
|
||||
self._missed = set()
|
||||
self._file_id = self.get_file_id()
|
||||
self._done = position
|
||||
|
||||
def cancel(self):
|
||||
super(ReceiveTransfer, self).cancel()
|
||||
remove(self._path)
|
||||
|
||||
def total_size(self):
|
||||
self._missed.add(self._file_size)
|
||||
return min(self._missed)
|
||||
|
||||
def write_chunk(self, position, data):
|
||||
"""
|
||||
Incoming chunk
|
||||
@ -225,12 +249,15 @@ class ReceiveTransfer(FileTransfer):
|
||||
if data is None:
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.signal()
|
||||
self.finished()
|
||||
else:
|
||||
data = bytearray(data)
|
||||
if self._file_size < position:
|
||||
self._file.seek(0, 2)
|
||||
self._file.write(b'\0' * (position - self._file_size))
|
||||
self._missed.add(self._file_size)
|
||||
else:
|
||||
self._missed.discard(position)
|
||||
self._file.seek(position)
|
||||
self._file.write(data)
|
||||
l = len(data)
|
||||
@ -258,6 +285,7 @@ class ReceiveToBuffer(FileTransfer):
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self.finished()
|
||||
else:
|
||||
data = bytes(data)
|
||||
l = len(data)
|
||||
|
@ -65,6 +65,14 @@ class Friend(contact.Contact):
|
||||
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
|
||||
@ -127,11 +135,11 @@ class Friend(contact.Contact):
|
||||
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() in (2, 3) and
|
||||
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() in (2, 3) and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
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())
|
||||
|
@ -6,13 +6,14 @@ import util
|
||||
class LibToxCore:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
self._libtoxcore = CDLL('libtoxcore.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# libtoxcore and libsodium must be installed in your os
|
||||
try:
|
||||
self._libtoxcore = CDLL('libtoxcore.so')
|
||||
except:
|
||||
self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._libtoxcore.__getattr__(item)
|
||||
@ -21,14 +22,15 @@ class LibToxCore:
|
||||
class LibToxAV:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# that /usr/lib/libtoxav.so must exists
|
||||
self._libtoxav = CDLL('libtoxav.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
# on Windows av api is in libtox.dll
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# /usr/lib/libtoxav.so must exists
|
||||
try:
|
||||
self._libtoxav = CDLL('libtoxav.so')
|
||||
except:
|
||||
self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._libtoxav.__getattr__(item)
|
||||
@ -37,14 +39,15 @@ class LibToxAV:
|
||||
class LibToxEncryptSave:
|
||||
|
||||
def __init__(self):
|
||||
if system() == 'Linux':
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
# on Windows profile encryption api is in libtox.dll
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll')
|
||||
else:
|
||||
raise OSError('Unknown system.')
|
||||
# /usr/lib/libtoxencryptsave.so must exists
|
||||
try:
|
||||
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||
except:
|
||||
self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so')
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._lib_tox_encrypt_save.__getattr__(item)
|
||||
|
@ -3,6 +3,7 @@ try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
QtCore.Slot = QtCore.pyqtSlot
|
||||
import profile
|
||||
from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
|
||||
from util import curr_directory, convert_time, curr_time
|
||||
@ -32,7 +33,7 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
else:
|
||||
self.setHtml(text)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
||||
font.setBold(False)
|
||||
self.setFont(font)
|
||||
@ -42,10 +43,31 @@ class MessageEdit(QtGui.QTextBrowser):
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
||||
quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8))
|
||||
quote.triggered.connect(self.quote_text)
|
||||
text = self.textCursor().selection().toPlainText()
|
||||
if not text:
|
||||
quote.setEnabled(False)
|
||||
else:
|
||||
import plugin_support
|
||||
submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text)
|
||||
if len(submenu):
|
||||
plug = menu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||
plug.addActions(submenu)
|
||||
menu.popup(event.globalPos())
|
||||
menu.exec_(event.globalPos())
|
||||
del menu
|
||||
|
||||
def quote_text(self):
|
||||
text = self.textCursor().selection().toPlainText()
|
||||
if text:
|
||||
import mainscreen
|
||||
window = mainscreen.MainWindow.get_instance()
|
||||
text = '>' + '\n>'.join(text.split('\n'))
|
||||
if window.messageEdit.toPlainText():
|
||||
text = '\n' + text
|
||||
window.messageEdit.appendPlainText(text)
|
||||
|
||||
def on_anchor_clicked(self, url):
|
||||
text = str(url.toString())
|
||||
if text.startswith('tox:'):
|
||||
@ -108,7 +130,7 @@ class MessageItem(QtGui.QWidget):
|
||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
@ -116,8 +138,6 @@ class MessageItem(QtGui.QWidget):
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.time.setFont(font)
|
||||
@ -159,6 +179,14 @@ class MessageItem(QtGui.QWidget):
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_avatar(self, pixmap):
|
||||
self.name.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.message.setAlignment(QtCore.Qt.AlignVCenter)
|
||||
self.setFixedHeight(max(self.height(), 36))
|
||||
self.name.setFixedHeight(self.height())
|
||||
self.message.setFixedHeight(self.height())
|
||||
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
|
||||
|
||||
|
||||
class ContactItem(QtGui.QWidget):
|
||||
"""
|
||||
@ -172,11 +200,12 @@ class ContactItem(QtGui.QWidget):
|
||||
self.avatar_label = QtGui.QLabel(self)
|
||||
size = 32 if mode else 64
|
||||
self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
self.avatar_label.setScaledContents(False)
|
||||
self.avatar_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(10 if mode else 12)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
@ -233,7 +262,7 @@ class UnreadMessagesCount(QtGui.QWidget):
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||
self.label.setVisible(False)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
@ -269,7 +298,7 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.name.setGeometry(QtCore.QRect(3, 7, 95, 20))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
@ -381,7 +410,7 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
if time + 1:
|
||||
m, s = divmod(time, 60)
|
||||
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
||||
if self.state != state:
|
||||
if self.state != state and self.state in ACTIVE_FILE_TRANSFERS:
|
||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||
self.cancel.setVisible(False)
|
||||
|
@ -57,7 +57,7 @@ class LoginScreen(CenteredWidget):
|
||||
self.load_profile.raise_()
|
||||
self.new_name.raise_()
|
||||
self.new_profile.raise_()
|
||||
self.toxygen.setGeometry(QtCore.QRect(160, 10, 90, 21))
|
||||
self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Impact")
|
||||
font.setPointSize(16)
|
||||
|
@ -8,9 +8,10 @@ except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from bootstrap import node_generator
|
||||
from mainscreen import MainWindow
|
||||
from callbacks import init_callbacks
|
||||
from callbacks import init_callbacks, stop, start
|
||||
from util import curr_directory, program_version
|
||||
import styles.style
|
||||
import platform
|
||||
import toxencryptsave
|
||||
from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen
|
||||
from plugin_support import PluginLoader
|
||||
@ -52,6 +53,9 @@ class Toxygen:
|
||||
app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.app = app
|
||||
|
||||
if platform.system() == 'Linux':
|
||||
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||
|
||||
# application color scheme
|
||||
with open(curr_directory() + '/styles/style.qss') as fl:
|
||||
dark_style = fl.read()
|
||||
@ -121,7 +125,19 @@ class Toxygen:
|
||||
set_pass.show()
|
||||
self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()"))
|
||||
self.app.exec_()
|
||||
ProfileHelper(Settings.get_default_path(), name).save_profile(self.tox.get_savedata())
|
||||
reply = QtGui.QMessageBox.question(None,
|
||||
'Profile {}'.format(name),
|
||||
QtGui.QApplication.translate("login",
|
||||
'Do you want to save profile in default folder? If no, profile will be saved in program folder',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
path = Settings.get_default_path()
|
||||
else:
|
||||
path = curr_directory()
|
||||
ProfileHelper(path, name).save_profile(self.tox.get_savedata())
|
||||
path = Settings.get_default_path()
|
||||
settings = Settings(name)
|
||||
if curr_lang in langs:
|
||||
@ -222,8 +238,16 @@ class Toxygen:
|
||||
self.p = UnlockAppScreen(toxencryptsave.ToxEncryptSave.get_instance(), correct_pass)
|
||||
self.p.show()
|
||||
|
||||
def tray_activated(reason):
|
||||
if reason == QtGui.QSystemTrayIcon.DoubleClick:
|
||||
show_window()
|
||||
|
||||
def close_app():
|
||||
settings.closing = True
|
||||
self.ms.close()
|
||||
|
||||
m.connect(show, QtCore.SIGNAL("triggered()"), show_window)
|
||||
m.connect(exit, QtCore.SIGNAL("triggered()"), lambda: app.exit())
|
||||
m.connect(exit, QtCore.SIGNAL("triggered()"), close_app)
|
||||
m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow())
|
||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
||||
@ -231,12 +255,14 @@ class Toxygen:
|
||||
|
||||
self.tray.setContextMenu(m)
|
||||
self.tray.show()
|
||||
self.tray.activated.connect(tray_activated)
|
||||
|
||||
self.ms.show()
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
|
||||
start()
|
||||
# init thread
|
||||
self.init = self.InitThread(self.tox, self.ms, self.tray)
|
||||
self.init.start()
|
||||
@ -256,6 +282,7 @@ class Toxygen:
|
||||
self.mainloop.stop = True
|
||||
self.avloop.stop = True
|
||||
plugin_helper.stop()
|
||||
stop()
|
||||
self.mainloop.wait()
|
||||
self.init.wait()
|
||||
self.avloop.wait()
|
||||
@ -415,7 +442,7 @@ def main():
|
||||
else: # started with argument(s)
|
||||
arg = sys.argv[1]
|
||||
if arg == '--version':
|
||||
print('Toxygen ' + program_version)
|
||||
print('Toxygen v' + program_version)
|
||||
return
|
||||
elif arg == '--help':
|
||||
print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version')
|
||||
|
@ -6,12 +6,14 @@ from list_items import *
|
||||
from widgets import MultilineEdit, LineEdit
|
||||
import plugin_support
|
||||
from mainscreen_widgets import *
|
||||
import settings
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
class MainWindow(QtGui.QMainWindow, Singleton):
|
||||
|
||||
def __init__(self, tox, reset, tray):
|
||||
super(MainWindow, self).__init__()
|
||||
super().__init__()
|
||||
Singleton.__init__(self)
|
||||
self.reset = reset
|
||||
self.tray = tray
|
||||
self.setAcceptDrops(True)
|
||||
@ -75,7 +77,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.actionAbout_program.triggered.connect(self.about_program)
|
||||
self.actionNetwork.triggered.connect(self.network_settings)
|
||||
self.actionAdd_friend.triggered.connect(self.add_contact)
|
||||
self.actionSettings.triggered.connect(self.profilesettings)
|
||||
self.actionSettings.triggered.connect(self.profile_settings)
|
||||
self.actionPrivacy_settings.triggered.connect(self.privacy_settings)
|
||||
self.actionInterface_settings.triggered.connect(self.interface_settings)
|
||||
self.actionNotifications.triggered.connect(self.notification_settings)
|
||||
@ -91,6 +93,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.WindowActivate:
|
||||
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.messages.repaint()
|
||||
return super(MainWindow, self).event(event)
|
||||
|
||||
def retranslateUi(self):
|
||||
@ -125,6 +128,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messageEdit.setObjectName("messageEdit")
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
self.messageEdit.setFont(font)
|
||||
|
||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
||||
@ -176,11 +180,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
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))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
Form.name.setFont(font)
|
||||
@ -193,9 +198,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.status_message.setObjectName("status_message")
|
||||
self.connection_status = Form.connection_status = StatusCircle(Form)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profilesettings
|
||||
self.status_message.mouseReleaseEvent = self.profilesettings
|
||||
self.name.mouseReleaseEvent = self.profilesettings
|
||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||
self.name.mouseReleaseEvent = self.profile_settings
|
||||
self.connection_status.raise_()
|
||||
Form.connection_status.setObjectName("connection_status")
|
||||
|
||||
@ -203,12 +208,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.resize(650, 100)
|
||||
self.account_avatar = QtGui.QLabel(Form)
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||
self.account_avatar.setScaledContents(True)
|
||||
self.account_avatar.setScaledContents(False)
|
||||
self.account_name = DataLabel(Form)
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||
self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setFamily(settings.Settings.get_instance()['font'])
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.account_name.setFont(font)
|
||||
@ -255,7 +260,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messages.setSpacing(1)
|
||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
# self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
|
||||
def load(pos):
|
||||
if not pos:
|
||||
@ -263,6 +268,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messages.verticalScrollBar().setValue(1)
|
||||
self.messages.verticalScrollBar().valueChanged.connect(load)
|
||||
self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
||||
self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
|
||||
def initUI(self, tox):
|
||||
self.setMinimumSize(920, 500)
|
||||
@ -319,16 +325,21 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.retranslateUi()
|
||||
self.profile = Profile(tox, self)
|
||||
|
||||
def closeEvent(self, *args, **kwargs):
|
||||
def closeEvent(self, event):
|
||||
s = Settings.get_instance()
|
||||
if not s['close_to_tray'] or s.closing:
|
||||
self.profile.save_history()
|
||||
self.profile.close()
|
||||
s = Settings.get_instance()
|
||||
s['x'] = self.pos().x()
|
||||
s['y'] = self.pos().y()
|
||||
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:
|
||||
event.ignore()
|
||||
self.hide()
|
||||
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||
@ -350,6 +361,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
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()))
|
||||
indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count())
|
||||
s = self.profile.export_history(self.profile.active_friend, True, indexes)
|
||||
clipboard = QtGui.QApplication.clipboard()
|
||||
clipboard.setText(s)
|
||||
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
|
||||
self.messages.clearSelection()
|
||||
else:
|
||||
super(MainWindow, self).keyPressEvent(event)
|
||||
|
||||
@ -362,7 +381,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
msgBox = QtGui.QMessageBox()
|
||||
msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||
text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text + util.program_version + '\nGitHub: github.com/xveduk/toxygen/')
|
||||
msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/')
|
||||
msgBox.exec_()
|
||||
|
||||
def network_settings(self):
|
||||
@ -374,10 +393,10 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.p_s.show()
|
||||
|
||||
def add_contact(self, link=''):
|
||||
self.a_c = AddContact(link)
|
||||
self.a_c = AddContact(link or '')
|
||||
self.a_c.show()
|
||||
|
||||
def profilesettings(self, *args):
|
||||
def profile_settings(self, *args):
|
||||
self.p_s = ProfileSettings()
|
||||
self.p_s.show()
|
||||
|
||||
@ -518,7 +537,12 @@ class MainWindow(QtGui.QMainWindow):
|
||||
if item is not None:
|
||||
self.listMenu = QtGui.QMenu()
|
||||
set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8))
|
||||
clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8))
|
||||
clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8))
|
||||
export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8))
|
||||
export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8))
|
||||
copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8))
|
||||
copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -540,6 +564,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
|
||||
self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend))
|
||||
self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend))
|
||||
self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num))
|
||||
self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"),
|
||||
lambda: self.export_history(num, False))
|
||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||
self.listMenu.move(parent_position + pos)
|
||||
self.listMenu.show()
|
||||
@ -559,6 +586,20 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.note = MultilineEdit(user, note, save_note)
|
||||
self.note.show()
|
||||
|
||||
def export_history(self, num, as_text=True):
|
||||
s = self.profile.export_history(num, as_text)
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(None,
|
||||
QtGui.QApplication.translate("MainWindow", 'Choose folder',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
curr_directory(),
|
||||
QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog)
|
||||
|
||||
if directory:
|
||||
name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html')
|
||||
with open(directory + '/' + name, 'wt') as fl:
|
||||
fl.write(s)
|
||||
|
||||
def set_alias(self, num):
|
||||
self.profile.set_alias(num)
|
||||
|
||||
@ -608,4 +649,3 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def filtering(self):
|
||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||
|
||||
|
@ -20,6 +20,11 @@ class MessageArea(QtGui.QPlainTextEdit):
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.matches(QtGui.QKeySequence.Paste):
|
||||
mimeData = QtGui.QApplication.clipboard().mimeData()
|
||||
if mimeData.hasUrls():
|
||||
for url in mimeData.urls():
|
||||
self.pasteEvent(url.toString())
|
||||
else:
|
||||
self.pasteEvent()
|
||||
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
modifiers = event.modifiers()
|
||||
@ -51,9 +56,13 @@ class MessageArea(QtGui.QPlainTextEdit):
|
||||
e.accept()
|
||||
|
||||
def dropEvent(self, e):
|
||||
if e.mimeData().hasFormat('text/plain'):
|
||||
if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'):
|
||||
e.accept()
|
||||
self.pasteEvent(e.mimeData().text())
|
||||
elif e.mimeData().hasUrls():
|
||||
for url in e.mimeData().urls():
|
||||
self.pasteEvent(url.toString())
|
||||
e.accept()
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
@ -330,7 +339,7 @@ class WelcomeScreen(CenteredWidget):
|
||||
self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day',
|
||||
None, QtGui.QApplication.UnicodeUTF8))
|
||||
import random
|
||||
num = random.randint(0, 8)
|
||||
num = random.randint(0, 10)
|
||||
if num == 0:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
@ -356,12 +365,20 @@ class WelcomeScreen(CenteredWidget):
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 6:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'New in Toxygen v0.2.2:<br>Users can lock application using profile password.<br>Compact contact list support<br>Bug fixes<br>Tox DNS improvements',
|
||||
'New in Toxygen v0.2.4:<br>File transfers update<br>Autoreconnection<br>Improvements<br>Bug fixes',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 7:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 8:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
elif num == 9:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'Use right click on inline image to save it',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
else:
|
||||
text = QtGui.QApplication.translate('WelcomeScreen',
|
||||
'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.',
|
||||
@ -375,4 +392,3 @@ class WelcomeScreen(CenteredWidget):
|
||||
s = settings.Settings.get_instance()
|
||||
s['show_welcome_screen'] = False
|
||||
s.save()
|
||||
|
||||
|
@ -37,6 +37,7 @@ class AddContact(CenteredWidget):
|
||||
self.error_label = DataLabel(self)
|
||||
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(Settings.get_instance()['font'])
|
||||
font.setPointSize(10)
|
||||
font.setWeight(30)
|
||||
self.error_label.setFont(font)
|
||||
@ -51,7 +52,6 @@ class AddContact(CenteredWidget):
|
||||
self.message.setObjectName("label_2")
|
||||
self.retranslateUi()
|
||||
self.message_edit.setText('Hello! Add me to your contact list please')
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
@ -102,6 +102,7 @@ class ProfileSettings(CenteredWidget):
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily(Settings.get_instance()['font'])
|
||||
font.setPointSize(18)
|
||||
font.setWeight(75)
|
||||
font.setBold(True)
|
||||
@ -263,16 +264,28 @@ class ProfileSettings(CenteredWidget):
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
bitmap.save(buffer, 'PNG')
|
||||
Profile.get_instance().set_avatar(str(byte_array.data()))
|
||||
Profile.get_instance().set_avatar(bytes(byte_array.data()))
|
||||
|
||||
def export_profile(self):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/'
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog,
|
||||
dir=curr_directory()) + '/'
|
||||
if directory != '/':
|
||||
ProfileHelper.get_instance().export_profile(directory)
|
||||
reply = QtGui.QMessageBox.question(None,
|
||||
QtGui.QApplication.translate("ProfileSettingsForm",
|
||||
'Use new path',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
QtGui.QApplication.translate("ProfileSettingsForm",
|
||||
'Do you want to move your profile to this location?',
|
||||
None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
settings = Settings.get_instance()
|
||||
settings.export(directory)
|
||||
profile = Profile.get_instance()
|
||||
profile.export_history(directory)
|
||||
profile.export_db(directory)
|
||||
ProfileHelper.get_instance().export_profile(directory, reply == QtGui.QMessageBox.Yes)
|
||||
|
||||
def closeEvent(self, event):
|
||||
profile = Profile.get_instance()
|
||||
@ -520,11 +533,12 @@ class NotificationsSettings(CenteredWidget):
|
||||
self.soundNotifications = QtGui.QCheckBox(self)
|
||||
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
||||
font = QtGui.QFont()
|
||||
s = Settings.get_instance()
|
||||
font.setFamily(s['font'])
|
||||
font.setPointSize(12)
|
||||
self.callsSound.setFont(font)
|
||||
self.soundNotifications.setFont(font)
|
||||
self.enableNotifications.setFont(font)
|
||||
s = Settings.get_instance()
|
||||
self.enableNotifications.setChecked(s['notifications'])
|
||||
self.soundNotifications.setChecked(s['sound_notifications'])
|
||||
self.callsSound.setChecked(s['calls_sound'])
|
||||
@ -554,19 +568,20 @@ class InterfaceSettings(CenteredWidget):
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("interfaceForm")
|
||||
self.setMinimumSize(QtCore.QSize(400, 550))
|
||||
self.setMaximumSize(QtCore.QSize(400, 550))
|
||||
self.setMinimumSize(QtCore.QSize(400, 650))
|
||||
self.setMaximumSize(QtCore.QSize(400, 650))
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
|
||||
settings = Settings.get_instance()
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
font.setFamily(settings['font'])
|
||||
self.label.setFont(font)
|
||||
self.themeSelect = QtGui.QComboBox(self)
|
||||
self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30))
|
||||
list_of_themes = ['dark']
|
||||
self.themeSelect.addItems(list_of_themes)
|
||||
settings = Settings.get_instance()
|
||||
theme = settings['theme']
|
||||
if theme in list_of_themes:
|
||||
index = list_of_themes.index(theme)
|
||||
@ -610,25 +625,38 @@ class InterfaceSettings(CenteredWidget):
|
||||
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
|
||||
|
||||
self.unread = QtGui.QPushButton(self)
|
||||
self.unread.setGeometry(QtCore.QRect(30, 425, 340, 30))
|
||||
self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30))
|
||||
self.unread.clicked.connect(self.select_color)
|
||||
|
||||
self.compact_mode = QtGui.QCheckBox(self)
|
||||
self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20))
|
||||
self.compact_mode.setChecked(settings['compact_mode'])
|
||||
|
||||
self.close_to_tray = QtGui.QCheckBox(self)
|
||||
self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20))
|
||||
self.close_to_tray.setChecked(settings['close_to_tray'])
|
||||
|
||||
self.show_avatars = QtGui.QCheckBox(self)
|
||||
self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20))
|
||||
self.show_avatars.setChecked(settings['show_avatars'])
|
||||
|
||||
self.choose_font = QtGui.QPushButton(self)
|
||||
self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30))
|
||||
self.choose_font.clicked.connect(self.new_font)
|
||||
|
||||
self.import_smileys = QtGui.QPushButton(self)
|
||||
self.import_smileys.setGeometry(QtCore.QRect(30, 465, 340, 30))
|
||||
self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30))
|
||||
self.import_smileys.clicked.connect(self.import_sm)
|
||||
|
||||
self.import_stickers = QtGui.QPushButton(self)
|
||||
self.import_stickers.setGeometry(QtCore.QRect(30, 505, 340, 30))
|
||||
self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30))
|
||||
self.import_stickers.clicked.connect(self.import_st)
|
||||
|
||||
self.retranslateUi()
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.show_avatars.setText(QtGui.QApplication.translate("interfaceForm", "Show avatars in chat", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -640,6 +668,8 @@ class InterfaceSettings(CenteredWidget):
|
||||
self.compact_mode.setText(QtGui.QApplication.translate("interfaceForm", "Compact contact list", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.import_smileys.setText(QtGui.QApplication.translate("interfaceForm", "Import smiley pack", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.import_stickers.setText(QtGui.QApplication.translate("interfaceForm", "Import sticker pack", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.close_to_tray.setText(QtGui.QApplication.translate("interfaceForm", "Close to tray", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.choose_font.setText(QtGui.QApplication.translate("interfaceForm", "Select font", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def import_st(self):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||
@ -669,6 +699,20 @@ class InterfaceSettings(CenteredWidget):
|
||||
dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/'
|
||||
copy(src, dest)
|
||||
|
||||
def new_font(self):
|
||||
settings = Settings.get_instance()
|
||||
font, ok = QtGui.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self)
|
||||
if ok:
|
||||
settings['font'] = font.family()
|
||||
settings.save()
|
||||
msgBox = QtGui.QMessageBox()
|
||||
text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
|
||||
def select_color(self):
|
||||
col = QtGui.QColorDialog.getColor()
|
||||
|
||||
@ -689,7 +733,11 @@ class InterfaceSettings(CenteredWidget):
|
||||
if settings['compact_mode'] != self.compact_mode.isChecked():
|
||||
settings['compact_mode'] = self.compact_mode.isChecked()
|
||||
restart = True
|
||||
if settings['show_avatars'] != self.show_avatars.isChecked():
|
||||
settings['show_avatars'] = self.show_avatars.isChecked()
|
||||
restart = True
|
||||
settings['smiley_pack'] = self.smiley_pack.currentText()
|
||||
settings['close_to_tray'] = self.close_to_tray.isChecked()
|
||||
smileys.SmileyLoader.get_instance().load_pack()
|
||||
language = self.lang_choose.currentText()
|
||||
if settings['language'] != language:
|
||||
@ -733,9 +781,11 @@ class AudioSettings(CenteredWidget):
|
||||
self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20))
|
||||
self.out_label = QtGui.QLabel(self)
|
||||
self.out_label.setGeometry(QtCore.QRect(25, 65, 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.out_label.setFont(font)
|
||||
self.input = QtGui.QComboBox(self)
|
||||
@ -743,7 +793,6 @@ class AudioSettings(CenteredWidget):
|
||||
self.output = QtGui.QComboBox(self)
|
||||
self.output.setGeometry(QtCore.QRect(25, 90, 350, 30))
|
||||
p = pyaudio.PyAudio()
|
||||
settings = Settings.get_instance()
|
||||
self.in_indexes, self.out_indexes = [], []
|
||||
for i in range(p.get_device_count()):
|
||||
device = p.get_device_info_by_index(i)
|
||||
|
@ -147,6 +147,16 @@ class PluginLoader(util.Singleton):
|
||||
continue
|
||||
return result
|
||||
|
||||
def get_message_menu(self, menu, selected_text):
|
||||
result = []
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
try:
|
||||
result.extend(elem[0].get_message_menu(menu, selected_text))
|
||||
except:
|
||||
continue
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
App is closing, stop all plugins
|
||||
|
@ -88,6 +88,15 @@ class PluginSuperClass:
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_message_menu(self, menu, text):
|
||||
"""
|
||||
This method creates items for menu which called on right click in message
|
||||
:param menu: menu instance
|
||||
:param text: selected text
|
||||
:return list of QAction's
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_window(self):
|
||||
"""
|
||||
This method should return window for plugins with GUI or None
|
||||
|
@ -41,6 +41,10 @@ class Profile(contact.Contact, Singleton):
|
||||
self._load_history = True
|
||||
settings = Settings.get_instance()
|
||||
self._show_online = settings['show_online_friends']
|
||||
self._show_avatars = settings['show_avatars']
|
||||
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))
|
||||
aliases = settings['friends_aliases']
|
||||
data = tox.self_get_friend_list()
|
||||
@ -78,6 +82,8 @@ class Profile(contact.Contact, Singleton):
|
||||
super(Profile, self).set_status(status)
|
||||
if status is not None:
|
||||
self._tox.self_set_status(status)
|
||||
else:
|
||||
QtCore.QTimer.singleShot(30000, self.reconnect)
|
||||
|
||||
def set_name(self, value):
|
||||
if self.name == value:
|
||||
@ -122,7 +128,7 @@ class Profile(contact.Contact, Singleton):
|
||||
friend.visibility = friend.visibility or friend.messages or friend.actions
|
||||
if friend.visibility:
|
||||
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250,
|
||||
40 if settings['compact_mode'] else 70))
|
||||
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
|
||||
@ -167,15 +173,16 @@ class Profile(contact.Contact, Singleton):
|
||||
self.send_typing(False)
|
||||
self._screen.typing.setVisible(False)
|
||||
if value is not None:
|
||||
if self._active_friend + 1:
|
||||
if self._active_friend + 1 and self._active_friend != value:
|
||||
try:
|
||||
self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
|
||||
except:
|
||||
pass
|
||||
self._active_friend = value
|
||||
friend = self._friends[value]
|
||||
self._friends[value].reset_messages()
|
||||
if self._active_friend != value:
|
||||
self._screen.messageEdit.setPlainText(friend.curr_text)
|
||||
self._active_friend = value
|
||||
self._friends[value].reset_messages()
|
||||
self._messages.clear()
|
||||
friend.load_corr()
|
||||
messages = friend.get_corr()[-PAGE_SIZE:]
|
||||
@ -224,11 +231,9 @@ class Profile(contact.Contact, Singleton):
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = curr_directory() + '/images/avatar.png'
|
||||
os.chdir(os.path.dirname(avatar_path))
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
||||
pixmap.load(avatar_path)
|
||||
self._screen.account_avatar.setScaledContents(False)
|
||||
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
|
||||
self._screen.account_avatar.repaint() # comment?
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self.update_filtration()
|
||||
except Exception as ex: # no friend found. ignore
|
||||
log('Friend value: ' + str(value))
|
||||
@ -283,6 +288,13 @@ class Profile(contact.Contact, Singleton):
|
||||
else:
|
||||
self.send_file(data[0], friend_number, True)
|
||||
friend.clear_unsent_files()
|
||||
for key in list(self._paused_file_transfers.keys()):
|
||||
data = self._paused_file_transfers[key]
|
||||
if not os.path.exists(data[0]):
|
||||
del self._paused_file_transfers[key]
|
||||
elif data[1] == friend_number and not data[2]:
|
||||
self.send_file(data[0], friend_number, True, key)
|
||||
del self._paused_file_transfers[key]
|
||||
if friend_number == self.get_active_number():
|
||||
self.update()
|
||||
except Exception as ex:
|
||||
@ -292,11 +304,18 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
Friend with specified number quit
|
||||
"""
|
||||
# TODO: fix and add full file resuming support
|
||||
self.get_friend_by_number(friend_number).status = None
|
||||
self.friend_typing(friend_number, False)
|
||||
if friend_number in self._call:
|
||||
self._call.finish_call(friend_number, True)
|
||||
for friend_num, file_num in list(self._file_transfers.keys()):
|
||||
if friend_num == friend_number:
|
||||
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:
|
||||
self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()]
|
||||
self.cancel_transfer(friend_num, file_num, True)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
@ -307,9 +326,12 @@ class Profile(contact.Contact, Singleton):
|
||||
Send typing notification to a friend
|
||||
"""
|
||||
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
|
||||
try:
|
||||
friend = self._friends[self._active_friend]
|
||||
if friend.status is not None:
|
||||
self._tox.self_set_typing(friend.number, typing)
|
||||
except:
|
||||
pass
|
||||
|
||||
def friend_typing(self, friend_number, typing):
|
||||
"""
|
||||
@ -397,7 +419,6 @@ class Profile(contact.Contact, Singleton):
|
||||
plugin_support.PluginLoader.get_instance().command(text[8:])
|
||||
self._screen.messageEdit.clear()
|
||||
elif text and friend_num + 1:
|
||||
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
|
||||
if text.startswith('/me '):
|
||||
message_type = TOX_MESSAGE_TYPE['ACTION']
|
||||
text = text[4:]
|
||||
@ -484,7 +505,7 @@ class Profile(contact.Contact, Singleton):
|
||||
data[1],
|
||||
data[3],
|
||||
False)
|
||||
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
|
||||
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer
|
||||
if message.get_status() is None:
|
||||
self.create_unsent_file_item(message)
|
||||
continue
|
||||
@ -496,19 +517,45 @@ class Profile(contact.Contact, Singleton):
|
||||
ft.signal()
|
||||
except:
|
||||
print('Incoming not started transfer - no info found')
|
||||
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
|
||||
self.create_inline_item(message.get_data())
|
||||
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image
|
||||
self.create_inline_item(message.get_data(), False)
|
||||
else: # info message
|
||||
data = message.get_data()
|
||||
self.create_message_item(data[0],
|
||||
data[2],
|
||||
'',
|
||||
data[3])
|
||||
data[3],
|
||||
False)
|
||||
self._load_history = True
|
||||
|
||||
def export_history(self, directory):
|
||||
def export_db(self, directory):
|
||||
self._history.export(directory)
|
||||
|
||||
def export_history(self, num, as_text=True, _range=None):
|
||||
friend = self._friends[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]
|
||||
else:
|
||||
corr = friend.get_corr()[_range[0]:]
|
||||
arr = []
|
||||
new_line = '\n' if as_text else '<br>'
|
||||
for message in corr:
|
||||
if type(message) is TextMessage:
|
||||
data = message.get_data()
|
||||
if as_text:
|
||||
x = '[{}] {}: {}\n'
|
||||
else:
|
||||
x = '[{}] <b>{}:</b> {}<br>'
|
||||
arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent',
|
||||
friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name,
|
||||
data[0]))
|
||||
s = new_line.join(arr)
|
||||
return s
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Factories for friend, message and file transfer items
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -533,6 +580,9 @@ class Profile(contact.Contact, Singleton):
|
||||
else:
|
||||
name = self._name
|
||||
item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages)
|
||||
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:
|
||||
@ -773,24 +823,34 @@ class Profile(contact.Contact, Singleton):
|
||||
Recreate tox instance
|
||||
:param restart: method which calls restart and returns new tox instance
|
||||
"""
|
||||
# TODO: file transfers!!
|
||||
for key in list(self._file_transfers.keys()):
|
||||
self._file_transfers[key].cancelled()
|
||||
del self._file_transfers[key]
|
||||
for friend in self._friends:
|
||||
self.friend_exit(friend.number)
|
||||
self._call.stop()
|
||||
del self._call
|
||||
del self._tox
|
||||
self._tox = restart()
|
||||
self._call = calls.AV(self._tox.AV)
|
||||
self.status = None
|
||||
for friend in self._friends:
|
||||
friend.status = None
|
||||
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))):
|
||||
self.reset(self._screen.reset)
|
||||
QtCore.QTimer.singleShot(30000, self.reconnect)
|
||||
|
||||
def close(self):
|
||||
for friend in self._friends:
|
||||
self.friend_exit(friend.number)
|
||||
for i in range(len(self._friends)):
|
||||
del self._friends[0]
|
||||
if hasattr(self, '_call'):
|
||||
self._call.stop()
|
||||
del self._call
|
||||
for i in range(len(self._friends)):
|
||||
del self._friends[0]
|
||||
s = Settings.get_instance()
|
||||
s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {}
|
||||
s.save()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
@ -808,7 +868,24 @@ class Profile(contact.Contact, Singleton):
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends']
|
||||
inline = (file_name in ALLOWED_FILES) and settings['allow_inline']
|
||||
if inline and size < 1024 * 1024:
|
||||
file_id = self._tox.file_get_file_id(friend_number, file_number)
|
||||
accepted = True
|
||||
if file_id in self._paused_file_transfers:
|
||||
data = self._paused_file_transfers[file_id]
|
||||
pos = data[-1] if os.path.exists(data[0]) else 0
|
||||
if pos >= size:
|
||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||
return
|
||||
self._tox.file_seek(friend_number, file_number, pos)
|
||||
self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos)
|
||||
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
||||
size,
|
||||
file_name,
|
||||
friend_number,
|
||||
file_number)
|
||||
elif inline and size < 1024 * 1024:
|
||||
self.accept_transfer(None, '', friend_number, file_number, size, True)
|
||||
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
@ -836,9 +913,10 @@ class Profile(contact.Contact, Singleton):
|
||||
file_name,
|
||||
friend_number,
|
||||
file_number)
|
||||
accepted = False
|
||||
if friend_number == self.get_active_number():
|
||||
item = self.create_file_transfer_item(tm)
|
||||
if (inline and size < 1024 * 1024) or auto:
|
||||
if accepted:
|
||||
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
else:
|
||||
@ -892,18 +970,14 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||
# if (friend_number, file_number) not in self._file_transfers:
|
||||
# print self._file_transfers
|
||||
# print (friend_number, file_number)
|
||||
# return
|
||||
tr = self._file_transfers[(friend_number, file_number)]
|
||||
if by_friend:
|
||||
tr.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||
tr.signal()
|
||||
else: # send seek control?
|
||||
else:
|
||||
tr.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||
|
||||
def accept_transfer(self, item, path, friend_number, file_number, size, inline=False):
|
||||
def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0):
|
||||
"""
|
||||
:param item: transfer item.
|
||||
:param path: path for saving
|
||||
@ -911,9 +985,11 @@ class Profile(contact.Contact, Singleton):
|
||||
:param file_number: file number
|
||||
:param size: file size
|
||||
:param inline: is inline image
|
||||
:param from_position: position for start
|
||||
"""
|
||||
path, file_name = os.path.split(path)
|
||||
new_file_name, i = file_name, 1
|
||||
if not from_position:
|
||||
while os.path.isfile(path + '/' + new_file_name): # file with same name already exists
|
||||
if '.' in file_name: # has extension
|
||||
d = file_name.rindex('.')
|
||||
@ -923,9 +999,10 @@ class Profile(contact.Contact, Singleton):
|
||||
i += 1
|
||||
path = os.path.join(path, new_file_name)
|
||||
if not inline:
|
||||
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number)
|
||||
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
|
||||
else:
|
||||
rt = ReceiveToBuffer(self._tox, friend_number, size, file_number)
|
||||
rt.set_transfer_finished_handler(self.transfer_finished)
|
||||
self._file_transfers[(friend_number, file_number)] = rt
|
||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
|
||||
if item is not None:
|
||||
@ -939,6 +1016,7 @@ class Profile(contact.Contact, Singleton):
|
||||
:param data: raw data - png
|
||||
"""
|
||||
self.send_inline(data, 'toxygen_inline.png')
|
||||
self._messages.repaint()
|
||||
|
||||
def send_sticker(self, path):
|
||||
with open(path, 'rb') as fl:
|
||||
@ -956,6 +1034,7 @@ class Profile(contact.Contact, Singleton):
|
||||
elif friend.status is None and is_resend:
|
||||
raise RuntimeError()
|
||||
st = SendFromBuffer(self._tox, friend.number, data, file_name)
|
||||
st.set_transfer_finished_handler(self.transfer_finished)
|
||||
self._file_transfers[(friend.number, st.get_file_number())] = st
|
||||
tm = TransferMessage(MESSAGE_OWNER['ME'],
|
||||
time.time(),
|
||||
@ -969,14 +1048,15 @@ class Profile(contact.Contact, Singleton):
|
||||
st.set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
def send_file(self, path, number=None, is_resend=False):
|
||||
def send_file(self, path, number=None, is_resend=False, file_id=None):
|
||||
"""
|
||||
Send file to current active friend
|
||||
:param path: file path
|
||||
:param number: friend_number
|
||||
:param is_resend: is 'offline' message
|
||||
:param file_id: file id of transfer
|
||||
"""
|
||||
friend_number = number or self.get_active_number()
|
||||
friend_number = self.get_active_number() if number is None else number
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
if friend.status is None and not is_resend:
|
||||
m = UnsentFile(path, None, time.time())
|
||||
@ -986,7 +1066,8 @@ class Profile(contact.Contact, Singleton):
|
||||
elif friend.status is None and is_resend:
|
||||
print('Error in sending')
|
||||
raise RuntimeError()
|
||||
st = SendTransfer(path, self._tox, friend_number)
|
||||
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
|
||||
st.set_transfer_finished_handler(self.transfer_finished)
|
||||
self._file_transfers[(friend_number, st.get_file_number())] = st
|
||||
tm = TransferMessage(MESSAGE_OWNER['ME'],
|
||||
time.time(),
|
||||
@ -995,23 +1076,32 @@ class Profile(contact.Contact, Singleton):
|
||||
os.path.basename(path),
|
||||
friend_number,
|
||||
st.get_file_number())
|
||||
if friend_number == self.get_active_number():
|
||||
item = self.create_file_transfer_item(tm)
|
||||
st.set_state_changed_handler(item.update)
|
||||
self._friends[self._active_friend].append_message(tm)
|
||||
self._messages.scrollToBottom()
|
||||
self._friends[friend_number].append_message(tm)
|
||||
|
||||
def incoming_chunk(self, friend_number, file_number, position, data):
|
||||
"""
|
||||
Incoming chunk
|
||||
"""
|
||||
if (friend_number, file_number) in self._file_transfers:
|
||||
self._file_transfers[(friend_number, file_number)].write_chunk(position, data)
|
||||
|
||||
def outgoing_chunk(self, friend_number, file_number, position, size):
|
||||
"""
|
||||
Outgoing chunk
|
||||
"""
|
||||
self._file_transfers[(friend_number, file_number)].send_chunk(position, size)
|
||||
|
||||
@QtCore.Slot(int, int)
|
||||
def transfer_finished(self, friend_number, file_number):
|
||||
transfer = self._file_transfers[(friend_number, file_number)]
|
||||
transfer.write_chunk(position, data)
|
||||
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
|
||||
if type(transfer) is ReceiveAvatar:
|
||||
t = type(transfer)
|
||||
if t is ReceiveAvatar:
|
||||
self.get_friend_by_number(friend_number).load_avatar()
|
||||
self.set_active(None)
|
||||
elif type(transfer) is ReceiveToBuffer: # inline image
|
||||
elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image
|
||||
print('inline')
|
||||
inline = InlineImage(transfer.get_data())
|
||||
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
@ -1025,39 +1115,11 @@ 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)
|
||||
else:
|
||||
elif t is not SendAvatar:
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE['FINISHED'])
|
||||
del self._file_transfers[(friend_number, file_number)]
|
||||
|
||||
def outgoing_chunk(self, friend_number, file_number, position, size):
|
||||
"""
|
||||
Outgoing chunk
|
||||
"""
|
||||
if (friend_number, file_number) in self._file_transfers:
|
||||
transfer = self._file_transfers[(friend_number, file_number)]
|
||||
transfer.send_chunk(position, size)
|
||||
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
|
||||
del self._file_transfers[(friend_number, file_number)]
|
||||
if type(transfer) is not SendAvatar:
|
||||
if type(transfer) is SendFromBuffer and Settings.get_instance()['allow_inline']: # inline
|
||||
inline = InlineImage(transfer.get_data())
|
||||
print('inline')
|
||||
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE[
|
||||
'FINISHED'],
|
||||
inline)
|
||||
if friend_number == self.get_active_number():
|
||||
count = self._messages.count()
|
||||
if count + i + 1 >= 0:
|
||||
elem = QtGui.QListWidgetItem()
|
||||
item = InlineImageItem(transfer.get_data(), self._messages.width(), elem)
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
self._messages.insertItem(count + i + 1, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
else:
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE['FINISHED'])
|
||||
del transfer
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars support
|
||||
@ -1111,6 +1173,8 @@ class Profile(contact.Contact, Singleton):
|
||||
"""User clicked audio button in main window"""
|
||||
num = self.get_active_number()
|
||||
if num not in self._call and self.is_active_online(): # start call
|
||||
if not Settings.get_instance().audio['enabled']:
|
||||
return
|
||||
self._call(num, audio, video)
|
||||
self._screen.active_call()
|
||||
if video:
|
||||
@ -1129,6 +1193,8 @@ class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
Incoming call from friend. Only audio is supported now
|
||||
"""
|
||||
if not Settings.get_instance().audio['enabled']:
|
||||
return
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
if video:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
|
||||
@ -1144,6 +1210,7 @@ class Profile(contact.Contact, Singleton):
|
||||
self._messages.scrollToBottom()
|
||||
else:
|
||||
friend.actions = True
|
||||
# TODO: dict of widgets
|
||||
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
|
||||
self._call_widget.set_pixmap(friend.get_pixmap())
|
||||
self._call_widget.show()
|
||||
@ -1170,6 +1237,7 @@ class Profile(contact.Contact, Singleton):
|
||||
self._screen.call_finished()
|
||||
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
||||
if hasattr(self, '_call_widget'):
|
||||
self._call_widget.close()
|
||||
del self._call_widget
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
friend.append_message(InfoMessage(text, time.time()))
|
||||
|
@ -1,8 +1,7 @@
|
||||
from platform import system
|
||||
import json
|
||||
import os
|
||||
import locale
|
||||
from util import Singleton, curr_directory, log
|
||||
from util import Singleton, curr_directory, log, copy
|
||||
import pyaudio
|
||||
from toxencryptsave import ToxEncryptSave
|
||||
import smileys
|
||||
@ -34,10 +33,19 @@ class Settings(dict, Singleton):
|
||||
super(Settings, self).__init__(Settings.get_default_settings())
|
||||
self.save()
|
||||
smileys.SmileyLoader(self)
|
||||
p = pyaudio.PyAudio()
|
||||
self.locked = False
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
||||
'output': p.get_default_output_device_info()['index']}
|
||||
self.closing = False
|
||||
p = pyaudio.PyAudio()
|
||||
input_devices = output_devices = 0
|
||||
for i in range(p.get_device_count()):
|
||||
device = p.get_device_info_by_index(i)
|
||||
if device["maxInputChannels"]:
|
||||
input_devices += 1
|
||||
if device["maxOutputChannels"]:
|
||||
output_devices += 1
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
||||
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
||||
'enabled': input_devices and output_devices}
|
||||
|
||||
@staticmethod
|
||||
def get_auto_profile():
|
||||
@ -109,7 +117,10 @@ class Settings(dict, Singleton):
|
||||
'auto_accept_path': None,
|
||||
'show_online_friends': False,
|
||||
'auto_accept_from_friends': [],
|
||||
'paused_file_transfers': {},
|
||||
'resend_files': True,
|
||||
'friends_aliases': [],
|
||||
'show_avatars': False,
|
||||
'typing_notifications': False,
|
||||
'calls_sound': True,
|
||||
'blocked': [],
|
||||
@ -126,7 +137,9 @@ class Settings(dict, Singleton):
|
||||
'unread_color': 'red',
|
||||
'save_unsent_only': False,
|
||||
'compact_mode': False,
|
||||
'show_welcome_screen': True
|
||||
'show_welcome_screen': True,
|
||||
'close_to_tray': False,
|
||||
'font': 'Times New Roman'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@ -193,12 +206,17 @@ class Settings(dict, Singleton):
|
||||
with open(path + str(self.name) + '.json', 'w') as fl:
|
||||
fl.write(text)
|
||||
|
||||
def update_path(self):
|
||||
self.path = ProfileHelper.get_path() + self.name + '.json'
|
||||
|
||||
@staticmethod
|
||||
def get_default_path():
|
||||
if system() == 'Linux':
|
||||
return os.getenv('HOME') + '/.config/tox/'
|
||||
elif system() == 'Windows':
|
||||
if system() == 'Windows':
|
||||
return os.getenv('APPDATA') + '/Tox/'
|
||||
elif system() == 'Darwin':
|
||||
return os.getenv('HOME') + '/Library/Application Support/Tox/'
|
||||
else:
|
||||
return os.getenv('HOME') + '/.config/tox/'
|
||||
|
||||
|
||||
class ProfileHelper(Singleton):
|
||||
@ -233,13 +251,18 @@ class ProfileHelper(Singleton):
|
||||
fl.write(data)
|
||||
print('Profile saved successfully')
|
||||
|
||||
def export_profile(self, new_path):
|
||||
new_path += os.path.basename(self._path)
|
||||
def export_profile(self, new_path, use_new_path):
|
||||
path = new_path + os.path.basename(self._path)
|
||||
with open(self._path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
with open(new_path, 'wb') as fout:
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
print('Profile exported successfully')
|
||||
copy(self._directory + 'avatars', new_path + 'avatars')
|
||||
if use_new_path:
|
||||
self._path = new_path + os.path.basename(self._path)
|
||||
self._directory = new_path
|
||||
Settings.get_instance().update_path()
|
||||
|
||||
@staticmethod
|
||||
def find_profiles():
|
||||
|
BIN
toxygen/smileys/ksk/angry.png
Normal file
After Width: | Height: | Size: 883 B |
BIN
toxygen/smileys/ksk/angry2.png
Normal file
After Width: | Height: | Size: 932 B |
BIN
toxygen/smileys/ksk/angry3.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
toxygen/smileys/ksk/blink.png
Normal file
After Width: | Height: | Size: 891 B |
BIN
toxygen/smileys/ksk/bluestar.png
Normal file
After Width: | Height: | Size: 809 B |
BIN
toxygen/smileys/ksk/calm.png
Normal file
After Width: | Height: | Size: 893 B |
1
toxygen/smileys/ksk/config.json
Normal file
@ -0,0 +1 @@
|
||||
{"BD": "cool2.png", "v_v": "calm.png", ":/": "getlost.png", ":(": "sad.png", ":)": "smile.png", ":*": "kiss.png", ":animal:": "pawn.png", "=|": "none.png", "=*": "kiss.png", ":heart:": "heart.png", "B]": "cool.png", "=o": "shocked.png", ":0": "shocked.png", "=S": "none2.png", "=]": "smile2.png", "=\\": "getlost.png", "B-)": "cool.png", ":pawn:": "pawn.png", "=O": "shocked.png", ">:\\": "angry2.png", ":redstar:": "redstar.png", ":o": "shocked.png", "=0": "shocked.png", "B-D": "cool2.png", ":|": "none.png", ":''(": "cry.png", "=/": "getlost.png", "=)": "smile.png", "=(": "sad.png", "B-]": "cool.png", ":O": "shocked.png", ":D": "grin.png", "B)": "cool.png", ":'(": "cry.png", ":]": "smile2.png", ":music:": "notes.png", ":P": "tongue.png", ":S": "none2.png", ":evil:": "evil.png", ":-O": "shocked.png", ":zzzzz:": "zzz.png", ">:[]": "angry.png", ";|": "none.png", ":-\\": "getlost.png", ":-]": "smile2.png", ":-S": "none2.png", ":-P": "tongue.png", ";o": "shocked.png", ";S": "none2.png", ":\\": "getlost.png", ";P": "tongue.png", ":pet:": "pawn.png", ":-o": "shocked.png", ";]": "blink.png", ";\\": "getlost.png", ":oops:": "oops.png", ":-|": "none.png", ";D": "grin.png", ";O": "shocked.png", "@->-": "flower.png", ";0": "shocked.png", ":zzz:": "zzz.png", ":cool2:": "cool2.png", "^_^": "pleased.png", ":)))": "grin.png", ";)": "blink.png", ";/": "getlost.png", ":-*": "kiss.png", ":-(": "sad.png", ":-)": "smile.png", "8-[]": "scared.png", ":cool:": "cool.png", ":kiss:": "kiss.png", ":notes:": "notes.png", ":calm:": "calm.png", ":-0": "shocked.png", ":greenstar:": "greenstar.png", ">:][": "angry.png", ">:]]": "evil2.png", "B))": "cool2.png", ">:)": "evil.png", ">:(": "angry3.png", ">:/": "angry2.png", ":lol:": "lol.png", ":scared:": "scared.png", ">:>": "evil.png", ">:<": "angry3.png", ">:D": "evil2.png", "B]]": "cool2.png", ">:((": "angry3.png", ">:[": "angry3.png", ":sick:": "unwell.png", ":-/": "getlost.png", ":cry:": "cry.png", "<3": "heart.png", ":leaf:": "leaf.png", ">:))": "evil2.png", ":bluestar:": "bluestar.png", ";-0": "shocked.png", ":weed:": "leaf.png", ":zzzz:": "zzz.png", ":sing:": "notes.png", ":yellowstar:": "yellowstar.png", ";-/": "getlost.png", ";-)": "blink.png", ":dead:": "dead.png", ";-S": "none2.png", "^^": "pleased.png", ";-P": "tongue.png", ";-]": "blink.png", ";-\\": "getlost.png", ":flower:": "flower.png", ":puke:": "unwell.png", ";-O": "shocked.png", ":love:": "heart.png", ";-o": "shocked.png", ":))))": "grin.png", ":))": "grin.png"}
|
BIN
toxygen/smileys/ksk/cool.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
toxygen/smileys/ksk/cool2.png
Normal file
After Width: | Height: | Size: 956 B |
BIN
toxygen/smileys/ksk/cry.png
Normal file
After Width: | Height: | Size: 956 B |
BIN
toxygen/smileys/ksk/dead.png
Normal file
After Width: | Height: | Size: 913 B |
BIN
toxygen/smileys/ksk/evil.png
Normal file
After Width: | Height: | Size: 888 B |
BIN
toxygen/smileys/ksk/evil2.png
Normal file
After Width: | Height: | Size: 929 B |
BIN
toxygen/smileys/ksk/flower.png
Normal file
After Width: | Height: | Size: 935 B |
BIN
toxygen/smileys/ksk/getlost.png
Normal file
After Width: | Height: | Size: 921 B |
BIN
toxygen/smileys/ksk/greenstar.png
Normal file
After Width: | Height: | Size: 822 B |
BIN
toxygen/smileys/ksk/grin.png
Normal file
After Width: | Height: | Size: 920 B |
BIN
toxygen/smileys/ksk/heart.png
Normal file
After Width: | Height: | Size: 829 B |
BIN
toxygen/smileys/ksk/kiss.png
Normal file
After Width: | Height: | Size: 996 B |
BIN
toxygen/smileys/ksk/leaf.png
Normal file
After Width: | Height: | Size: 913 B |
BIN
toxygen/smileys/ksk/lol.png
Normal file
After Width: | Height: | Size: 957 B |
BIN
toxygen/smileys/ksk/none.png
Normal file
After Width: | Height: | Size: 882 B |
BIN
toxygen/smileys/ksk/none2.png
Normal file
After Width: | Height: | Size: 890 B |
BIN
toxygen/smileys/ksk/notes.png
Normal file
After Width: | Height: | Size: 751 B |
BIN
toxygen/smileys/ksk/oops.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
toxygen/smileys/ksk/pawn.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
toxygen/smileys/ksk/pleased.png
Normal file
After Width: | Height: | Size: 937 B |
BIN
toxygen/smileys/ksk/redstar.png
Normal file
After Width: | Height: | Size: 782 B |
BIN
toxygen/smileys/ksk/sad.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
toxygen/smileys/ksk/scared.png
Normal file
After Width: | Height: | Size: 897 B |
BIN
toxygen/smileys/ksk/shocked.png
Normal file
After Width: | Height: | Size: 967 B |
BIN
toxygen/smileys/ksk/smile.png
Normal file
After Width: | Height: | Size: 885 B |
BIN
toxygen/smileys/ksk/smile2.png
Normal file
After Width: | Height: | Size: 886 B |
BIN
toxygen/smileys/ksk/tongue.png
Normal file
After Width: | Height: | Size: 918 B |
BIN
toxygen/smileys/ksk/unwell.png
Normal file
After Width: | Height: | Size: 888 B |
BIN
toxygen/smileys/ksk/yellowstar.png
Normal file
After Width: | Height: | Size: 792 B |
BIN
toxygen/smileys/ksk/zzz.png
Normal file
After Width: | Height: | Size: 990 B |
BIN
toxygen/stickers/tox/tox_logo.png
Executable file
After Width: | Height: | Size: 31 KiB |
BIN
toxygen/stickers/tox/tox_logo_1.png
Executable file
After Width: | Height: | Size: 27 KiB |
@ -1245,10 +1245,20 @@ QPushButton:hover
|
||||
}
|
||||
|
||||
#messages:item:selected
|
||||
{
|
||||
background-color: #1E90FF;
|
||||
}
|
||||
|
||||
MessageEdit
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#messages:item:selected QListWidgetItem
|
||||
{
|
||||
background-color: #1E90FF;
|
||||
}
|
||||
|
||||
#friends_list:item:selected
|
||||
{
|
||||
background-color: #333333;
|
||||
|
@ -12,8 +12,6 @@ class ToxAV:
|
||||
peers.
|
||||
"""
|
||||
|
||||
libtoxav = LibToxAV()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Creation and destruction
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -24,9 +22,10 @@ class ToxAV:
|
||||
|
||||
:param tox_pointer: pointer to Tox instance
|
||||
"""
|
||||
self.libtoxav = LibToxAV()
|
||||
toxav_err_new = c_int()
|
||||
ToxAV.libtoxav.toxav_new.restype = POINTER(c_void_p)
|
||||
self._toxav_pointer = ToxAV.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
|
||||
self.libtoxav.toxav_new.restype = POINTER(c_void_p)
|
||||
self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new))
|
||||
toxav_err_new = toxav_err_new.value
|
||||
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
|
||||
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
|
||||
@ -48,7 +47,7 @@ class ToxAV:
|
||||
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
|
||||
function, no other functions may be called and the av pointer becomes invalid.
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_kill(self._toxav_pointer)
|
||||
self.libtoxav.toxav_kill(self._toxav_pointer)
|
||||
|
||||
def get_tox_pointer(self):
|
||||
"""
|
||||
@ -56,8 +55,8 @@ class ToxAV:
|
||||
|
||||
:return: pointer to the Tox instance
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||
return ToxAV.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# A/V event loop
|
||||
@ -70,14 +69,14 @@ class ToxAV:
|
||||
|
||||
:return: interval in milliseconds
|
||||
"""
|
||||
return ToxAV.libtoxav.toxav_iteration_interval(self._toxav_pointer)
|
||||
return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
|
||||
|
||||
def iterate(self):
|
||||
"""
|
||||
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
|
||||
milliseconds. It is best called in the separate thread from tox_iterate.
|
||||
"""
|
||||
ToxAV.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||
self.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Call setup
|
||||
@ -97,7 +96,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_call = c_int()
|
||||
result = ToxAV.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
c_uint32(video_bit_rate), byref(toxav_err_call))
|
||||
toxav_err_call = toxav_err_call.value
|
||||
if toxav_err_call == TOXAV_ERR_CALL['OK']:
|
||||
@ -131,7 +130,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
|
||||
self.call_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||
|
||||
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
|
||||
"""
|
||||
@ -146,7 +145,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_answer = c_int()
|
||||
result = ToxAV.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||
c_uint32(video_bit_rate), byref(toxav_err_answer))
|
||||
toxav_err_answer = toxav_err_answer.value
|
||||
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
|
||||
@ -184,7 +183,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
|
||||
self.call_state_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Call control
|
||||
@ -199,7 +198,7 @@ class ToxAV:
|
||||
:return: True on success.
|
||||
"""
|
||||
toxav_err_call_control = c_int()
|
||||
result = ToxAV.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
|
||||
result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
|
||||
byref(toxav_err_call_control))
|
||||
toxav_err_call_control = toxav_err_call_control.value
|
||||
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
|
||||
@ -241,7 +240,7 @@ class ToxAV:
|
||||
24000, or 48000.
|
||||
"""
|
||||
toxav_err_send_frame = c_int()
|
||||
result = ToxAV.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
|
||||
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number),
|
||||
cast(pcm, c_void_p),
|
||||
c_size_t(sample_count), c_uint8(channels),
|
||||
c_uint32(sampling_rate), byref(toxav_err_send_frame))
|
||||
@ -281,7 +280,7 @@ class ToxAV:
|
||||
:param v: V (Chroma) plane data.
|
||||
"""
|
||||
toxav_err_send_frame = c_int()
|
||||
result = ToxAV.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
|
||||
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
|
||||
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
|
||||
byref(toxav_err_send_frame))
|
||||
toxav_err_send_frame = toxav_err_send_frame.value
|
||||
@ -328,7 +327,7 @@ class ToxAV:
|
||||
"""
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
|
||||
self.audio_receive_frame_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||
|
||||
def callback_video_receive_frame(self, callback, user_data):
|
||||
"""
|
||||
@ -360,4 +359,4 @@ class ToxAV:
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8),
|
||||
POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p)
|
||||
self.video_receive_frame_cb = c_callback(callback)
|
||||
ToxAV.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
|
||||
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
|
||||
|
@ -36,10 +36,9 @@ TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
|
||||
|
||||
class ToxEncryptSave(util.Singleton):
|
||||
|
||||
libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||
self._passphrase = None
|
||||
|
||||
def set_password(self, passphrase):
|
||||
|
@ -2,7 +2,7 @@ import os
|
||||
import time
|
||||
import shutil
|
||||
|
||||
program_version = '0.2.2'
|
||||
program_version = '0.2.4'
|
||||
|
||||
|
||||
def log(data):
|
||||
|
@ -9,7 +9,7 @@ class DataLabel(QtGui.QLabel):
|
||||
Label with elided text
|
||||
"""
|
||||
def setText(self, text):
|
||||
text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text)
|
||||
text = ''.join(c if c <= '\U0010FFFF' else '\u25AF' for c in text)
|
||||
metrics = QtGui.QFontMetrics(self.font())
|
||||
text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width())
|
||||
super().setText(text)
|
||||
|