Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/send chatstates #97

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions src/Engine-XMPP/Protocols/Xmpp/XmppProtocolManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,36 @@ public override void CloseChat(FrontendManager fm, ChatModel chat)
}
}

[MethodImpl(MethodImplOptions.Synchronized)]
public override void SetChatState(FrontendManager fm, ChatModel chat, ChatState state)
{
base.SetChatState(fm, chat, state);
if (!(chat is PersonChatModel)) {
return;
}
var msg = new Message(chat.ID);
switch (state) {
case ChatState.Active:
msg.Chatstate = Chatstate.active;
break;
case ChatState.Inactive:
msg.Chatstate = Chatstate.inactive;
break;
case ChatState.Composing:
msg.Chatstate = Chatstate.composing;
break;
case ChatState.Paused:
msg.Chatstate = Chatstate.paused;
break;
case ChatState.Gone:
msg.Chatstate = Chatstate.gone;
break;
default:
throw new ArgumentOutOfRangeException();
}
JabberClient.Send(msg);
}

[MethodImpl(MethodImplOptions.Synchronized)]
public override void SetPresenceStatus(PresenceStatus status,
string message)
Expand Down Expand Up @@ -1144,17 +1174,23 @@ void _Say(ChatModel chat, string text, bool send, bool display)
XmppPersonModel person = GetOrCreateContact(_person.ID, _person.IdentityName);
Jid jid = person.Jid;
if (!String.IsNullOrEmpty(jid.Resource)) {
JabberClient.Send(new Message(jid, XmppMessageType.chat, text));
var msg = new Message(jid, XmppMessageType.chat, text);
msg.Chatstate = Chatstate.active;
JabberClient.Send(msg);
} else {
var resources = person.GetResourcesWithHighestPriority();
if (resources.Count == 0) {
// no connected resource, send to bare jid
JabberClient.Send(new Message(jid.Bare, XmppMessageType.chat, text));
var msg = new Message(jid.Bare, XmppMessageType.chat, text);
msg.Chatstate = Chatstate.active;
JabberClient.Send(msg);
} else {
foreach (var res in resources) {
Jid j = new Jid(jid);
j.Resource = res.Name;
JabberClient.Send(new Message(j, XmppMessageType.chat, text));
var msg = new Message(j, XmppMessageType.chat, text);
msg.Chatstate = Chatstate.active;
JabberClient.Send(msg);
}
}
}
Expand Down
37 changes: 37 additions & 0 deletions src/Engine/Chats/ChatState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Smuxi - Smart MUltipleXed Irc
//
// Copyright (c) 2013 oliver
//
// Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
//
// This program 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; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;

namespace Smuxi.Engine
{
public enum ChatState {
// after sending a message or w/o any state
Active,
// this chat window is not the active one
Inactive,
// is typing right now
Composing,
// was typing but stopped
Paused,
// the chat window has been closed
Gone
}
}

1 change: 1 addition & 0 deletions src/Engine/Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Compile Include="TextColorPalettes.cs" />
<Compile Include="Servers\JabbR\JabbRHub.cs" />
<Compile Include="CertificateValidator.cs" />
<Compile Include="Chats\ChatState.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Protocols\" />
Expand Down
1 change: 1 addition & 0 deletions src/Engine/Protocols/IProtocolManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,6 @@ PresenceStatus PresenceStatus {
void CloseChat(FrontendManager fm, ChatModel chat);

void SetPresenceStatus(PresenceStatus status, string message);
void SetChatState(FrontendManager fm, ChatModel chat, ChatState state);
}
}
5 changes: 5 additions & 0 deletions src/Engine/Protocols/ProtocolManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ public abstract void Connect(FrontendManager fm,
public abstract void SetPresenceStatus(PresenceStatus status,
string message);

public virtual void SetChatState(FrontendManager fm, ChatModel chat,
ChatState state)
{
}

protected void NotConnected(CommandModel cmd)
{
var msg = CreateMessageBuilder();
Expand Down
14 changes: 14 additions & 0 deletions src/Frontend-GNOME-XMPP/XmppPersonChatView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,20 @@ void _OnMenuWhoisItemActivated(object sender, EventArgs e)
});
}

public override void OnSwitchedFrom()
{
base.OnSwitchedFrom();
var entry = Frontend.MainWindow.Entry;
entry.SetChatState(ChatState.Inactive);
}

public override void OnSwitchedTo()
{
base.OnSwitchedTo();
var entry = Frontend.MainWindow.Entry;
entry.SetChatState(ChatState.Active);
}

void _OnMenuAddToContactsItemActivated(object sender, EventArgs e)
{
Trace.Call(sender, e);
Expand Down
98 changes: 37 additions & 61 deletions src/Frontend-GNOME/ChatViewManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

namespace Smuxi.Frontend.Gnome
{
public class ChatViewManager : ChatViewManagerBase
public class ChatViewManager : ChatViewManagerBase<ChatView>
{
#if LOG4NET
private static readonly log4net.ILog f_Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Expand All @@ -46,14 +46,13 @@ public class ChatViewManager : ChatViewManagerBase
bool AutoSwitchPersonChats { get; set; }
bool AutoSwitchGroupChats { get; set; }

public event ChatViewManagerChatAddedEventHandler ChatAdded;
public event ChatViewManagerChatRemovedEventHandler ChatRemoved;
public event EventHandler<ChatViewManagerChatSyncedEventArgs> ChatSynced;

public override IChatView ActiveChat {
public override ChatView ActiveChat {
get {
return CurrentChatView;
}
set {
CurrentChatView = value as ChatView;
}
}

public ChatView CurrentChatView {
Expand Down Expand Up @@ -106,6 +105,8 @@ public bool IsSensitive {
public ChatViewManager(Notebook notebook, Gtk.TreeView treeView)
{
f_Notebook = notebook;
f_Notebook.SwitchPage += HandleBeforeSwitchPage;
f_Notebook.SwitchPage += HandleSwitchPage;
f_TreeView = treeView;
SyncedChats = new List<ChatView>();
SyncManager = new ChatViewSyncManager();
Expand All @@ -114,6 +115,33 @@ public ChatViewManager(Notebook notebook, Gtk.TreeView treeView)
SyncManager.WorkerException += OnWorkerException;
}

void HandleSwitchPage(object sender, Gtk.SwitchPageArgs e)
{
Trace.Call(sender, e);

if (f_Notebook.IsBrowseModeEnabled) {
return;
}

// synchronize FrontManager.CurrenPage
ChatView chatView = f_Notebook.GetChat((int)e.PageNum);
if (chatView == null) {
return;
}
chatView.OnSwitchedTo();
}

[GLib.ConnectBefore]
void HandleBeforeSwitchPage(object sender, Gtk.SwitchPageArgs e)
{
if (f_Notebook.IsBrowseModeEnabled) {
return;
}

var chatView = CurrentChatView;
chatView.OnSwitchedFrom();
}

/// <remarks>
/// This method is thread safe.
/// </remarks>
Expand Down Expand Up @@ -141,9 +169,7 @@ public override void RemoveChat(ChatModel chat)
SyncManager.Remove(chat);
SyncedChats.Remove(chatView);

if (ChatRemoved != null) {
ChatRemoved(this, new ChatViewManagerChatRemovedEventArgs(chatView));
}
OnChatRemoved(new ChatViewManagerChatRemovedEventArgs<ChatView>(chatView));

chatView.Dispose();
}
Expand Down Expand Up @@ -289,9 +315,7 @@ void OnChatAdded(object sender, ChatViewAddedEventArgs e)
}
}

if (ChatAdded != null) {
ChatAdded(this, new ChatViewManagerChatAddedEventArgs(chatView));
}
OnChatAdded(new ChatViewManagerChatAddedEventArgs<ChatView>(chatView));
return false;
});
}
Expand Down Expand Up @@ -341,9 +365,7 @@ void OnChatSynced(object sender, ChatViewSyncedEventArgs e)
chatView.ScrollToEnd();

SyncedChats.Add(chatView);
if (ChatSynced != null) {
ChatSynced(this, new ChatViewManagerChatSyncedEventArgs(chatView));
}
OnChatSynced(new ChatViewManagerChatSyncedEventArgs<ChatView>(chatView));
return false;
});
}
Expand Down Expand Up @@ -410,50 +432,4 @@ int GetSortedChatPosition(ChatView chatView)
return idx;
}
}

public delegate void ChatViewManagerChatAddedEventHandler(object sender, ChatViewManagerChatAddedEventArgs e);

public class ChatViewManagerChatAddedEventArgs : EventArgs
{
private ChatView f_ChatView;

public ChatView ChatView {
get {
return f_ChatView;
}
}

public ChatViewManagerChatAddedEventArgs(ChatView chatView)
{
f_ChatView = chatView;
}
}

public delegate void ChatViewManagerChatRemovedEventHandler(object sender, ChatViewManagerChatRemovedEventArgs e);

public class ChatViewManagerChatRemovedEventArgs : EventArgs
{
private ChatView f_ChatView;

public ChatView ChatView {
get {
return f_ChatView;
}
}

public ChatViewManagerChatRemovedEventArgs(ChatView chatView)
{
f_ChatView = chatView;
}
}

public class ChatViewManagerChatSyncedEventArgs : EventArgs
{
public ChatView ChatView { get; private set; }

public ChatViewManagerChatSyncedEventArgs(ChatView chatView)
{
ChatView = chatView;
}
}
}
64 changes: 64 additions & 0 deletions src/Frontend-GNOME/Entry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ public Entry(ChatViewManager chatViewManager)
Activated += _OnActivated;
KeyPressEvent += new Gtk.KeyPressEventHandler(_OnKeyPress);
PasteClipboard += _OnClipboardPasted;

Buffer.Changed += delegate {
if (Buffer.Text.Length == 0) {
return;
}
SetChatState(ChatState.Composing);
};

LastChatState = ChatState.Gone;
}

public void UpdateHistoryChangedLine()
Expand Down Expand Up @@ -464,6 +473,61 @@ protected virtual void ProcessKey(Gtk.KeyPressEventArgs e)
}
}

ChatState LastChatState { get; set; }
DateTime LastChatStateCall { get; set; }
bool ComposingTimeoutActive { get; set; }

bool ChatStateComposingTimeout()
{
if (LastChatState != ChatState.Composing) {
ComposingTimeoutActive = false;
return false;
}
var msleft = (LastChatStateCall + TimeSpan.FromSeconds(2)) - DateTime.Now;
if (msleft > TimeSpan.Zero) {
GLib.Timeout.Add((uint)msleft.Milliseconds, ChatStateComposingTimeout);
return false;
}
SetChatState(ChatState.Paused);
ComposingTimeoutActive = false;
return false;
}

public void SetChatState(ChatState type)
{
if (Frontend.EngineVersion < new Version(0, 9, 1)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next feature release that could contain this feature is 0.10, thus this should be < 0, 10 (0.9.1 will be a bugfix release from the stable branch)

return;
}
var chatView = ChatViewManager.CurrentChatView as PersonChatView;
if (chatView == null) {
return;
}

if (type == ChatState.Composing) {
LastChatStateCall = DateTime.Now;
}
if (LastChatState == type) {
// no duplication
return;
}
LastChatState = type;
var protocol = Frontend.FrontendManager.CurrentProtocolManager;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a remoting call, but what is the purpose here?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh getting the ProtocolManager of the current chat view. This needs to be done with local state of ChatViewManager. Doesnt ChatView has ProtocolManage property these days?

if (protocol == null) {
return;
}

if (type == ChatState.Composing) {
if (!ComposingTimeoutActive) {
ComposingTimeoutActive = true;
GLib.Timeout.Add(2000, ChatStateComposingTimeout);
}
}

protocol.SetChatState(Frontend.FrontendManager,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remoting call, use background thread.

chatView.PersonChatModel,
type);
}

private void _OnActivated(object sender, EventArgs e)
{
Trace.Call(sender, e);
Expand Down
Loading