Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
02aa2981b5 | |||
bde1160562 | |||
3936f652e9 | |||
fa5b77fd2d | |||
cbeb25e0be | |||
3cb961957c | |||
65930716f9 | |||
03e34cc43f | |||
48360ea54c | |||
def6e10b93 | |||
24d27f9f47 | |||
7bdc506ff2 | |||
23b045070a | |||
6a7a38ef02 | |||
0e86fd66df | |||
e375dca9cc | |||
fd9bfa52f3 | |||
5fd5a9bd85 | |||
e75190f767 | |||
96705ed1cc | |||
5c4bfceee7 | |||
85061454a6 | |||
fe4af18196 | |||
183dfadc77 | |||
d3c8c99048 | |||
63774ba5df | |||
53a381222f | |||
7e63d9634a | |||
f6affc14ef | |||
9bf072e122 | |||
3ed4e8f511 | |||
94eb292b16 | |||
a0b23b0faa | |||
04f9852050 | |||
01854ff2d0 | |||
298dba4d6c | |||
bc248a9e30 | |||
7901aad3e7 | |||
6a244b30c1 | |||
17a8d81486 | |||
c114e9b6c3 | |||
672dafc701 | |||
d02ee92508 | |||
42617c8816 | |||
453b5e25be | |||
8fb263dadc | |||
4b4075606e | |||
b02e063302 | |||
cc8c5f7e99 | |||
51ec1af369 | |||
74d7c67d8c | |||
b40c0a868b | |||
b6d13166bc |
@ -33,24 +33,25 @@ Toxygen is cross-platform [Tox](https://tox.chat/) client written on Python
|
||||
- [x] File resuming
|
||||
- [x] Save file encryption
|
||||
- [x] Plugins support
|
||||
- [x] Read receipts
|
||||
- [x] Faux offline messaging
|
||||
- [ ] Video
|
||||
- [ ] Group chats
|
||||
- [ ] Read receipts
|
||||
- [ ] Faux offline messaging
|
||||
- [ ] Desktop sharing
|
||||
|
||||
###Downloads
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
[Download last stable version](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
|
||||
[Download develop version](https://github.com/xveduk/toxygen/archive/develop.zip)
|
||||
|
||||
[Releases](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
###Screenshots
|
||||
*Toxygen on Ubuntu and Windows*
|
||||

|
||||

|
||||
|
||||
|
||||
###Docs
|
||||
[Check /docs/ for more info](/docs/)
|
||||
|
||||
|
@ -9,13 +9,13 @@ Help us find all bugs in Toxygen! Please provide following info:
|
||||
- Toxygen executable info - .py or precompiled binary
|
||||
- Steps to reproduce the bug
|
||||
|
||||
Want to see new feature in Toxygen? [Open issue!](https://github.com/xveduk/toxygen/issues)
|
||||
Want to see new feature in Toxygen? [Ask for it!](https://github.com/xveduk/toxygen/issues)
|
||||
|
||||
#Pull requests
|
||||
|
||||
Developer? Feel free to open pull request. Our dev team is small so we glad to get help.
|
||||
Don't know what to do? Impove UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
||||
You can find our TODO's in code and [here](/README.md)
|
||||
Don't know what to do? Improve UI, fix [issues](https://github.com/xveduk/toxygen/issues) or implement features from our TODO list.
|
||||
You can find our TODO's in code and [here](/README.md). Also you can implement plugins for Toxygen.
|
||||
|
||||
#Translations
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
# How to install Toxygen
|
||||
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
|
||||
## From source code (recommended for developers)
|
||||
|
||||
### Windows
|
||||
|
||||
1. [Download and install latest Python 2.7](https://www.python.org/downloads/windows/)
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) *(PyQt4 support will be added later)*
|
||||
2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
3. Install PyAudio: ``python -m pip install pyaudio``
|
||||
4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
5. Unpack archive
|
||||
@ -23,22 +26,23 @@
|
||||
|
||||
### Linux
|
||||
|
||||
- Install Python2.7:
|
||||
Dependencies:
|
||||
|
||||
1. Install Python2.7:
|
||||
``sudo apt-get install python2.7``
|
||||
- [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) *(PyQt4 support will be added later)*
|
||||
- Install PyAudio:
|
||||
2. [Install PySide](https://wiki.qt.io/PySide_Binaries_Linux) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download)
|
||||
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:
|
||||
```bash
|
||||
sudo apt-get install portaudio19-dev
|
||||
sudo pip install pyaudio
|
||||
```
|
||||
- [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
- Unpack archive
|
||||
- Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) in your system (install in /usr/lib/)
|
||||
- Run app:
|
||||
``python main.py``
|
||||
Toxygen:
|
||||
|
||||
## Use precompiled binary:
|
||||
[Check our releases page](https://github.com/xveduk/toxygen/releases)
|
||||
1. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip)
|
||||
2. Unpack archive
|
||||
3. Run app:
|
||||
``python main.py``
|
||||
|
||||
## Compile Toxygen
|
||||
Check [compile.md](/docs/compile.md) for more info
|
||||
|
@ -49,5 +49,5 @@ Plugin's methods should not raise exceptions.
|
||||
|
||||
#Examples
|
||||
|
||||
You can find example of a plugin in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
||||
You can find examples in [official repo](https://github.com/ingvar1995/toxygen_plugins)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Plugins
|
||||
|
||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python module and directory with plugin's data which provide some additional functionality.
|
||||
Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python module (.py file) and directory with plugin's data which provide some additional functionality.
|
||||
|
||||
#How to write plugin
|
||||
|
||||
|
11
docs/smileys_and_stickers.md
Normal file
@ -0,0 +1,11 @@
|
||||
#Smileys
|
||||
|
||||
Toxygen support smileys. Smiley is small picture which replaces some combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json:
|
||||
|
||||
{":)": "one.png", ":(": "two.png"}
|
||||
|
||||
Animated smileys (.gif) are supported too.
|
||||
|
||||
#Stickers
|
||||
|
||||
Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there.
|
BIN
docs/ubuntu.png
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 109 KiB |
BIN
docs/windows.png
Before Width: | Height: | Size: 317 KiB After Width: | Height: | Size: 81 KiB |
@ -70,20 +70,22 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
||||
|
||||
def __init__(self, fl):
|
||||
self.stop = False
|
||||
self.wf = wave.open(fl, 'rb')
|
||||
self.fl = fl
|
||||
self.wf = wave.open(self.fl, 'rb')
|
||||
self.p = pyaudio.PyAudio()
|
||||
self.stream = self.p.open(
|
||||
format=self.p.get_format_from_width(self.wf.getsampwidth()),
|
||||
channels=self.wf.getnchannels(),
|
||||
rate=self.wf.getframerate(),
|
||||
output=True
|
||||
)
|
||||
output=True)
|
||||
|
||||
def play(self):
|
||||
data = self.wf.readframes(self.chunk)
|
||||
while data and not self.stop:
|
||||
self.stream.write(data)
|
||||
while not self.stop:
|
||||
data = self.wf.readframes(self.chunk)
|
||||
while data and not self.stop:
|
||||
self.stream.write(data)
|
||||
data = self.wf.readframes(self.chunk)
|
||||
self.wf = wave.open(self.fl, 'rb')
|
||||
|
||||
def close(self):
|
||||
self.stream.close()
|
||||
@ -107,3 +109,31 @@ class IncomingCallWidget(widgets.CenteredWidget):
|
||||
|
||||
def set_pixmap(self, pixmap):
|
||||
self.avatar_label.setPixmap(pixmap)
|
||||
|
||||
|
||||
class AudioMessageRecorder(widgets.CenteredWidget):
|
||||
|
||||
def __init__(self, friend_number, name):
|
||||
super(AudioMessageRecorder, self).__init__()
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(10, 20, 250, 20))
|
||||
text = QtGui.QApplication.translate("MenuWindow", "Send audio message to friend {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.label.setText(text.format(name))
|
||||
self.record = QtGui.QPushButton(self)
|
||||
self.record.setGeometry(QtCore.QRect(20, 100, 150, 150))
|
||||
|
||||
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Start recording", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
self.record.clicked.connect(self.start_or_stop_recording)
|
||||
self.recording = False
|
||||
self.friend_num = friend_number
|
||||
|
||||
def start_or_stop_recording(self):
|
||||
if not self.recording:
|
||||
self.recording = True
|
||||
self.record.setText(QtGui.QApplication.translate("MenuWindow", "Stop recording", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.close()
|
||||
|
||||
|
||||
|
@ -68,6 +68,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(profile.update_filtration)
|
||||
|
||||
|
||||
@ -75,7 +76,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
||||
"""
|
||||
Check friend's connection status (offline, udp, tcp)
|
||||
"""
|
||||
print "Friend #{} connected! Friend's status: {}".format(friend_num, new_status)
|
||||
print "Friend #{} connection status: {}".format(friend_num, new_status)
|
||||
profile = Profile.get_instance()
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
if new_status == TOX_CONNECTION['NONE']:
|
||||
@ -85,7 +86,7 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
elif friend.status is None:
|
||||
invoke_in_main_thread(profile.send_avatar, friend_num)
|
||||
PluginLoader.get_instance().friend_online(friend_num)
|
||||
invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num)
|
||||
|
||||
|
||||
def friend_name(tox, friend_num, name, size, user_data):
|
||||
@ -93,11 +94,8 @@ def friend_name(tox, friend_num, name, size, user_data):
|
||||
Friend changed his name
|
||||
"""
|
||||
profile = Profile.get_instance()
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
print 'New name: ', str(friend_num), str(name)
|
||||
invoke_in_main_thread(friend.set_name, name)
|
||||
if profile.get_active_number() == friend_num:
|
||||
invoke_in_main_thread(profile.set_active)
|
||||
print 'New name: ', friend_num, name
|
||||
invoke_in_main_thread(profile.new_name, friend_num, name)
|
||||
|
||||
|
||||
def friend_status_message(tox, friend_num, status_message, size, user_data):
|
||||
@ -109,6 +107,7 @@ def friend_status_message(tox, friend_num, status_message, size, user_data):
|
||||
friend = profile.get_friend_by_number(friend_num)
|
||||
invoke_in_main_thread(friend.set_status_message, status_message)
|
||||
print 'User #{} has new status: {}'.format(friend_num, status_message)
|
||||
invoke_in_main_thread(profile.send_messages, friend_num)
|
||||
if profile.get_active_number() == friend_num:
|
||||
invoke_in_main_thread(profile.set_active)
|
||||
|
||||
@ -127,6 +126,7 @@ def friend_message(window, tray):
|
||||
invoke_in_main_thread(tray_notification, friend.name, message.decode('utf8'), tray, window)
|
||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||
return wrapped
|
||||
|
||||
|
||||
@ -145,6 +145,13 @@ def friend_request(tox, public_key, message, message_size, user_data):
|
||||
def friend_typing(tox, friend_number, typing, user_data):
|
||||
invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing)
|
||||
|
||||
|
||||
def friend_read_receipt(tox, friend_number, message_id, user_data):
|
||||
profile = Profile.get_instance()
|
||||
profile.get_friend_by_number(friend_number).dec_receipt()
|
||||
if friend_number == profile.get_active_number():
|
||||
invoke_in_main_thread(profile.receipt)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - file transfers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -171,9 +178,11 @@ def tox_file_recv(window, tray):
|
||||
if not window.isActiveWindow():
|
||||
friend = profile.get_friend_by_number(friend_number)
|
||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
invoke_in_main_thread(tray_notification, 'File from ' + friend.name, file_name, tray, window)
|
||||
file_from = QtGui.QApplication.translate("Callback", "File from", None, QtGui.QApplication.UnicodeUTF8)
|
||||
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
|
||||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png'))
|
||||
else: # AVATAR
|
||||
print 'Avatar'
|
||||
invoke_in_main_thread(profile.incoming_avatar,
|
||||
@ -194,7 +203,7 @@ def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, us
|
||||
position,
|
||||
None)
|
||||
else:
|
||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position,chunk[:length])
|
||||
Profile.get_instance().incoming_chunk(friend_number, file_number, position, chunk[:length])
|
||||
|
||||
|
||||
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
||||
@ -216,11 +225,11 @@ def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||
Friend cancelled, paused or resumed file transfer
|
||||
"""
|
||||
if file_control == TOX_FILE_CONTROL['CANCEL']:
|
||||
Profile.get_instance().cancel_transfer(friend_number, file_number, True)
|
||||
invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True)
|
||||
elif file_control == TOX_FILE_CONTROL['PAUSE']:
|
||||
Profile.get_instance().pause_transfer(friend_number, file_number, True)
|
||||
invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True)
|
||||
elif file_control == TOX_FILE_CONTROL['RESUME']:
|
||||
Profile.get_instance().resume_transfer(friend_number, file_number, True)
|
||||
invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - custom packets
|
||||
@ -270,7 +279,7 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud
|
||||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
print audio_samples_per_channel, audio_channels_count, rate
|
||||
# print audio_samples_per_channel, audio_channels_count, rate
|
||||
Profile.get_instance().call.chunk(
|
||||
''.join(chr(x) for x in samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||
audio_channels_count,
|
||||
@ -298,6 +307,7 @@ def init_callbacks(tox, window, tray):
|
||||
tox.callback_friend_status_message(friend_status_message, 0)
|
||||
tox.callback_friend_request(friend_request, 0)
|
||||
tox.callback_friend_typing(friend_typing, 0)
|
||||
tox.callback_friend_read_receipt(friend_read_receipt, 0)
|
||||
|
||||
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
||||
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
||||
|
@ -4,6 +4,7 @@ import threading
|
||||
import settings
|
||||
from toxav_enums import *
|
||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
||||
# TODO: add widget for call
|
||||
|
||||
CALL_TYPE = {
|
||||
'NONE': 0,
|
||||
|
113
src/contact.py
Normal file
@ -0,0 +1,113 @@
|
||||
import os
|
||||
from settings import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
|
||||
|
||||
class Contact(object):
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._widget.name.setText(name)
|
||||
self._widget.status_message.setText(status_message)
|
||||
self._tox_id = tox_id
|
||||
self.load_avatar()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = value.decode('utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = value.decode('utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
||||
pixmap.load(avatar_path)
|
||||
self._widget.avatar_label.setScaledContents(False)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
@ -1,6 +1,6 @@
|
||||
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||
from os.path import basename, getsize, exists
|
||||
from os import remove, rename
|
||||
from os.path import basename, getsize, exists, dirname
|
||||
from os import remove, rename, chdir
|
||||
from time import time, sleep
|
||||
from tox import Tox
|
||||
import settings
|
||||
@ -9,21 +9,35 @@ try:
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore
|
||||
|
||||
# TODO: threads!
|
||||
|
||||
|
||||
TOX_FILE_TRANSFER_STATE = {
|
||||
'RUNNING': 0,
|
||||
'PAUSED': 1,
|
||||
'CANCELED': 2,
|
||||
'PAUSED_BY_USER': 1,
|
||||
'CANCELLED': 2,
|
||||
'FINISHED': 3,
|
||||
'PAUSED_BY_FRIEND': 4
|
||||
'PAUSED_BY_FRIEND': 4,
|
||||
'INCOMING_NOT_STARTED': 5,
|
||||
'OUTGOING_NOT_STARTED': 6
|
||||
}
|
||||
|
||||
ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6)
|
||||
|
||||
PAUSED_FILE_TRANSFERS = (1, 4, 5, 6)
|
||||
|
||||
DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6)
|
||||
|
||||
SHOW_PROGRESS_BAR = (0, 1, 4)
|
||||
|
||||
ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png')
|
||||
|
||||
|
||||
class StateSignal(QtCore.QObject):
|
||||
try:
|
||||
signal = QtCore.Signal(int, float) # state and progress
|
||||
signal = QtCore.Signal(int, float, int) # state and progress
|
||||
except:
|
||||
signal = QtCore.pyqtSignal(int, float) # state and progress - pyqt4
|
||||
signal = QtCore.pyqtSignal(int, float, int) # state and progress - pyqt4
|
||||
|
||||
|
||||
class FileTransfer(QtCore.QObject):
|
||||
@ -38,7 +52,7 @@ class FileTransfer(QtCore.QObject):
|
||||
self._friend_number = friend_number
|
||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||
self._file_number = file_number
|
||||
self._creation_time = time()
|
||||
self._creation_time = None
|
||||
self._size = float(size)
|
||||
self._done = 0
|
||||
self._state_changed = StateSignal()
|
||||
@ -50,7 +64,12 @@ class FileTransfer(QtCore.QObject):
|
||||
self._state_changed.signal.connect(handler)
|
||||
|
||||
def signal(self):
|
||||
self._state_changed.signal.emit(self.state, self._done / self._size if self._size else 0)
|
||||
percentage = self._done / self._size if self._size else 0
|
||||
if self._creation_time is None or not percentage:
|
||||
t = -1
|
||||
else:
|
||||
t = ((time() - self._creation_time) / percentage) * (1 - percentage)
|
||||
self._state_changed.signal.emit(self.state, percentage, int(t))
|
||||
|
||||
def get_file_number(self):
|
||||
return self._file_number
|
||||
@ -62,14 +81,14 @@ class FileTransfer(QtCore.QObject):
|
||||
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
|
||||
def cancelled(self):
|
||||
if hasattr(self, '_file'):
|
||||
sleep(0.1)
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.state = TOX_FILE_TRANSFER_STATE['CANCELLED']
|
||||
self.signal()
|
||||
|
||||
def pause(self, by_friend):
|
||||
if not by_friend:
|
||||
@ -100,6 +119,7 @@ class SendTransfer(FileTransfer):
|
||||
else:
|
||||
size = 0
|
||||
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||
basename(path).encode('utf-8') if path else '')
|
||||
|
||||
@ -109,6 +129,8 @@ class SendTransfer(FileTransfer):
|
||||
:param position: start position in file
|
||||
:param size: chunk max size
|
||||
"""
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if size:
|
||||
self._file.seek(position)
|
||||
data = self._file.read(size)
|
||||
@ -119,7 +141,7 @@ class SendTransfer(FileTransfer):
|
||||
if hasattr(self, '_file'):
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
|
||||
|
||||
class SendAvatar(SendTransfer):
|
||||
@ -143,6 +165,7 @@ class SendFromBuffer(FileTransfer):
|
||||
|
||||
def __init__(self, tox, friend_number, data, file_name):
|
||||
super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data))
|
||||
self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
|
||||
self._data = data
|
||||
self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], len(data), None, file_name)
|
||||
|
||||
@ -150,14 +173,28 @@ class SendFromBuffer(FileTransfer):
|
||||
return self._data
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if size:
|
||||
data = self._data[position:position + size]
|
||||
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||
self._done += size
|
||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||
self.signal()
|
||||
else:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
|
||||
|
||||
class SendFromFileBuffer(SendTransfer):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(SendFromFileBuffer, self).__init__(*args)
|
||||
|
||||
def send_chunk(self, position, size):
|
||||
super(SendFromFileBuffer, self).send_chunk(position, size)
|
||||
if not size:
|
||||
chdir(dirname(self._path))
|
||||
remove(self._path)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Receive file
|
||||
@ -182,23 +219,24 @@ class ReceiveTransfer(FileTransfer):
|
||||
:param position: position in file to save data
|
||||
:param data: raw data (string)
|
||||
"""
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self._file.close()
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
else:
|
||||
data = ''.join(chr(x) for x in data)
|
||||
data = bytearray(data)
|
||||
if self._file_size < position:
|
||||
self._file.seek(0, 2)
|
||||
self._file.write('\0' * (position - self._file_size))
|
||||
self._file.seek(position)
|
||||
self._file.write(data)
|
||||
self._file.flush()
|
||||
l = len(data)
|
||||
if position + l > self._file_size:
|
||||
self._file_size = position + l
|
||||
self._done += l
|
||||
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||
self.signal()
|
||||
|
||||
|
||||
class ReceiveToBuffer(FileTransfer):
|
||||
@ -215,9 +253,11 @@ class ReceiveToBuffer(FileTransfer):
|
||||
return self._data
|
||||
|
||||
def write_chunk(self, position, data):
|
||||
if self._creation_time is None:
|
||||
self._creation_time = time()
|
||||
if data is None:
|
||||
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||
self._state_changed.signal.emit(self.state, 1)
|
||||
self.signal()
|
||||
else:
|
||||
data = ''.join(chr(x) for x in data)
|
||||
l = len(data)
|
||||
@ -266,6 +306,7 @@ class ReceiveAvatar(ReceiveTransfer):
|
||||
if self.state:
|
||||
avatar_path = self._path[:-4]
|
||||
if exists(avatar_path):
|
||||
chdir(dirname(avatar_path))
|
||||
remove(avatar_path)
|
||||
rename(self._path, avatar_path)
|
||||
|
||||
|
223
src/friend.py
Normal file
@ -0,0 +1,223 @@
|
||||
import contact
|
||||
from messages import *
|
||||
from history import *
|
||||
import util
|
||||
|
||||
|
||||
class Friend(contact.Contact):
|
||||
"""
|
||||
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, *args):
|
||||
"""
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
super(Friend, self).__init__(*args)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
self._receipts = 0
|
||||
self._curr_text = ''
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
data = self._message_getter.get(PAGE_SIZE)
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = map(lambda tupl: TextMessage(*tupl), data)
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
||||
return map(lambda x: x.get_data(), messages[-self._unsaved_messages:]) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr)
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return messages
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
def clear_corr(self):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
# don't delete data about active file transfer
|
||||
self._corr = filter(lambda x: x.get_type() in (2, 3) and x.get_status() >= 2, self._corr)
|
||||
self._unsaved_messages = 0
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def update_transfer_data(self, file_number, status, inline=None):
|
||||
"""
|
||||
Update status of active transfer and load inline if needed
|
||||
"""
|
||||
try:
|
||||
tr = filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
|
||||
self._corr)[0]
|
||||
tr.set_status(status)
|
||||
i = self._corr.index(tr)
|
||||
if inline: # inline was loaded
|
||||
self._corr.insert(i, inline)
|
||||
return i - len(self._corr)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_unsent_files(self):
|
||||
messages = filter(lambda x: type(x) is UnsentFile, self._corr)
|
||||
return messages
|
||||
|
||||
def clear_unsent_files(self):
|
||||
self._corr = filter(lambda x: type(x) is not UnsentFile, self._corr)
|
||||
|
||||
def delete_one_unsent_file(self, time):
|
||||
self._corr = filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
if not self._alias:
|
||||
super(Friend, self).set_name(value)
|
||||
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
@ -10,7 +10,8 @@ PAGE_SIZE = 42
|
||||
|
||||
MESSAGE_OWNER = {
|
||||
'ME': 0,
|
||||
'FRIEND': 1
|
||||
'FRIEND': 1,
|
||||
'NOT_SENT': 2
|
||||
}
|
||||
|
||||
|
||||
@ -22,12 +23,15 @@ class History(object):
|
||||
path = settings.ProfileHelper.get_path() + self._name + '.hstr'
|
||||
if os.path.exists(path):
|
||||
decr = LibToxEncryptSave.get_instance()
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
if decr.is_data_encrypted(data):
|
||||
data = decr.pass_decrypt(data)
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
try:
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
if decr.is_data_encrypted(data):
|
||||
data = decr.pass_decrypt(data)
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
except:
|
||||
os.remove(path)
|
||||
db = connect(name + '.hstr')
|
||||
cursor = db.cursor()
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS friends('
|
||||
@ -50,6 +54,9 @@ class History(object):
|
||||
new_path = directory + self._name + '.hstr'
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
encr = LibToxEncryptSave.get_instance()
|
||||
if encr.has_password():
|
||||
data = encr.pass_encrypt(data)
|
||||
with open(new_path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
|
||||
@ -110,6 +117,21 @@ class History(object):
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def update_messages(self, tox_id, unsent_time):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
try:
|
||||
cursor = db.cursor()
|
||||
cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 '
|
||||
'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;')
|
||||
db.commit()
|
||||
except:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
pass
|
||||
|
||||
def delete_messages(self, tox_id):
|
||||
chdir(settings.ProfileHelper.get_path())
|
||||
db = connect(self._name + '.hstr')
|
||||
|
BIN
src/images/audio_message.png
Executable file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/images/busy.png
Normal file
After Width: | Height: | Size: 329 B |
BIN
src/images/busy_notification.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
src/images/icon_new_messages.png
Executable file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/images/idle.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
src/images/idle_notification.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
src/images/menu.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/images/offline.png
Normal file
After Width: | Height: | Size: 159 B |
BIN
src/images/offline_notification.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
src/images/online.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
src/images/online_notification.png
Normal file
After Width: | Height: | Size: 351 B |
BIN
src/images/search.png
Executable file → Normal file
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
src/images/smiley.png
Executable file
After Width: | Height: | Size: 5.6 KiB |
BIN
src/images/spinner.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/images/sticker.png
Executable file
After Width: | Height: | Size: 94 KiB |
BIN
src/images/video_message.png
Executable file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/images/videocall.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
@ -4,75 +4,153 @@ try:
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import profile
|
||||
from file_transfers import TOX_FILE_TRANSFER_STATE
|
||||
from util import curr_directory, convert_time
|
||||
from messages import FILE_TRANSFER_MESSAGE_STATUS
|
||||
from widgets import DataLabel
|
||||
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
|
||||
from widgets import DataLabel, create_menu
|
||||
import cgi
|
||||
import smileys
|
||||
import settings
|
||||
|
||||
|
||||
class MessageEdit(QtGui.QTextEdit):
|
||||
class MessageEdit(QtGui.QTextBrowser):
|
||||
|
||||
def __init__(self, text, width, parent=None):
|
||||
def __init__(self, text, width, message_type, parent=None):
|
||||
super(MessageEdit, self).__init__(parent)
|
||||
self.urls = {}
|
||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
|
||||
self.document().setTextWidth(width)
|
||||
self.setPlainText(text)
|
||||
self.setOpenExternalLinks(True)
|
||||
self.setAcceptRichText(True)
|
||||
self.setOpenLinks(False)
|
||||
self.setSearchPaths([smileys.SmileyLoader.get_instance().get_smileys_path()])
|
||||
self.document().setDefaultStyleSheet('a { color: #306EFF; }')
|
||||
text = self.decoratedText(text)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
self.setHtml('<p style="color: #5CB3FF; font: italic; font-size: 20px;" >' + text + '</p>')
|
||||
else:
|
||||
self.setHtml(text)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPixelSize(14)
|
||||
font.setPixelSize(settings.Settings.get_instance()['message_font_size'])
|
||||
font.setBold(False)
|
||||
self.setFont(font)
|
||||
self.setFixedHeight(self.document().size().height())
|
||||
self.resize(width, self.document().size().height())
|
||||
self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse)
|
||||
self.anchorClicked.connect(self.on_anchor_clicked)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = create_menu(self.createStandardContextMenu(event.pos()))
|
||||
menu.popup(event.globalPos())
|
||||
menu.exec_(event.globalPos())
|
||||
del menu
|
||||
|
||||
def on_anchor_clicked(self, url):
|
||||
text = str(url.toString())
|
||||
if text.startswith('tox:'):
|
||||
import menu
|
||||
self.add_contact = menu.AddContact(text[4:])
|
||||
self.add_contact.show()
|
||||
else:
|
||||
QtGui.QDesktopServices.openUrl(url)
|
||||
self.clearFocus()
|
||||
|
||||
def addAnimation(self, url, fileName):
|
||||
movie = QtGui.QMovie(self)
|
||||
movie.setFileName(fileName)
|
||||
self.urls[movie] = url
|
||||
movie.frameChanged[int].connect(lambda x: self.animate(movie))
|
||||
movie.start()
|
||||
|
||||
def animate(self, movie):
|
||||
self.document().addResource(QtGui.QTextDocument.ImageResource,
|
||||
self.urls[movie],
|
||||
movie.currentPixmap())
|
||||
self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth())
|
||||
|
||||
def decoratedText(self, text):
|
||||
text = cgi.escape(text) # replace < and >
|
||||
exp = QtCore.QRegExp(
|
||||
'('
|
||||
'(?:\\b)((www\\.)|(http[s]?|ftp)://)'
|
||||
'\\w+\\S+)'
|
||||
'|(?:\\b)(file:///)([\\S| ]*)'
|
||||
'|(?:\\b)(tox:[a-zA-Z\\d]{76}$)'
|
||||
'|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)'
|
||||
'|(?:\\b)(tox:\\S+@\\S+)')
|
||||
offset = exp.indexIn(text, 0)
|
||||
while offset != -1: # add links
|
||||
url = exp.cap()
|
||||
if exp.cap(2) == 'www.':
|
||||
html = '<a href="http://{0}">{0}</a>'.format(url)
|
||||
else:
|
||||
html = '<a href="{0}">{0}</a>'.format(url)
|
||||
text = text[:offset] + html + text[offset + len(exp.cap()):]
|
||||
offset += len(html)
|
||||
offset = exp.indexIn(text, offset)
|
||||
arr = text.split('\n')
|
||||
for i in range(len(arr)): # quotes
|
||||
if arr[i].startswith('>'):
|
||||
arr[i] = '<font color="green"><b>' + arr[i][4:] + '</b></font>'
|
||||
text = '<br>'.join(arr)
|
||||
text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys
|
||||
return text
|
||||
|
||||
|
||||
class MessageItem(QtGui.QWidget):
|
||||
"""
|
||||
Message in messages list
|
||||
"""
|
||||
def __init__(self, text, time, user='', message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
||||
def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(0, 2, 95, 20))
|
||||
self.name.setGeometry(QtCore.QRect(2, 2, 95, 20))
|
||||
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(11)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setObjectName("name")
|
||||
self.name.setText(user)
|
||||
|
||||
self.time = QtGui.QLabel(self)
|
||||
self.time.setGeometry(QtCore.QRect(parent.width() - 50, 0, 50, 25))
|
||||
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)
|
||||
self.time.setObjectName("time")
|
||||
self.time.setText(time)
|
||||
|
||||
self.message = MessageEdit(text, parent.width() - 150, self)
|
||||
if not sent:
|
||||
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
||||
self.time.setMovie(movie)
|
||||
movie.start()
|
||||
self.t = time
|
||||
else:
|
||||
self.time.setText(time)
|
||||
|
||||
self.message = MessageEdit(text, parent.width() - 150, message_type, self)
|
||||
if message_type != TOX_MESSAGE_TYPE['NORMAL']:
|
||||
self.name.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.time.setStyleSheet("QLabel { color: #5CB3FF; }")
|
||||
self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 150, self.message.height()))
|
||||
self.setFixedHeight(self.message.height())
|
||||
|
||||
if message_type == TOX_MESSAGE_TYPE['ACTION']:
|
||||
self.name.setStyleSheet("QLabel { color: #4169E1; }")
|
||||
self.message.setStyleSheet("QTextEdit { color: #4169E1; }")
|
||||
else:
|
||||
if text[0] == '>':
|
||||
self.message.setStyleSheet("QTextEdit { color: green; }")
|
||||
if text[-1] == '<':
|
||||
self.message.setStyleSheet("QTextEdit { color: red; }")
|
||||
def mark_as_sent(self):
|
||||
if hasattr(self, 't'):
|
||||
self.time.setText(self.t)
|
||||
del self.t
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ContactItem(QtGui.QWidget):
|
||||
"""
|
||||
Contact in friends list
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.setBaseSize(QtCore.QSize(250, 70))
|
||||
@ -80,22 +158,21 @@ class ContactItem(QtGui.QWidget):
|
||||
self.avatar_label.setGeometry(QtCore.QRect(3, 3, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
self.name = DataLabel(self)
|
||||
self.name.setGeometry(QtCore.QRect(70, 10, 160, 25))
|
||||
self.name.setGeometry(QtCore.QRect(75, 10, 150, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.name.setFont(font)
|
||||
self.name.setObjectName("name")
|
||||
self.status_message = DataLabel(self)
|
||||
self.status_message.setGeometry(QtCore.QRect(70, 30, 180, 20))
|
||||
self.status_message.setGeometry(QtCore.QRect(75, 30, 170, 20))
|
||||
font.setPointSize(10)
|
||||
font.setBold(False)
|
||||
self.status_message.setFont(font)
|
||||
self.status_message.setObjectName("status_message")
|
||||
self.connection_status = StatusCircle(self)
|
||||
self.connection_status.setGeometry(QtCore.QRect(220, 5, 32, 32))
|
||||
self.connection_status.setObjectName("connection_status")
|
||||
self.connection_status.setGeometry(QtCore.QRect(230, 5, 32, 32))
|
||||
self.messages = UnreadMessagesCount(self)
|
||||
self.messages.setGeometry(QtCore.QRect(52, 50, 30, 20))
|
||||
|
||||
|
||||
class StatusCircle(QtGui.QWidget):
|
||||
@ -105,36 +182,57 @@ class StatusCircle(QtGui.QWidget):
|
||||
def __init__(self, parent):
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.setGeometry(0, 0, 32, 32)
|
||||
self.data = None
|
||||
self.messages = False
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||
self.unread = False
|
||||
|
||||
def paintEvent(self, event):
|
||||
paint = QtGui.QPainter()
|
||||
paint.begin(self)
|
||||
paint.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
k = 16
|
||||
rad_x = rad_y = 5
|
||||
if self.data is None:
|
||||
color = QtCore.Qt.transparent
|
||||
def update(self, status, unread_messages=None):
|
||||
if unread_messages is None:
|
||||
unread_messages = self.unread
|
||||
else:
|
||||
if self.data == TOX_USER_STATUS['NONE']:
|
||||
color = QtGui.QColor(50, 205, 50)
|
||||
elif self.data == TOX_USER_STATUS['AWAY']:
|
||||
color = QtGui.QColor(255, 200, 50)
|
||||
else: # self.data == TOX_USER_STATUS['BUSY']:
|
||||
color = QtGui.QColor(255, 50, 0)
|
||||
self.unread = unread_messages
|
||||
if status == TOX_USER_STATUS['NONE']:
|
||||
name = 'online'
|
||||
elif status == TOX_USER_STATUS['AWAY']:
|
||||
name = 'idle'
|
||||
elif status == TOX_USER_STATUS['BUSY']:
|
||||
name = 'busy'
|
||||
else:
|
||||
name = 'offline'
|
||||
if unread_messages:
|
||||
name += '_notification'
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 32, 32))
|
||||
else:
|
||||
self.label.setGeometry(QtCore.QRect(2, 0, 32, 32))
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name))
|
||||
self.label.setPixmap(pixmap)
|
||||
|
||||
paint.setPen(color)
|
||||
center = QtCore.QPoint(k, k)
|
||||
paint.setBrush(color)
|
||||
paint.drawEllipse(center, rad_x, rad_y)
|
||||
if self.messages:
|
||||
if color == QtCore.Qt.transparent:
|
||||
color = QtCore.Qt.darkRed
|
||||
paint.setBrush(QtCore.Qt.transparent)
|
||||
paint.setPen(color)
|
||||
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
||||
paint.end()
|
||||
|
||||
class UnreadMessagesCount(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(UnreadMessagesCount, self).__init__(parent)
|
||||
self.resize(30, 20)
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(0, 0, 30, 20))
|
||||
self.label.setVisible(False)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter)
|
||||
color = settings.Settings.get_instance()['unread_color']
|
||||
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||
|
||||
def update(self, messages_count):
|
||||
color = settings.Settings.get_instance()['unread_color']
|
||||
self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }')
|
||||
if messages_count:
|
||||
self.label.setVisible(True)
|
||||
self.label.setText(str(messages_count))
|
||||
else:
|
||||
self.label.setVisible(False)
|
||||
|
||||
|
||||
class FileTransferItem(QtGui.QListWidget):
|
||||
@ -143,13 +241,12 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
|
||||
QtGui.QListWidget.__init__(self, parent)
|
||||
self.resize(QtCore.QSize(width, 34))
|
||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||
elif state in (FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED'], FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']):
|
||||
elif state in PAUSED_FILE_TRANSFERS:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||
else:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
|
||||
self.state = state
|
||||
|
||||
self.name = DataLabel(self)
|
||||
@ -175,18 +272,18 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.cancel.setIcon(icon)
|
||||
self.cancel.setIconSize(QtCore.QSize(30, 30))
|
||||
self.cancel.setVisible(state > 1)
|
||||
self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS)
|
||||
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
||||
self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}')
|
||||
|
||||
self.accept_or_pause = QtGui.QPushButton(self)
|
||||
self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30))
|
||||
if state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
||||
if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||
self.accept_or_pause.setVisible(True)
|
||||
self.button_update('accept')
|
||||
elif state in (0, 1, 5):
|
||||
elif state in DO_NOT_SHOW_ACCEPT_BUTTON:
|
||||
self.accept_or_pause.setVisible(False)
|
||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # setup for continue
|
||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue
|
||||
self.accept_or_pause.setVisible(True)
|
||||
self.button_update('resume')
|
||||
else: # pause
|
||||
@ -200,11 +297,10 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20))
|
||||
self.pb.setValue(0)
|
||||
self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }')
|
||||
if state < 2:
|
||||
self.pb.setVisible(False)
|
||||
self.pb.setVisible(state in SHOW_PROGRESS_BAR)
|
||||
|
||||
self.file_name = DataLabel(self)
|
||||
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 400, 20))
|
||||
self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20))
|
||||
font.setPointSize(12)
|
||||
self.file_name.setFont(font)
|
||||
file_size = size / 1024
|
||||
@ -216,7 +312,13 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
file_size = '{}KB'.format(file_size)
|
||||
file_data = u'{} {}'.format(file_size, file_name)
|
||||
self.file_name.setText(file_data)
|
||||
self.file_name.setToolTip(file_name)
|
||||
self.saved_name = file_name
|
||||
self.time_left = QtGui.QLabel(self)
|
||||
self.time_left.setGeometry(QtCore.QRect(width - 87, 7, 30, 20))
|
||||
font.setPointSize(10)
|
||||
self.time_left.setFont(font)
|
||||
self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.paused = False
|
||||
|
||||
@ -229,23 +331,24 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.pb.setVisible(False)
|
||||
|
||||
def accept_or_pause_transfer(self, friend_number, file_number, size):
|
||||
if self.state == FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED']:
|
||||
if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']:
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||
QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8),
|
||||
curr_directory(),
|
||||
QtGui.QFileDialog.ShowDirsOnly)
|
||||
self.pb.setVisible(True)
|
||||
if directory:
|
||||
pr = profile.Profile.get_instance()
|
||||
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
||||
self.button_update('pause')
|
||||
elif self.state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']: # resume
|
||||
elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume
|
||||
self.paused = False
|
||||
profile.Profile.get_instance().resume_transfer(friend_number, file_number)
|
||||
self.button_update('pause')
|
||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['OUTGOING']
|
||||
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||
else: # pause
|
||||
self.paused = True
|
||||
self.state = FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']
|
||||
self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
|
||||
profile.Profile.get_instance().pause_transfer(friend_number, file_number)
|
||||
self.button_update('resume')
|
||||
self.accept_or_pause.clearFocus()
|
||||
@ -256,63 +359,125 @@ class FileTransferItem(QtGui.QListWidget):
|
||||
self.accept_or_pause.setIcon(icon)
|
||||
self.accept_or_pause.setIconSize(QtCore.QSize(30, 30))
|
||||
|
||||
def convert(self, state):
|
||||
# convert TOX_FILE_TRANSFER_STATE to FILE_TRANSFER_MESSAGE_STATUS
|
||||
d = {0: 2, 1: 6, 2: 1, 3: 0, 4: 5}
|
||||
return d[state]
|
||||
|
||||
@QtCore.Slot(int, float)
|
||||
def update(self, state, progress):
|
||||
@QtCore.Slot(int, float, int)
|
||||
def update(self, state, progress, time):
|
||||
self.pb.setValue(int(progress * 100))
|
||||
state = self.convert(state)
|
||||
if time + 1:
|
||||
m, s = divmod(time, 60)
|
||||
self.time_left.setText('{0:02d}:{1:02d}'.format(m, s))
|
||||
if self.state != state:
|
||||
if state == FILE_TRANSFER_MESSAGE_STATUS['CANCELLED']:
|
||||
if state == TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #B40404; }')
|
||||
self.cancel.setVisible(False)
|
||||
self.accept_or_pause.setVisible(False)
|
||||
self.pb.setVisible(False)
|
||||
self.state = state
|
||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['FINISHED']:
|
||||
self.time_left.setVisible(False)
|
||||
elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
|
||||
self.accept_or_pause.setVisible(False)
|
||||
self.pb.setVisible(False)
|
||||
self.cancel.setVisible(False)
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
self.state = state
|
||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND']:
|
||||
self.time_left.setVisible(False)
|
||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']:
|
||||
self.accept_or_pause.setVisible(False)
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||
self.state = state
|
||||
elif state == FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']:
|
||||
self.time_left.setVisible(False)
|
||||
elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']:
|
||||
self.button_update('resume') # setup button continue
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
self.state = state
|
||||
self.time_left.setVisible(False)
|
||||
elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']:
|
||||
self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }')
|
||||
self.accept_or_pause.setVisible(False)
|
||||
self.time_left.setVisible(False)
|
||||
self.pb.setVisible(False)
|
||||
elif not self.paused: # active
|
||||
self.pb.setVisible(True)
|
||||
self.accept_or_pause.setVisible(True) # setup to pause
|
||||
self.button_update('pause')
|
||||
self.setStyleSheet('QListWidget { border: 1px solid green; }')
|
||||
self.state = state
|
||||
self.time_left.setVisible(True)
|
||||
|
||||
def mark_as_sent(self):
|
||||
return False
|
||||
|
||||
|
||||
class InlineImageItem(QtGui.QWidget):
|
||||
class UnsentFileItem(FileTransferItem):
|
||||
|
||||
def __init__(self, data, width, parent=None):
|
||||
def __init__(self, file_name, size, user, time, width, parent=None):
|
||||
super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1,
|
||||
TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent)
|
||||
self._time = time
|
||||
self.pb.setVisible(False)
|
||||
movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif')
|
||||
self.time.setMovie(movie)
|
||||
movie.start()
|
||||
|
||||
QtGui.QWidget.__init__(self, parent)
|
||||
self.resize(QtCore.QSize(width, 500))
|
||||
def cancel_transfer(self, *args):
|
||||
pr = profile.Profile.get_instance()
|
||||
pr.cancel_not_started_transfer(self._time)
|
||||
|
||||
|
||||
class InlineImageItem(QtGui.QScrollArea):
|
||||
|
||||
def __init__(self, data, width, elem):
|
||||
|
||||
QtGui.QScrollArea.__init__(self)
|
||||
self.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self._elem = elem
|
||||
self._image_label = QtGui.QLabel(self)
|
||||
self._image_label.raise_()
|
||||
self._image_label.setAutoFillBackground(True)
|
||||
self.setWidget(self._image_label)
|
||||
self._image_label.setScaledContents(False)
|
||||
self.pixmap = QtGui.QPixmap()
|
||||
self.pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
|
||||
max_size = width - 40
|
||||
if self.pixmap.width() <= max_size:
|
||||
self._image_label.setPixmap(self.pixmap)
|
||||
self.resize(QtCore.QSize(max_size, self.pixmap.height()))
|
||||
self._pixmap = QtGui.QPixmap()
|
||||
self._pixmap.loadFromData(QtCore.QByteArray(data), "PNG")
|
||||
self._max_size = width - 30
|
||||
self._resize_needed = not (self._pixmap.width() <= self._max_size)
|
||||
self._full_size = not self._resize_needed
|
||||
if not self._resize_needed:
|
||||
self._image_label.setPixmap(self._pixmap)
|
||||
self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5))
|
||||
self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
|
||||
else:
|
||||
pixmap = self.pixmap.scaled(max_size, max_size, QtCore.Qt.KeepAspectRatio)
|
||||
pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
|
||||
self._image_label.setPixmap(pixmap)
|
||||
self.resize(QtCore.QSize(max_size, pixmap.height()))
|
||||
self.resize(QtCore.QSize(self._max_size + 5, pixmap.height()))
|
||||
self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height())
|
||||
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline
|
||||
if self._full_size:
|
||||
pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio)
|
||||
self._image_label.setPixmap(pixmap)
|
||||
self.resize(QtCore.QSize(self._max_size, pixmap.height()))
|
||||
self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height())
|
||||
else:
|
||||
self._image_label.setPixmap(self._pixmap)
|
||||
self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17))
|
||||
self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height())
|
||||
self._full_size = not self._full_size
|
||||
self._elem.setSizeHint(QtCore.QSize(self.width(), self.height()))
|
||||
elif event.button() == QtCore.Qt.RightButton: # save inline
|
||||
directory = QtGui.QFileDialog.getExistingDirectory(self,
|
||||
QtGui.QApplication.translate("MainWindow",
|
||||
'Choose folder', None,
|
||||
QtGui.QApplication.UnicodeUTF8),
|
||||
curr_directory(),
|
||||
QtGui.QFileDialog.ShowDirsOnly)
|
||||
if directory:
|
||||
fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png')
|
||||
self._pixmap.save(fl, 'PNG')
|
||||
|
||||
return False
|
||||
|
||||
def mark_as_sent(self):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@ except ImportError:
|
||||
from widgets import *
|
||||
|
||||
|
||||
class NickEdit(QtGui.QPlainTextEdit):
|
||||
class NickEdit(LineEdit):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(NickEdit, self).__init__(parent)
|
||||
@ -71,6 +71,7 @@ class LoginScreen(CenteredWidget):
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
36
src/main.py
@ -9,7 +9,7 @@ from bootstrap import node_generator
|
||||
from mainscreen import MainWindow
|
||||
from profile import tox_factory
|
||||
from callbacks import init_callbacks
|
||||
from util import curr_directory, get_style
|
||||
from util import curr_directory
|
||||
import styles.style
|
||||
import locale
|
||||
import toxencryptsave
|
||||
@ -20,10 +20,17 @@ from plugin_support import PluginLoader
|
||||
|
||||
class Toxygen(object):
|
||||
|
||||
def __init__(self, path=None):
|
||||
def __init__(self, path_or_uri=None):
|
||||
super(Toxygen, self).__init__()
|
||||
self.tox = self.ms = self.init = self.mainloop = self.avloop = None
|
||||
self.path = path
|
||||
if path_or_uri is None:
|
||||
self.uri = self.path = None
|
||||
elif path_or_uri.startswith('tox:'):
|
||||
self.path = None
|
||||
self.uri = path_or_uri[4:]
|
||||
else:
|
||||
self.path = path_or_uri
|
||||
self.uri = None
|
||||
|
||||
def enter_pass(self, data):
|
||||
"""
|
||||
@ -64,13 +71,13 @@ class Toxygen(object):
|
||||
self.tox = tox_factory(data, settings)
|
||||
else:
|
||||
auto_profile = Settings.get_auto_profile()
|
||||
if not auto_profile:
|
||||
if not auto_profile[0]:
|
||||
# show login screen if default profile not found
|
||||
current_locale = QtCore.QLocale()
|
||||
curr_lang = current_locale.languageToString(current_locale.language())
|
||||
langs = Settings.supported_languages()
|
||||
if curr_lang in map(lambda x: x[0], langs):
|
||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
||||
if curr_lang in langs:
|
||||
lang_path = langs[curr_lang]
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(curr_directory() + '/translations/' + lang_path)
|
||||
app.installTranslator(translator)
|
||||
@ -112,7 +119,7 @@ class Toxygen(object):
|
||||
settings = Settings(name)
|
||||
self.tox = tox_factory(data, settings)
|
||||
|
||||
if ProfileHelper.is_active_profile(path, name): # profile is in use
|
||||
if Settings.is_active_profile(path, name): # profile is in use
|
||||
reply = QtGui.QMessageBox.question(None,
|
||||
'Profile {}'.format(name),
|
||||
QtGui.QApplication.translate("login", 'Looks like other instance of Toxygen uses this profile! Continue?', None, QtGui.QApplication.UnicodeUTF8),
|
||||
@ -123,18 +130,18 @@ class Toxygen(object):
|
||||
else:
|
||||
settings.set_active_profile()
|
||||
|
||||
lang = filter(lambda x: x[0] == settings['language'], Settings.supported_languages())[0]
|
||||
lang = Settings.supported_languages()[settings['language']]
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(curr_directory() + '/translations/' + lang[1])
|
||||
translator.load(curr_directory() + '/translations/' + lang)
|
||||
app.installTranslator(translator)
|
||||
app.translator = translator
|
||||
|
||||
self.ms = MainWindow(self.tox, self.reset)
|
||||
|
||||
# tray icon
|
||||
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
self.tray.setObjectName('tray')
|
||||
|
||||
self.ms = MainWindow(self.tox, self.reset, self.tray)
|
||||
|
||||
class Menu(QtGui.QMenu):
|
||||
|
||||
def newStatus(self, status):
|
||||
@ -186,11 +193,11 @@ class Toxygen(object):
|
||||
sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0))
|
||||
sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1))
|
||||
sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2))
|
||||
|
||||
self.tray.setContextMenu(m)
|
||||
self.tray.show()
|
||||
|
||||
self.ms.show()
|
||||
QtGui.QApplication.setStyle(get_style(settings['theme'])) # set application style
|
||||
|
||||
plugin_helper = PluginLoader(self.tox, settings) # plugin support
|
||||
plugin_helper.load()
|
||||
@ -205,6 +212,9 @@ class Toxygen(object):
|
||||
self.avloop = self.ToxAVIterateThread(self.tox.AV)
|
||||
self.avloop.start()
|
||||
|
||||
if self.uri is not None:
|
||||
self.ms.add_contact(self.uri)
|
||||
|
||||
app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()"))
|
||||
app.exec_()
|
||||
self.init.stop = True
|
||||
@ -336,6 +346,6 @@ class Toxygen(object):
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 1:
|
||||
toxygen = Toxygen()
|
||||
else: # path to profile
|
||||
else: # path to profile or tox: uri
|
||||
toxygen = Toxygen(sys.argv[1])
|
||||
toxygen.main()
|
||||
|
@ -3,43 +3,18 @@
|
||||
from menu import *
|
||||
from profile import *
|
||||
from list_items import *
|
||||
from widgets import QRightClickButton
|
||||
from widgets import MultilineEdit, LineEdit
|
||||
import plugin_support
|
||||
|
||||
|
||||
class MessageArea(QtGui.QPlainTextEdit):
|
||||
|
||||
def __init__(self, parent, form):
|
||||
super(MessageArea, self).__init__(parent)
|
||||
self.parent = form
|
||||
self.timer = QtCore.QTimer(self)
|
||||
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Return:
|
||||
modifiers = event.modifiers()
|
||||
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
||||
self.appendPlainText('')
|
||||
else:
|
||||
if self.timer.isActive():
|
||||
self.timer.stop()
|
||||
self.parent.profile.send_typing(False)
|
||||
self.parent.send_message()
|
||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
||||
else:
|
||||
self.parent.profile.send_typing(True)
|
||||
if self.timer.isActive():
|
||||
self.timer.stop()
|
||||
self.timer.start(5000)
|
||||
super(MessageArea, self).keyPressEvent(event)
|
||||
from mainscreen_widgets import *
|
||||
|
||||
|
||||
class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def __init__(self, tox, reset):
|
||||
def __init__(self, tox, reset, tray):
|
||||
super(MainWindow, self).__init__()
|
||||
self.reset = reset
|
||||
self.tray = tray
|
||||
self.setAcceptDrops(True)
|
||||
self.initUI(tox)
|
||||
|
||||
def setup_menu(self, MainWindow):
|
||||
@ -105,6 +80,11 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def languageChange(self, *args, **kwargs):
|
||||
self.retranslateUi()
|
||||
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.WindowActivate:
|
||||
self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png'))
|
||||
return super(MainWindow, self).event(event)
|
||||
|
||||
def retranslateUi(self):
|
||||
self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -121,8 +101,6 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.online_contacts.clear()
|
||||
@ -131,7 +109,6 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.online_contacts.setCurrentIndex(int(Settings.get_instance()['show_online_friends']))
|
||||
|
||||
def setup_right_bottom(self, Form):
|
||||
Form.setObjectName("right_bottom")
|
||||
Form.resize(650, 60)
|
||||
self.messageEdit = MessageArea(Form, self)
|
||||
self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55))
|
||||
@ -140,68 +117,58 @@ class MainWindow(QtGui.QMainWindow):
|
||||
font.setPointSize(10)
|
||||
self.messageEdit.setFont(font)
|
||||
|
||||
self.screenshotButton = QRightClickButton(Form)
|
||||
self.screenshotButton.setGeometry(QtCore.QRect(455, 3, 55, 55))
|
||||
self.screenshotButton.setObjectName("screenshotButton")
|
||||
|
||||
self.fileTransferButton = QtGui.QPushButton(Form)
|
||||
self.fileTransferButton.setGeometry(QtCore.QRect(510, 3, 55, 55))
|
||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
||||
|
||||
self.sendMessageButton = QtGui.QPushButton(Form)
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55))
|
||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
||||
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/send.png')
|
||||
self.menuButton = MenuButton(Form, self.show_menu)
|
||||
self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55)))
|
||||
|
||||
pixmap = QtGui.QPixmap('send.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.sendMessageButton.setIcon(icon)
|
||||
self.sendMessageButton.setIconSize(QtCore.QSize(45, 60))
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/file.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.fileTransferButton.setIcon(icon)
|
||||
self.fileTransferButton.setIconSize(QtCore.QSize(40, 40))
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/screenshot.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.screenshotButton.setIcon(icon)
|
||||
self.screenshotButton.setIconSize(QtCore.QSize(40, 60))
|
||||
|
||||
self.fileTransferButton.clicked.connect(self.send_file)
|
||||
self.screenshotButton.clicked.connect(self.send_screenshot)
|
||||
pixmap = QtGui.QPixmap('menu.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.menuButton.setIcon(icon)
|
||||
self.menuButton.setIconSize(QtCore.QSize(40, 40))
|
||||
|
||||
self.sendMessageButton.clicked.connect(self.send_message)
|
||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: self.send_screenshot(True))
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def setup_left_center_menu(self, Form):
|
||||
Form.resize(270, 25)
|
||||
self.search_label = QtGui.QLabel(Form)
|
||||
self.search_label.setGeometry(QtCore.QRect(3, 0, 25, 25))
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(25, 25))
|
||||
self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20))
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.load(curr_directory() + '/images/search.png')
|
||||
self.search_label.setScaledContents(False)
|
||||
self.search_label.setPixmap(pixmap.scaled(25, 25, QtCore.Qt.KeepAspectRatio))
|
||||
self.search_label.setPixmap(pixmap)
|
||||
|
||||
self.contact_name = QtGui.QLineEdit(Form)
|
||||
self.contact_name.setGeometry(QtCore.QRect(30, 0, 120, 25))
|
||||
self.contact_name = LineEdit(Form)
|
||||
self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25))
|
||||
self.contact_name.setObjectName("contact_name")
|
||||
self.contact_name.textChanged.connect(self.filtering)
|
||||
|
||||
self.online_contacts = QtGui.QComboBox(Form)
|
||||
self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25))
|
||||
self.online_contacts.activated[int].connect(lambda x: self.filtering())
|
||||
self.search_label.raise_()
|
||||
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def setup_left_top(self, Form):
|
||||
Form.setObjectName("left_top")
|
||||
Form.setCursor(QtCore.Qt.PointingHandCursor)
|
||||
Form.setMinimumSize(QtCore.QSize(250, 80))
|
||||
Form.setMaximumSize(QtCore.QSize(250, 80))
|
||||
Form.setBaseSize(QtCore.QSize(250, 80))
|
||||
Form.setMinimumSize(QtCore.QSize(270, 100))
|
||||
Form.setMaximumSize(QtCore.QSize(270, 100))
|
||||
Form.setBaseSize(QtCore.QSize(270, 100))
|
||||
self.avatar_label = Form.avatar_label = QtGui.QLabel(Form)
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 15, 64, 64))
|
||||
self.avatar_label.setGeometry(QtCore.QRect(5, 30, 64, 64))
|
||||
self.avatar_label.setScaledContents(True)
|
||||
self.name = Form.name = DataLabel(Form)
|
||||
Form.name.setGeometry(QtCore.QRect(80, 25, 150, 25))
|
||||
Form.name.setGeometry(QtCore.QRect(75, 40, 150, 25))
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("Times New Roman")
|
||||
font.setPointSize(14)
|
||||
@ -209,16 +176,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.name.setFont(font)
|
||||
Form.name.setObjectName("name")
|
||||
self.status_message = Form.status_message = DataLabel(Form)
|
||||
Form.status_message.setGeometry(QtCore.QRect(80, 55, 170, 20))
|
||||
Form.status_message.setGeometry(QtCore.QRect(75, 60, 170, 25))
|
||||
font.setPointSize(12)
|
||||
font.setBold(False)
|
||||
Form.status_message.setFont(font)
|
||||
Form.status_message.setObjectName("status_message")
|
||||
self.connection_status = Form.connection_status = StatusCircle(self)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 29, 64, 64))
|
||||
Form.connection_status.setMinimumSize(QtCore.QSize(32, 32))
|
||||
Form.connection_status.setMaximumSize(QtCore.QSize(32, 32))
|
||||
Form.connection_status.setBaseSize(QtCore.QSize(32, 32))
|
||||
self.connection_status = Form.connection_status = StatusCircle(Form)
|
||||
Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32))
|
||||
self.avatar_label.mouseReleaseEvent = self.profile_settings
|
||||
self.status_message.mouseReleaseEvent = self.profile_settings
|
||||
self.name.mouseReleaseEvent = self.profile_settings
|
||||
@ -226,10 +190,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||
Form.connection_status.setObjectName("connection_status")
|
||||
|
||||
def setup_right_top(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(650, 80)
|
||||
Form.resize(650, 100)
|
||||
self.account_avatar = QtGui.QLabel(Form)
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 17, 64, 64))
|
||||
self.account_avatar.setGeometry(QtCore.QRect(10, 30, 64, 64))
|
||||
self.account_avatar.setScaledContents(True)
|
||||
self.account_name = DataLabel(Form)
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 25, 400, 25))
|
||||
@ -250,10 +213,14 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.callButton = QtGui.QPushButton(Form)
|
||||
self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
self.callButton.setObjectName("callButton")
|
||||
self.callButton.clicked.connect(self.call)
|
||||
self.callButton.clicked.connect(lambda: self.profile.call_click(True))
|
||||
self.videocallButton = QtGui.QPushButton(Form)
|
||||
self.videocallButton.setGeometry(QtCore.QRect(550, 30, 50, 50))
|
||||
self.videocallButton.setObjectName("videocallButton")
|
||||
self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True))
|
||||
self.update_call_state('call')
|
||||
self.typing = QtGui.QLabel(Form)
|
||||
self.typing.setGeometry(QtCore.QRect(500, 40, 50, 30))
|
||||
self.typing.setGeometry(QtCore.QRect(500, 50, 50, 30))
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(50, 30))
|
||||
pixmap.load(curr_directory() + '/images/typing.png')
|
||||
self.typing.setScaledContents(False)
|
||||
@ -275,6 +242,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.messages = QtGui.QListWidget(widget)
|
||||
self.messages.setGeometry(0, 0, 620, 310)
|
||||
self.messages.setObjectName("messages")
|
||||
self.messages.setSpacing(1)
|
||||
self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.messages.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
@ -288,37 +256,50 @@ class MainWindow(QtGui.QMainWindow):
|
||||
|
||||
def initUI(self, tox):
|
||||
self.setMinimumSize(920, 500)
|
||||
self.setGeometry(400, 400, 920, 500)
|
||||
s = Settings.get_instance()
|
||||
self.setGeometry(s['x'], s['y'], s['width'], s['height'])
|
||||
self.setWindowTitle('Toxygen')
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
main = QtGui.QWidget()
|
||||
grid = QtGui.QGridLayout()
|
||||
search = QtGui.QWidget()
|
||||
self.setup_left_center_menu(search)
|
||||
grid.addWidget(search, 1, 0)
|
||||
name = QtGui.QWidget()
|
||||
self.setup_left_top(name)
|
||||
grid.addWidget(name, 0, 0)
|
||||
messages = QtGui.QWidget()
|
||||
self.setup_right_center(messages)
|
||||
grid.addWidget(messages, 1, 1, 2, 1)
|
||||
info = QtGui.QWidget()
|
||||
self.setup_right_top(info)
|
||||
grid.addWidget(info, 0, 1)
|
||||
message_buttons = QtGui.QWidget()
|
||||
self.setup_right_bottom(message_buttons)
|
||||
grid.addWidget(message_buttons, 3, 1)
|
||||
main_list = QtGui.QWidget()
|
||||
messages = QtGui.QWidget()
|
||||
message_buttons = QtGui.QWidget()
|
||||
self.setup_left_center_menu(search)
|
||||
self.setup_left_top(name)
|
||||
self.setup_right_center(messages)
|
||||
self.setup_right_top(info)
|
||||
self.setup_right_bottom(message_buttons)
|
||||
self.setup_left_center(main_list)
|
||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
||||
grid.setColumnMinimumWidth(1, 500)
|
||||
grid.setColumnMinimumWidth(0, 270)
|
||||
grid.setRowMinimumHeight(0, 82)
|
||||
if not Settings.get_instance()['mirror_mode']:
|
||||
grid.addWidget(search, 1, 0)
|
||||
grid.addWidget(name, 0, 0)
|
||||
grid.addWidget(messages, 1, 1, 2, 1)
|
||||
grid.addWidget(info, 0, 1)
|
||||
grid.addWidget(message_buttons, 3, 1)
|
||||
grid.addWidget(main_list, 2, 0, 2, 1)
|
||||
grid.setColumnMinimumWidth(1, 500)
|
||||
grid.setColumnMinimumWidth(0, 270)
|
||||
else:
|
||||
grid.addWidget(search, 1, 1)
|
||||
grid.addWidget(name, 0, 1)
|
||||
grid.addWidget(messages, 1, 0, 2, 1)
|
||||
grid.addWidget(info, 0, 0)
|
||||
grid.addWidget(message_buttons, 3, 0)
|
||||
grid.addWidget(main_list, 2, 1, 2, 1)
|
||||
grid.setColumnMinimumWidth(0, 500)
|
||||
grid.setColumnMinimumWidth(1, 270)
|
||||
grid.setSpacing(0)
|
||||
grid.setContentsMargins(0, 0, 0, 0)
|
||||
grid.setRowMinimumHeight(0, 100)
|
||||
grid.setRowMinimumHeight(1, 25)
|
||||
grid.setRowMinimumHeight(2, 410)
|
||||
grid.setRowMinimumHeight(3, 60)
|
||||
grid.setRowMinimumHeight(2, 320)
|
||||
grid.setRowMinimumHeight(3, 55)
|
||||
grid.setColumnStretch(1, 1)
|
||||
grid.setRowStretch(1, 1)
|
||||
grid.setRowStretch(2, 1)
|
||||
main.setLayout(grid)
|
||||
self.setCentralWidget(main)
|
||||
self.setup_menu(self)
|
||||
@ -331,21 +312,29 @@ class MainWindow(QtGui.QMainWindow):
|
||||
def closeEvent(self, *args, **kwargs):
|
||||
self.profile.save_history()
|
||||
self.profile.close()
|
||||
s = Settings.get_instance()
|
||||
s['x'] = self.pos().x()
|
||||
s['y'] = self.pos().y()
|
||||
s['width'] = self.width()
|
||||
s['height'] = self.height()
|
||||
s.save()
|
||||
QtGui.QApplication.closeAllWindows()
|
||||
|
||||
def resizeEvent(self, *args, **kwargs):
|
||||
self.messages.setGeometry(0, 0, self.width() - 300, self.height() - 172)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 142)
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 370, 20, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 420, 30, 50, 30))
|
||||
self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155)
|
||||
self.friends_list.setGeometry(0, 0, 270, self.height() - 125)
|
||||
|
||||
self.messageEdit.setGeometry(QtCore.QRect(120, 2, self.width() - 490, 55))
|
||||
self.screenshotButton.setGeometry(QtCore.QRect(0, 2, 55, 55))
|
||||
self.fileTransferButton.setGeometry(QtCore.QRect(60, 2, 55, 55))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 360, 2, 60, 55))
|
||||
self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 40, 50, 50))
|
||||
self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 40, 50, 50))
|
||||
self.typing.setGeometry(QtCore.QRect(self.width() - 450, 50, 50, 30))
|
||||
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 30, self.width() - 520, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 50, self.width() - 520, 25))
|
||||
self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55))
|
||||
self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55))
|
||||
self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55))
|
||||
|
||||
self.account_name.setGeometry(QtCore.QRect(100, 40, self.width() - 560, 25))
|
||||
self.account_status.setGeometry(QtCore.QRect(100, 60, self.width() - 560, 25))
|
||||
self.messageEdit.setFocus()
|
||||
self.profile.update()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
@ -374,8 +363,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.p_s = PluginsSettings()
|
||||
self.p_s.show()
|
||||
|
||||
def add_contact(self):
|
||||
self.a_c = AddContact()
|
||||
def add_contact(self, link=''):
|
||||
self.a_c = AddContact(link)
|
||||
self.a_c.show()
|
||||
|
||||
def profile_settings(self, *args):
|
||||
@ -398,6 +387,15 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.audio_s = AudioSettings()
|
||||
self.audio_s.show()
|
||||
|
||||
def show_menu(self):
|
||||
if not hasattr(self, 'menu'):
|
||||
self.menu = DropdownMenu(self)
|
||||
self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270,
|
||||
self.height() - 100,
|
||||
150,
|
||||
100))
|
||||
self.menu.show()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messages, calls and file transfers
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
@ -407,23 +405,40 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.profile.send_message(text)
|
||||
|
||||
def send_file(self):
|
||||
if self.profile.is_active_online(): # active friend exists and online
|
||||
choose_file = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
||||
choose = QtGui.QApplication.translate("MainWindow", choose_file, None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.menu.hide()
|
||||
if self.profile.active_friend + 1:
|
||||
choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8)
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, choose)
|
||||
if name[0]:
|
||||
self.profile.send_file(name[0])
|
||||
|
||||
def send_screenshot(self, hide=False):
|
||||
if self.profile.is_active_online(): # active friend exists and online
|
||||
self.menu.hide()
|
||||
if self.profile.active_friend + 1:
|
||||
self.sw = ScreenShotWindow(self)
|
||||
self.sw.show()
|
||||
if hide:
|
||||
self.hide()
|
||||
|
||||
def call(self):
|
||||
if self.profile.is_active_online(): # active friend exists and online
|
||||
self.profile.call_click(True)
|
||||
def send_smiley(self):
|
||||
self.menu.hide()
|
||||
if self.profile.active_friend + 1:
|
||||
self.smiley = SmileyWindow(self)
|
||||
self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||
self.y() + self.height() - 200,
|
||||
self.smiley.width(),
|
||||
self.smiley.height()))
|
||||
self.smiley.show()
|
||||
|
||||
def send_sticker(self):
|
||||
self.menu.hide()
|
||||
if self.profile.active_friend + 1:
|
||||
self.sticker = StickerWindow(self)
|
||||
self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(),
|
||||
self.y() + self.height() - 200,
|
||||
self.sticker.width(),
|
||||
self.sticker.height()))
|
||||
self.sticker.show()
|
||||
|
||||
def active_call(self):
|
||||
self.update_call_state('finish_call')
|
||||
@ -435,10 +450,16 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.update_call_state('call')
|
||||
|
||||
def update_call_state(self, fl):
|
||||
# TODO: do smth with video call button
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl))
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.callButton.setIcon(icon)
|
||||
self.callButton.setIconSize(QtCore.QSize(50, 50))
|
||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/videocall.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.videocallButton.setIcon(icon)
|
||||
self.videocallButton.setIconSize(QtCore.QSize(35, 35))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Functions which called when user open context menu in friends list
|
||||
@ -458,6 +479,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||
copy_key_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Copy public key', None, QtGui.QApplication.UnicodeUTF8))
|
||||
auto_accept_item = self.listMenu.addAction(auto)
|
||||
remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8))
|
||||
notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num)
|
||||
if len(submenu):
|
||||
plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -467,10 +490,26 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
||||
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
||||
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
||||
self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend))
|
||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||
self.listMenu.move(parent_position + pos)
|
||||
self.listMenu.show()
|
||||
|
||||
def show_note(self, friend):
|
||||
s = Settings.get_instance()
|
||||
note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else ''
|
||||
user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8)
|
||||
user = u'{} {}'.format(user, friend.name)
|
||||
|
||||
def save_note(text):
|
||||
if friend.tox_id in s['notes']:
|
||||
del s['notes'][friend.tox_id]
|
||||
if text:
|
||||
s['notes'][friend.tox_id] = text
|
||||
s.save()
|
||||
self.note = MultilineEdit(user, note, save_note)
|
||||
self.note.show()
|
||||
|
||||
def set_alias(self, num):
|
||||
self.profile.set_alias(num)
|
||||
|
||||
@ -503,71 +542,13 @@ class MainWindow(QtGui.QMainWindow):
|
||||
self.profile.set_active(num)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
x, y = event.x(), event.y()
|
||||
pos = self.connection_status.pos()
|
||||
if (pos.x() < x < pos.x() + 32) and (pos.y() < y < pos.y() + 32):
|
||||
x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y()
|
||||
if (x < event.x() < x + 32) and (y < event.y() < y + 32):
|
||||
self.profile.change_status()
|
||||
else:
|
||||
super(self.__class__, self).mouseReleaseEvent(event)
|
||||
super(MainWindow, self).mouseReleaseEvent(event)
|
||||
|
||||
def filtering(self):
|
||||
self.profile.filtration(self.online_contacts.currentIndex() == 1, self.contact_name.text())
|
||||
|
||||
|
||||
class ScreenShotWindow(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(ScreenShotWindow, self).__init__()
|
||||
self.parent = parent
|
||||
self.setMouseTracking(True)
|
||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.showFullScreen()
|
||||
self.setWindowOpacity(0.5)
|
||||
self.rubberband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle, None)
|
||||
|
||||
def closeEvent(self, *args):
|
||||
if self.parent.isHidden():
|
||||
self.parent.show()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.origin = event.pos()
|
||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||
self.rubberband.show()
|
||||
QtGui.QWidget.mousePressEvent(self, event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.rubberband.isVisible():
|
||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
|
||||
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
|
||||
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
|
||||
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
|
||||
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
|
||||
self.setMask(left + right + top + bottom)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.rubberband.isVisible():
|
||||
self.rubberband.hide()
|
||||
rect = self.rubberband.geometry()
|
||||
print rect
|
||||
if rect.width() and rect.height():
|
||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
||||
rect.x() + 4,
|
||||
rect.y() + 4,
|
||||
rect.width() - 8,
|
||||
rect.height() - 8)
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
p.save(buffer, 'PNG')
|
||||
Profile.get_instance().send_screenshot(str(byte_array.data()))
|
||||
self.close()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self.rubberband.setHidden(True)
|
||||
self.close()
|
||||
else:
|
||||
super(ScreenShotWindow, self).keyPressEvent(event)
|
||||
|
||||
|
||||
|
||||
|
313
src/mainscreen_widgets.py
Normal file
@ -0,0 +1,313 @@
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from widgets import RubberBand, create_menu, QRightClickButton
|
||||
from profile import Profile
|
||||
import smileys
|
||||
import util
|
||||
|
||||
|
||||
class MessageArea(QtGui.QPlainTextEdit):
|
||||
"""User types messages here"""
|
||||
|
||||
def __init__(self, parent, form):
|
||||
super(MessageArea, self).__init__(parent)
|
||||
self.parent = form
|
||||
self.setAcceptDrops(True)
|
||||
self.timer = QtCore.QTimer(self)
|
||||
self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False))
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.matches(QtGui.QKeySequence.Paste):
|
||||
self.pasteEvent()
|
||||
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
modifiers = event.modifiers()
|
||||
if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier:
|
||||
self.insertPlainText('\n')
|
||||
else:
|
||||
if self.timer.isActive():
|
||||
self.timer.stop()
|
||||
self.parent.profile.send_typing(False)
|
||||
self.parent.send_message()
|
||||
elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText():
|
||||
self.appendPlainText(Profile.get_instance().get_last_message())
|
||||
else:
|
||||
self.parent.profile.send_typing(True)
|
||||
if self.timer.isActive():
|
||||
self.timer.stop()
|
||||
self.timer.start(5000)
|
||||
super(MessageArea, self).keyPressEvent(event)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = create_menu(self.createStandardContextMenu())
|
||||
menu.exec_(event.globalPos())
|
||||
del menu
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
e.accept()
|
||||
|
||||
def dragMoveEvent(self, e):
|
||||
e.accept()
|
||||
|
||||
def dropEvent(self, e):
|
||||
if e.mimeData().hasFormat('text/plain'):
|
||||
e.accept()
|
||||
self.pasteEvent(e.mimeData().text())
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
def pasteEvent(self, text=None):
|
||||
text = text or QtGui.QApplication.clipboard().text()
|
||||
if text.startswith('file://'):
|
||||
self.parent.profile.send_file(text[7:])
|
||||
else:
|
||||
self.insertPlainText(text)
|
||||
|
||||
|
||||
class ScreenShotWindow(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(ScreenShotWindow, self).__init__()
|
||||
self.parent = parent
|
||||
self.setMouseTracking(True)
|
||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.showFullScreen()
|
||||
self.setWindowOpacity(0.5)
|
||||
self.rubberband = RubberBand()
|
||||
|
||||
def closeEvent(self, *args):
|
||||
if self.parent.isHidden():
|
||||
self.parent.show()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self.origin = event.pos()
|
||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||
self.rubberband.show()
|
||||
QtGui.QWidget.mousePressEvent(self, event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self.rubberband.isVisible():
|
||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized())
|
||||
left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height()))
|
||||
right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height()))
|
||||
top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y())
|
||||
bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height())
|
||||
self.setMask(left + right + top + bottom)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.rubberband.isVisible():
|
||||
self.rubberband.hide()
|
||||
rect = self.rubberband.geometry()
|
||||
print rect
|
||||
if rect.width() and rect.height():
|
||||
p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(),
|
||||
rect.x() + 4,
|
||||
rect.y() + 4,
|
||||
rect.width() - 8,
|
||||
rect.height() - 8)
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
p.save(buffer, 'PNG')
|
||||
Profile.get_instance().send_screenshot(str(byte_array.data()))
|
||||
self.close()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self.rubberband.setHidden(True)
|
||||
self.close()
|
||||
else:
|
||||
super(ScreenShotWindow, self).keyPressEvent(event)
|
||||
|
||||
|
||||
class SmileyWindow(QtGui.QWidget):
|
||||
"""
|
||||
Smiley selection window
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super(SmileyWindow, self).__init__()
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||
inst = smileys.SmileyLoader.get_instance()
|
||||
self.data = inst.get_smileys()
|
||||
count = len(self.data)
|
||||
self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page
|
||||
if count % self.page_size == 0:
|
||||
self.page_count = count / self.page_size
|
||||
else:
|
||||
self.page_count = int(count / float(self.page_size) + 0.5)
|
||||
self.page = 0
|
||||
self.radio = []
|
||||
self.parent = parent
|
||||
for i in range(self.page_count): # buttons with smileys
|
||||
elem = QtGui.QRadioButton(self)
|
||||
elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20))
|
||||
elem.clicked.connect(lambda i=i: self.checked(i))
|
||||
self.radio.append(elem)
|
||||
width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 / 10)
|
||||
self.setMaximumSize(width, 200)
|
||||
self.setMinimumSize(width, 200)
|
||||
self.buttons = []
|
||||
for i in range(self.page_size): # pages - radio buttons
|
||||
b = QtGui.QPushButton(self)
|
||||
b.setGeometry(QtCore.QRect((i / 8) * 20 + 5, (i % 8) * 20, 20, 20))
|
||||
b.clicked.connect(lambda i=i: self.clicked(i))
|
||||
self.buttons.append(b)
|
||||
self.checked(0)
|
||||
|
||||
def checked(self, pos): # new page opened
|
||||
self.radio[self.page].setChecked(False)
|
||||
self.radio[pos].setChecked(True)
|
||||
self.page = pos
|
||||
start = self.page * self.page_size
|
||||
for i in range(self.page_size):
|
||||
try:
|
||||
self.buttons[i].setVisible(True)
|
||||
pixmap = QtGui.QPixmap(self.data[start + i][1])
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.buttons[i].setIcon(icon)
|
||||
except:
|
||||
self.buttons[i].setVisible(False)
|
||||
|
||||
def clicked(self, pos): # smiley selected
|
||||
pos += self.page * self.page_size
|
||||
smiley = self.data[pos][0]
|
||||
self.parent.messageEdit.insertPlainText(smiley)
|
||||
self.close()
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.close()
|
||||
|
||||
|
||||
class MenuButton(QtGui.QPushButton):
|
||||
|
||||
def __init__(self, parent, enter):
|
||||
super(MenuButton, self).__init__(parent)
|
||||
self.enter = enter
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.enter()
|
||||
super(MenuButton, self).enterEvent(event)
|
||||
|
||||
|
||||
class DropdownMenu(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(DropdownMenu, self).__init__(parent)
|
||||
self.installEventFilter(self)
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||
self.setMaximumSize(150, 100)
|
||||
self.setMinimumSize(150, 100)
|
||||
self.screenshotButton = QRightClickButton(self)
|
||||
self.screenshotButton.setGeometry(QtCore.QRect(0, 50, 50, 50))
|
||||
self.screenshotButton.setObjectName("screenshotButton")
|
||||
|
||||
self.fileTransferButton = QtGui.QPushButton(self)
|
||||
self.fileTransferButton.setGeometry(QtCore.QRect(50, 50, 50, 50))
|
||||
self.fileTransferButton.setObjectName("fileTransferButton")
|
||||
|
||||
self.audioMessageButton = QtGui.QPushButton(self)
|
||||
self.audioMessageButton.setGeometry(QtCore.QRect(100, 50, 50, 50))
|
||||
|
||||
self.smileyButton = QtGui.QPushButton(self)
|
||||
self.smileyButton.setGeometry(QtCore.QRect(0, 0, 50, 50))
|
||||
|
||||
self.videoMessageButton = QtGui.QPushButton(self)
|
||||
self.videoMessageButton.setGeometry(QtCore.QRect(100, 0, 50, 50))
|
||||
|
||||
self.stickerButton = QtGui.QPushButton(self)
|
||||
self.stickerButton.setGeometry(QtCore.QRect(50, 0, 50, 50))
|
||||
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.fileTransferButton.setIcon(icon)
|
||||
self.fileTransferButton.setIconSize(QtCore.QSize(40, 40))
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.screenshotButton.setIcon(icon)
|
||||
self.screenshotButton.setIconSize(QtCore.QSize(40, 50))
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.audioMessageButton.setIcon(icon)
|
||||
self.audioMessageButton.setIconSize(QtCore.QSize(40, 40))
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.smileyButton.setIcon(icon)
|
||||
self.smileyButton.setIconSize(QtCore.QSize(40, 40))
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.videoMessageButton.setIcon(icon)
|
||||
self.videoMessageButton.setIconSize(QtCore.QSize(45, 45))
|
||||
pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png')
|
||||
icon = QtGui.QIcon(pixmap)
|
||||
self.stickerButton.setIcon(icon)
|
||||
self.stickerButton.setIconSize(QtCore.QSize(45, 45))
|
||||
|
||||
self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.smileyButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Add smiley", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.stickerButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send sticker", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
self.fileTransferButton.clicked.connect(parent.send_file)
|
||||
self.screenshotButton.clicked.connect(parent.send_screenshot)
|
||||
self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True))
|
||||
self.smileyButton.clicked.connect(parent.send_smiley)
|
||||
self.stickerButton.clicked.connect(parent.send_sticker)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.close()
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QtCore.QEvent.WindowDeactivate:
|
||||
self.close()
|
||||
return False
|
||||
|
||||
|
||||
class StickerItem(QtGui.QWidget):
|
||||
|
||||
def __init__(self, fl):
|
||||
super(StickerItem, self).__init__()
|
||||
self._image_label = QtGui.QLabel(self)
|
||||
self.path = fl
|
||||
self.pixmap = QtGui.QPixmap()
|
||||
self.pixmap.load(fl)
|
||||
if self.pixmap.width() > 150:
|
||||
self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio)
|
||||
self.setFixedSize(150, self.pixmap.height())
|
||||
self._image_label.setPixmap(self.pixmap)
|
||||
|
||||
|
||||
class StickerWindow(QtGui.QWidget):
|
||||
"""Sticker selection window"""
|
||||
|
||||
def __init__(self, parent):
|
||||
super(StickerWindow, self).__init__()
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
||||
self.setMaximumSize(250, 200)
|
||||
self.setMinimumSize(250, 200)
|
||||
self.list = QtGui.QListWidget(self)
|
||||
self.list.setGeometry(QtCore.QRect(0, 0, 250, 200))
|
||||
self.arr = smileys.sticker_loader()
|
||||
for sticker in self.arr:
|
||||
item = StickerItem(sticker)
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(250, item.height()))
|
||||
self.list.addItem(elem)
|
||||
self.list.setItemWidget(elem, item)
|
||||
self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
|
||||
self.list.setSpacing(3)
|
||||
self.list.clicked.connect(self.click)
|
||||
self.parent = parent
|
||||
|
||||
def click(self, index):
|
||||
num = index.row()
|
||||
self.parent.profile.send_sticker(self.arr[num])
|
||||
self.close()
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.close()
|
||||
|
||||
|
278
src/menu.py
@ -4,8 +4,8 @@ except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from settings import *
|
||||
from profile import Profile
|
||||
from util import get_style, curr_directory
|
||||
from widgets import CenteredWidget, DataLabel
|
||||
from util import curr_directory
|
||||
from widgets import CenteredWidget, DataLabel, LineEdit
|
||||
import pyaudio
|
||||
import toxencryptsave
|
||||
import plugin_support
|
||||
@ -14,11 +14,11 @@ import plugin_support
|
||||
class AddContact(CenteredWidget):
|
||||
"""Add contact form"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, tox_id=''):
|
||||
super(AddContact, self).__init__()
|
||||
self.initUI()
|
||||
self.initUI(tox_id)
|
||||
|
||||
def initUI(self):
|
||||
def initUI(self, tox_id):
|
||||
self.setObjectName('AddContact')
|
||||
self.resize(568, 306)
|
||||
self.sendRequestButton = QtGui.QPushButton(self)
|
||||
@ -27,24 +27,25 @@ class AddContact(CenteredWidget):
|
||||
self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0))
|
||||
self.sendRequestButton.setObjectName("sendRequestButton")
|
||||
self.sendRequestButton.clicked.connect(self.add_friend)
|
||||
self.tox_id = QtGui.QLineEdit(self)
|
||||
self.tox_id = LineEdit(self)
|
||||
self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27))
|
||||
self.tox_id.setObjectName("lineEdit")
|
||||
self.tox_id.setText(tox_id)
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(60, 10, 80, 20))
|
||||
self.label.setGeometry(QtCore.QRect(50, 10, 80, 20))
|
||||
self.error_label = DataLabel(self)
|
||||
self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(10)
|
||||
font.setWeight(30)
|
||||
self.error_label.setFont(font)
|
||||
self.error_label.setStyleSheet("QLabel { color: red; }")
|
||||
self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }")
|
||||
self.label.setObjectName("label")
|
||||
self.message_edit = QtGui.QTextEdit(self)
|
||||
self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151))
|
||||
self.message_edit.setObjectName("textEdit")
|
||||
self.message = QtGui.QLabel(self)
|
||||
self.message.setGeometry(QtCore.QRect(60, 70, 101, 31))
|
||||
self.message.setGeometry(QtCore.QRect(50, 70, 101, 31))
|
||||
self.message.setFont(font)
|
||||
self.message.setObjectName("label_2")
|
||||
self.retranslateUi()
|
||||
@ -70,6 +71,7 @@ class AddContact(CenteredWidget):
|
||||
self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.tox_id.setPlaceholderText(QtGui.QApplication.translate('AddContact', "TOX ID or public key of contact", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
|
||||
class ProfileSettings(CenteredWidget):
|
||||
@ -81,17 +83,17 @@ class ProfileSettings(CenteredWidget):
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("ProfileSettingsForm")
|
||||
self.setMinimumSize(QtCore.QSize(650, 520))
|
||||
self.setMaximumSize(QtCore.QSize(650, 520))
|
||||
self.nick = QtGui.QLineEdit(self)
|
||||
self.setMinimumSize(QtCore.QSize(700, 600))
|
||||
self.setMaximumSize(QtCore.QSize(700, 600))
|
||||
self.nick = LineEdit(self)
|
||||
self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27))
|
||||
self.nick.setObjectName("nick")
|
||||
profile = Profile.get_instance()
|
||||
self.nick.setText(profile.name)
|
||||
self.status = QtGui.QLineEdit(self)
|
||||
self.status.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
||||
self.status.setObjectName("status")
|
||||
self.status.setText(profile.status_message)
|
||||
self.status = QtGui.QComboBox(self)
|
||||
self.status.setGeometry(QtCore.QRect(400, 60, 200, 27))
|
||||
self.status_message = LineEdit(self)
|
||||
self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27))
|
||||
self.status_message.setText(profile.status_message)
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(40, 30, 91, 25))
|
||||
font = QtGui.QFont()
|
||||
@ -99,62 +101,66 @@ class ProfileSettings(CenteredWidget):
|
||||
font.setWeight(75)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
self.label.setObjectName("label")
|
||||
self.label_2 = QtGui.QLabel(self)
|
||||
self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25))
|
||||
self.label_2.setFont(font)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.label_3 = QtGui.QLabel(self)
|
||||
self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25))
|
||||
self.label_3.setFont(font)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.tox_id = QtGui.QLabel(self)
|
||||
self.tox_id.setGeometry(QtCore.QRect(10, 210, self.width(), 21))
|
||||
self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21))
|
||||
font.setPointSize(10)
|
||||
self.tox_id.setFont(font)
|
||||
self.tox_id.setObjectName("tox_id")
|
||||
s = profile.tox_id
|
||||
self.tox_id.setText(s)
|
||||
self.copyId = QtGui.QPushButton(self)
|
||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 160, 30))
|
||||
self.copyId.setObjectName("copyId")
|
||||
self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30))
|
||||
self.copyId.clicked.connect(self.copy)
|
||||
self.export = QtGui.QPushButton(self)
|
||||
self.export.setGeometry(QtCore.QRect(210, 250, 160, 30))
|
||||
self.export.setObjectName("export")
|
||||
self.export.setGeometry(QtCore.QRect(230, 250, 180, 30))
|
||||
self.export.clicked.connect(self.export_profile)
|
||||
self.new_nospam = QtGui.QPushButton(self)
|
||||
self.new_nospam.setGeometry(QtCore.QRect(380, 250, 160, 30))
|
||||
self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30))
|
||||
self.new_nospam.clicked.connect(self.new_no_spam)
|
||||
|
||||
self.new_avatar = QtGui.QPushButton(self)
|
||||
self.new_avatar.setGeometry(QtCore.QRect(400, 50, 200, 50))
|
||||
self.new_avatar.setGeometry(QtCore.QRect(40, 300, 180, 30))
|
||||
self.delete_avatar = QtGui.QPushButton(self)
|
||||
self.delete_avatar.setGeometry(QtCore.QRect(400, 120, 200, 50))
|
||||
self.delete_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30))
|
||||
self.delete_avatar.clicked.connect(self.reset_avatar)
|
||||
self.new_avatar.clicked.connect(self.set_avatar)
|
||||
self.profile_pass = QtGui.QLabel(self)
|
||||
self.profile_pass.setGeometry(QtCore.QRect(40, 300, 300, 50))
|
||||
self.profile_pass.setGeometry(QtCore.QRect(40, 340, 300, 30))
|
||||
font.setPointSize(18)
|
||||
self.profile_pass.setFont(font)
|
||||
self.password = QtGui.QLineEdit(self)
|
||||
self.password.setGeometry(QtCore.QRect(30, 350, 300, 30))
|
||||
self.password = LineEdit(self)
|
||||
self.password.setGeometry(QtCore.QRect(40, 380, 300, 30))
|
||||
self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||
self.leave_blank = QtGui.QLabel(self)
|
||||
self.leave_blank.setGeometry(QtCore.QRect(340, 350, 300, 30))
|
||||
self.confirm_password = QtGui.QLineEdit(self)
|
||||
self.confirm_password.setGeometry(QtCore.QRect(30, 400, 300, 30))
|
||||
self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30))
|
||||
self.confirm_password = LineEdit(self)
|
||||
self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30))
|
||||
self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password)
|
||||
self.set_password = QtGui.QPushButton(self)
|
||||
self.set_password.setGeometry(QtCore.QRect(30, 450, 300, 30))
|
||||
self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30))
|
||||
self.set_password.clicked.connect(self.new_password)
|
||||
self.not_match = QtGui.QLabel(self)
|
||||
self.not_match.setGeometry(QtCore.QRect(340, 400, 300, 30))
|
||||
self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30))
|
||||
self.not_match.setVisible(False)
|
||||
self.not_match.setStyleSheet('QLabel { color: #F70D1A; }')
|
||||
self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||
self.warning = QtGui.QLabel(self)
|
||||
self.warning.setGeometry(QtCore.QRect(30, 490, 500, 30))
|
||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
||||
self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30))
|
||||
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||
self.default = QtGui.QPushButton(self)
|
||||
self.default.setGeometry(QtCore.QRect(40, 550, 620, 30))
|
||||
path, name = Settings.get_auto_profile()
|
||||
self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name
|
||||
self.default.clicked.connect(self.auto_profile)
|
||||
self.retranslateUi()
|
||||
if profile.status is not None:
|
||||
self.status.setCurrentIndex(profile.status)
|
||||
else:
|
||||
self.status.setVisible(False)
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
def retranslateUi(self):
|
||||
@ -174,6 +180,27 @@ class ProfileSettings(CenteredWidget):
|
||||
self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Online", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Away", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Busy", None, QtGui.QApplication.UnicodeUTF8))
|
||||
if self.auto:
|
||||
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def auto_profile(self):
|
||||
if self.auto:
|
||||
Settings.reset_auto_profile()
|
||||
else:
|
||||
Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name)
|
||||
self.auto = not self.auto
|
||||
if self.auto:
|
||||
self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.default.setText(
|
||||
QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def new_password(self):
|
||||
if self.password.text() == self.confirm_password.text():
|
||||
@ -208,11 +235,17 @@ class ProfileSettings(CenteredWidget):
|
||||
|
||||
def set_avatar(self):
|
||||
choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8)
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Image Files (*.png)')
|
||||
name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)')
|
||||
if name[0]:
|
||||
with open(name[0], 'rb') as f:
|
||||
data = f.read()
|
||||
Profile.get_instance().set_avatar(data)
|
||||
bitmap = QtGui.QPixmap(name[0])
|
||||
bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
|
||||
mode=QtCore.Qt.SmoothTransformation)
|
||||
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
bitmap.save(buffer, 'PNG')
|
||||
Profile.get_instance().set_avatar(str(byte_array.data()))
|
||||
|
||||
def export_profile(self):
|
||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
||||
@ -226,7 +259,8 @@ class ProfileSettings(CenteredWidget):
|
||||
def closeEvent(self, event):
|
||||
profile = Profile.get_instance()
|
||||
profile.set_name(self.nick.text().encode('utf-8'))
|
||||
profile.set_status_message(self.status.text().encode('utf-8'))
|
||||
profile.set_status_message(self.status_message.text().encode('utf-8'))
|
||||
profile.set_status(self.status.currentIndex())
|
||||
|
||||
|
||||
class NetworkSettings(CenteredWidget):
|
||||
@ -254,10 +288,10 @@ class NetworkSettings(CenteredWidget):
|
||||
self.http = QtGui.QCheckBox(self)
|
||||
self.http.setGeometry(QtCore.QRect(20, 70, 97, 22))
|
||||
self.proxy.setObjectName("proxy")
|
||||
self.proxyip = QtGui.QLineEdit(self)
|
||||
self.proxyip = LineEdit(self)
|
||||
self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27))
|
||||
self.proxyip.setObjectName("proxyip")
|
||||
self.proxyport = QtGui.QLineEdit(self)
|
||||
self.proxyport = LineEdit(self)
|
||||
self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27))
|
||||
self.proxyport.setObjectName("proxyport")
|
||||
self.label = QtGui.QLabel(self)
|
||||
@ -276,7 +310,7 @@ class NetworkSettings(CenteredWidget):
|
||||
self.http.setChecked(settings['proxy_type'] == 1)
|
||||
self.warning = QtGui.QLabel(self)
|
||||
self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60))
|
||||
self.warning.setStyleSheet('QLabel { color: #F70D1A; }')
|
||||
self.warning.setStyleSheet('QLabel { color: #BC1C1C; }')
|
||||
self.retranslateUi()
|
||||
self.proxy.stateChanged.connect(lambda x: self.activate())
|
||||
self.activate()
|
||||
@ -331,18 +365,13 @@ class PrivacySettings(CenteredWidget):
|
||||
self.setMaximumSize(QtCore.QSize(350, 550))
|
||||
self.saveHistory = QtGui.QCheckBox(self)
|
||||
self.saveHistory.setGeometry(QtCore.QRect(10, 20, 291, 22))
|
||||
self.saveHistory.setObjectName("saveHistory")
|
||||
self.fileautoaccept = QtGui.QCheckBox(self)
|
||||
self.fileautoaccept.setGeometry(QtCore.QRect(10, 60, 271, 22))
|
||||
self.fileautoaccept.setObjectName("fileautoaccept")
|
||||
|
||||
self.typingNotifications = QtGui.QCheckBox(self)
|
||||
self.typingNotifications.setGeometry(QtCore.QRect(10, 100, 350, 30))
|
||||
self.typingNotifications.setObjectName("typingNotifications")
|
||||
self.inlines = QtGui.QCheckBox(self)
|
||||
self.inlines.setGeometry(QtCore.QRect(10, 140, 350, 30))
|
||||
self.inlines.setObjectName("inlines")
|
||||
|
||||
|
||||
self.auto_path = QtGui.QLabel(self)
|
||||
self.auto_path.setGeometry(QtCore.QRect(10, 190, 350, 30))
|
||||
self.path = QtGui.QPlainTextEdit(self)
|
||||
@ -382,7 +411,7 @@ class PrivacySettings(CenteredWidget):
|
||||
self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by TOX ID:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by public key:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -435,9 +464,9 @@ class NotificationsSettings(CenteredWidget):
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("notificationsForm")
|
||||
self.resize(350, 200)
|
||||
self.setMinimumSize(QtCore.QSize(350, 200))
|
||||
self.setMaximumSize(QtCore.QSize(350, 200))
|
||||
self.resize(350, 180)
|
||||
self.setMinimumSize(QtCore.QSize(350, 180))
|
||||
self.setMaximumSize(QtCore.QSize(350, 180))
|
||||
self.enableNotifications = QtGui.QCheckBox(self)
|
||||
self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18))
|
||||
self.callsSound = QtGui.QCheckBox(self)
|
||||
@ -446,7 +475,6 @@ class NotificationsSettings(CenteredWidget):
|
||||
self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
font.setBold(True)
|
||||
self.callsSound.setFont(font)
|
||||
self.soundNotifications.setFont(font)
|
||||
self.enableNotifications.setFont(font)
|
||||
@ -473,7 +501,6 @@ class NotificationsSettings(CenteredWidget):
|
||||
|
||||
class InterfaceSettings(CenteredWidget):
|
||||
"""Interface settings form"""
|
||||
|
||||
def __init__(self):
|
||||
super(InterfaceSettings, self).__init__()
|
||||
self.initUI()
|
||||
@ -481,39 +508,65 @@ class InterfaceSettings(CenteredWidget):
|
||||
|
||||
def initUI(self):
|
||||
self.setObjectName("interfaceForm")
|
||||
self.resize(300, 300)
|
||||
self.setMinimumSize(QtCore.QSize(300, 300))
|
||||
self.setMaximumSize(QtCore.QSize(300, 300))
|
||||
self.setBaseSize(QtCore.QSize(300, 300))
|
||||
self.setMinimumSize(QtCore.QSize(400, 450))
|
||||
self.setMaximumSize(QtCore.QSize(400, 450))
|
||||
self.label = QtGui.QLabel(self)
|
||||
self.label.setGeometry(QtCore.QRect(30, 20, 91, 21))
|
||||
self.label.setGeometry(QtCore.QRect(30, 10, 370, 20))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(16)
|
||||
font.setWeight(75)
|
||||
font.setPointSize(14)
|
||||
font.setBold(True)
|
||||
self.label.setFont(font)
|
||||
self.label.setObjectName("label")
|
||||
self.themeSelect = QtGui.QComboBox(self)
|
||||
self.themeSelect.setGeometry(QtCore.QRect(30, 60, 160, 30))
|
||||
self.themeSelect.setObjectName("themeSelect")
|
||||
list_of_themes = ['default', 'windows', 'gtk', 'cde', 'plastique', 'motif']
|
||||
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']
|
||||
index = list_of_themes.index(theme)
|
||||
if theme in list_of_themes:
|
||||
index = list_of_themes.index(theme)
|
||||
else:
|
||||
index = 0
|
||||
self.themeSelect.setCurrentIndex(index)
|
||||
self.lang_choose = QtGui.QComboBox(self)
|
||||
self.lang_choose.setGeometry(QtCore.QRect(30, 150, 160, 30))
|
||||
self.lang_choose.setObjectName("comboBox")
|
||||
self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30))
|
||||
supported = Settings.supported_languages()
|
||||
for elem in supported:
|
||||
self.lang_choose.addItem(elem[0])
|
||||
lang = settings['language']
|
||||
index = map(lambda x: x[0], supported).index(lang)
|
||||
self.lang_choose.setCurrentIndex(index)
|
||||
for key in supported:
|
||||
self.lang_choose.insertItem(0, key)
|
||||
if settings['language'] == key:
|
||||
self.lang_choose.setCurrentIndex(0)
|
||||
self.lang = QtGui.QLabel(self)
|
||||
self.lang.setGeometry(QtCore.QRect(30, 110, 121, 31))
|
||||
self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20))
|
||||
self.lang.setFont(font)
|
||||
self.mirror_mode = QtGui.QCheckBox(self)
|
||||
self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20))
|
||||
self.mirror_mode.setChecked(settings['mirror_mode'])
|
||||
self.smileys = QtGui.QCheckBox(self)
|
||||
self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20))
|
||||
self.smileys.setChecked(settings['smileys'])
|
||||
self.smiley_pack_label = QtGui.QLabel(self)
|
||||
self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20))
|
||||
self.smiley_pack_label.setFont(font)
|
||||
self.smiley_pack = QtGui.QComboBox(self)
|
||||
self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30))
|
||||
sm = smileys.SmileyLoader.get_instance()
|
||||
self.smiley_pack.addItems(sm.get_packs_list())
|
||||
try:
|
||||
ind = sm.get_packs_list().index(settings['smiley_pack'])
|
||||
except:
|
||||
ind = sm.get_packs_list().index('default')
|
||||
self.smiley_pack.setCurrentIndex(ind)
|
||||
self.messages_font_size_label = QtGui.QLabel(self)
|
||||
self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20))
|
||||
self.messages_font_size_label.setFont(font)
|
||||
self.messages_font_size = QtGui.QComboBox(self)
|
||||
self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30))
|
||||
self.messages_font_size.addItems([str(x) for x in range(10, 19)])
|
||||
self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10)
|
||||
|
||||
self.unread = QtGui.QPushButton(self)
|
||||
self.unread.setGeometry(QtCore.QRect(30, 380, 340, 40))
|
||||
self.unread.clicked.connect(self.select_color)
|
||||
|
||||
self.retranslateUi()
|
||||
QtCore.QMetaObject.connectSlotsByName(self)
|
||||
|
||||
@ -521,25 +574,54 @@ class InterfaceSettings(CenteredWidget):
|
||||
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))
|
||||
self.smileys.setText(QtGui.QApplication.translate("interfaceForm", "Smileys", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.smiley_pack_label.setText(QtGui.QApplication.translate("interfaceForm", "Smiley pack:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.mirror_mode.setText(QtGui.QApplication.translate("interfaceForm", "Mirror mode", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.messages_font_size_label.setText(QtGui.QApplication.translate("interfaceForm", "Messages font size:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.unread.setText(QtGui.QApplication.translate("interfaceForm", "Select unread messages notification color", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def select_color(self):
|
||||
col = QtGui.QColorDialog.getColor()
|
||||
|
||||
if col.isValid():
|
||||
settings = Settings.get_instance()
|
||||
name = col.name()
|
||||
settings['unread_color'] = name
|
||||
settings.save()
|
||||
|
||||
def closeEvent(self, event):
|
||||
settings = Settings.get_instance()
|
||||
style = str(self.themeSelect.currentText())
|
||||
settings['theme'] = style
|
||||
QtGui.QApplication.setStyle(get_style(style))
|
||||
settings['theme'] = str(self.themeSelect.currentText())
|
||||
settings['smileys'] = self.smileys.isChecked()
|
||||
if settings['mirror_mode'] != self.mirror_mode.isChecked():
|
||||
settings['mirror_mode'] = self.mirror_mode.isChecked()
|
||||
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_()
|
||||
settings['smiley_pack'] = self.smiley_pack.currentText()
|
||||
smileys.SmileyLoader.get_instance().load_pack()
|
||||
language = self.lang_choose.currentText()
|
||||
if settings['language'] != language:
|
||||
settings['language'] = language
|
||||
index = self.lang_choose.currentIndex()
|
||||
path = Settings.supported_languages()[index][1]
|
||||
text = self.lang_choose.currentText()
|
||||
path = Settings.supported_languages()[text]
|
||||
app = QtGui.QApplication.instance()
|
||||
app.removeTranslator(app.translator)
|
||||
app.translator.load(curr_directory() + '/translations/' + path)
|
||||
app.installTranslator(app.translator)
|
||||
settings['message_font_size'] = self.messages_font_size.currentIndex() + 10
|
||||
Profile.get_instance().update()
|
||||
settings.save()
|
||||
|
||||
|
||||
class AudioSettings(CenteredWidget):
|
||||
"""
|
||||
Audio calls settings form
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(AudioSettings, self).__init__()
|
||||
@ -593,6 +675,9 @@ class AudioSettings(CenteredWidget):
|
||||
|
||||
|
||||
class PluginsSettings(CenteredWidget):
|
||||
"""
|
||||
Plugins settings form
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PluginsSettings, self).__init__()
|
||||
@ -633,8 +718,10 @@ class PluginsSettings(CenteredWidget):
|
||||
self.window.show()
|
||||
else:
|
||||
msgBox = QtGui.QMessageBox()
|
||||
text = (QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
text = QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
msgBox.setWindowTitle(QtGui.QApplication.translate("PluginsForm", 'Error', None,
|
||||
QtGui.QApplication.UnicodeUTF8))
|
||||
msgBox.setText(text)
|
||||
msgBox.exec_()
|
||||
|
||||
@ -646,13 +733,18 @@ class PluginsSettings(CenteredWidget):
|
||||
|
||||
def show_data(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
plugin = self.data[ind]
|
||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.label.setText(descr)
|
||||
if plugin[1]:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
if len(self.data):
|
||||
plugin = self.data[ind]
|
||||
descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self.label.setText(descr)
|
||||
if plugin[1]:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
else:
|
||||
self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.open.setVisible(False)
|
||||
self.button.setVisible(False)
|
||||
self.label.setText(QtGui.QApplication.translate("PluginsForm", "No plugins found", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
||||
def button_click(self):
|
||||
ind = self.comboBox.currentIndex()
|
||||
|
@ -4,17 +4,8 @@ MESSAGE_TYPE = {
|
||||
'TEXT': 0,
|
||||
'ACTION': 1,
|
||||
'FILE_TRANSFER': 2,
|
||||
'INLINE': 3
|
||||
}
|
||||
|
||||
FILE_TRANSFER_MESSAGE_STATUS = {
|
||||
'FINISHED': 0,
|
||||
'CANCELLED': 1,
|
||||
'OUTGOING': 2,
|
||||
'INCOMING_NOT_STARTED': 3,
|
||||
'INCOMING_STARTED': 4,
|
||||
'PAUSED_BY_FRIEND': 5,
|
||||
'PAUSED_BY_USER': 6
|
||||
'INLINE': 3,
|
||||
'INFO_MESSAGE': 4
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +22,9 @@ class Message(object):
|
||||
def get_owner(self):
|
||||
return self._owner
|
||||
|
||||
def mark_as_sent(self):
|
||||
self._owner = 0
|
||||
|
||||
|
||||
class TextMessage(Message):
|
||||
"""
|
||||
@ -58,7 +52,7 @@ class TransferMessage(Message):
|
||||
self._friend_number, self._file_number = friend_number, file_number
|
||||
|
||||
def is_active(self, file_number):
|
||||
return self._file_number == file_number and self._status > 1
|
||||
return self._file_number == file_number and self._status not in (2, 3)
|
||||
|
||||
def get_friend_number(self):
|
||||
return self._friend_number
|
||||
@ -76,6 +70,18 @@ class TransferMessage(Message):
|
||||
return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status
|
||||
|
||||
|
||||
class UnsentFile(Message):
|
||||
def __init__(self, path, data, time):
|
||||
super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
|
||||
self._data, self._path = data, path
|
||||
|
||||
def get_data(self):
|
||||
return self._path, self._data, self._time
|
||||
|
||||
def get_status(self):
|
||||
return None
|
||||
|
||||
|
||||
class InlineImage(Message):
|
||||
"""
|
||||
Inline image
|
||||
@ -87,3 +93,9 @@ class InlineImage(Message):
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
|
||||
class InfoMessage(TextMessage):
|
||||
|
||||
def __init__(self, message, time):
|
||||
super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
|
@ -1,11 +1,11 @@
|
||||
from widgets import CenteredWidget
|
||||
from widgets import CenteredWidget, LineEdit
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
|
||||
class PasswordArea(QtGui.QLineEdit):
|
||||
class PasswordArea(LineEdit):
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PasswordArea, self).__init__(parent)
|
||||
|
@ -29,6 +29,9 @@ class PluginLoader(util.Singleton):
|
||||
Load all plugins in plugins folder
|
||||
"""
|
||||
path = util.curr_directory() + '/plugins/'
|
||||
if not os.path.exists(path):
|
||||
util.log('Plugin dir not found')
|
||||
return
|
||||
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||
for fl in files:
|
||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
||||
@ -76,6 +79,9 @@ class PluginLoader(util.Singleton):
|
||||
self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:length]), friend_number)
|
||||
|
||||
def friend_online(self, friend_number):
|
||||
"""
|
||||
Friend with specified number is online
|
||||
"""
|
||||
for elem in self._plugins.values():
|
||||
if elem[1]:
|
||||
elem[0].friend_connected(friend_number)
|
||||
|
@ -125,11 +125,12 @@ class PluginSuperClass(object):
|
||||
New command. On 'help' this method should provide user list of available commands
|
||||
:param command: string with command
|
||||
"""
|
||||
msgbox = QtGui.QMessageBox()
|
||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||
msgbox.setWindowTitle(title.format(self._name))
|
||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
||||
msgbox.exec_()
|
||||
if command == 'help':
|
||||
msgbox = QtGui.QMessageBox()
|
||||
title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8)
|
||||
msgbox.setWindowTitle(title.format(self._name))
|
||||
msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8))
|
||||
msgbox.exec_()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Translations support
|
||||
@ -142,11 +143,11 @@ class PluginSuperClass(object):
|
||||
app = QtGui.QApplication.instance()
|
||||
langs = self._settings.supported_languages()
|
||||
curr_lang = self._settings['language']
|
||||
if curr_lang in map(lambda x: x[0], langs):
|
||||
if curr_lang in langs:
|
||||
if self._translator is not None:
|
||||
app.removeTranslator(self._translator)
|
||||
self._translator = QtCore.QTranslator()
|
||||
lang_path = filter(lambda x: x[0] == curr_lang, langs)[0][1]
|
||||
lang_path = langs[curr_lang]
|
||||
self._translator.load(path_to_data(self._short_name) + lang_path)
|
||||
app.installTranslator(self._translator)
|
||||
|
||||
|
628
src/profile.py
@ -1,11 +1,9 @@
|
||||
from list_items import MessageItem, ContactItem, FileTransferItem, InlineImageItem
|
||||
from list_items import *
|
||||
try:
|
||||
from PySide import QtCore, QtGui
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from tox import Tox
|
||||
import os
|
||||
from messages import *
|
||||
from friend import *
|
||||
from settings import *
|
||||
from toxcore_enums_and_consts import *
|
||||
from ctypes import *
|
||||
@ -19,269 +17,7 @@ import avwidgets
|
||||
import plugin_support
|
||||
|
||||
|
||||
class Contact(object):
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._widget.name.setText(name)
|
||||
self._widget.status_message.setText(status_message)
|
||||
self._tox_id = tox_id
|
||||
self.load_avatar()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = value.decode('utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = value.decode('utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._widget.connection_status.data = self._status = value
|
||||
self._widget.connection_status.repaint()
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
os.chdir(ProfileHelper.get_path() + 'avatars/')
|
||||
if not os.path.isfile(avatar_path): # load default image
|
||||
avatar_path = 'avatar.png'
|
||||
os.chdir(curr_directory() + '/images/')
|
||||
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
||||
pixmap.load(avatar_path)
|
||||
self._widget.avatar_label.setScaledContents(False)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
|
||||
|
||||
class Friend(Contact):
|
||||
"""
|
||||
Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, *args):
|
||||
"""
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
super(Friend, self).__init__(*args)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = False
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
data = self._message_getter.get(PAGE_SIZE)
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = map(lambda tupl: TextMessage(*tupl), data)
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
||||
return map(lambda x: x.get_data(), messages[-self._unsaved_messages:]) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: tuple (message, owner, unix_time, message_type)
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = filter(lambda x: x.get_type() <= 1 and not x.get_owner(), self._corr)
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def last_message_owner(self):
|
||||
messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
||||
if messages:
|
||||
return messages[-1].get_owner()
|
||||
else:
|
||||
return -1
|
||||
|
||||
def clear_corr(self):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
self._corr = filter(lambda x: x.get_type() == 2 and x.get_status() >= 2, self._corr)
|
||||
self._unsaved_messages = 0
|
||||
|
||||
def update_transfer_data(self, file_number, status, inline=None):
|
||||
"""
|
||||
Update status of active transfer and load inline if needed
|
||||
"""
|
||||
try:
|
||||
tr = filter(lambda x: x.get_type() == 2 and x.is_active(file_number), self._corr)[0]
|
||||
tr.set_status(status)
|
||||
if inline: # inline was loaded
|
||||
i = self._corr.index(tr)
|
||||
self._corr.insert(i, inline)
|
||||
return i - len(self._corr)
|
||||
except Exception as ex:
|
||||
log('Update transfer data failed: ' + str(ex))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
if not self._alias:
|
||||
super(self.__class__, self).set_name(value)
|
||||
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def set_messages(self, value):
|
||||
self._widget.connection_status.messages = self._new_messages = value
|
||||
self._widget.connection_status.repaint()
|
||||
|
||||
messages = property(get_messages, set_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
||||
|
||||
|
||||
class Profile(Contact, Singleton):
|
||||
class Profile(contact.Contact, Singleton):
|
||||
"""
|
||||
Profile of current toxygen user. Contains friends list, tox instance
|
||||
"""
|
||||
@ -337,17 +73,29 @@ class Profile(Contact, Singleton):
|
||||
|
||||
def set_status(self, status):
|
||||
super(Profile, self).set_status(status)
|
||||
self._tox.self_set_status(status)
|
||||
if status is not None:
|
||||
self._tox.self_set_status(status)
|
||||
|
||||
def set_name(self, value):
|
||||
super(self.__class__, self).set_name(value)
|
||||
tmp = self.name
|
||||
super(Profile, self).set_name(value)
|
||||
self._tox.self_set_name(self._name.encode('utf-8'))
|
||||
if tmp != value:
|
||||
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
message = message.format(tmp, value)
|
||||
for friend in self._friends:
|
||||
friend.append_message(InfoMessage(message, time.time()))
|
||||
if self._active_friend + 1:
|
||||
self.create_message_item(message, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
def set_status_message(self, value):
|
||||
super(self.__class__, self).set_status_message(value)
|
||||
super(Profile, self).set_status_message(value)
|
||||
self._tox.self_set_status_message(self._status_message.encode('utf-8'))
|
||||
|
||||
def new_nospam(self):
|
||||
"""Sets new nospam part of tox id"""
|
||||
import random
|
||||
self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
|
||||
self._tox_id = self._tox.self_get_address()
|
||||
@ -366,6 +114,7 @@ class Profile(Contact, Singleton):
|
||||
filter_str = filter_str.lower()
|
||||
for index, friend in enumerate(self._friends):
|
||||
friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower())
|
||||
friend.visibility = friend.visibility or friend.messages or friend.actions
|
||||
if friend.visibility:
|
||||
self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 70))
|
||||
else:
|
||||
@ -413,10 +162,15 @@ class Profile(Contact, Singleton):
|
||||
self.send_typing(False)
|
||||
self._screen.typing.setVisible(False)
|
||||
if value is not None:
|
||||
if self._active_friend + 1:
|
||||
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].set_messages(False)
|
||||
self._screen.messageEdit.clear()
|
||||
self._friends[value].reset_messages()
|
||||
self._screen.messageEdit.setPlainText(friend.curr_text)
|
||||
self._messages.clear()
|
||||
friend.load_corr()
|
||||
messages = friend.get_corr()[-PAGE_SIZE:]
|
||||
@ -425,19 +179,28 @@ class Profile(Contact, Singleton):
|
||||
data = message.get_data()
|
||||
self.create_message_item(data[0],
|
||||
convert_time(data[2]),
|
||||
friend.name if data[1] else self._name,
|
||||
data[1],
|
||||
data[3])
|
||||
elif message.get_type() == 2:
|
||||
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
|
||||
if message.get_status() is None:
|
||||
self.create_unsent_file_item(message)
|
||||
continue
|
||||
item = self.create_file_transfer_item(message)
|
||||
if message.get_status() >= 2: # active file transfer
|
||||
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
||||
try:
|
||||
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
||||
ft.set_state_changed_handler(item.update)
|
||||
ft.signal()
|
||||
except:
|
||||
print 'Incoming not started transfer - no info found'
|
||||
else: # inline
|
||||
elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline
|
||||
self.create_inline_item(message.get_data())
|
||||
else: # info message
|
||||
data = message.get_data()
|
||||
self.create_message_item(data[0],
|
||||
convert_time(data[2]),
|
||||
'',
|
||||
data[3])
|
||||
self._messages.scrollToBottom()
|
||||
if value in self._call:
|
||||
self._screen.active_call()
|
||||
@ -477,18 +240,54 @@ class Profile(Contact, Singleton):
|
||||
def is_active_online(self):
|
||||
return self._active_friend + 1 and self._friends[self._active_friend].status is not None
|
||||
|
||||
def new_name(self, number, name):
|
||||
friend = self.get_friend_by_number(number)
|
||||
tmp = friend.name
|
||||
friend.set_name(name)
|
||||
name = name.decode('utf-8')
|
||||
if friend.name == name and tmp != name:
|
||||
message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, QtGui.QApplication.UnicodeUTF8)
|
||||
message = message.format(tmp, name)
|
||||
friend.append_message(InfoMessage(message, time.time()))
|
||||
friend.actions = True
|
||||
if number == self.get_active_number():
|
||||
self.create_message_item(message, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
self.set_active(None)
|
||||
|
||||
def update(self):
|
||||
if self._active_friend + 1:
|
||||
self.set_active(self._active_friend)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend connection status callbacks
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_files(self, friend_number):
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
files = friend.get_unsent_files()
|
||||
try:
|
||||
for fl in files:
|
||||
data = fl.get_data()
|
||||
if data[1] is not None:
|
||||
self.send_inline(data[1], data[0], friend_number, True)
|
||||
else:
|
||||
self.send_file(data[0], friend_number, True)
|
||||
friend.clear_unsent_files()
|
||||
if friend_number == self.get_active_number():
|
||||
self.update()
|
||||
except Exception as ex:
|
||||
print 'Exception in file sending: ' + str(ex)
|
||||
|
||||
def friend_exit(self, friend_number):
|
||||
"""
|
||||
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 key in filter(lambda x: x[0] == friend_number, self._file_transfers.keys()):
|
||||
self._file_transfers[key].cancelled()
|
||||
del self._file_transfers[key]
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Typing notifications
|
||||
@ -498,7 +297,7 @@ class Profile(Contact, Singleton):
|
||||
"""
|
||||
Send typing notification to a friend
|
||||
"""
|
||||
if Settings.get_instance()['typing_notifications']:
|
||||
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
|
||||
friend = self._friends[self._active_friend]
|
||||
if friend.status is not None:
|
||||
self._tox.self_set_typing(friend.number, typing)
|
||||
@ -514,6 +313,24 @@ class Profile(Contact, Singleton):
|
||||
# Private messages
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def receipt(self):
|
||||
i = 0
|
||||
while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent():
|
||||
i += 1
|
||||
|
||||
def send_messages(self, friend_number):
|
||||
"""
|
||||
Send 'offline' messages to friend
|
||||
"""
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
friend.load_corr()
|
||||
messages = friend.unsent_messages()
|
||||
try:
|
||||
for message in messages:
|
||||
self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8'))
|
||||
except:
|
||||
pass
|
||||
|
||||
def split_and_send(self, number, message_type, message):
|
||||
"""
|
||||
Message splitting
|
||||
@ -545,16 +362,17 @@ class Profile(Contact, Singleton):
|
||||
:param message: text of message
|
||||
"""
|
||||
if friend_num == self.get_active_number(): # add message to list
|
||||
user_name = Profile.get_instance().get_active_name()
|
||||
self.create_message_item(message.decode('utf-8'), curr_time(), user_name, message_type)
|
||||
self.create_message_item(message.decode('utf-8'), curr_time(), MESSAGE_OWNER['FRIEND'], message_type)
|
||||
self._messages.scrollToBottom()
|
||||
self._friends[self._active_friend].append_message(
|
||||
TextMessage(message.decode('utf-8'), MESSAGE_OWNER['FRIEND'], time.time(), message_type))
|
||||
else:
|
||||
friend = self.get_friend_by_number(friend_num)
|
||||
friend.set_messages(True)
|
||||
friend.inc_messages()
|
||||
friend.append_message(
|
||||
TextMessage(message.decode('utf-8'), MESSAGE_OWNER['FRIEND'], time.time(), message_type))
|
||||
if not friend.visibility:
|
||||
self.update_filtration()
|
||||
|
||||
def send_message(self, text):
|
||||
"""
|
||||
@ -564,18 +382,20 @@ class Profile(Contact, Singleton):
|
||||
if text.startswith('/plugin '):
|
||||
plugin_support.PluginLoader.get_instance().command(text[8:])
|
||||
self._screen.messageEdit.clear()
|
||||
elif self.is_active_online() and text:
|
||||
elif text and self._active_friend + 1:
|
||||
if text.startswith('/me '):
|
||||
message_type = TOX_MESSAGE_TYPE['ACTION']
|
||||
text = text[4:]
|
||||
else:
|
||||
message_type = TOX_MESSAGE_TYPE['NORMAL']
|
||||
friend = self._friends[self._active_friend]
|
||||
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
|
||||
self.create_message_item(text, curr_time(), self._name, message_type)
|
||||
friend.inc_receipts()
|
||||
if friend.status is not None:
|
||||
self.split_and_send(friend.number, message_type, text.encode('utf-8'))
|
||||
self.create_message_item(text, curr_time(), MESSAGE_OWNER['NOT_SENT'], message_type)
|
||||
self._screen.messageEdit.clear()
|
||||
self._messages.scrollToBottom()
|
||||
friend.append_message(TextMessage(text, MESSAGE_OWNER['ME'], time.time(), message_type))
|
||||
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], time.time(), message_type))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
@ -592,10 +412,16 @@ class Profile(Contact, Singleton):
|
||||
if not self._history.friend_exists_in_db(friend.tox_id):
|
||||
self._history.add_friend_to_db(friend.tox_id)
|
||||
self._history.save_messages_to_db(friend.tox_id, messages)
|
||||
unsent_messages = friend.unsent_messages()
|
||||
unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1
|
||||
self._history.update_messages(friend.tox_id, unsent_time)
|
||||
self._history.save()
|
||||
del self._history
|
||||
|
||||
def clear_history(self, num=None):
|
||||
"""
|
||||
Clear chat history
|
||||
"""
|
||||
if num is not None:
|
||||
friend = self._friends[num]
|
||||
friend.clear_corr()
|
||||
@ -621,18 +447,33 @@ class Profile(Contact, Singleton):
|
||||
data.reverse()
|
||||
data = data[self._messages.count():self._messages.count() + PAGE_SIZE]
|
||||
for message in data:
|
||||
if message.get_type() <= 1:
|
||||
if message.get_type() <= 1: # text message
|
||||
data = message.get_data()
|
||||
self.create_message_item(data[0],
|
||||
convert_time(data[2]),
|
||||
friend.name if data[1] else self._name,
|
||||
data[1],
|
||||
data[3],
|
||||
False)
|
||||
elif message.get_type() == 2:
|
||||
elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']:
|
||||
if message.get_status() is None:
|
||||
self.create_unsent_file_item(message)
|
||||
continue
|
||||
item = self.create_file_transfer_item(message, False)
|
||||
if message.get_status() >= 2:
|
||||
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
||||
ft.set_state_changed_handler(item.update)
|
||||
if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer
|
||||
try:
|
||||
ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())]
|
||||
ft.set_state_changed_handler(item.update)
|
||||
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())
|
||||
else: # info message
|
||||
data = message.get_data()
|
||||
self.create_message_item(data[0],
|
||||
convert_time(data[2]),
|
||||
'',
|
||||
data[3])
|
||||
|
||||
def export_history(self, directory):
|
||||
self._history.export(directory)
|
||||
@ -653,8 +494,14 @@ class Profile(Contact, Singleton):
|
||||
self._screen.friends_list.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def create_message_item(self, text, time, name, message_type, append=True):
|
||||
item = MessageItem(text, time, name, message_type, self._messages)
|
||||
def create_message_item(self, text, time, owner, message_type, append=True):
|
||||
if message_type == MESSAGE_TYPE['INFO_MESSAGE']:
|
||||
name = ''
|
||||
elif owner == MESSAGE_OWNER['FRIEND']:
|
||||
name = self.get_active_name()
|
||||
else:
|
||||
name = self._name
|
||||
item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages)
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
@ -677,9 +524,24 @@ class Profile(Contact, Singleton):
|
||||
self._messages.setItemWidget(elem, item)
|
||||
return item
|
||||
|
||||
def create_inline_item(self, data, append=True):
|
||||
item = InlineImageItem(data, self._messages.width())
|
||||
def create_unsent_file_item(self, message, append=True):
|
||||
data = message.get_data()
|
||||
item = UnsentFileItem(os.path.basename(data[0]),
|
||||
os.path.getsize(data[0]) if data[1] is None else len(data[1]),
|
||||
self.name,
|
||||
data[2],
|
||||
self._messages.width())
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
else:
|
||||
self._messages.insertItem(0, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
|
||||
def create_inline_item(self, data, append=True):
|
||||
elem = QtGui.QListWidgetItem()
|
||||
item = InlineImageItem(data, self._messages.width(), elem)
|
||||
elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height()))
|
||||
if append:
|
||||
self._messages.addItem(elem)
|
||||
@ -692,6 +554,9 @@ class Profile(Contact, Singleton):
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_alias(self, num):
|
||||
"""
|
||||
Set new alias for friend
|
||||
"""
|
||||
friend = self._friends[num]
|
||||
name = friend.name.encode('utf-8')
|
||||
dialog = QtGui.QApplication.translate('MainWindow',
|
||||
@ -701,10 +566,11 @@ class Profile(Contact, Singleton):
|
||||
title = QtGui.QApplication.translate('MainWindow',
|
||||
'Set alias',
|
||||
None, QtGui.QApplication.UnicodeUTF8)
|
||||
|
||||
text, ok = QtGui.QInputDialog.getText(None,
|
||||
title,
|
||||
dialog)
|
||||
dialog,
|
||||
QtGui.QLineEdit.Normal,
|
||||
name.decode('utf-8'))
|
||||
if ok:
|
||||
settings = Settings.get_instance()
|
||||
aliases = settings['friends_aliases']
|
||||
@ -736,13 +602,15 @@ class Profile(Contact, Singleton):
|
||||
:param num: number of friend in list
|
||||
"""
|
||||
friend = self._friends[num]
|
||||
settings = Settings.get_instance()
|
||||
try:
|
||||
settings = Settings.get_instance()
|
||||
index = map(lambda x: x[0], settings['friends_aliases']).index(friend.tox_id)
|
||||
del settings['friends_aliases'][index]
|
||||
settings.save()
|
||||
except:
|
||||
pass
|
||||
if friend.tox_id in settings['notes']:
|
||||
del settings['notes'][friend.tox_id]
|
||||
settings.save()
|
||||
self.clear_history(num)
|
||||
if self._history.friend_exists_in_db(friend.tox_id):
|
||||
self._history.delete_friend_from_db(friend.tox_id)
|
||||
@ -758,6 +626,9 @@ class Profile(Contact, Singleton):
|
||||
ProfileHelper.get_instance().save_profile(data)
|
||||
|
||||
def add_friend(self, tox_id):
|
||||
"""
|
||||
Adds friend to list
|
||||
"""
|
||||
num = self._tox.friend_add_norequest(tox_id) # num - friend number
|
||||
item = self.create_friend_item()
|
||||
try:
|
||||
@ -881,7 +752,7 @@ class Profile(Contact, Singleton):
|
||||
friend.status = None
|
||||
|
||||
def close(self):
|
||||
if hasattr(self, '_stop'):
|
||||
if hasattr(self, '_call'):
|
||||
self._call.stop()
|
||||
del self._call
|
||||
|
||||
@ -900,12 +771,12 @@ class Profile(Contact, Singleton):
|
||||
settings = Settings.get_instance()
|
||||
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 == 'toxygen_inline.png' or file_name == 'utox-inline.png') and settings['allow_inline']
|
||||
inline = (file_name in ALLOWED_FILES) and settings['allow_inline']
|
||||
if inline and size < 1024 * 1024:
|
||||
self.accept_transfer(None, '', friend_number, file_number, size, True)
|
||||
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
FILE_TRANSFER_MESSAGE_STATUS['INCOMING_STARTED'],
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
||||
size,
|
||||
file_name,
|
||||
friend_number,
|
||||
@ -926,7 +797,7 @@ class Profile(Contact, Singleton):
|
||||
self.accept_transfer(None, path + '/' + new_file_name, friend_number, file_number, size)
|
||||
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
FILE_TRANSFER_MESSAGE_STATUS['INCOMING_STARTED'],
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'],
|
||||
size,
|
||||
new_file_name,
|
||||
friend_number,
|
||||
@ -934,7 +805,7 @@ class Profile(Contact, Singleton):
|
||||
else:
|
||||
tm = TransferMessage(MESSAGE_OWNER['FRIEND'],
|
||||
time.time(),
|
||||
FILE_TRANSFER_MESSAGE_STATUS['INCOMING_NOT_STARTED'],
|
||||
TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'],
|
||||
size,
|
||||
file_name,
|
||||
friend_number,
|
||||
@ -945,7 +816,7 @@ class Profile(Contact, Singleton):
|
||||
self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
else:
|
||||
friend.set_messages(True)
|
||||
friend.actions = True
|
||||
|
||||
friend.append_message(tm)
|
||||
|
||||
@ -956,8 +827,8 @@ class Profile(Contact, Singleton):
|
||||
:param file_number: file number
|
||||
:param already_cancelled: was cancelled by friend
|
||||
"""
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['CANCELLED'])
|
||||
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
TOX_FILE_TRANSFER_STATE['CANCELLED'])
|
||||
if (friend_number, file_number) in self._file_transfers:
|
||||
tr = self._file_transfers[(friend_number, file_number)]
|
||||
if not already_cancelled:
|
||||
@ -968,7 +839,17 @@ class Profile(Contact, Singleton):
|
||||
del tr
|
||||
del self._file_transfers[(friend_number, file_number)]
|
||||
else:
|
||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||
if not already_cancelled:
|
||||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||
if friend_number == self.get_active_number():
|
||||
tmp = self._messages.count() + i
|
||||
if tmp >= 0:
|
||||
self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'],
|
||||
0, -1)
|
||||
|
||||
def cancel_not_started_transfer(self, time):
|
||||
self._friends[self._active_friend].delete_one_unsent_file(time)
|
||||
self.update()
|
||||
|
||||
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
||||
"""
|
||||
@ -976,7 +857,7 @@ class Profile(Contact, Singleton):
|
||||
"""
|
||||
tr = self._file_transfers[(friend_number, file_number)]
|
||||
tr.pause(by_friend)
|
||||
t = FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND'] if by_friend else FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_USER']
|
||||
t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number, t)
|
||||
|
||||
def resume_transfer(self, friend_number, file_number, by_friend=False):
|
||||
@ -984,7 +865,11 @@ class Profile(Contact, Singleton):
|
||||
Resume transfer with specified data
|
||||
"""
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['OUTGOING'])
|
||||
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']
|
||||
@ -1010,21 +895,37 @@ class Profile(Contact, Singleton):
|
||||
if item is not None:
|
||||
rt.set_state_changed_handler(item.update)
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['INCOMING_STARTED'])
|
||||
TOX_FILE_TRANSFER_STATE['RUNNING'])
|
||||
|
||||
def send_screenshot(self, data):
|
||||
"""
|
||||
Send screenshot to current active friend
|
||||
:param data: raw data - png
|
||||
"""
|
||||
friend = self._friends[self._active_friend]
|
||||
st = SendFromBuffer(self._tox, friend.number, data, 'toxygen_inline.png')
|
||||
self.send_inline(data, 'toxygen_inline.png')
|
||||
|
||||
def send_sticker(self, path):
|
||||
with open(path) as fl:
|
||||
data = fl.read()
|
||||
self.send_inline(data, 'sticker.png')
|
||||
|
||||
def send_inline(self, data, file_name, friend_number=None, is_resend=False):
|
||||
friend_number = friend_number or self.get_active_number()
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
if friend.status is None and not is_resend:
|
||||
m = UnsentFile(file_name, data, time.time())
|
||||
friend.append_message(m)
|
||||
self.update()
|
||||
return
|
||||
elif friend.status is None and is_resend:
|
||||
raise RuntimeError()
|
||||
st = SendFromBuffer(self._tox, friend.number, data, file_name)
|
||||
self._file_transfers[(friend.number, st.get_file_number())] = st
|
||||
tm = TransferMessage(MESSAGE_OWNER['ME'],
|
||||
time.time(),
|
||||
FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND'], # OUTGOING NOT STARTED
|
||||
TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
|
||||
len(data),
|
||||
'toxygen_inline.png',
|
||||
file_name,
|
||||
friend.number,
|
||||
st.get_file_number())
|
||||
item = self.create_file_transfer_item(tm)
|
||||
@ -1032,20 +933,28 @@ class Profile(Contact, Singleton):
|
||||
st.set_state_changed_handler(item.update)
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
def send_file(self, path, number=None):
|
||||
def send_file(self, path, number=None, is_resend=False):
|
||||
"""
|
||||
Send file to current active friend
|
||||
:param path: file path
|
||||
:param number: friend_number
|
||||
:param is_resend: is 'offline' message
|
||||
"""
|
||||
friend_number = number or self.get_active_number()
|
||||
if self.get_friend_by_number(friend_number).status is None:
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
if friend.status is None and not is_resend:
|
||||
m = UnsentFile(path, None, time.time())
|
||||
friend.append_message(m)
|
||||
self.update()
|
||||
return
|
||||
elif friend.status is None and is_resend:
|
||||
print 'Error in sending'
|
||||
raise RuntimeError()
|
||||
st = SendTransfer(path, self._tox, friend_number)
|
||||
self._file_transfers[(friend_number, st.get_file_number())] = st
|
||||
tm = TransferMessage(MESSAGE_OWNER['ME'],
|
||||
time.time(),
|
||||
FILE_TRANSFER_MESSAGE_STATUS['PAUSED_BY_FRIEND'], # OUTGOING NOT STARTED
|
||||
TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'],
|
||||
os.path.getsize(path),
|
||||
os.path.basename(path),
|
||||
friend_number,
|
||||
@ -1056,46 +965,61 @@ class Profile(Contact, Singleton):
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
def incoming_chunk(self, friend_number, file_number, position, data):
|
||||
"""
|
||||
Incoming chunk
|
||||
"""
|
||||
if (friend_number, file_number) in self._file_transfers:
|
||||
transfer = self._file_transfers[(friend_number, file_number)]
|
||||
transfer.write_chunk(position, data)
|
||||
if transfer.state in (2, 3): # finished or cancelled
|
||||
if transfer.state not in ACTIVE_FILE_TRANSFERS: # finished or cancelled
|
||||
if type(transfer) is ReceiveAvatar:
|
||||
self.get_friend_by_number(friend_number).load_avatar()
|
||||
self.set_active(None)
|
||||
elif type(transfer) is ReceiveToBuffer:
|
||||
elif type(transfer) is ReceiveToBuffer: # inline image
|
||||
inline = InlineImage(transfer.get_data())
|
||||
i = self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['FINISHED'],
|
||||
TOX_FILE_TRANSFER_STATE['FINISHED'],
|
||||
inline)
|
||||
if friend_number == self.get_active_number():
|
||||
count = self._messages.count()
|
||||
item = InlineImageItem(transfer.get_data(), self._messages.width())
|
||||
elem = QtGui.QListWidgetItem()
|
||||
elem.setSizeHint(QtCore.QSize(600, item.height()))
|
||||
self._messages.insertItem(count + i + 1, elem)
|
||||
self._messages.setItemWidget(elem, item)
|
||||
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,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['FINISHED'])
|
||||
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 in (2, 3): # finished or cancelled
|
||||
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())
|
||||
self.get_friend_by_number(friend_number).update_transfer_data(file_number,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['FINISHED'],
|
||||
inline)
|
||||
self.update()
|
||||
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,
|
||||
FILE_TRANSFER_MESSAGE_STATUS['FINISHED'])
|
||||
TOX_FILE_TRANSFER_STATE['FINISHED'])
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars support
|
||||
@ -1119,7 +1043,7 @@ class Profile(Contact, Singleton):
|
||||
:param size: size of avatar or 0 (default avatar)
|
||||
"""
|
||||
ra = ReceiveAvatar(self._tox, friend_number, size, file_number)
|
||||
if ra.state != TOX_FILE_TRANSFER_STATE['CANCELED']:
|
||||
if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self._file_transfers[(friend_number, file_number)] = ra
|
||||
else:
|
||||
self.get_friend_by_number(friend_number).load_avatar()
|
||||
@ -1151,6 +1075,15 @@ class Profile(Contact, Singleton):
|
||||
if num not in self._call and self.is_active_online(): # start call
|
||||
self._call(num, audio, video)
|
||||
self._screen.active_call()
|
||||
if video:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
else:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
self._friends[self._active_friend].append_message(InfoMessage(text, time.time()))
|
||||
self.create_message_item(text, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
elif num in self._call: # finish or cancel call if you call with active friend
|
||||
self.stop_call(num, False)
|
||||
|
||||
@ -1159,15 +1092,20 @@ class Profile(Contact, Singleton):
|
||||
Incoming call from friend. Only audio is supported now
|
||||
"""
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
if video:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
else:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None,
|
||||
QtGui.QApplication.UnicodeUTF8)
|
||||
friend.append_message(InfoMessage(text, time.time()))
|
||||
self._incoming_calls.add(friend_number)
|
||||
if friend_number == self.get_active_number():
|
||||
self._screen.incoming_call()
|
||||
self.create_message_item(text, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
else:
|
||||
friend.set_messages(True)
|
||||
if video:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None, QtGui.QApplication.UnicodeUTF8)
|
||||
else:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None, QtGui.QApplication.UnicodeUTF8)
|
||||
friend.actions = True
|
||||
self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name)
|
||||
self._call_widget.set_pixmap(friend.get_pixmap())
|
||||
self._call_widget.show()
|
||||
@ -1188,10 +1126,18 @@ class Profile(Contact, Singleton):
|
||||
"""
|
||||
if friend_number in self._incoming_calls:
|
||||
self._incoming_calls.remove(friend_number)
|
||||
text = QtGui.QApplication.translate("incoming_call", "Call declined", None, QtGui.QApplication.UnicodeUTF8)
|
||||
else:
|
||||
text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8)
|
||||
self._screen.call_finished()
|
||||
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
||||
if hasattr(self, '_call_widget'):
|
||||
del self._call_widget
|
||||
friend = self.get_friend_by_number(friend_number)
|
||||
friend.append_message(InfoMessage(text, time.time()))
|
||||
if friend_number == self.get_active_number():
|
||||
self.create_message_item(text, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
self._messages.scrollToBottom()
|
||||
|
||||
|
||||
def tox_factory(data=None, settings=None):
|
||||
|
@ -5,6 +5,7 @@ import locale
|
||||
from util import Singleton, curr_directory, log
|
||||
import pyaudio
|
||||
from toxencryptsave import LibToxEncryptSave
|
||||
import smileys
|
||||
|
||||
|
||||
class Settings(Singleton, dict):
|
||||
@ -13,24 +14,25 @@ class Settings(Singleton, dict):
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.path = ProfileHelper.get_path() + str(name) + '.json'
|
||||
self.path = ProfileHelper.get_path() + unicode(name) + '.json'
|
||||
self.name = name
|
||||
if os.path.isfile(self.path):
|
||||
with open(self.path) as fl:
|
||||
with open(self.path, 'rb') as fl:
|
||||
data = fl.read()
|
||||
inst = LibToxEncryptSave.get_instance()
|
||||
if inst.has_password():
|
||||
data = inst.pass_decrypt(data)
|
||||
try:
|
||||
if inst.is_data_encrypted(data):
|
||||
data = inst.pass_decrypt(data)
|
||||
info = json.loads(data)
|
||||
except Exception as ex:
|
||||
info = Settings.get_default_settings()
|
||||
log('Parsing settings error: ' + str(ex))
|
||||
super(self.__class__, self).__init__(info)
|
||||
super(Settings, self).__init__(info)
|
||||
self.upgrade()
|
||||
else:
|
||||
super(self.__class__, self).__init__(Settings.get_default_settings())
|
||||
super(Settings, self).__init__(Settings.get_default_settings())
|
||||
self.save()
|
||||
smileys.SmileyLoader(self)
|
||||
p = pyaudio.PyAudio()
|
||||
self.audio = {'input': p.get_default_input_device_info()['index'],
|
||||
'output': p.get_default_output_device_info()['index']}
|
||||
@ -44,13 +46,42 @@ class Settings(Singleton, dict):
|
||||
auto = json.loads(data)
|
||||
if 'path' in auto and 'name' in auto:
|
||||
return unicode(auto['path']), unicode(auto['name'])
|
||||
return '', ''
|
||||
|
||||
@staticmethod
|
||||
def set_auto_profile(path, name):
|
||||
p = Settings.get_default_path() + 'toxygen.json'
|
||||
data = json.dumps({'path': unicode(path.decode(locale.getpreferredencoding())), 'name': unicode(name)})
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
data['path'] = unicode(path.decode(locale.getpreferredencoding()))
|
||||
data['name'] = unicode(name)
|
||||
with open(p, 'w') as fl:
|
||||
fl.write(data)
|
||||
fl.write(json.dumps(data))
|
||||
|
||||
@staticmethod
|
||||
def reset_auto_profile():
|
||||
p = Settings.get_default_path() + 'toxygen.json'
|
||||
with open(p) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
if 'path' in data:
|
||||
del data['path']
|
||||
del data['name']
|
||||
with open(p, 'w') as fl:
|
||||
fl.write(json.dumps(data))
|
||||
|
||||
@staticmethod
|
||||
def is_active_profile(path, name):
|
||||
path = path.decode(locale.getpreferredencoding()) + name + '.tox'
|
||||
settings = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(settings):
|
||||
with open(settings) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
if 'active_profile' in data:
|
||||
return path in data['active_profile']
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_default_settings():
|
||||
@ -80,16 +111,26 @@ class Settings(Singleton, dict):
|
||||
'typing_notifications': False,
|
||||
'calls_sound': True,
|
||||
'blocked': [],
|
||||
'plugins': []
|
||||
'plugins': [],
|
||||
'notes': {},
|
||||
'smileys': True,
|
||||
'smiley_pack': 'default',
|
||||
'mirror_mode': False,
|
||||
'width': 920,
|
||||
'height': 500,
|
||||
'x': 400,
|
||||
'y': 400,
|
||||
'message_font_size': 14,
|
||||
'unread_color': 'red'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def supported_languages():
|
||||
return [
|
||||
('English', 'en_EN'),
|
||||
('Russian', 'ru_RU'),
|
||||
('French', 'fr_FR')
|
||||
]
|
||||
return {
|
||||
'English': 'en_EN',
|
||||
'Russian': 'ru_RU',
|
||||
'French': 'fr_FR'
|
||||
}
|
||||
|
||||
def upgrade(self):
|
||||
default = Settings.get_default_settings()
|
||||
@ -104,7 +145,7 @@ class Settings(Singleton, dict):
|
||||
inst = LibToxEncryptSave.get_instance()
|
||||
if inst.has_password():
|
||||
text = inst.pass_encrypt(text)
|
||||
with open(self.path, 'w') as fl:
|
||||
with open(self.path, 'wb') as fl:
|
||||
fl.write(text)
|
||||
|
||||
def close(self):
|
||||
@ -215,18 +256,6 @@ class ProfileHelper(Singleton):
|
||||
result.append((path + '/', name))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def is_active_profile(path, name):
|
||||
path = path.decode(locale.getpreferredencoding()) + name + '.tox'
|
||||
settings = Settings.get_default_path() + 'toxygen.json'
|
||||
if os.path.isfile(settings):
|
||||
with open(settings) as fl:
|
||||
data = fl.read()
|
||||
data = json.loads(data)
|
||||
if 'active_profile' in data:
|
||||
return path in data['active_profile']
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_path():
|
||||
return ProfileHelper.get_instance().get_dir()
|
||||
|
90
src/smileys.py
Normal file
@ -0,0 +1,90 @@
|
||||
import util
|
||||
import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
try:
|
||||
from PySide import QtCore
|
||||
except ImportError:
|
||||
from PyQt4 import QtCore
|
||||
|
||||
|
||||
class SmileyLoader(util.Singleton):
|
||||
"""
|
||||
Class which loads smileys packs and insert smileys into messages
|
||||
"""
|
||||
|
||||
def __init__(self, settings):
|
||||
self._settings = settings
|
||||
self._curr_pack = None # current pack name
|
||||
self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str)
|
||||
self._list = [] # smileys list without duplicates
|
||||
self.load_pack()
|
||||
|
||||
def load_pack(self):
|
||||
"""
|
||||
Loads smiley pack
|
||||
"""
|
||||
pack_name = self._settings['smiley_pack']
|
||||
if self._settings['smileys'] and self._curr_pack != pack_name:
|
||||
self._curr_pack = pack_name
|
||||
path = self.get_smileys_path() + 'config.json'
|
||||
try:
|
||||
with open(path) as fl:
|
||||
self._smileys = json.loads(fl.read())
|
||||
fl.seek(0)
|
||||
tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
|
||||
print 'Smiley pack {} loaded'.format(pack_name)
|
||||
keys, values, self._list = [], [], []
|
||||
for key, value in tmp.items():
|
||||
value = self.get_smileys_path() + value
|
||||
if value not in values:
|
||||
keys.append(key)
|
||||
values.append(value)
|
||||
self._list = zip(keys, values)
|
||||
except Exception as ex:
|
||||
self._smileys = {}
|
||||
self._list = []
|
||||
print 'Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex)
|
||||
|
||||
def get_smileys_path(self):
|
||||
return util.curr_directory() + '/smileys/' + self._curr_pack + '/'
|
||||
|
||||
def get_packs_list(self):
|
||||
d = util.curr_directory() + '/smileys/'
|
||||
return [x[1] for x in os.walk(d)][0]
|
||||
|
||||
def get_smileys(self):
|
||||
return self._list[:]
|
||||
|
||||
def add_smileys_to_text(self, text, edit):
|
||||
"""
|
||||
Adds smileys to text
|
||||
:param text: message
|
||||
:param edit: MessageEdit instance
|
||||
:return text with smileys
|
||||
"""
|
||||
if not self._settings['smileys'] or not len(self._smileys):
|
||||
return text
|
||||
arr = text.split(' ')
|
||||
for i in range(len(arr)):
|
||||
if arr[i] in self._smileys:
|
||||
file_name = self._smileys[arr[i]] # image name
|
||||
arr[i] = u'<img title=\"{}\" src=\"{}\" />'.format(arr[i], file_name)
|
||||
if file_name.endswith('.gif'): # animated smiley
|
||||
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
|
||||
return ' '.join(arr)
|
||||
|
||||
|
||||
def sticker_loader():
|
||||
"""
|
||||
:return list of stickers
|
||||
"""
|
||||
result = []
|
||||
d = util.curr_directory() + '/stickers/'
|
||||
keys = [x[1] for x in os.walk(d)][0]
|
||||
for key in keys:
|
||||
path = d + key + '/'
|
||||
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
|
||||
files = map(lambda f: unicode(path + f), files)
|
||||
result.extend(files)
|
||||
return result
|
BIN
src/smileys/animated/_ao.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/smileys/animated/aa.gif
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/smileys/animated/ab.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/smileys/animated/ac.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/smileys/animated/ad.gif
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/smileys/animated/ae.gif
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/smileys/animated/af.gif
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/smileys/animated/ag.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/smileys/animated/ah.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/smileys/animated/ai.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/smileys/animated/aj.gif
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src/smileys/animated/ak.gif
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
src/smileys/animated/al.gif
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/smileys/animated/am.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/smileys/animated/an.gif
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
src/smileys/animated/ao.gif
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
src/smileys/animated/ap.gif
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src/smileys/animated/aq.gif
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/smileys/animated/ar.gif
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/smileys/animated/as.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/smileys/animated/at.gif
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/smileys/animated/au.gif
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/smileys/animated/av.gif
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
src/smileys/animated/aw.gif
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/smileys/animated/ax.gif
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
src/smileys/animated/ay.gif
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/smileys/animated/az.gif
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/smileys/animated/ba.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/smileys/animated/bb.gif
Normal file
After Width: | Height: | Size: 868 B |
BIN
src/smileys/animated/bc.gif
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/smileys/animated/bd.gif
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
src/smileys/animated/be.gif
Normal file
After Width: | Height: | Size: 1010 B |
BIN
src/smileys/animated/bf.gif
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
src/smileys/animated/bg.gif
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
src/smileys/animated/bh.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/smileys/animated/bi.gif
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
src/smileys/animated/bj.gif
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src/smileys/animated/bk.gif
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src/smileys/animated/bl.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bm.gif
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
src/smileys/animated/bn.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bo.gif
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
src/smileys/animated/bp.gif
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/smileys/animated/bq.gif
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
src/smileys/animated/br.gif
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
src/smileys/animated/bs.gif
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/smileys/animated/bt.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/smileys/animated/bu.gif
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/smileys/animated/bv.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
src/smileys/animated/bw.gif
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
src/smileys/animated/bx.gif
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
src/smileys/animated/by.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/smileys/animated/bz.gif
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/smileys/animated/ca.gif
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
src/smileys/animated/cb.gif
Normal file
After Width: | Height: | Size: 8.0 KiB |