+ use an svg for the default avatar

+ When we accept a watcher and add it as a contact, it has no display name.
  We can take it from offline/online presence info instead
+ When a contact is removed and it had "Exchange presence info" enabled, we
  should remove its uris from the allowed contact list and add them to the
  blocked contact list (actually check if this is what we want)
+ When "Exchange availability information" is disabled, we still get
  presence updates until blink is restarted
+ If a uri was blocked then later added to a contact, it will still retain
  an addressbook policy with policy=blocked, even if "Exchange availability
  information" is enabled. Despite this, the accounts seem to exchange
  presence info without problems.
+ Add the ability to merge contacts

ZRTP
----

- decide between separate checkbox to enable/disable RTP encryption vs
  having "Disabled" integrated in the combobox
- key negotiation notes label in preferences


Video fixes
-----------

- consider if we should always show the mute/hold/close buttons (even in
  attached mode)?
- run the preview at the normal framerate before we connect (while big)?
- hide scrollbar in chat widget when video is overlayed on it?
- right click on camera preview bring up context menu to select camera
- make detaching animation have a duration that is proportional with the
  distance traveled, so that it appears to be similarly fast no matter
  how far it detaches
- maybe don't show the camera preview if the video device is None
- hide preview (and buttons?) while we animate?
- preview limited to parent (resize still has issues)
- double click to restore default size for preview? (might be problematic)

- if audio is removed blink-qt puts the session on hold 5 seconds later
  when the AudioSessionItem is destroyed
- custom icons for each window (chat, video, file transfer, ...)


Code refactoring
----------------

+ adjust web view spacing and margins for widgets (relative to window borders)

+ do not kill greenlets but interrupt commands instead (sipsimple)
- refactor how models/dialogs/windows are created and where are they kept

Issues
------

- tls cert text editors have the clear button cover the text
- have a random conference room be joined if no room is specified
- raise publish/subscribe intervals to 3600? what about register?
- review the change to only play a hangup tone when session has audio/video
- apply the default font/size from the theme to the input textbox?
- in Smooth Operator check if the src attribute is still needed in elements
  that have the x-color class. there used to be a rule that matched elements
  with an x-color class which also had a src attribute pointing to a
  particular image, but since that was removed, no other rule matching
  an element with the x-color class cares for the src attribute. check if
  the src attribute on a span tag means anything, if not it can probably be
  safely removed from message.html and message_continuation.html
- don't show selected audio device on the incoming dialog for chat
- reconsider the busy button on the incoming dialog (replace with ignore?)
- when a session is closed the chat window shows: "Disconnected: Connection
  was closed cleanly", but when calling oneself and a loop is detected is
  only says "Disconnected". It should actually only say Disconnected when
  the connection is ended normally and show the failure reason if not.
  Also when a connection is ended voluntarily it should not care if there
  is a failure while stopping the streams.
- decide what to do about having keyboard shortcuts for hold/hangup in the
  chat window (list may not be visible all the time and here we also
  differentiate between hangup and delete session)
- move tray icon from the main window to Blink?
- there are session transitions that do not change the state (for example
  a stream that is removed, either by local or remote, never switches the
  state to sent/received_proposal and back. this means that one cannot
  rely on BlinkSessionDidChangeState alone to handle session transitions,
  but instead needs to also listed to BlinkSessionDidRemoveStream.
- I got an incoming call and the contact was found as a google contact, but
  in history I have no name and the original uri. if I dial back, it doesn't
  find the contact and says number@domain for the name.
- not sure about passing a Contact object to the session instead of
  contact.settings.
- the DummyContact should follow the other contact APIs (have an id, ...)
  in order to be usable in their place.
- have a contact.default_streams that returns a list of StreamDescription?
- have a contact.account property that returns the best account for outgoing?

Ideas:
------

- chat window alternate minimum sizes:
  900x550 (230 splitter), 925x550 (240 splitter), 950x550 (250 splitter)


CPU usage increases for video:
------------------------------

painting camera preview @10fps (static image no producer connected)

in chat:   +4-5%
detached:  +3-4%
fullscren: +4-7%

producer @25fps connected, not painting

in chat:    +8-9%
detached:   +8-9%
fullscreen: +6-7%

producer @25fps connected, painting @10fps

in chat:    +13-14%
detached:   +12%
fullscreen: +12-15%


- exceptions:

Unhandled Error
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/application.py", line 141, in _run_reactor
    reactor.run(installSignalHandlers=False)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1199, in run
    self.mainLoop()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1208, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 828, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 241, in _spawn
    g.switch()
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 237, in _spawn_startup
    return cb(*args, **kw)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 380, in add_participant
    referral_handler.start()
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 181, in start
    if not self.session.remote_focus:
exceptions.AttributeError: 'ReferralHandler' object has no attribute 'session'
Traceback (most recent call last):
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 5015, in dropEvent
    if model.handleDroppedData(event.mimeData(), event.dropAction(), self.indexAt(event.pos())):
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 4840, in handleDroppedData
    return handler(mime_data, action, index)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 4850, in _DH_ApplicationXBlinkContactList
    self.session.server_conference.add_participant(contact, contact.uri)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 1410, in add_participant
    if contact_uri.uri in self.participants:
AttributeError: 'NoneType' object has no attribute 'uri'

Unhandled Error
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/application.py", line 141, in _run_reactor
    reactor.run(installSignalHandlers=False)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1199, in run
    self.mainLoop()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1208, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 828, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 241, in _spawn
    g.switch()
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 237, in _spawn_startup
    return cb(*args, **kw)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 380, in add_participant
    referral_handler.start()
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 181, in start
    if not self.session.remote_focus:
exceptions.AttributeError: 'ReferralHandler' object has no attribute 'session'

Unhandled Error
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/application.py", line 141, in _run_reactor
    reactor.run(installSignalHandlers=False)
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1199, in run
    self.mainLoop()
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 1208, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 828, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 241, in _spawn
    g.switch()
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 237, in _spawn_startup
    return cb(*args, **kw)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 380, in add_participant
    referral_handler.start()
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 181, in start
    if not self.session.remote_focus:
exceptions.AttributeError: 'ReferralHandler' object has no attribute 'session'
Traceback (most recent call last):
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 5015, in dropEvent
    if model.handleDroppedData(event.mimeData(), event.dropAction(), self.indexAt(event.pos())):
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 4840, in handleDroppedData
    return handler(mime_data, action, index)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 4850, in _DH_ApplicationXBlinkContactList
    self.session.server_conference.add_participant(contact, contact.uri)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 1410, in add_participant
    if contact_uri.uri in self.participants:
AttributeError: 'NoneType' object has no attribute 'uri'
error: Exception occured while calling function handle_notification in the GUI thread
Traceback (most recent call last):
  File "/home/dan/work/voip/blink-qt/blink/__init__.py", line 278, in _EH_CallFunctionEvent
    event.function(*event.args, **event.kw)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 5531, in handle_notification
    handler(notification)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 5589, in _NH_SIPSessionNewIncoming
    session.send_ring_indication()
  File "<string>", line 1, in send_ring_indication
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 118, in wrapper
    raise IllegalStateError('cannot call %s in %s state' % (func.__name__, obj.state))
IllegalStateError: cannot call send_ring_indication in terminated state


error: Exception occured in observer <blink.chatwindow.ChatWindow object at 0xab33d5cc> while handling notification 'BlinkSessionInfoUpdated'
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
    observer.handle_notification(notification)
  File "<string>", line 1, in handle_notification
  File "/home/dan/work/voip/blink-qt/blink/util.py", line 36, in wrapper
    function(*args, **kw)
  File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1653, in handle_notification
    handler(notification)
  File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1767, in _NH_BlinkSessionInfoUpdated
    self._update_session_info_panel(elements=notification.data.elements)
  File "/home/dan/work/voip/blink-qt/blink/chatwindow.py", line 1481, in _update_session_info_panel
    self.video_value_label.setText(video_info.codec or 'N/A')
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 158, in codec
    return '{0.codec_name} {0.framerate:.3g}fps'.format(self) if self.codec_name else None
ValueError: Unknown format code 'g' for object of type 'str'


error: Exception occured in observer <blink.sessions.AudioSessionListView object at 0xab545464> while handling notification 'BlinkActiveSessionDidChange'
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
    observer.handle_notification(notification)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 2632, in handle_notification
    handler(notification)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 2646, in _NH_BlinkActiveSessionDidChange
    position = model.sessions.index(notification.data.active_session.items.audio)
ValueError: None is not in list


error: Exception occured in observer <sipsimple.streams.msrp.ScreenSharingStream object at 0x7fa65c2a3550> while handling notification 'MediaStreamWillEnd'
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/application/notification.py", line 216, in post_notification
    observer.handle_notification(notification)
  File "/home/saghul/work/ag-projects/video/python-sipsimple/sipsimple/streams/msrp.py", line 280, in handle_notification
    handler(notification)
  File "/home/saghul/work/ag-projects/video/python-sipsimple/sipsimple/streams/msrp.py", line 1050, in _NH_MediaStreamWillEnd
    notification.center.remove_observer(self, sender=self.handler)
  File "/usr/lib/python2.7/dist-packages/application/notification.py", line 163, in remove_observer
    raise KeyError("observer %r not registered for %r events from %r" % (observer, name, sender))
KeyError: 'observer <sipsimple.streams.msrp.ScreenSharingStream object at 0x7fa65c2a3550> not registered for Any events from <blink.sessions.ExternalVNCServerHandler object at 0x7fa65c2a3dd0>'


Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/twisted/internet/base.py", line 824, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/usr/lib/python2.7/dist-packages/eventlib/coros.py", line 253, in _do_acquire
    waiter.switch()
  File "/usr/lib/python2.7/dist-packages/eventlib/api.py", line 235, in _spawn_startup
    return cb(*args, **kw)
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 1809, in hold
    self._send_hold()
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 1986, in _send_hold
    notification = self._channel.wait()
  File "/usr/lib/python2.7/dist-packages/eventlib/coros.py", line 478, in wait
    api.getcurrent().throw(*exc)
MediaStreamDidFailError
Traceback (most recent call last):
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 1130, in _SH_HangupButtonClicked
    self.end()
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 1102, in end
    self.blink_session.remove_stream(self.audio_stream)
  File "/home/dan/work/voip/blink-qt/blink/sessions.py", line 592, in remove_stream
    self.sip_session.remove_stream(stream)
  File "<string>", line 1, in remove_stream
  File "/home/dan/work/voip/python-sipsimple/sipsimple/session.py", line 100, in wrapper
    raise IllegalStateError('cannot call %s in %s state' % (func.__name__, obj.state))
sipsimple.session.IllegalStateError: cannot call remove_stream in sending_proposal state


Presence
--------

- Is picking the most recent timestamp a good winning method?
- Calculate user idleness
- Add a GUI element for the offline note
- Delete own icon if we don't get anything back from XCAP?
- Unify settings for inbound and outbound presence