[whiteboard] Remove plugin (may be ported later)
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
from .plugin import WhiteboardPlugin
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 806 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,9 +0,0 @@
|
|||||||
[info]
|
|
||||||
name: Whiteboard
|
|
||||||
short_name: whiteboard
|
|
||||||
version: 1.4.0
|
|
||||||
description: Shows a whiteboard in chat. python-pygoocanvas is required.
|
|
||||||
authors = Yann Leboulanger <asterix@lagaule.org>
|
|
||||||
homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/WhiteboardPlugin
|
|
||||||
min_gajim_version: 1.4.0-dev1
|
|
||||||
max_gajim_version: 1.4.90
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 989 B |
@@ -1,494 +0,0 @@
|
|||||||
# Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
|
|
||||||
# Copyright (C) 2010 Yann Leboulanger <asterix AT lagaule.org>
|
|
||||||
#
|
|
||||||
# This file is part of the Whiteboard Plugin.
|
|
||||||
#
|
|
||||||
# Gajim is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published
|
|
||||||
# by the Free Software Foundation; version 3 only.
|
|
||||||
#
|
|
||||||
# Gajim is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
'''
|
|
||||||
Whiteboard plugin.
|
|
||||||
|
|
||||||
:author: Yann Leboulanger <asterix@lagaule.org>
|
|
||||||
:since: 1st November 2010
|
|
||||||
:copyright: Copyright (2010) Yann Leboulanger <asterix@lagaule.org>
|
|
||||||
:license: GPL
|
|
||||||
'''
|
|
||||||
|
|
||||||
from urllib.parse import quote
|
|
||||||
from gi.repository import Gio
|
|
||||||
from gi.repository import GLib
|
|
||||||
from nbxmpp import Message
|
|
||||||
|
|
||||||
from gajim import common
|
|
||||||
|
|
||||||
from gajim.common import app
|
|
||||||
from gajim.common import ged
|
|
||||||
from gajim.common import helpers
|
|
||||||
from gajim.common.jingle_session import JingleSession
|
|
||||||
from gajim.common.jingle_content import JingleContent
|
|
||||||
from gajim.common.jingle_transport import JingleTransport
|
|
||||||
from gajim.common.jingle_transport import TransportType
|
|
||||||
|
|
||||||
from gajim.gui.dialogs import DialogButton
|
|
||||||
from gajim.gui.dialogs import ConfirmationDialog
|
|
||||||
|
|
||||||
from gajim.plugins import GajimPlugin
|
|
||||||
from gajim.plugins.gajimplugin import GajimPluginException
|
|
||||||
from gajim.plugins.helpers import log_calls
|
|
||||||
from gajim.plugins.helpers import log
|
|
||||||
from gajim.plugins.plugins_i18n import _
|
|
||||||
|
|
||||||
from whiteboard.whiteboard_widget import Whiteboard
|
|
||||||
from whiteboard.whiteboard_widget import HAS_GOOCANVAS
|
|
||||||
|
|
||||||
NS_JINGLE_XHTML = 'urn:xmpp:tmp:jingle:apps:xhtml'
|
|
||||||
NS_JINGLE_SXE = 'urn:xmpp:tmp:jingle:transports:sxe'
|
|
||||||
NS_SXE = 'urn:xmpp:sxe:0'
|
|
||||||
|
|
||||||
|
|
||||||
class WhiteboardPlugin(GajimPlugin):
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def init(self):
|
|
||||||
self.config_dialog = None
|
|
||||||
self.events_handlers = {
|
|
||||||
'jingle-request-received': (ged.GUI1, self._nec_jingle_received),
|
|
||||||
'jingle-connected-received': (ged.GUI1,
|
|
||||||
self._nec_jingle_connected),
|
|
||||||
'jingle-disconnected-received': (ged.GUI1,
|
|
||||||
self._nec_jingle_disconnected),
|
|
||||||
'raw-message-received': (ged.GUI1, self._nec_raw_message),
|
|
||||||
}
|
|
||||||
self.gui_extension_points = {
|
|
||||||
'chat_control': (self.connect_with_chat_control,
|
|
||||||
self.disconnect_from_chat_control),
|
|
||||||
'chat_control_base_update_toolbar': (self.update_button_state,
|
|
||||||
None),
|
|
||||||
'update_caps': (self._update_caps, None),
|
|
||||||
}
|
|
||||||
self.controls = []
|
|
||||||
self.sid = None
|
|
||||||
self.announce_caps = True
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _update_caps(self, _account, features):
|
|
||||||
if not self.announce_caps:
|
|
||||||
return
|
|
||||||
|
|
||||||
features.append(NS_JINGLE_SXE)
|
|
||||||
features.append(NS_SXE)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def activate(self):
|
|
||||||
if not HAS_GOOCANVAS:
|
|
||||||
raise GajimPluginException('python-pygoocanvas is missing!')
|
|
||||||
|
|
||||||
self.announce_caps = True
|
|
||||||
for con in app.connections.values():
|
|
||||||
con.get_module('Caps').update_caps()
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def deactivate(self):
|
|
||||||
self.announce_caps = False
|
|
||||||
for con in app.connections.values():
|
|
||||||
con.get_module('Caps').update_caps()
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def connect_with_chat_control(self, control):
|
|
||||||
for base in self.controls:
|
|
||||||
if base.chat_control == control:
|
|
||||||
self.controls.remove(base)
|
|
||||||
|
|
||||||
if control.is_chat:
|
|
||||||
base = Base(self, control)
|
|
||||||
self.controls.append(base)
|
|
||||||
self.update_button_state(control)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def disconnect_from_chat_control(self, chat_control):
|
|
||||||
for base in self.controls:
|
|
||||||
base.disconnect_from_chat_control()
|
|
||||||
self.controls = []
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def update_button_state(self, control):
|
|
||||||
for base in self.controls:
|
|
||||||
if base.chat_control == control:
|
|
||||||
if (control.contact.supports(NS_JINGLE_SXE) and
|
|
||||||
control.contact.supports(NS_SXE)):
|
|
||||||
base.enable_action(True)
|
|
||||||
else:
|
|
||||||
base.enable_action(False)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def show_request_dialog(self, account, fjid, jid, sid, content_types):
|
|
||||||
def _on_accept():
|
|
||||||
session = app.connections[account].get_module(
|
|
||||||
'Jingle').get_jingle_session(fjid, sid)
|
|
||||||
self.sid = session.sid
|
|
||||||
if not session.accepted:
|
|
||||||
session.approve_session()
|
|
||||||
for content in content_types:
|
|
||||||
session.approve_content('xhtml')
|
|
||||||
for _jid in (fjid, jid):
|
|
||||||
ctrl = app.window.get_control(account, _jid)
|
|
||||||
if ctrl:
|
|
||||||
break
|
|
||||||
if not ctrl:
|
|
||||||
# Create it
|
|
||||||
app.interface.start_chat_from_jid(account, jid)
|
|
||||||
ctrl = app.window.get_control(account, jid)
|
|
||||||
session = session.contents[('initiator', 'xhtml')]
|
|
||||||
ctrl.draw_whiteboard(session)
|
|
||||||
|
|
||||||
def _on_decline():
|
|
||||||
session = app.connections[account].get_module(
|
|
||||||
'Jingle').get_jingle_session(fjid, sid)
|
|
||||||
session.decline_session()
|
|
||||||
|
|
||||||
client = app.get_client(account)
|
|
||||||
contact = client.get_module('Contacts').get_contact(jid)
|
|
||||||
|
|
||||||
ConfirmationDialog(
|
|
||||||
_('Incoming Whiteboard'),
|
|
||||||
_('Incoming Whiteboard Request'),
|
|
||||||
_('%(name)s (%(jid)s) wants to start a whiteboard with '
|
|
||||||
'you.') % {'name': contact.name, 'jid': jid},
|
|
||||||
[DialogButton.make('Cancel',
|
|
||||||
text=_('_Decline'),
|
|
||||||
callback=_on_decline),
|
|
||||||
DialogButton.make('OK',
|
|
||||||
text=_('_Accept'),
|
|
||||||
callback=_on_accept)],
|
|
||||||
transient_for=app.window).show()
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _nec_jingle_received(self, obj):
|
|
||||||
if not HAS_GOOCANVAS:
|
|
||||||
return
|
|
||||||
content_types = obj.contents.media
|
|
||||||
if content_types != 'xhtml':
|
|
||||||
return
|
|
||||||
self.show_request_dialog(
|
|
||||||
obj.conn.name,
|
|
||||||
obj.fjid,
|
|
||||||
obj.jid,
|
|
||||||
obj.sid,
|
|
||||||
content_types)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _nec_jingle_connected(self, obj):
|
|
||||||
if not HAS_GOOCANVAS:
|
|
||||||
return
|
|
||||||
account = obj.conn.name
|
|
||||||
ctrl = app.window.get_control(account, obj.jid)
|
|
||||||
if not ctrl:
|
|
||||||
return
|
|
||||||
session = app.connections[obj.conn.name].get_module(
|
|
||||||
'Jingle').get_jingle_session(obj.fjid, obj.sid)
|
|
||||||
|
|
||||||
if ('initiator', 'xhtml') not in session.contents:
|
|
||||||
return
|
|
||||||
|
|
||||||
session = session.contents[('initiator', 'xhtml')]
|
|
||||||
ctrl.draw_whiteboard(session)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _nec_jingle_disconnected(self, obj):
|
|
||||||
for base in self.controls:
|
|
||||||
if base.sid == obj.sid:
|
|
||||||
base.stop_whiteboard(reason=obj.reason)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _nec_raw_message(self, obj):
|
|
||||||
if not HAS_GOOCANVAS:
|
|
||||||
return
|
|
||||||
if obj.stanza.getTag('sxe', namespace=NS_SXE):
|
|
||||||
account = obj.conn.name
|
|
||||||
|
|
||||||
try:
|
|
||||||
fjid = helpers.get_full_jid_from_iq(obj.stanza)
|
|
||||||
except helpers.InvalidFormat:
|
|
||||||
obj.conn.dispatch('ERROR', (_('Invalid XMPP Address'),
|
|
||||||
_('A message from a non-valid XMPP address '
|
|
||||||
'arrived. It has been ignored.')))
|
|
||||||
|
|
||||||
jid = app.get_jid_without_resource(fjid)
|
|
||||||
ctrl = app.window.get_control(account, jid)
|
|
||||||
if not ctrl:
|
|
||||||
return
|
|
||||||
sxe = obj.stanza.getTag('sxe')
|
|
||||||
if not sxe:
|
|
||||||
return
|
|
||||||
sid = sxe.getAttr('session')
|
|
||||||
if (jid, sid) not in obj.conn.get_module('Jingle')._sessions:
|
|
||||||
pass
|
|
||||||
# newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
|
|
||||||
# sid=sid)
|
|
||||||
# self.addJingle(newjingle)
|
|
||||||
|
|
||||||
# We already have such session in dispatcher
|
|
||||||
session = obj.conn.get_module('Jingle').get_jingle_session(fjid,
|
|
||||||
sid)
|
|
||||||
cn = session.contents[('initiator', 'xhtml')]
|
|
||||||
error = obj.stanza.getTag('error')
|
|
||||||
if error:
|
|
||||||
action = 'iq-error'
|
|
||||||
else:
|
|
||||||
action = 'edit'
|
|
||||||
|
|
||||||
cn.on_stanza(obj.stanza, sxe, error, action)
|
|
||||||
# def __editCB(self, stanza, content, error, action):
|
|
||||||
# new_tags = sxe.getTags('new')
|
|
||||||
# remove_tags = sxe.getTags('remove')
|
|
||||||
|
|
||||||
# if new_tags is not None:
|
|
||||||
# # Process new elements
|
|
||||||
# for tag in new_tags:
|
|
||||||
# if tag.getAttr('type') == 'element':
|
|
||||||
# ctrl.whiteboard.recieve_element(tag)
|
|
||||||
# elif tag.getAttr('type') == 'attr':
|
|
||||||
# ctrl.whiteboard.recieve_attr(tag)
|
|
||||||
# ctrl.whiteboard.apply_new()
|
|
||||||
|
|
||||||
# if remove_tags is not None:
|
|
||||||
# # Delete rids
|
|
||||||
# for tag in remove_tags:
|
|
||||||
# target = tag.getAttr('target')
|
|
||||||
# ctrl.whiteboard.image.del_rid(target)
|
|
||||||
|
|
||||||
# Stop propagating this event, it's handled
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Base(object):
|
|
||||||
def __init__(self, plugin, chat_control):
|
|
||||||
self.plugin = plugin
|
|
||||||
self.chat_control = chat_control
|
|
||||||
self.chat_control.draw_whiteboard = self.draw_whiteboard
|
|
||||||
self.contact = self.chat_control.contact
|
|
||||||
self.account = self.chat_control.account
|
|
||||||
|
|
||||||
self.jid = self.contact.jid
|
|
||||||
|
|
||||||
self.add_action()
|
|
||||||
self.whiteboard = None
|
|
||||||
self.sid = None
|
|
||||||
|
|
||||||
def add_action(self):
|
|
||||||
action_name = 'toggle-whiteboard-' + self.chat_control.control_id
|
|
||||||
act = Gio.SimpleAction.new_stateful(
|
|
||||||
action_name, None, GLib.Variant.new_boolean(False))
|
|
||||||
act.connect('change-state', self.on_whiteboard_button_toggled)
|
|
||||||
app.window.add_action(act)
|
|
||||||
|
|
||||||
self.chat_control.control_menu.append(
|
|
||||||
'WhiteBoard', 'win.' + action_name)
|
|
||||||
|
|
||||||
def enable_action(self, state):
|
|
||||||
action_name = 'toggle-whiteboard-' + self.chat_control.control_id
|
|
||||||
app.window.lookup_action(action_name).set_enabled(state)
|
|
||||||
|
|
||||||
def draw_whiteboard(self, content):
|
|
||||||
hbox = self.chat_control.xml.get_object('chat_control_hbox')
|
|
||||||
if len(hbox.get_children()) == 1:
|
|
||||||
self.whiteboard = Whiteboard(self.account, self.contact, content,
|
|
||||||
self.plugin)
|
|
||||||
# Set minimum size
|
|
||||||
self.whiteboard.hbox.set_size_request(300, 0)
|
|
||||||
hbox.pack_start(self.whiteboard.hbox, False, False, 0)
|
|
||||||
self.whiteboard.hbox.show_all()
|
|
||||||
self.enable_action(True)
|
|
||||||
content.control = self
|
|
||||||
self.sid = content.session.sid
|
|
||||||
|
|
||||||
def on_whiteboard_button_toggled(self, action, param):
|
|
||||||
"""
|
|
||||||
Popup whiteboard
|
|
||||||
"""
|
|
||||||
action.set_state(param)
|
|
||||||
state = param.get_boolean()
|
|
||||||
if state:
|
|
||||||
if not self.whiteboard:
|
|
||||||
self.start_whiteboard()
|
|
||||||
else:
|
|
||||||
self.stop_whiteboard()
|
|
||||||
|
|
||||||
def start_whiteboard(self):
|
|
||||||
conn = app.connections[self.chat_control.account]
|
|
||||||
jingle = JingleSession(conn, weinitiate=True, jid=self.jid)
|
|
||||||
self.sid = jingle.sid
|
|
||||||
conn.get_module('Jingle')._sessions[jingle.sid] = jingle
|
|
||||||
content = JingleWhiteboard(jingle)
|
|
||||||
content.control = self
|
|
||||||
jingle.add_content('xhtml', content)
|
|
||||||
jingle.start_session()
|
|
||||||
|
|
||||||
def stop_whiteboard(self, reason=None):
|
|
||||||
conn = app.connections[self.chat_control.account]
|
|
||||||
self.sid = None
|
|
||||||
session = conn.get_module('Jingle').get_jingle_session(self.jid,
|
|
||||||
media='xhtml')
|
|
||||||
if session:
|
|
||||||
session.end_session()
|
|
||||||
self.enable_action(False)
|
|
||||||
if reason:
|
|
||||||
txt = _('Whiteboard stopped: %(reason)s') % {'reason': reason}
|
|
||||||
self.chat_control.add_info_message(txt)
|
|
||||||
if not self.whiteboard:
|
|
||||||
return
|
|
||||||
hbox = self.chat_control.xml.get_object('chat_control_hbox')
|
|
||||||
if self.whiteboard.hbox in hbox.get_children():
|
|
||||||
if hasattr(self.whiteboard, 'hbox'):
|
|
||||||
hbox.remove(self.whiteboard.hbox)
|
|
||||||
self.whiteboard = None
|
|
||||||
|
|
||||||
def disconnect_from_chat_control(self):
|
|
||||||
menu = self.chat_control.control_menu
|
|
||||||
for i in range(menu.get_n_items()):
|
|
||||||
label = menu.get_item_attribute_value(i, 'label')
|
|
||||||
if label.get_string() == 'WhiteBoard':
|
|
||||||
menu.remove(i)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class JingleWhiteboard(JingleContent):
|
|
||||||
''' Jingle Whiteboard sessions consist of xhtml content'''
|
|
||||||
def __init__(self, session, transport=None, senders=None):
|
|
||||||
if not transport:
|
|
||||||
transport = JingleTransportSXE()
|
|
||||||
JingleContent.__init__(self, session, transport, senders)
|
|
||||||
self.media = 'xhtml'
|
|
||||||
self.negotiated = True # There is nothing to negotiate
|
|
||||||
self.last_rid = 0
|
|
||||||
self.callbacks['session-accept'] += [self._sessionAcceptCB]
|
|
||||||
self.callbacks['session-terminate'] += [self._stop]
|
|
||||||
self.callbacks['session-terminate-sent'] += [self._stop]
|
|
||||||
self.callbacks['edit'] = [self._EditCB]
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _EditCB(self, stanza, content, error, action):
|
|
||||||
new_tags = content.getTags('new')
|
|
||||||
remove_tags = content.getTags('remove')
|
|
||||||
if not self.control.whiteboard:
|
|
||||||
return
|
|
||||||
|
|
||||||
if new_tags is not None:
|
|
||||||
# Process new elements
|
|
||||||
for tag in new_tags:
|
|
||||||
if tag.getAttr('type') == 'element':
|
|
||||||
self.control.whiteboard.recieve_element(tag)
|
|
||||||
elif tag.getAttr('type') == 'attr':
|
|
||||||
self.control.whiteboard.recieve_attr(tag)
|
|
||||||
self.control.whiteboard.apply_new()
|
|
||||||
|
|
||||||
if remove_tags is not None:
|
|
||||||
# Delete rids
|
|
||||||
for tag in remove_tags:
|
|
||||||
target = tag.getAttr('target')
|
|
||||||
self.control.whiteboard.image.del_rid(target)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def _sessionAcceptCB(self, stanza, content, error, action):
|
|
||||||
log.debug('session accepted')
|
|
||||||
self.session.connection.dispatch(
|
|
||||||
'WHITEBOARD_ACCEPTED', (self.session.peerjid, self.session.sid))
|
|
||||||
|
|
||||||
def generate_rids(self, x):
|
|
||||||
# Generates x number of rids and returns in list
|
|
||||||
rids = []
|
|
||||||
for x in range(x):
|
|
||||||
rids.append(str(self.last_rid))
|
|
||||||
self.last_rid += 1
|
|
||||||
return rids
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def send_whiteboard_node(self, items, rids):
|
|
||||||
# Takes int rid and dict items and sends it as a node
|
|
||||||
# sends new item
|
|
||||||
jid = self.session.peerjid
|
|
||||||
sid = self.session.sid
|
|
||||||
message = Message(to=jid)
|
|
||||||
sxe = message.addChild(name='sxe', attrs={'session': sid},
|
|
||||||
namespace=NS_SXE)
|
|
||||||
|
|
||||||
for x in rids:
|
|
||||||
if items[x]['type'] == 'element':
|
|
||||||
parent = x
|
|
||||||
attrs = {'rid': x,
|
|
||||||
'name': items[x]['data'][0].getName(),
|
|
||||||
'type': items[x]['type']}
|
|
||||||
sxe.addChild(name='new', attrs=attrs)
|
|
||||||
if items[x]['type'] == 'attr':
|
|
||||||
attr_name = items[x]['data']
|
|
||||||
chdata = items[parent]['data'][0].getAttr(attr_name)
|
|
||||||
attrs = {'rid': x,
|
|
||||||
'name': attr_name,
|
|
||||||
'type': items[x]['type'],
|
|
||||||
'chdata': chdata,
|
|
||||||
'parent': parent}
|
|
||||||
sxe.addChild(name='new', attrs=attrs)
|
|
||||||
self.session.connection.connection.send(message)
|
|
||||||
|
|
||||||
@log_calls('WhiteboardPlugin')
|
|
||||||
def delete_whiteboard_node(self, rids):
|
|
||||||
message = Message(to=self.session.peerjid)
|
|
||||||
sxe = message.addChild(name='sxe', attrs={'session': self.session.sid},
|
|
||||||
namespace=NS_SXE)
|
|
||||||
|
|
||||||
for x in rids:
|
|
||||||
sxe.addChild(name='remove', attrs={'target': x})
|
|
||||||
self.session.connection.connection.send(message)
|
|
||||||
|
|
||||||
def send_items(self, items, rids):
|
|
||||||
# Receives dict items and a list of rids of items to send
|
|
||||||
# TODO: Is there a less clumsy way that doesn't involve passing
|
|
||||||
# whole list?
|
|
||||||
self.send_whiteboard_node(items, rids)
|
|
||||||
|
|
||||||
def del_item(self, rids):
|
|
||||||
self.delete_whiteboard_node(rids)
|
|
||||||
|
|
||||||
def encode(self, xml):
|
|
||||||
# Encodes it sendable string
|
|
||||||
return 'data:text/xml,' + quote(xml)
|
|
||||||
|
|
||||||
def _fill_content(self, content):
|
|
||||||
content.addChild(NS_JINGLE_XHTML + ' description')
|
|
||||||
|
|
||||||
def _stop(self, *things):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_content(desc):
|
|
||||||
return JingleWhiteboard
|
|
||||||
|
|
||||||
|
|
||||||
common.jingle_content.contents[NS_JINGLE_XHTML] = get_content
|
|
||||||
|
|
||||||
|
|
||||||
class JingleTransportSXE(JingleTransport):
|
|
||||||
def __init__(self, node=None):
|
|
||||||
JingleTransport.__init__(self, TransportType.SOCKS5)
|
|
||||||
|
|
||||||
def make_transport(self, candidates=None):
|
|
||||||
transport = JingleTransport.make_transport(self, candidates)
|
|
||||||
transport.setNamespace(NS_JINGLE_SXE)
|
|
||||||
transport.setTagData('host', 'TODO')
|
|
||||||
return transport
|
|
||||||
|
|
||||||
|
|
||||||
common.jingle_transport.transports[NS_JINGLE_SXE] = JingleTransportSXE
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,454 +0,0 @@
|
|||||||
# Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
|
|
||||||
# Copyright (C) 2010-2017 Yann Leboulanger <asterix AT lagaule.org>
|
|
||||||
#
|
|
||||||
# This file is part of the Whiteboard Plugin.
|
|
||||||
#
|
|
||||||
# Gajim is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published
|
|
||||||
# by the Free Software Foundation; version 3 only.
|
|
||||||
#
|
|
||||||
# Gajim is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
from nbxmpp import Node
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
from gajim.common import app
|
|
||||||
|
|
||||||
from gajim.gui.filechoosers import NativeFileChooserDialog
|
|
||||||
from gajim.gui.filechoosers import Filter
|
|
||||||
|
|
||||||
from gajim.plugins.helpers import get_builder
|
|
||||||
from gajim.plugins.plugins_i18n import _
|
|
||||||
|
|
||||||
try:
|
|
||||||
import gi
|
|
||||||
gi.require_version('GooCanvas', '2.0')
|
|
||||||
from gi.repository import GooCanvas
|
|
||||||
HAS_GOOCANVAS = True
|
|
||||||
except ValueError:
|
|
||||||
HAS_GOOCANVAS = False
|
|
||||||
|
|
||||||
|
|
||||||
class SvgSaveDialog(NativeFileChooserDialog):
|
|
||||||
|
|
||||||
_title = _('Save File as…')
|
|
||||||
_filters = [Filter(_('All files'), '*', False),
|
|
||||||
Filter(_('SVG files'), '*.svg', True)]
|
|
||||||
_action = Gtk.FileChooserAction.SAVE
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
A whiteboard widget made for Gajim.
|
|
||||||
- Ummu
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class Whiteboard(object):
|
|
||||||
def __init__(self, account, contact, session, plugin):
|
|
||||||
self.plugin = plugin
|
|
||||||
path = plugin.local_file_path('whiteboard_widget.ui')
|
|
||||||
self._ui = get_builder(path)
|
|
||||||
|
|
||||||
self.canvas = GooCanvas.Canvas()
|
|
||||||
self.hbox = self._ui.whiteboard_hbox
|
|
||||||
self._ui.whiteboard_hbox.pack_start(self.canvas, True, True, 0)
|
|
||||||
self._ui.whiteboard_hbox.reorder_child(self.canvas, 0)
|
|
||||||
self.root = self.canvas.get_root_item()
|
|
||||||
self.tool_buttons = [
|
|
||||||
self._ui.brush_button,
|
|
||||||
self._ui.oval_button,
|
|
||||||
self._ui.line_button,
|
|
||||||
self._ui.delete_button
|
|
||||||
]
|
|
||||||
self._ui.brush_button.set_active(True)
|
|
||||||
|
|
||||||
# Events
|
|
||||||
self.canvas.connect('button-press-event', self.button_press_event)
|
|
||||||
self.canvas.connect('button-release-event', self.button_release_event)
|
|
||||||
self.canvas.connect('motion-notify-event', self.motion_notify_event)
|
|
||||||
self.canvas.connect('item-created', self.item_created)
|
|
||||||
|
|
||||||
# Config
|
|
||||||
self.line_width = 2
|
|
||||||
self._ui.size_scale.set_value(2)
|
|
||||||
c = self._ui.fg_color_button.get_rgba()
|
|
||||||
self.color = int(c.red*255*256*256*256 +
|
|
||||||
c.green*255*256*256 +
|
|
||||||
c.blue*255*256 + 255)
|
|
||||||
|
|
||||||
# SVG Storage
|
|
||||||
self.image = SVGObject(self.root, session)
|
|
||||||
|
|
||||||
self._ui.connect_signals(self)
|
|
||||||
|
|
||||||
# Temporary Variables for items
|
|
||||||
self.item_temp = None
|
|
||||||
self.item_temp_coords = (0, 0)
|
|
||||||
self.item_data = None
|
|
||||||
|
|
||||||
# Will be instance of {ID: {type:'element'
|
|
||||||
# data:[node, goocanvas]},
|
|
||||||
# ID2: {}}
|
|
||||||
self.receiving = {}
|
|
||||||
|
|
||||||
def _on_tool_button_toggled(self, widget):
|
|
||||||
for btn in self.tool_buttons:
|
|
||||||
if btn == widget:
|
|
||||||
continue
|
|
||||||
btn.set_active(False)
|
|
||||||
|
|
||||||
def _on_brush_button_toggled(self, widget):
|
|
||||||
if widget.get_active():
|
|
||||||
self.image.draw_tool = 'brush'
|
|
||||||
self._on_tool_button_toggled(widget)
|
|
||||||
|
|
||||||
def _on_oval_button_toggled(self, widget):
|
|
||||||
if widget.get_active():
|
|
||||||
self.image.draw_tool = 'oval'
|
|
||||||
self._on_tool_button_toggled(widget)
|
|
||||||
|
|
||||||
def _on_line_button_toggled(self, widget):
|
|
||||||
if widget.get_active():
|
|
||||||
self.image.draw_tool = 'line'
|
|
||||||
self._on_tool_button_toggled(widget)
|
|
||||||
|
|
||||||
def _on_delete_button_toggled(self, widget):
|
|
||||||
if widget.get_active():
|
|
||||||
self.image.draw_tool = 'delete'
|
|
||||||
self._on_tool_button_toggled(widget)
|
|
||||||
|
|
||||||
def _on_clear_button_clicked(self, widget):
|
|
||||||
self.image.clear_canvas()
|
|
||||||
|
|
||||||
def _on_fg_color_button_color_set(self, widget):
|
|
||||||
c = self._ui.fg_color_button.get_rgba()
|
|
||||||
self.color = int(
|
|
||||||
c.red*255*256*256*256 +
|
|
||||||
c.green*255*256*256 +
|
|
||||||
c.blue*255*256 + 255)
|
|
||||||
|
|
||||||
def _on_size_scale_format_value(self, widget):
|
|
||||||
self.line_width = int(widget.get_value())
|
|
||||||
|
|
||||||
def _on_export_button_clicked(self, widget):
|
|
||||||
SvgSaveDialog(self.image.export_svg,
|
|
||||||
file_name=_('whiteboard.svg'),
|
|
||||||
path=app.settings.get('last_save_dir'),
|
|
||||||
transient_for=app.app.get_active_window())
|
|
||||||
|
|
||||||
def item_created(self, canvas, item, model):
|
|
||||||
item.connect('button-press-event', self.item_button_press_events)
|
|
||||||
|
|
||||||
def item_button_press_events(self, item, target_item, event):
|
|
||||||
if self.image.draw_tool == 'delete':
|
|
||||||
self.image.del_item(item)
|
|
||||||
|
|
||||||
def button_press_event(self, widget, event):
|
|
||||||
x = event.x
|
|
||||||
y = event.y
|
|
||||||
self.item_temp_coords = (x, y)
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'brush':
|
|
||||||
self.item_temp = GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=x,
|
|
||||||
center_y=y,
|
|
||||||
radius_x=1,
|
|
||||||
radius_y=1,
|
|
||||||
stroke_color_rgba=self.color,
|
|
||||||
fill_color=self.color,
|
|
||||||
line_width=self.line_width)
|
|
||||||
self.item_data = 'M %s,%s L ' % (x, y)
|
|
||||||
|
|
||||||
elif self.image.draw_tool == 'oval':
|
|
||||||
self.item_data = True
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'line':
|
|
||||||
self.item_data = 'M %s,%s L' % (x, y)
|
|
||||||
|
|
||||||
def motion_notify_event(self, widget, event):
|
|
||||||
x = event.x
|
|
||||||
y = event.y
|
|
||||||
if self.item_temp is not None:
|
|
||||||
self.item_temp.remove()
|
|
||||||
|
|
||||||
if self.item_data is not None:
|
|
||||||
if self.image.draw_tool == 'brush':
|
|
||||||
self.item_data = self.item_data + '%s,%s ' % (x, y)
|
|
||||||
self.item_temp = GooCanvas.CanvasPath(
|
|
||||||
parent=self.root,
|
|
||||||
data=self.item_data,
|
|
||||||
line_width=self.line_width,
|
|
||||||
stroke_color_rgba=self.color)
|
|
||||||
elif self.image.draw_tool == 'oval':
|
|
||||||
self.item_temp = GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=self.item_temp_coords[0] +
|
|
||||||
(x - self.item_temp_coords[0]) / 2,
|
|
||||||
center_y=self.item_temp_coords[1] +
|
|
||||||
(y - self.item_temp_coords[1]) / 2,
|
|
||||||
radius_x=abs(x - self.item_temp_coords[0]) / 2,
|
|
||||||
radius_y=abs(y - self.item_temp_coords[1]) / 2,
|
|
||||||
stroke_color_rgba=self.color,
|
|
||||||
line_width=self.line_width)
|
|
||||||
elif self.image.draw_tool == 'line':
|
|
||||||
self.item_data = 'M %s,%s L' % self.item_temp_coords
|
|
||||||
self.item_data = self.item_data + ' %s,%s' % (x, y)
|
|
||||||
self.item_temp = GooCanvas.CanvasPath(
|
|
||||||
parent=self.root,
|
|
||||||
data=self.item_data,
|
|
||||||
line_width=self.line_width,
|
|
||||||
stroke_color_rgba=self.color)
|
|
||||||
|
|
||||||
def button_release_event(self, widget, event):
|
|
||||||
x = event.x
|
|
||||||
y = event.y
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'brush':
|
|
||||||
self.item_data = self.item_data + '%s,%s' % (x, y)
|
|
||||||
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
|
|
||||||
GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=x,
|
|
||||||
center_y=y,
|
|
||||||
radius_x=1,
|
|
||||||
radius_y=1,
|
|
||||||
stroke_color_rgba=self.color,
|
|
||||||
fill_color=self.color,
|
|
||||||
line_width=self.line_width)
|
|
||||||
self.image.add_path(self.item_data, self.line_width, self.color)
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'oval':
|
|
||||||
cx = self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2
|
|
||||||
cy = self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2
|
|
||||||
rx = abs(x - self.item_temp_coords[0]) / 2
|
|
||||||
ry = abs(y - self.item_temp_coords[1]) / 2
|
|
||||||
self.image.add_ellipse(cx, cy, rx, ry, self.line_width, self.color)
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'line':
|
|
||||||
self.item_data = 'M %s,%s L' % self.item_temp_coords
|
|
||||||
self.item_data = self.item_data + ' %s,%s' % (x, y)
|
|
||||||
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
|
|
||||||
GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=x,
|
|
||||||
center_y=y,
|
|
||||||
radius_x=1,
|
|
||||||
radius_y=1,
|
|
||||||
stroke_color_rgba=self.color,
|
|
||||||
fill_color_rgba=self.color,
|
|
||||||
line_width=self.line_width)
|
|
||||||
self.image.add_path(self.item_data, self.line_width, self.color)
|
|
||||||
|
|
||||||
if self.image.draw_tool == 'delete':
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.item_data = None
|
|
||||||
if self.item_temp is not None:
|
|
||||||
self.item_temp.remove()
|
|
||||||
self.item_temp = None
|
|
||||||
|
|
||||||
def recieve_element(self, element):
|
|
||||||
node = self.image.g.addChild(name=element.getAttr('name'))
|
|
||||||
self.image.g.addChild(node=node)
|
|
||||||
self.receiving[element.getAttr('rid')] = {'type': 'element',
|
|
||||||
'data': [node],
|
|
||||||
'children': []}
|
|
||||||
|
|
||||||
def recieve_attr(self, element):
|
|
||||||
node = self.receiving[element.getAttr('parent')]['data'][0]
|
|
||||||
node.setAttr(element.getAttr('name'), element.getAttr('chdata'))
|
|
||||||
|
|
||||||
self.receiving[element.getAttr('rid')] = {'type': 'attr',
|
|
||||||
'data': element.getAttr(
|
|
||||||
'name'),
|
|
||||||
'parent': node}
|
|
||||||
self.receiving[element.getAttr('parent')]['children'].append(
|
|
||||||
element.getAttr('rid'))
|
|
||||||
|
|
||||||
def apply_new(self):
|
|
||||||
for x in self.receiving.keys():
|
|
||||||
if self.receiving[x]['type'] == 'element':
|
|
||||||
self.image.add_recieved(x, self.receiving)
|
|
||||||
|
|
||||||
self.receiving = {}
|
|
||||||
|
|
||||||
|
|
||||||
class SVGObject():
|
|
||||||
''' A class to store the svg document and make changes to it.'''
|
|
||||||
def __init__(self, root, session, height=300, width=300):
|
|
||||||
# Will be instance of {ID: {type:'element',
|
|
||||||
# data:[node, goocanvas]},
|
|
||||||
# ID2: {}}
|
|
||||||
self.items = {}
|
|
||||||
self.root = root
|
|
||||||
self.draw_tool = 'brush'
|
|
||||||
|
|
||||||
# SXE session
|
|
||||||
self.session = session
|
|
||||||
|
|
||||||
# Initialize svg document
|
|
||||||
self.svg = Node(node='<svg/>')
|
|
||||||
self.svg.setAttr('version', '1.1')
|
|
||||||
self.svg.setAttr('height', str(height))
|
|
||||||
self.svg.setAttr('width', str(width))
|
|
||||||
self.svg.setAttr('xmlns', 'http://www.w3.org/2000/svg')
|
|
||||||
# TODO: Make this settable
|
|
||||||
self.g = self.svg.addChild(name='g')
|
|
||||||
self.g.setAttr('fill', 'none')
|
|
||||||
self.g.setAttr('stroke-linecap', 'round')
|
|
||||||
|
|
||||||
def add_path(self, data, line_width, color):
|
|
||||||
'''
|
|
||||||
Adds the path to the items listing, both minidom node and goocanvas
|
|
||||||
object in a tuple
|
|
||||||
'''
|
|
||||||
goocanvas_obj = GooCanvas.CanvasPath(
|
|
||||||
parent=self.root,
|
|
||||||
data=data,
|
|
||||||
line_width=line_width,
|
|
||||||
stroke_color_rgba=color)
|
|
||||||
goocanvas_obj.connect('button-press-event',
|
|
||||||
self.item_button_press_events)
|
|
||||||
|
|
||||||
node = self.g.addChild(name='path')
|
|
||||||
node.setAttr('d', data)
|
|
||||||
node.setAttr('stroke-width', str(line_width))
|
|
||||||
node.setAttr('stroke', str(color))
|
|
||||||
self.g.addChild(node=node)
|
|
||||||
|
|
||||||
rids = self.session.generate_rids(4)
|
|
||||||
self.items[rids[0]] = {'type': 'element',
|
|
||||||
'data': [node, goocanvas_obj],
|
|
||||||
'children': rids[1:]}
|
|
||||||
self.items[rids[1]] = {'type': 'attr',
|
|
||||||
'data': 'd',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[2]] = {'type': 'attr',
|
|
||||||
'data': 'stroke-width',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[3]] = {'type': 'attr',
|
|
||||||
'data': 'stroke',
|
|
||||||
'parent': node}
|
|
||||||
|
|
||||||
self.session.send_items(self.items, rids)
|
|
||||||
|
|
||||||
def add_recieved(self, parent_rid, new_items):
|
|
||||||
'''
|
|
||||||
Adds the path to the items listing, both minidom node and goocanvas
|
|
||||||
object in a tuple
|
|
||||||
'''
|
|
||||||
node = new_items[parent_rid]['data'][0]
|
|
||||||
|
|
||||||
self.items[parent_rid] = new_items[parent_rid]
|
|
||||||
for x in new_items[parent_rid]['children']:
|
|
||||||
self.items[x] = new_items[x]
|
|
||||||
|
|
||||||
if node.getName() == 'path':
|
|
||||||
goocanvas_obj = GooCanvas.CanvasPath(
|
|
||||||
parent=self.root,
|
|
||||||
data=node.getAttr('d'),
|
|
||||||
line_width=int(node.getAttr('stroke-width')),
|
|
||||||
stroke_color_rgba=int(node.getAttr('stroke')))
|
|
||||||
|
|
||||||
if node.getName() == 'ellipse':
|
|
||||||
goocanvas_obj = GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=float(node.getAttr('cx')),
|
|
||||||
center_y=float(node.getAttr('cy')),
|
|
||||||
radius_x=float(node.getAttr('rx')),
|
|
||||||
radius_y=float(node.getAttr('ry')),
|
|
||||||
stroke_color_rgba=int(node.getAttr('stroke')),
|
|
||||||
line_width=float(node.getAttr('stroke-width')))
|
|
||||||
|
|
||||||
self.items[parent_rid]['data'].append(goocanvas_obj)
|
|
||||||
goocanvas_obj.connect('button-press-event',
|
|
||||||
self.item_button_press_events)
|
|
||||||
|
|
||||||
def add_ellipse(self, cx, cy, rx, ry, line_width, stroke_color):
|
|
||||||
'''
|
|
||||||
Adds the ellipse to the items listing, both minidom node and goocanvas
|
|
||||||
object in a tuple
|
|
||||||
'''
|
|
||||||
goocanvas_obj = GooCanvas.CanvasEllipse(
|
|
||||||
parent=self.root,
|
|
||||||
center_x=cx,
|
|
||||||
center_y=cy,
|
|
||||||
radius_x=rx,
|
|
||||||
radius_y=ry,
|
|
||||||
stroke_color_rgba=stroke_color,
|
|
||||||
line_width=line_width)
|
|
||||||
goocanvas_obj.connect('button-press-event',
|
|
||||||
self.item_button_press_events)
|
|
||||||
|
|
||||||
node = self.g.addChild(name='ellipse')
|
|
||||||
node.setAttr('cx', str(cx))
|
|
||||||
node.setAttr('cy', str(cy))
|
|
||||||
node.setAttr('rx', str(rx))
|
|
||||||
node.setAttr('ry', str(ry))
|
|
||||||
node.setAttr('stroke-width', str(line_width))
|
|
||||||
node.setAttr('stroke', str(stroke_color))
|
|
||||||
self.g.addChild(node=node)
|
|
||||||
|
|
||||||
rids = self.session.generate_rids(7)
|
|
||||||
self.items[rids[0]] = {'type': 'element',
|
|
||||||
'data': [node, goocanvas_obj],
|
|
||||||
'children': rids[1:]}
|
|
||||||
self.items[rids[1]] = {'type': 'attr',
|
|
||||||
'data': 'cx',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[2]] = {'type': 'attr',
|
|
||||||
'data': 'cy',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[3]] = {'type': 'attr',
|
|
||||||
'data': 'rx',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[4]] = {'type': 'attr',
|
|
||||||
'data': 'ry',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[5]] = {'type': 'attr',
|
|
||||||
'data': 'stroke-width',
|
|
||||||
'parent': node}
|
|
||||||
self.items[rids[6]] = {'type': 'attr',
|
|
||||||
'data': 'stroke',
|
|
||||||
'parent': node}
|
|
||||||
|
|
||||||
self.session.send_items(self.items, rids)
|
|
||||||
|
|
||||||
def del_item(self, item):
|
|
||||||
rids = []
|
|
||||||
for x in list(self.items.keys()):
|
|
||||||
if self.items[x]['type'] == 'element':
|
|
||||||
if self.items[x]['data'][1] == item:
|
|
||||||
for y in self.items[x]['children']:
|
|
||||||
rids.append(y)
|
|
||||||
self.del_rid(y)
|
|
||||||
rids.append(x)
|
|
||||||
self.del_rid(x)
|
|
||||||
break
|
|
||||||
self.session.del_item(rids)
|
|
||||||
|
|
||||||
def clear_canvas(self):
|
|
||||||
for x in list(self.items.keys()):
|
|
||||||
if self.items[x]['type'] == 'element':
|
|
||||||
self.del_rid(x)
|
|
||||||
|
|
||||||
def del_rid(self, rid):
|
|
||||||
if self.items[rid]['type'] == 'element':
|
|
||||||
self.items[rid]['data'][1].remove()
|
|
||||||
del self.items[rid]
|
|
||||||
|
|
||||||
def export_svg(self, filename):
|
|
||||||
f = open(filename, 'w')
|
|
||||||
f.writelines(str(self.svg))
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def item_button_press_events(self, item, target_item, event):
|
|
||||||
self.del_item(item)
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Generated with glade 3.22.1 -->
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk+" version="3.20"/>
|
|
||||||
<object class="GtkAdjustment" id="adjustment1">
|
|
||||||
<property name="lower">1</property>
|
|
||||||
<property name="upper">110</property>
|
|
||||||
<property name="value">2</property>
|
|
||||||
<property name="step_increment">1</property>
|
|
||||||
<property name="page_increment">10</property>
|
|
||||||
<property name="page_size">10</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkBox" id="whiteboard_hbox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="border_width">3</property>
|
|
||||||
<property name="spacing">6</property>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="border_width">6</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">6</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton" id="brush_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Draw freehand lines</property>
|
|
||||||
<signal name="toggled" handler="_on_brush_button_toggled" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="pixbuf">brush_tool.png</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton" id="oval_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Draw circles and ellipses</property>
|
|
||||||
<signal name="toggled" handler="_on_oval_button_toggled" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="pixbuf">oval_tool.png</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton" id="line_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Draw straight lines</property>
|
|
||||||
<signal name="toggled" handler="_on_line_button_toggled" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="pixbuf">line_tool.png</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScale" id="size_scale">
|
|
||||||
<property name="height_request">68</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Line width</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="adjustment">adjustment1</property>
|
|
||||||
<property name="inverted">True</property>
|
|
||||||
<property name="digits">0</property>
|
|
||||||
<property name="value_pos">bottom</property>
|
|
||||||
<signal name="value-changed" handler="_on_size_scale_format_value" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">3</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkColorButton" id="fg_color_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Foreground color</property>
|
|
||||||
<property name="rgba">rgb(0,0,0)</property>
|
|
||||||
<signal name="color-set" handler="_on_fg_color_button_color_set" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">4</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton" id="delete_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Remove individual figures</property>
|
|
||||||
<signal name="toggled" handler="_on_delete_button_toggled" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="icon_name">edit-delete</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">5</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="clear_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Cleanup canvas</property>
|
|
||||||
<signal name="clicked" handler="_on_clear_button_clicked" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="icon_name">edit-clear</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">6</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="export_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">True</property>
|
|
||||||
<property name="receives_default">True</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Export image to SVG file</property>
|
|
||||||
<property name="margin_top">6</property>
|
|
||||||
<signal name="clicked" handler="_on_export_button_clicked" swapped="no"/>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="icon_name">document-save-as</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">7</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">False</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
||||||
Reference in New Issue
Block a user