Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4d806f5fc | |||
4854b6151d | |||
c755b4a52a | |||
7505b06ddf | |||
ace663804e | |||
2ff41313f8 | |||
1e1772e306 | |||
300b28bdfa | |||
1f4e81af35 | |||
335d646c42 | |||
b6f5123495 |
57
README.md
57
README.md
@ -14,35 +14,34 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
|
|||||||
|
|
||||||
### Features:
|
### Features:
|
||||||
|
|
||||||
- [x] 1v1 messages
|
- 1v1 messages
|
||||||
- [x] File transfers
|
- File transfers
|
||||||
- [x] Audio calls
|
- Audio calls
|
||||||
- [x] Video calls
|
- Video calls
|
||||||
- [x] Plugins support
|
- Plugins support
|
||||||
- [x] Chat history
|
- Desktop sharing
|
||||||
- [x] Emoticons
|
- Chat history
|
||||||
- [x] Stickers
|
- Emoticons
|
||||||
- [x] Screenshots
|
- Stickers
|
||||||
- [x] Name lookups (toxme.io support)
|
- Screenshots
|
||||||
- [x] Save file encryption
|
- Name lookups (toxme.io support)
|
||||||
- [x] Profile import and export
|
- Save file encryption
|
||||||
- [x] Faux offline messaging
|
- Profile import and export
|
||||||
- [x] Faux offline file transfers
|
- Faux offline messaging
|
||||||
- [x] Inline images
|
- Faux offline file transfers
|
||||||
- [x] Message splitting
|
- Inline images
|
||||||
- [x] Proxy support
|
- Message splitting
|
||||||
- [x] Avatars
|
- Proxy support
|
||||||
- [x] Multiprofile
|
- Avatars
|
||||||
- [x] Multilingual
|
- Multiprofile
|
||||||
- [x] Sound notifications
|
- Multilingual
|
||||||
- [x] Contact aliases
|
- Sound notifications
|
||||||
- [x] Contact blocking
|
- Contact aliases
|
||||||
- [x] Typing notifications
|
- Contact blocking
|
||||||
- [x] Changing nospam
|
- Typing notifications
|
||||||
- [x] File resuming
|
- Changing nospam
|
||||||
- [x] Read receipts
|
- File resuming
|
||||||
- [ ] Desktop sharing
|
- Read receipts
|
||||||
- [ ] Group chats
|
|
||||||
|
|
||||||
### Downloads
|
### Downloads
|
||||||
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
[Releases](https://github.com/toxygen-project/toxygen/releases)
|
||||||
|
@ -17,7 +17,7 @@ Run app using ``toxygen`` command.
|
|||||||
2. Install PortAudio:
|
2. Install PortAudio:
|
||||||
``sudo apt-get install portaudio19-dev``
|
``sudo apt-get install portaudio19-dev``
|
||||||
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
|
3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5``
|
||||||
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html)
|
4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv``
|
||||||
5. Install toxygen:
|
5. Install toxygen:
|
||||||
``sudo pip3 install toxygen``
|
``sudo pip3 install toxygen``
|
||||||
6. Run toxygen using ``toxygen`` command.
|
6. Run toxygen using ``toxygen`` command.
|
||||||
@ -38,10 +38,10 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r
|
|||||||
2. Install PyQt5: ``pip install pyqt5``
|
2. Install PyQt5: ``pip install pyqt5``
|
||||||
3. Install PyAudio: ``pip install pyaudio``
|
3. Install PyAudio: ``pip install pyaudio``
|
||||||
4. Install numpy: ``pip install numpy``
|
4. Install numpy: ``pip install numpy``
|
||||||
5. Install OpenCV: ``pip install opencv-python`` or via ``sudo apt-get install python3-opencv``
|
5. Install OpenCV: ``pip install opencv-python``
|
||||||
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip)
|
||||||
7. Unpack archive
|
7. Unpack archive
|
||||||
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\
|
8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\
|
||||||
9. Run \toxygen\main.py.
|
9. Run \toxygen\main.py.
|
||||||
|
|
||||||
Optional: install toxygen using setup.py: ``python setup.py install``
|
Optional: install toxygen using setup.py: ``python setup.py install``
|
||||||
|
17
setup.py
17
setup.py
@ -8,10 +8,23 @@ import sys
|
|||||||
|
|
||||||
version = program_version + '.0'
|
version = program_version + '.0'
|
||||||
|
|
||||||
MODULES = ['PyQt5', 'PyAudio', 'numpy']
|
|
||||||
|
|
||||||
if system() == 'Windows':
|
if system() == 'Windows':
|
||||||
MODULES.append('opencv-python')
|
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python']
|
||||||
|
else:
|
||||||
|
MODULES = []
|
||||||
|
try:
|
||||||
|
import pyaudio
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('PyAudio')
|
||||||
|
try:
|
||||||
|
import PyQt5
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('PyQt5')
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except ImportError:
|
||||||
|
MODULES.append('numpy')
|
||||||
|
|
||||||
|
|
||||||
class InstallScript(install):
|
class InstallScript(install):
|
||||||
|
@ -6,6 +6,7 @@ from toxav_enums import *
|
|||||||
import cv2
|
import cv2
|
||||||
import itertools
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import screen_sharing
|
||||||
# TODO: play sound until outgoing call will be started or cancelled
|
# TODO: play sound until outgoing call will be started or cancelled
|
||||||
|
|
||||||
|
|
||||||
@ -203,10 +204,14 @@ class AV:
|
|||||||
self._video_width = s.video['width']
|
self._video_width = s.video['width']
|
||||||
self._video_height = s.video['height']
|
self._video_height = s.video['height']
|
||||||
|
|
||||||
self._video = cv2.VideoCapture(s.video['device'])
|
if s.video['device'] == -1:
|
||||||
self._video.set(cv2.CAP_PROP_FPS, 25)
|
self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'],
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
s.video['width'], s.video['height'])
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
else:
|
||||||
|
self._video = cv2.VideoCapture(s.video['device'])
|
||||||
|
self._video.set(cv2.CAP_PROP_FPS, 25)
|
||||||
|
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
||||||
|
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
||||||
|
|
||||||
self._video_thread = threading.Thread(target=self.send_video)
|
self._video_thread = threading.Thread(target=self.send_video)
|
||||||
self._video_thread.start()
|
self._video_thread.start()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
import smileys
|
import smileys
|
||||||
import util
|
import util
|
||||||
@ -71,38 +71,12 @@ class MessageArea(QtWidgets.QPlainTextEdit):
|
|||||||
self.insertPlainText(text)
|
self.insertPlainText(text)
|
||||||
|
|
||||||
|
|
||||||
class ScreenShotWindow(QtWidgets.QWidget):
|
class ScreenShotWindow(RubberBandWindow):
|
||||||
|
|
||||||
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()
|
|
||||||
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
|
|
||||||
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
def closeEvent(self, *args):
|
||||||
if self.parent.isHidden():
|
if self.parent.isHidden():
|
||||||
self.parent.show()
|
self.parent.show()
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
|
||||||
self.origin = event.pos()
|
|
||||||
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
|
||||||
self.rubberband.show()
|
|
||||||
QtWidgets.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):
|
def mouseReleaseEvent(self, event):
|
||||||
if self.rubberband.isVisible():
|
if self.rubberband.isVisible():
|
||||||
self.rubberband.hide()
|
self.rubberband.hide()
|
||||||
@ -121,13 +95,6 @@ class ScreenShotWindow(QtWidgets.QWidget):
|
|||||||
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
Profile.get_instance().send_screenshot(bytes(byte_array.data()))
|
||||||
self.close()
|
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(QtWidgets.QWidget):
|
class SmileyWindow(QtWidgets.QWidget):
|
||||||
"""
|
"""
|
||||||
|
@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
from settings import *
|
from settings import *
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
from util import curr_directory, copy
|
from util import curr_directory, copy
|
||||||
from widgets import CenteredWidget, DataLabel, LineEdit
|
from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow
|
||||||
import pyaudio
|
import pyaudio
|
||||||
import toxes
|
import toxes
|
||||||
import plugin_support
|
import plugin_support
|
||||||
@ -248,11 +248,11 @@ class ProfileSettings(CenteredWidget):
|
|||||||
def set_avatar(self):
|
def set_avatar(self):
|
||||||
choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar")
|
choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar")
|
||||||
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)',
|
name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)',
|
||||||
QtGui.QComboBoxQtWidgets.QFileDialog.DontUseNativeDialog)
|
options=QtWidgets.QFileDialog.DontUseNativeDialog)
|
||||||
if name[0]:
|
if name[0]:
|
||||||
bitmap = QtGui.QPixmap(name[0])
|
bitmap = QtGui.QPixmap(name[0])
|
||||||
bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio,
|
bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio,
|
||||||
mode=QtCore.Qt.SmoothTransformation)
|
transformMode=QtCore.Qt.SmoothTransformation)
|
||||||
|
|
||||||
byte_array = QtCore.QByteArray()
|
byte_array = QtCore.QByteArray()
|
||||||
buffer = QtCore.QBuffer(byte_array)
|
buffer = QtCore.QBuffer(byte_array)
|
||||||
@ -261,14 +261,14 @@ class ProfileSettings(CenteredWidget):
|
|||||||
Profile.get_instance().set_avatar(bytes(byte_array.data()))
|
Profile.get_instance().set_avatar(bytes(byte_array.data()))
|
||||||
|
|
||||||
def export_profile(self):
|
def export_profile(self):
|
||||||
directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog,
|
directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(),
|
||||||
dir=curr_directory()) + '/'
|
QtWidgets.QFileDialog.DontUseNativeDialog) + '/'
|
||||||
if directory != '/':
|
if directory != '/':
|
||||||
reply = QtWidgets.QMessageBox.question(None,
|
reply = QtWidgets.QMessageBox.question(None,
|
||||||
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
||||||
'Use new path'),
|
'Use new path'),
|
||||||
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
QtWidgets.QApplication.translate("ProfileSettingsForm",
|
||||||
'Do you want to move your profile to this location?'),
|
'Do you want to move your profile to this location?'),
|
||||||
QtWidgets.QMessageBox.Yes,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtWidgets.QMessageBox.No)
|
QtWidgets.QMessageBox.No)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
@ -802,6 +802,18 @@ class AudioSettings(CenteredWidget):
|
|||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
|
||||||
|
class DesktopAreaSelectionWindow(RubberBandWindow):
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if self.rubberband.isVisible():
|
||||||
|
self.rubberband.hide()
|
||||||
|
rect = self.rubberband.geometry()
|
||||||
|
width, height = rect.width(), rect.height()
|
||||||
|
if width >= 8 and height >= 8:
|
||||||
|
self.parent.save(rect.x(), rect.y(), width, height)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
class VideoSettings(CenteredWidget):
|
class VideoSettings(CenteredWidget):
|
||||||
"""
|
"""
|
||||||
Audio calls settings form
|
Audio calls settings form
|
||||||
@ -812,6 +824,7 @@ class VideoSettings(CenteredWidget):
|
|||||||
self.initUI()
|
self.initUI()
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
self.center()
|
self.center()
|
||||||
|
self.desktopAreaSelection = None
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setObjectName("videoSettingsForm")
|
self.setObjectName("videoSettingsForm")
|
||||||
@ -831,9 +844,16 @@ class VideoSettings(CenteredWidget):
|
|||||||
self.input = QtWidgets.QComboBox(self)
|
self.input = QtWidgets.QComboBox(self)
|
||||||
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
self.input.setGeometry(QtCore.QRect(25, 30, 350, 30))
|
||||||
self.input.currentIndexChanged.connect(self.selectionChanged)
|
self.input.currentIndexChanged.connect(self.selectionChanged)
|
||||||
|
self.button = QtWidgets.QPushButton(self)
|
||||||
|
self.button.clicked.connect(self.button_clicked)
|
||||||
|
self.button.setGeometry(QtCore.QRect(25, 70, 350, 30))
|
||||||
import cv2
|
import cv2
|
||||||
self.devices = []
|
self.devices = [-1]
|
||||||
self.frame_max_sizes = []
|
screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
size = screen.size()
|
||||||
|
self.frame_max_sizes = [(size.width(), size.height())]
|
||||||
|
desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop")
|
||||||
|
self.input.addItem(desktop)
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
v = cv2.VideoCapture(i)
|
v = cv2.VideoCapture(i)
|
||||||
if v.isOpened():
|
if v.isOpened():
|
||||||
@ -855,6 +875,10 @@ class VideoSettings(CenteredWidget):
|
|||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
|
self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings"))
|
||||||
self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
|
self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:"))
|
||||||
|
self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region"))
|
||||||
|
|
||||||
|
def button_clicked(self):
|
||||||
|
self.desktopAreaSelection = DesktopAreaSelectionWindow(self)
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
try:
|
try:
|
||||||
@ -867,7 +891,23 @@ class VideoSettings(CenteredWidget):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('Saving video settings error: ' + str(ex))
|
print('Saving video settings error: ' + str(ex))
|
||||||
|
|
||||||
|
def save(self, x, y, width, height):
|
||||||
|
self.desktopAreaSelection = None
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
settings.video['device'] = -1
|
||||||
|
settings.video['width'] = width
|
||||||
|
settings.video['height'] = height
|
||||||
|
settings.video['x'] = x
|
||||||
|
settings.video['y'] = y
|
||||||
|
settings.save()
|
||||||
|
|
||||||
def selectionChanged(self):
|
def selectionChanged(self):
|
||||||
|
if self.input.currentIndex() == 0:
|
||||||
|
self.button.setVisible(True)
|
||||||
|
self.video_size.setVisible(False)
|
||||||
|
else:
|
||||||
|
self.button.setVisible(False)
|
||||||
|
self.video_size.setVisible(True)
|
||||||
width, height = self.frame_max_sizes[self.input.currentIndex()]
|
width, height = self.frame_max_sizes[self.input.currentIndex()]
|
||||||
self.video_size.clear()
|
self.video_size.clear()
|
||||||
dims = [
|
dims = [
|
||||||
|
@ -663,15 +663,15 @@ class Profile(basecontact.BaseContact, Singleton):
|
|||||||
friend = self._contacts[num]
|
friend = self._contacts[num]
|
||||||
name = friend.name
|
name = friend.name
|
||||||
dialog = QtWidgets.QApplication.translate('MainWindow',
|
dialog = QtWidgets.QApplication.translate('MainWindow',
|
||||||
"Enter new alias for friend {} or leave empty to use friend's name:")
|
"Enter new alias for friend {} or leave empty to use friend's name:")
|
||||||
dialog = dialog.format(name)
|
dialog = dialog.format(name)
|
||||||
title = QtWidgets.QApplication.translate('MainWindow',
|
title = QtWidgets.QApplication.translate('MainWindow',
|
||||||
'Set alias')
|
'Set alias')
|
||||||
text, ok = QtGui.QInputDialog.getText(None,
|
text, ok = QtWidgets.QInputDialog.getText(None,
|
||||||
title,
|
title,
|
||||||
dialog,
|
dialog,
|
||||||
QtWidgets.QLineEdit.Normal,
|
QtWidgets.QLineEdit.Normal,
|
||||||
name)
|
name)
|
||||||
if ok:
|
if ok:
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
aliases = settings['friends_aliases']
|
aliases = settings['friends_aliases']
|
||||||
|
22
toxygen/screen_sharing.py
Normal file
22
toxygen/screen_sharing.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import numpy as np
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
|
||||||
|
class DesktopGrabber:
|
||||||
|
|
||||||
|
def __init__(self, x, y, width, height):
|
||||||
|
self._x = x
|
||||||
|
self._y = y
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
self._width -= width % 4
|
||||||
|
self._height -= height % 4
|
||||||
|
self._screen = QtWidgets.QApplication.primaryScreen()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
|
||||||
|
image = pixmap.toImage()
|
||||||
|
s = image.bits().asstring(self._width * self._height * 4)
|
||||||
|
arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
|
||||||
|
|
||||||
|
return True, arr
|
@ -47,7 +47,7 @@ class Settings(dict, Singleton):
|
|||||||
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1,
|
||||||
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
'output': p.get_default_output_device_info()['index'] if output_devices else -1,
|
||||||
'enabled': input_devices and output_devices}
|
'enabled': input_devices and output_devices}
|
||||||
self.video = {'device': 0, 'width': 640, 'height': 480}
|
self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_auto_profile():
|
def get_auto_profile():
|
||||||
@ -57,7 +57,10 @@ class Settings(dict, Singleton):
|
|||||||
data = fl.read()
|
data = fl.read()
|
||||||
auto = json.loads(data)
|
auto = json.loads(data)
|
||||||
if 'path' in auto and 'name' in auto:
|
if 'path' in auto and 'name' in auto:
|
||||||
return str(auto['path']), str(auto['name'])
|
path = str(auto['path'])
|
||||||
|
name = str(auto['name'])
|
||||||
|
if os.path.isfile(append_slash(path) + name + '.tox'):
|
||||||
|
return path, name
|
||||||
return '', ''
|
return '', ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -2,3 +2,28 @@
|
|||||||
{
|
{
|
||||||
padding-left: 22px;
|
padding-left: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageEdit
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit::focus
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageItem::focus
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit:hover
|
||||||
|
{
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEdit
|
||||||
|
{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import sys
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
program_version = '0.3.0'
|
program_version = '0.3.1'
|
||||||
|
|
||||||
|
|
||||||
def cached(func):
|
def cached(func):
|
||||||
|
@ -77,6 +77,42 @@ class RubberBand(QtWidgets.QRubberBand):
|
|||||||
self.painter.end()
|
self.painter.end()
|
||||||
|
|
||||||
|
|
||||||
|
class RubberBandWindow(QtWidgets.QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__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()
|
||||||
|
self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint)
|
||||||
|
self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
def mousePressEvent(self, event):
|
||||||
|
self.origin = event.pos()
|
||||||
|
self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize()))
|
||||||
|
self.rubberband.show()
|
||||||
|
QtWidgets.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 keyPressEvent(self, event):
|
||||||
|
if event.key() == QtCore.Qt.Key_Escape:
|
||||||
|
self.rubberband.setHidden(True)
|
||||||
|
self.close()
|
||||||
|
else:
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
def create_menu(menu):
|
def create_menu(menu):
|
||||||
"""
|
"""
|
||||||
:return translated menu
|
:return translated menu
|
||||||
|
Reference in New Issue
Block a user