@@ -427,11 +427,17 @@ def __init__(self):
427427 layout .addWidget (self .user_button , alignment = Qt .AlignTop )
428428
429429 def setup (self , window , controller ):
430+ self .controller = controller
431+ self .controller .update_authenticated_user .connect (self ._on_update_authenticated_user )
430432 self .user_button .setup (controller )
431433 self .login_button .setup (window )
432434
435+ @pyqtSlot (User )
436+ def _on_update_authenticated_user (self , db_user : User ) -> None :
437+ self .set_user (db_user )
438+
433439 def set_user (self , db_user : User ):
434- self .user_icon .setText (_ ( db_user .initials ) )
440+ self .user_icon .setText (db_user .initials )
435441 self .user_button .set_username (db_user .fullname )
436442
437443 def show (self ):
@@ -1639,6 +1645,59 @@ def validate(self):
16391645 self .error (_ ("Please enter a username, passphrase and " "two-factor code." ))
16401646
16411647
1648+ class SenderIcon (QWidget ):
1649+ """
1650+ Represents a reply to a source.
1651+ """
1652+
1653+ SENDER_ICON_CSS = load_css ("sender_icon.css" )
1654+
1655+ def __init__ (self ) -> None :
1656+ super ().__init__ ()
1657+ self .sender_is_current_user = False
1658+ self .setObjectName ("SenderIcon" )
1659+ self .setStyleSheet (self .SENDER_ICON_CSS )
1660+ self .setFixedSize (QSize (48 , 48 ))
1661+ font = QFont ()
1662+ font .setLetterSpacing (QFont .AbsoluteSpacing , 0.58 )
1663+ self .label = QLabel ()
1664+ self .label .setAlignment (Qt .AlignCenter )
1665+ self .label .setFont (font )
1666+ layout = QVBoxLayout ()
1667+ layout .setContentsMargins (0 , 0 , 0 , 0 )
1668+ layout .setSpacing (0 )
1669+ layout .addWidget (self .label )
1670+ self .setLayout (layout )
1671+
1672+ def set_sender (self , sender : User , sender_is_current_user : bool ) -> None :
1673+ self .sender_is_current_user = sender_is_current_user
1674+ if not sender or not sender .initials :
1675+ self .label .setPixmap (load_image ("deleted-user.png" ))
1676+ else :
1677+ self .label .setText (sender .initials )
1678+
1679+ def set_normal_styles (self ):
1680+ self .setStyleSheet ("" )
1681+ if self .sender_is_current_user :
1682+ self .setObjectName ("SenderIcon_current_user" )
1683+ else :
1684+ self .setObjectName ("SenderIcon" )
1685+ self .setStyleSheet (self .SENDER_ICON_CSS )
1686+
1687+ def set_failed_styles (self ):
1688+ self .setStyleSheet ("" )
1689+ self .setObjectName ("SenderIcon_failed" )
1690+ self .setStyleSheet (self .SENDER_ICON_CSS )
1691+
1692+ def set_pending_styles (self ):
1693+ self .setStyleSheet ("" )
1694+ if self .sender_is_current_user :
1695+ self .setObjectName ("SenderIcon_current_user_pending" )
1696+ else :
1697+ self .setObjectName ("SenderIcon_pending" )
1698+ self .setStyleSheet (self .SENDER_ICON_CSS )
1699+
1700+
16421701class SpeechBubble (QWidget ):
16431702 """
16441703 Represents a speech bubble that's part of a conversation between a source
@@ -1682,6 +1741,10 @@ def __init__(
16821741 self .color_bar .setObjectName ("SpeechBubble_status_bar" )
16831742 self .color_bar .setStyleSheet (self .STATUS_BAR_CSS )
16841743
1744+ # User icon
1745+ self .sender_icon = SenderIcon ()
1746+ self .sender_icon .hide ()
1747+
16851748 # Speech bubble
16861749 self .speech_bubble = QWidget ()
16871750 self .speech_bubble .setObjectName ("SpeechBubble_container" )
@@ -1698,6 +1761,7 @@ def __init__(
16981761 self .bubble_area_layout = QHBoxLayout ()
16991762 self .bubble_area_layout .setContentsMargins (0 , self .TOP_MARGIN , 0 , self .BOTTOM_MARGIN )
17001763 bubble_area .setLayout (self .bubble_area_layout )
1764+ self .bubble_area_layout .addWidget (self .sender_icon , alignment = Qt .AlignBottom )
17011765 self .bubble_area_layout .addWidget (self .speech_bubble )
17021766
17031767 # Add widget to layout
@@ -1779,6 +1843,7 @@ class ReplyWidget(SpeechBubble):
17791843
17801844 def __init__ (
17811845 self ,
1846+ controller : Controller ,
17821847 message_uuid : str ,
17831848 message : str ,
17841849 reply_status : str ,
@@ -1787,11 +1852,16 @@ def __init__(
17871852 message_succeeded_signal ,
17881853 message_failed_signal ,
17891854 index : int ,
1855+ sender : User ,
1856+ sender_is_current_user : bool ,
17901857 error : bool = False ,
17911858 ) -> None :
17921859 super ().__init__ (message_uuid , message , update_signal , download_error_signal , index , error )
1860+ self .controller = controller
1861+ self .status = reply_status
17931862 self .uuid = message_uuid
1794-
1863+ self .sender = sender
1864+ self .sender_is_current_user = sender_is_current_user
17951865 self .error = QWidget ()
17961866 error_layout = QHBoxLayout ()
17971867 error_layout .setContentsMargins (0 , 0 , 0 , 0 )
@@ -1806,18 +1876,21 @@ def __init__(
18061876 self .error .hide ()
18071877
18081878 self .bubble_area_layout .addWidget (self .error )
1879+ self .sender_icon .set_sender (sender , self .sender_is_current_user )
1880+ self .sender_icon .show ()
18091881
18101882 message_succeeded_signal .connect (self ._on_reply_success )
18111883 message_failed_signal .connect (self ._on_reply_failure )
1884+ self .controller .update_authenticated_user .connect (self ._on_update_authenticated_user )
18121885
1813- if reply_status == "SUCCEEDED" :
1814- self . set_normal_styles ()
1815- self . error . hide ( )
1816- elif reply_status == "FAILED" :
1817- self .set_failed_styles ()
1818- self .error . show ()
1819- elif reply_status == "PENDING" :
1820- self .set_pending_styles ()
1886+ self . _set_reply_state ()
1887+
1888+ @ pyqtSlot ( User )
1889+ def _on_update_authenticated_user ( self , db_user : User ) -> None :
1890+ sender_is_current_user = True if db_user . uuid == self .sender . uuid else False
1891+ self .sender_is_current_user = sender_is_current_user
1892+ self . sender_icon . sender_is_current_user = sender_is_current_user
1893+ self ._set_reply_state ()
18211894
18221895 @pyqtSlot (str , str , str )
18231896 def _on_reply_success (self , source_id : str , message_uuid : str , content : str ) -> None :
@@ -1826,8 +1899,8 @@ def _on_reply_success(self, source_id: str, message_uuid: str, content: str) ->
18261899 signal matches the uuid of this widget.
18271900 """
18281901 if message_uuid == self .uuid :
1829- self .set_normal_styles ()
1830- self .error . hide ()
1902+ self .status = "SUCCEEDED"
1903+ self ._set_reply_state ()
18311904
18321905 @pyqtSlot (str )
18331906 def _on_reply_failure (self , message_uuid : str ) -> None :
@@ -1836,31 +1909,54 @@ def _on_reply_failure(self, message_uuid: str) -> None:
18361909 signal matches the uuid of this widget.
18371910 """
18381911 if message_uuid == self .uuid :
1912+ self .status = "FAILED"
1913+ self ._set_reply_state ()
1914+
1915+ def _set_reply_state (self ) -> None :
1916+ if self .status == "SUCCEEDED" :
1917+ self .set_normal_styles ()
1918+ self .error .hide ()
1919+ elif self .status == "PENDING" :
1920+ self .set_pending_styles ()
1921+ elif self .status == "FAILED" :
18391922 self .set_failed_styles ()
18401923 self .error .show ()
18411924
18421925 def set_normal_styles (self ):
18431926 self .message .setStyleSheet ("" )
18441927 self .message .setObjectName ("SpeechBubble_message" )
18451928 self .message .setStyleSheet (self .MESSAGE_CSS )
1929+
1930+ self .sender_icon .set_normal_styles ()
1931+
18461932 self .color_bar .setStyleSheet ("" )
1847- self .color_bar .setObjectName ("ReplyWidget_status_bar" )
1933+ if self .sender_is_current_user :
1934+ self .color_bar .setObjectName ("ReplyWidget_status_bar_current_user" )
1935+ else :
1936+ self .color_bar .setObjectName ("ReplyWidget_status_bar" )
18481937 self .color_bar .setStyleSheet (self .STATUS_BAR_CSS )
18491938
1850- def set_failed_styles (self ):
1939+ def set_pending_styles (self ):
18511940 self .message .setStyleSheet ("" )
1852- self .message .setObjectName ("ReplyWidget_message_failed " )
1941+ self .message .setObjectName ("ReplyWidget_message_pending " )
18531942 self .message .setStyleSheet (self .MESSAGE_CSS )
1943+
1944+ self .sender_icon .set_pending_styles ()
1945+
18541946 self .color_bar .setStyleSheet ("" )
1855- self .color_bar .setObjectName ("ReplyWidget_status_bar_failed" )
1947+ if self .sender_is_current_user :
1948+ self .color_bar .setObjectName ("ReplyWidget_status_bar_pending_current_user" )
1949+ else :
1950+ self .color_bar .setObjectName ("ReplyWidget_status_bar_pending" )
18561951 self .color_bar .setStyleSheet (self .STATUS_BAR_CSS )
18571952
1858- def set_pending_styles (self ):
1953+ def set_failed_styles (self ):
18591954 self .message .setStyleSheet ("" )
1860- self .message .setObjectName ("ReplyWidget_message_pending " )
1955+ self .message .setObjectName ("ReplyWidget_message_failed " )
18611956 self .message .setStyleSheet (self .MESSAGE_CSS )
1957+ self .sender_icon .set_failed_styles ()
18621958 self .color_bar .setStyleSheet ("" )
1863- self .color_bar .setObjectName ("ReplyWidget_status_bar_pending " )
1959+ self .color_bar .setObjectName ("ReplyWidget_status_bar_failed " )
18641960 self .color_bar .setStyleSheet (self .STATUS_BAR_CSS )
18651961
18661962
@@ -2775,12 +2871,22 @@ def update_conversation(self, collection: list) -> None:
27752871 item_widget .message .text () != conversation_item .content
27762872 ) and conversation_item .content :
27772873 item_widget .message .setText (conversation_item .content )
2874+
2875+ # Keep reply sender information up-to-date
2876+ if isinstance (item_widget , ReplyWidget ):
2877+ self .controller .session .refresh (conversation_item .journalist )
2878+ if self .controller .authenticated_user == conversation_item .journalist :
2879+ current_user = True
2880+ self .controller .update_authenticated_user .emit (conversation_item .journalist )
2881+ else :
2882+ current_user = False
2883+ item_widget .sender_icon .set_sender (conversation_item .journalist , current_user )
27782884 else :
27792885 # add a new item to be displayed.
27802886 if isinstance (conversation_item , Message ):
27812887 self .add_message (conversation_item , index )
27822888 elif isinstance (conversation_item , (DraftReply , Reply )):
2783- self .add_reply (conversation_item , index )
2889+ self .add_reply (conversation_item , conversation_item . journalist , index )
27842890 else :
27852891 self .add_file (conversation_item , index )
27862892
@@ -2836,7 +2942,7 @@ def add_message(self, message: Message, index) -> None:
28362942 self .current_messages [message .uuid ] = conversation_item
28372943 self .conversation_updated .emit ()
28382944
2839- def add_reply (self , reply : Union [DraftReply , Reply ], index ) -> None :
2945+ def add_reply (self , reply : Union [DraftReply , Reply ], sender : User , index : int ) -> None :
28402946 """
28412947 Add a reply from a journalist to the source.
28422948 """
@@ -2845,8 +2951,16 @@ def add_reply(self, reply: Union[DraftReply, Reply], index) -> None:
28452951 except AttributeError :
28462952 send_status = "SUCCEEDED"
28472953
2848- logger .debug ("adding reply: with status {}" .format (send_status ))
2954+ if (
2955+ self .controller .authenticated_user
2956+ and self .controller .authenticated_user .id == reply .journalist_id
2957+ ):
2958+ sender_is_current_user = True
2959+ else :
2960+ sender_is_current_user = False
2961+
28492962 conversation_item = ReplyWidget (
2963+ self .controller ,
28502964 reply .uuid ,
28512965 str (reply ),
28522966 send_status ,
@@ -2855,17 +2969,25 @@ def add_reply(self, reply: Union[DraftReply, Reply], index) -> None:
28552969 self .controller .reply_succeeded ,
28562970 self .controller .reply_failed ,
28572971 index ,
2972+ sender ,
2973+ sender_is_current_user ,
28582974 getattr (reply , "download_error" , None ) is not None ,
28592975 )
2976+
28602977 self .scroll .add_widget_to_conversation (index , conversation_item , Qt .AlignRight )
28612978 self .current_messages [reply .uuid ] = conversation_item
28622979
28632980 def add_reply_from_reply_box (self , uuid : str , content : str ) -> None :
28642981 """
28652982 Add a reply from the reply box.
28662983 """
2984+ if not self .controller .authenticated_user :
2985+ logger .error ("User is no longer authenticated so cannot send reply." )
2986+ return
2987+
28672988 index = len (self .current_messages )
28682989 conversation_item = ReplyWidget (
2990+ self .controller ,
28692991 uuid ,
28702992 content ,
28712993 "PENDING" ,
@@ -2874,6 +2996,8 @@ def add_reply_from_reply_box(self, uuid: str, content: str) -> None:
28742996 self .controller .reply_succeeded ,
28752997 self .controller .reply_failed ,
28762998 index ,
2999+ self .controller .authenticated_user ,
3000+ True ,
28773001 )
28783002 self .scroll .add_widget_to_conversation (index , conversation_item , Qt .AlignRight )
28793003 self .current_messages [uuid ] = conversation_item
@@ -3046,6 +3170,7 @@ def send_reply(self) -> None:
30463170 self .controller .send_reply (self .source .uuid , reply_uuid , reply_text )
30473171 self .reply_sent .emit (self .source .uuid , reply_uuid , reply_text )
30483172
3173+ @pyqtSlot (bool )
30493174 def _on_authentication_changed (self , authenticated : bool ) -> None :
30503175 try :
30513176 self .update_authentication_state (authenticated )
0 commit comments