tryton 7.4.7__py3-none-any.whl → 7.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tryton might be problematic. Click here for more details.
- tryton/__init__.py +1 -1
- tryton/bus.py +113 -69
- tryton/chat.py +179 -0
- tryton/client.py +7 -0
- tryton/common/__init__.py +15 -11
- tryton/common/button.py +1 -1
- tryton/common/cellrendererfloat.py +1 -1
- tryton/common/cellrenderertext.py +2 -2
- tryton/common/common.py +91 -19
- tryton/common/domain_parser.py +8 -6
- tryton/common/environment.py +2 -2
- tryton/common/number_entry.py +12 -6
- tryton/common/selection.py +1 -1
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +26 -16
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +29 -18
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +28 -16
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -18
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +25 -16
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +26 -16
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +25 -16
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +25 -16
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +28 -18
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +23 -16
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +29 -18
- tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.po +26 -18
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +30 -18
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +25 -16
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +31 -18
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +148 -177
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +32 -19
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +28 -16
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +33 -18
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +25 -16
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -18
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +27 -18
- tryton/data/pixmaps/tryton/tryton-chat.svg +1 -0
- tryton/data/pixmaps/tryton/tryton-note.svg +1 -4
- tryton/gui/window/attachment.py +2 -2
- tryton/gui/window/board.py +1 -1
- tryton/gui/window/form.py +57 -10
- tryton/gui/window/note.py +2 -2
- tryton/gui/window/tabcontent.py +8 -1
- tryton/gui/window/view_board/action.py +1 -1
- tryton/gui/window/view_form/model/field.py +38 -29
- tryton/gui/window/view_form/model/group.py +4 -4
- tryton/gui/window/view_form/model/record.py +17 -4
- tryton/gui/window/view_form/screen/screen.py +25 -7
- tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +1 -1
- tryton/gui/window/view_form/view/calendar_gtk/toolbar.py +1 -1
- tryton/gui/window/view_form/view/form.py +2 -1
- tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/calendar_.py +4 -4
- tryton/gui/window/view_form/view/form_gtk/char.py +42 -5
- tryton/gui/window/view_form/view/form_gtk/checkbox.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +53 -15
- tryton/gui/window/view_form/view/form_gtk/float.py +3 -7
- tryton/gui/window/view_form/view/form_gtk/image.py +4 -4
- tryton/gui/window/view_form/view/form_gtk/integer.py +1 -1
- tryton/gui/window/view_form/view/form_gtk/many2many.py +3 -4
- tryton/gui/window/view_form/view/form_gtk/many2one.py +2 -2
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/one2many.py +11 -8
- tryton/gui/window/view_form/view/form_gtk/progressbar.py +2 -2
- tryton/gui/window/view_form/view/form_gtk/pyson.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/reference.py +4 -4
- tryton/gui/window/view_form/view/form_gtk/richtextbox.py +5 -5
- tryton/gui/window/view_form/view/form_gtk/selection.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +8 -6
- tryton/gui/window/view_form/view/form_gtk/textbox.py +4 -4
- tryton/gui/window/view_form/view/form_gtk/timedelta.py +3 -3
- tryton/gui/window/view_form/view/form_gtk/url.py +2 -2
- tryton/gui/window/view_form/view/form_gtk/widget.py +1 -1
- tryton/gui/window/view_form/view/graph_gtk/bar.py +7 -7
- tryton/gui/window/view_form/view/graph_gtk/graph.py +2 -2
- tryton/gui/window/view_form/view/graph_gtk/line.py +5 -5
- tryton/gui/window/view_form/view/graph_gtk/pie.py +2 -2
- tryton/gui/window/view_form/view/list.py +110 -52
- tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -2
- tryton/gui/window/view_form/view/list_gtk/widget.py +22 -20
- tryton/gui/window/view_form/view/screen_container.py +13 -1
- tryton/gui/window/win_csv.py +2 -2
- tryton/gui/window/win_export.py +9 -7
- tryton/gui/window/win_form.py +73 -39
- tryton/gui/window/win_import.py +5 -6
- tryton/gui/window/wizard.py +11 -11
- tryton/jsonrpc.py +2 -2
- tryton/plugins/__init__.py +0 -1
- tryton/pyson.py +18 -18
- tryton/rpc.py +7 -5
- {tryton-7.4.7.dist-info → tryton-7.6.0.dist-info}/METADATA +8 -7
- {tryton-7.4.7.dist-info → tryton-7.6.0.dist-info}/RECORD +122 -120
- {tryton-7.4.7.dist-info → tryton-7.6.0.dist-info}/WHEEL +1 -1
- {tryton-7.4.7.data → tryton-7.6.0.data}/scripts/tryton +0 -0
- {tryton-7.4.7.dist-info → tryton-7.6.0.dist-info/licenses}/LICENSE +0 -0
- {tryton-7.4.7.dist-info → tryton-7.6.0.dist-info}/top_level.txt +0 -0
tryton/__init__.py
CHANGED
tryton/bus.py
CHANGED
|
@@ -7,6 +7,7 @@ import socket
|
|
|
7
7
|
import threading
|
|
8
8
|
import time
|
|
9
9
|
import uuid
|
|
10
|
+
from collections import defaultdict
|
|
10
11
|
from urllib.error import HTTPError
|
|
11
12
|
from urllib.request import Request, urlopen
|
|
12
13
|
|
|
@@ -18,82 +19,125 @@ from tryton.jsonrpc import object_hook
|
|
|
18
19
|
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
CHANNELS = [
|
|
23
|
-
'client:%s' % ID,
|
|
24
|
-
]
|
|
22
|
+
class Bus:
|
|
25
23
|
|
|
24
|
+
ID = str(uuid.uuid4())
|
|
25
|
+
current_thread = None
|
|
26
|
+
channel_actions = defaultdict(list)
|
|
27
|
+
listening = False
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
data=json.dumps({
|
|
55
|
-
'last_message': last_message,
|
|
56
|
-
'channels': CHANNELS,
|
|
57
|
-
}).encode('utf-8'),
|
|
58
|
-
headers=headers)
|
|
59
|
-
logger.info('poll channels %s with last message %s',
|
|
60
|
-
CHANNELS, last_message)
|
|
61
|
-
try:
|
|
62
|
-
response = urlopen(request, timeout=bus_timeout)
|
|
63
|
-
wait = 1
|
|
64
|
-
except socket.timeout:
|
|
65
|
-
wait = 1
|
|
66
|
-
continue
|
|
67
|
-
except Exception as error:
|
|
68
|
-
if isinstance(error, HTTPError):
|
|
69
|
-
if error.code in (301, 302, 303, 307, 308):
|
|
70
|
-
url = error.headers.get('Location')
|
|
29
|
+
@classmethod
|
|
30
|
+
def listen(cls, connection):
|
|
31
|
+
if not CONFIG['thread']:
|
|
32
|
+
return
|
|
33
|
+
listener = threading.Thread(
|
|
34
|
+
target=cls._listen, args=(connection,), daemon=True)
|
|
35
|
+
listener.start()
|
|
36
|
+
cls.current_thread = listener.ident
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _listen(cls, connection):
|
|
40
|
+
bus_timeout = CONFIG['client.bus_timeout']
|
|
41
|
+
session = connection.session
|
|
42
|
+
authorization = base64.b64encode(session.encode('utf-8'))
|
|
43
|
+
headers = {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'Authorization': b'Session ' + authorization,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
thread_id = threading.get_ident()
|
|
49
|
+
wait = 1
|
|
50
|
+
last_message = None
|
|
51
|
+
url = None
|
|
52
|
+
while connection.session == session:
|
|
53
|
+
if url is None:
|
|
54
|
+
if connection.url is None:
|
|
55
|
+
time.sleep(1)
|
|
71
56
|
continue
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
url = connection.url + '/bus'
|
|
58
|
+
cls.listening = True
|
|
59
|
+
|
|
60
|
+
channels = list(cls.channel_actions)
|
|
61
|
+
request = Request(url,
|
|
62
|
+
data=json.dumps({
|
|
63
|
+
'last_message': last_message,
|
|
64
|
+
'channels': channels,
|
|
65
|
+
}).encode('utf-8'),
|
|
66
|
+
headers=headers)
|
|
67
|
+
logger.info('poll channels %s with last message %s',
|
|
68
|
+
channels, last_message)
|
|
69
|
+
try:
|
|
70
|
+
response = urlopen(request, timeout=bus_timeout)
|
|
71
|
+
wait = 1
|
|
72
|
+
except socket.timeout:
|
|
73
|
+
wait = 1
|
|
74
|
+
continue
|
|
75
|
+
except Exception as error:
|
|
76
|
+
if thread_id != cls.current_thread:
|
|
74
77
|
break
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
if isinstance(error, HTTPError):
|
|
79
|
+
if error.code in (301, 302, 303, 307, 308):
|
|
80
|
+
url = error.headers.get('Location')
|
|
81
|
+
continue
|
|
82
|
+
elif error.code == 501:
|
|
83
|
+
logger.info("Bus not supported")
|
|
84
|
+
break
|
|
85
|
+
logger.error(
|
|
86
|
+
"An exception occurred while connecting to the bus. "
|
|
87
|
+
"Sleeping for %s seconds",
|
|
88
|
+
wait, exc_info=error)
|
|
89
|
+
cls.listening = False
|
|
90
|
+
time.sleep(min(wait, bus_timeout))
|
|
91
|
+
wait *= 2
|
|
92
|
+
continue
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
if connection.session != session:
|
|
95
|
+
break
|
|
96
|
+
if thread_id != cls.current_thread:
|
|
97
|
+
break
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
data = json.loads(response.read(), object_hook=object_hook)
|
|
100
|
+
if data['message']:
|
|
101
|
+
last_message = data['message']['message_id']
|
|
102
|
+
GLib.idle_add(cls.handle, data['channel'], data['message'])
|
|
103
|
+
cls.listening = False
|
|
90
104
|
|
|
105
|
+
@classmethod
|
|
106
|
+
def handle(cls, channel, message):
|
|
107
|
+
for callback in cls.channel_actions[channel]:
|
|
108
|
+
callback(message)
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
@classmethod
|
|
111
|
+
def register(cls, channel, function):
|
|
112
|
+
from tryton import rpc
|
|
113
|
+
|
|
114
|
+
restart = channel not in cls.channel_actions
|
|
115
|
+
cls.channel_actions[channel].append(function)
|
|
116
|
+
if restart:
|
|
117
|
+
# We can not really abort a thread, so we will just start a new one
|
|
118
|
+
# and ignore the result of the one already running
|
|
119
|
+
Bus.listen(rpc.CONNECTION)
|
|
94
120
|
|
|
121
|
+
@classmethod
|
|
122
|
+
def unregister(cls, channel, function):
|
|
123
|
+
try:
|
|
124
|
+
cls.channel_actions[channel].remove(function)
|
|
125
|
+
except ValueError:
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
if not cls.channel_actions[channel]:
|
|
129
|
+
del cls.channel_actions[channel]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def popup_notification(message):
|
|
133
|
+
if message['type'] != 'notification':
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
from tryton.gui.main import Main
|
|
95
137
|
app = Main()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
app.show_notification(
|
|
139
|
+
message.get('title', ''), message.get('body', ''),
|
|
140
|
+
message.get('priority', 1))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
Bus.register(f'client:{Bus.ID}', popup_notification)
|
tryton/chat.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
import gettext
|
|
4
|
+
|
|
5
|
+
from gi.repository import Gdk, Gio, GObject, Gtk
|
|
6
|
+
|
|
7
|
+
from tryton import common, rpc
|
|
8
|
+
from tryton.bus import Bus
|
|
9
|
+
|
|
10
|
+
_ = gettext.gettext
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageItem(GObject.GObject):
|
|
14
|
+
|
|
15
|
+
def __init__(self, message):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.message = message
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MessageList(GObject.GObject, Gio.ListModel):
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self._messages = []
|
|
25
|
+
self._items = {}
|
|
26
|
+
|
|
27
|
+
def do_get_item(self, position):
|
|
28
|
+
if position >= len(self._messages):
|
|
29
|
+
return None
|
|
30
|
+
message = self._messages[position]
|
|
31
|
+
if message['id'] not in self._items:
|
|
32
|
+
self._items[message['id']] = MessageItem(message)
|
|
33
|
+
return self._items[message['id']]
|
|
34
|
+
|
|
35
|
+
def do_get_item_type(self):
|
|
36
|
+
return MessageItem
|
|
37
|
+
|
|
38
|
+
def do_get_n_items(self):
|
|
39
|
+
return len(self._messages)
|
|
40
|
+
|
|
41
|
+
def clear(self):
|
|
42
|
+
self.emit('items-changed', 0, len(self._messages), 0)
|
|
43
|
+
self._messages = []
|
|
44
|
+
self._items = {}
|
|
45
|
+
|
|
46
|
+
def append(self, message):
|
|
47
|
+
self._messages.append(message)
|
|
48
|
+
self.emit('items-changed', len(self._messages) - 1, 0, 1)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Chat:
|
|
52
|
+
|
|
53
|
+
def __init__(self, record):
|
|
54
|
+
self.record = record
|
|
55
|
+
Bus.register(f"chat:{self.record}", self.notify)
|
|
56
|
+
self.widget = self.__build()
|
|
57
|
+
|
|
58
|
+
def unregister(self):
|
|
59
|
+
Bus.unregister(f"chat:{self.record}", self.notify)
|
|
60
|
+
|
|
61
|
+
def send_message(self, message, internal):
|
|
62
|
+
rpc.execute(
|
|
63
|
+
'model', 'ir.chat.channel', 'post', self.record, message,
|
|
64
|
+
'internal' if internal else 'public', rpc.CONTEXT)
|
|
65
|
+
|
|
66
|
+
def get_messages(self):
|
|
67
|
+
return rpc.execute(
|
|
68
|
+
'model', 'ir.chat.channel', 'get', self.record, rpc.CONTEXT)
|
|
69
|
+
|
|
70
|
+
def notify(self, message):
|
|
71
|
+
self.refresh()
|
|
72
|
+
|
|
73
|
+
def refresh(self):
|
|
74
|
+
self._messages.clear()
|
|
75
|
+
for post in self.get_messages():
|
|
76
|
+
self._messages.append(post)
|
|
77
|
+
self.widget.show_all()
|
|
78
|
+
|
|
79
|
+
def scroll_to_bottom(*args):
|
|
80
|
+
self._messages_sw.disconnect(signal_id)
|
|
81
|
+
vadj = self._messages_sw.get_vadjustment()
|
|
82
|
+
vadj.props.value = vadj.get_upper()
|
|
83
|
+
signal_id = self._messages_sw.connect(
|
|
84
|
+
'size-allocate', scroll_to_bottom)
|
|
85
|
+
|
|
86
|
+
def __build(self):
|
|
87
|
+
widget = Gtk.VBox()
|
|
88
|
+
widget.set_spacing(3)
|
|
89
|
+
|
|
90
|
+
def _submit(button):
|
|
91
|
+
buffer = input_.get_buffer()
|
|
92
|
+
self.send_message(
|
|
93
|
+
buffer.get_text(
|
|
94
|
+
buffer.get_start_iter(), buffer.get_end_iter(), False),
|
|
95
|
+
internal.get_active())
|
|
96
|
+
buffer.set_text('')
|
|
97
|
+
if not Bus.listening:
|
|
98
|
+
self.refresh()
|
|
99
|
+
|
|
100
|
+
def _keypress(entry, event):
|
|
101
|
+
if (event.state & Gdk.ModifierType.CONTROL_MASK
|
|
102
|
+
and event.keyval == Gdk.KEY_Return):
|
|
103
|
+
_submit(None)
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
self._messages = MessageList()
|
|
107
|
+
chat_messages = Gtk.ListBox.new()
|
|
108
|
+
chat_messages.props.activate_on_single_click = False
|
|
109
|
+
chat_messages.set_selection_mode(Gtk.SelectionMode.NONE)
|
|
110
|
+
chat_messages.bind_model(self._messages, self.create_message)
|
|
111
|
+
self._messages_sw = scrolledwindow = Gtk.ScrolledWindow()
|
|
112
|
+
viewport = Gtk.Viewport()
|
|
113
|
+
viewport.add(chat_messages)
|
|
114
|
+
viewport.set_valign(Gtk.Align.END)
|
|
115
|
+
scrolledwindow.set_shadow_type(Gtk.ShadowType.NONE)
|
|
116
|
+
scrolledwindow.set_policy(
|
|
117
|
+
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
118
|
+
scrolledwindow.add(viewport)
|
|
119
|
+
widget.pack_start(scrolledwindow, True, True, 0)
|
|
120
|
+
|
|
121
|
+
input_ = Gtk.TextView()
|
|
122
|
+
input_.connect('key-press-event', _keypress)
|
|
123
|
+
input_.set_size_request(-1, 100)
|
|
124
|
+
widget.pack_start(input_, False, True, 0)
|
|
125
|
+
|
|
126
|
+
internal = Gtk.CheckButton.new_with_mnemonic(
|
|
127
|
+
_("Make this an _internal message"))
|
|
128
|
+
widget.pack_start(internal, False, True, 0)
|
|
129
|
+
|
|
130
|
+
submit = Gtk.Button.new_with_label(_("Send"))
|
|
131
|
+
submit.connect('clicked', _submit)
|
|
132
|
+
widget.pack_start(submit, False, False, 0)
|
|
133
|
+
|
|
134
|
+
return widget
|
|
135
|
+
|
|
136
|
+
def create_message(self, item):
|
|
137
|
+
message = item.message
|
|
138
|
+
|
|
139
|
+
row = Gtk.ListBoxRow()
|
|
140
|
+
row.set_selectable(False)
|
|
141
|
+
row.set_margin_top(5)
|
|
142
|
+
|
|
143
|
+
hbox = Gtk.HBox(spacing=10)
|
|
144
|
+
row.add(hbox)
|
|
145
|
+
|
|
146
|
+
if avatar_url := message.get('avatar_url'):
|
|
147
|
+
image = Gtk.Image()
|
|
148
|
+
pixbuf = common.IconFactory.get_pixbuf_url(
|
|
149
|
+
avatar_url, size=32, size_param='s',
|
|
150
|
+
callback=image.set_from_pixbuf)
|
|
151
|
+
image.set_from_pixbuf(pixbuf)
|
|
152
|
+
image.set_valign(Gtk.Align.START)
|
|
153
|
+
hbox.pack_start(image, False, False, 0)
|
|
154
|
+
|
|
155
|
+
bubble = Gtk.VBox()
|
|
156
|
+
hbox.pack_end(bubble, True, True, 0)
|
|
157
|
+
|
|
158
|
+
author = Gtk.Label(label=message['author'])
|
|
159
|
+
author.set_xalign(0)
|
|
160
|
+
author.get_style_context().add_class('dim')
|
|
161
|
+
timestamp = Gtk.Label(label=message['timestamp'].strftime('%x %X'))
|
|
162
|
+
timestamp.set_xalign(1)
|
|
163
|
+
timestamp.get_style_context().add_class('dim')
|
|
164
|
+
|
|
165
|
+
meta = Gtk.HBox()
|
|
166
|
+
meta.pack_start(author, True, True, 0)
|
|
167
|
+
meta.pack_end(timestamp, False, False, 0)
|
|
168
|
+
|
|
169
|
+
content = Gtk.Label(label=message['content'])
|
|
170
|
+
content.set_xalign(0)
|
|
171
|
+
content.set_line_wrap(True)
|
|
172
|
+
content.set_selectable(True)
|
|
173
|
+
content.get_style_context().add_class(
|
|
174
|
+
f"chat-content-{message['audience']}")
|
|
175
|
+
|
|
176
|
+
bubble.pack_start(meta, False, False, 0)
|
|
177
|
+
bubble.pack_start(content, False, False, 0)
|
|
178
|
+
|
|
179
|
+
return row
|
tryton/client.py
CHANGED
|
@@ -33,6 +33,13 @@ def main():
|
|
|
33
33
|
label.warning {
|
|
34
34
|
color: @warning_color;
|
|
35
35
|
}
|
|
36
|
+
label.dim {
|
|
37
|
+
font-size: smaller;
|
|
38
|
+
font-weight: lighter;
|
|
39
|
+
}
|
|
40
|
+
.chat-content-internal {
|
|
41
|
+
font-weight: lighter;
|
|
42
|
+
}
|
|
36
43
|
.window-title, .wizard-title {
|
|
37
44
|
font-size: large;
|
|
38
45
|
font-weight: bold;
|
tryton/common/__init__.py
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
from . import timedelta
|
|
4
4
|
from .common import (
|
|
5
|
-
COLOR_SCHEMES, MODELACCESS, MODELHISTORY, MODELNAME,
|
|
6
|
-
TRYTON_ICON, VIEW_SEARCH, IconFactory, Logout,
|
|
7
|
-
RPCException, RPCExecute, RPCProgress,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
generateColorscheme, get_align, get_credentials,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
selection, setup_window, slugify, sur,
|
|
15
|
-
untimezoned_date, url_open, userwarning,
|
|
5
|
+
COLOR_SCHEMES, MODELACCESS, MODELCHAT, MODELHISTORY, MODELNAME,
|
|
6
|
+
MODELNOTIFICATION, TRYTON_ICON, VIEW_SEARCH, IconFactory, Logout,
|
|
7
|
+
RPCContextReload, RPCException, RPCExecute, RPCProgress, SolidColorFactory,
|
|
8
|
+
Tooltips, apply_label_attributes, ask, check_version, concurrency,
|
|
9
|
+
data2pixbuf, date_format, ellipsize, error, file_open, file_selection,
|
|
10
|
+
file_write, filter_domain, generateColorscheme, get_align, get_credentials,
|
|
11
|
+
get_gdk_backend, get_hostname, get_monitor_size, get_port,
|
|
12
|
+
get_sensible_widget, get_toplevel_window, hex2rgb, highlight_rgb, humanize,
|
|
13
|
+
idle_add, mailto, message, node_attributes, open_documentation, play_sound,
|
|
14
|
+
process_exception, resize_pixbuf, selection, setup_window, slugify, sur,
|
|
15
|
+
sur_3b, timezoned_date, to_xml, untimezoned_date, url_open, userwarning,
|
|
16
|
+
warning)
|
|
16
17
|
from .domain_inversion import (
|
|
17
18
|
concat, domain_inversion, eval_domain, extract_reference_models,
|
|
18
19
|
filter_leaf, inverse_leaf, localize_domain, merge,
|
|
@@ -28,10 +29,12 @@ __all__ = [
|
|
|
28
29
|
MODELHISTORY,
|
|
29
30
|
MODELNAME,
|
|
30
31
|
MODELNOTIFICATION,
|
|
32
|
+
MODELCHAT,
|
|
31
33
|
RPCContextReload,
|
|
32
34
|
RPCException,
|
|
33
35
|
RPCExecute,
|
|
34
36
|
RPCProgress,
|
|
37
|
+
SolidColorFactory,
|
|
35
38
|
TRYTON_ICON,
|
|
36
39
|
Tooltips,
|
|
37
40
|
VIEW_SEARCH,
|
|
@@ -57,6 +60,7 @@ __all__ = [
|
|
|
57
60
|
get_credentials,
|
|
58
61
|
get_gdk_backend,
|
|
59
62
|
get_hostname,
|
|
63
|
+
get_monitor_size,
|
|
60
64
|
get_port,
|
|
61
65
|
get_sensible_widget,
|
|
62
66
|
get_toplevel_window,
|
tryton/common/button.py
CHANGED
|
@@ -14,7 +14,7 @@ class Button(Gtk.Button):
|
|
|
14
14
|
def __init__(self, attrs=None):
|
|
15
15
|
self.attrs = attrs or {}
|
|
16
16
|
self.label = '_' + attrs.get('string', '').replace('_', '__')
|
|
17
|
-
super(
|
|
17
|
+
super().__init__(label=self.label, stock=None,
|
|
18
18
|
use_underline=True)
|
|
19
19
|
self._set_icon(attrs.get('icon'))
|
|
20
20
|
|
|
@@ -10,7 +10,7 @@ from .cellrendererinteger import CellRendererInteger
|
|
|
10
10
|
class CellRendererFloat(CellRendererInteger):
|
|
11
11
|
|
|
12
12
|
def __init__(self):
|
|
13
|
-
super(
|
|
13
|
+
super().__init__()
|
|
14
14
|
self.digits = None
|
|
15
15
|
self.monetary = False
|
|
16
16
|
self.convert = float
|
|
@@ -6,7 +6,7 @@ from gi.repository import GObject, Gtk
|
|
|
6
6
|
class CellRendererText(Gtk.CellRendererText):
|
|
7
7
|
|
|
8
8
|
def __init__(self):
|
|
9
|
-
super(
|
|
9
|
+
super().__init__()
|
|
10
10
|
self.connect('editing-started', self.__class__.on_editing_started)
|
|
11
11
|
|
|
12
12
|
def on_editing_started(self, editable, path):
|
|
@@ -16,7 +16,7 @@ class CellRendererText(Gtk.CellRendererText):
|
|
|
16
16
|
class CellRendererTextCompletion(CellRendererText):
|
|
17
17
|
|
|
18
18
|
def __init__(self, set_completion):
|
|
19
|
-
super(
|
|
19
|
+
super().__init__()
|
|
20
20
|
self.set_completion = set_completion
|
|
21
21
|
|
|
22
22
|
def on_editing_started(self, editable, path):
|