using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media.Imaging; using Hik.Samples.Scs.IrcChat.Arguments; using Hik.Samples.Scs.IrcChat.Client; using Hik.Samples.Scs.IrcChat.Controls; using Microsoft.Win32; namespace Hik.Samples.Scs.IrcChat.Windows { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window, IChatRoomView, ILoginFormView, IMessagingAreaContainer { #region ILoginFormView implementation /// /// IP address of server to be connected. /// public string ServerIpAddress { get { return (string)Dispatcher.Invoke(new Func(() => txtServerIpAddress.Text)); } } /// /// TCP Port number of server to be connected. /// public int ServerTcpPort { get { return (int)Dispatcher.Invoke(new Func(() => Convert.ToInt32(txtServerPort.Text))); } } /// /// User Login informations to be used while logging on to the server. /// public UserInfo CurrentUserInfo { get { return (UserInfo)Dispatcher.Invoke( new Func(() => new UserInfo { Nick = txtNick.Text, Status = UserStatus.Available, AvatarBytes = GetBytesOfCurrentUserAvatar() })); } } #endregion #region IChatRoomView implementation /// /// This method is called when a message is sent to chat room. /// /// Nick of sender /// Message public void OnMessageReceived(string nick, ChatMessage message) { Dispatcher.Invoke(new Action(() => messagingArea.MessageReceived(nick, message))); } /// /// This method is called when a private message is sent to the current user. /// /// Nick of sender /// The message public void OnPrivateMessageReceived(string nick, ChatMessage message) { Dispatcher.Invoke(new Action(() => OnPrivateMessageReceivedInternal(nick, message))); } /// /// This method is called when user successfully logged in to chat server. /// public void OnLoggedIn() { Dispatcher.Invoke(new Action(OnLoggedInInternal)); } /// /// This method is used to inform view if login is failed. /// /// Detail of error public void OnLoginError(string errorMessage) { MessageBox.Show(errorMessage, "Login Error!", MessageBoxButton.OK, MessageBoxImage.Error); } /// /// This method is called when connection to server is closed. /// public void OnLoggedOut() { Dispatcher.Invoke(new Action(OnLoggedOutInternal)); } /// /// This methos is used to add a new user to user list in room view. /// /// Informations of new user public void AddUserToList(UserInfo userInfo) { Dispatcher.Invoke(new Action(() => AddUserToListInternal(userInfo))); } /// /// This metrhod is used to remove a user (that is disconnected from server) from user list in room view. /// /// Nick of user to remove public void RemoveUserFromList(string nick) { Dispatcher.Invoke(new Action(() => RemoveUserFromListInternal(nick))); } /// /// This method is called from chat server to inform that a user changed his/her status. /// /// Nick of the user /// New status of the user public void OnUserStatusChange(string nick, UserStatus newStatus) { Dispatcher.Invoke(new Action(() => OnUserStatusChangeInternal(nick, newStatus))); } #endregion #region IMessagingAreaContainer implementation /// /// Sends a message to the room. /// public void SendMessage(ChatMessage message) { _controller.SendMessageToRoom(message); } #endregion #region Private fields /// /// Reference to the controller object. /// private readonly IChatController _controller; /// /// List of open private chat windows. /// private readonly SortedList _privateChatWindows; /// /// Reference to the user preferences. /// private readonly UserPreferences _userPreferences; #endregion #region Constructor and Initialize methods /// /// Creates a new form with a reference to the controller object. /// /// Reference to the controller object public MainWindow(IChatController controller) { _controller = controller; _privateChatWindows = new SortedList(); _userPreferences = UserPreferences.Current; InitializeComponent(); InitializeControls(); } /// /// Initializes some controls. /// private void InitializeControls() { messagingArea.MessagingAreaContainer = this; txtNick.Text = _userPreferences.Nick; txtServerIpAddress.Text = _userPreferences.ServerIpAddress; txtServerPort.Text = _userPreferences.ServerTcpPort.ToString(); InitializeUserAvatar(); } /// /// Initializes and shows user avatar. /// private void InitializeUserAvatar() { try { ChangeCurrentUserAvatar(_userPreferences.AvatarFile); } catch (Exception ex) { MessageBox.Show("Can not load avatar image. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion #region Handlers for events of window and controls /// /// Handles Loaded event of this Window. /// /// Source of event /// Event arguments private void Window_Loaded(object sender, RoutedEventArgs e) { txtNick.Focus(); txtNick.SelectAll(); } /// /// Hansles Closing event of this window. /// /// Source of event /// Event arguments private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { _userPreferences.Save(); _controller.Disconnect(); } /// /// Handles TextChanged event of txtNick (on login screen). /// /// Source of event /// Event arguments private void txtNick_TextChanged(object sender, TextChangedEventArgs e) { lblCurrentUserNick.Content = txtNick.Text; } /// /// Handles Click event of 'Change to female' right menu item of avatar menu. /// Changes login avatar to default female avatar. /// /// Source of event /// Event arguments private void ChangeToFemale_Click(object sender, RoutedEventArgs e) { try { ChangeCurrentUserAvatar(Path.Combine(ClientHelper.GetCurrentDirectory(), @"Images\user_female.png")); } catch (Exception ex) { MessageBox.Show("Can not load avatar image. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Handles Click event of 'Change to male' right menu item of avatar menu. /// Changes login avatar to default male avatar. /// /// Source of event /// Event arguments private void ChangeToMale_Click(object sender, RoutedEventArgs e) { try { ChangeCurrentUserAvatar(Path.Combine(ClientHelper.GetCurrentDirectory(), @"Images\user_male.png")); } catch (Exception ex) { MessageBox.Show("Can not load avatar image. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Handles Click event of 'Select a picture...' menu item. /// /// Source of event /// Event arguments private void SelectAvatar_Click(object sender, RoutedEventArgs e) { try { var avatarSelectDialog = new OpenFileDialog(); avatarSelectDialog.Filter = "JPG Files|*.jpg|JPEG Files|*.jpeg|GIF Files|*.gif|PNG files|*.png|BMP Files|*.bmp"; if (avatarSelectDialog.ShowDialog() == true) { var selectedFile = avatarSelectDialog.FileName; if (ClientHelper.GetFileSize(selectedFile) > (100 * 1024)) { MessageBox.Show("You can not select avatar file larger than 100 KB.", "Warning!", MessageBoxButton.OK, MessageBoxImage.Warning); return; } ChangeCurrentUserAvatar(selectedFile); } } catch (Exception ex) { MessageBox.Show("Can not load avatar image. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Handles Client event of Login button. /// /// Source of event /// Event arguments private void btnLogin_Click(object sender, RoutedEventArgs e) { ConnectToServer(); } /// /// Handles KeyDown of Login form (actually the Border named brdConnect that contains login controls) /// /// Source of event /// Event arguments private void LoginForm_KeyDown(object sender, KeyEventArgs e) { //If user pressed to enter in login form, connect to server if (e.Key == Key.Enter) { ConnectToServer(); } } /// /// Handles SelectionChanged event of user status combobox /// /// Source of event /// Event arguments private void cmbCurrentUserStatus_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!IsInitialized || cmbCurrentUserStatus.SelectedIndex < 0 || _controller == null) { return; } try { var newStatus = GetCurrentUserStatus(); _controller.ChangeStatus(newStatus); //Change user's status on all open private chat windows foreach (var chatWindow in _privateChatWindows.Values.ToList()) { chatWindow.CurrentUserStatus = newStatus; } } catch (Exception ex) { MessageBox.Show("Can not changes status. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// Handles MouseDoubleClick event of all User cards. /// /// Source of event /// Event arguments private void UserCard_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if (e.ChangedButton != MouseButton.Left) { return; } var userCard = e.Source as UserCardControl; if (userCard == null) { return; } if (_privateChatWindows.ContainsKey(userCard.UserNick)) { _privateChatWindows[userCard.UserNick].Activate(); } else { _privateChatWindows[userCard.UserNick] = CreatePrivateChatWindow(userCard); } } /// /// Handles Closed event of Private chat windows. /// /// Source of event /// Event arguments private void PrivateChatWindow_Closed(object sender, EventArgs e) { var privateChatWindow = sender as PrivateChatWindow; if (privateChatWindow == null) { return; } if (_privateChatWindows.ContainsKey(privateChatWindow.RemoteUserNick)) { _privateChatWindows.Remove(privateChatWindow.RemoteUserNick); } } #endregion #region Private methods /// /// Connects to the server. /// This method is called on login. /// private void ConnectToServer() { if(string.IsNullOrEmpty(txtNick.Text)) { MessageBox.Show("You must enter a nick to login to server.", "Warning!", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrEmpty(txtServerIpAddress.Text)) { MessageBox.Show("You must enter IP address to connect to server.", "Warning!", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (string.IsNullOrEmpty(txtServerPort.Text)) { MessageBox.Show("You must enter TCP port to connect to server.", "Warning!", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { _controller.Connect(); _userPreferences.Nick = txtNick.Text; _userPreferences.ServerIpAddress = txtServerIpAddress.Text; try { _userPreferences.ServerTcpPort = Convert.ToInt32(txtServerPort.Text); } catch { } _userPreferences.Save(); } catch (Exception ex) { MessageBox.Show("Can not connected to the server. Check Server IP and port. Error Detail: " + ex.Message, "Error!", MessageBoxButton.OK, MessageBoxImage.Error); } } /// /// This method is used to hide Login form when user succussfully logged into server. /// private void OnLoggedInInternal() { grdConnect.Visibility = Visibility.Collapsed; brdConnect.Visibility = Visibility.Collapsed; } /// /// This method is used to show Login form when connection to server is broken/closed. /// private void OnLoggedOutInternal() { foreach (var privateChatWindow in _privateChatWindows.Values.ToList()) { privateChatWindow.Close(); } spUsers.Children.Clear(); grdConnect.Visibility = Visibility.Visible; brdConnect.Visibility = Visibility.Visible; } /// /// This method is used to send private message to proper private messaging window. /// /// Nick of sender /// Message private void OnPrivateMessageReceivedInternal(string nick, ChatMessage message) { var userCard = FindUserInList(nick); if (userCard == null) { return; } if (!_privateChatWindows.ContainsKey(nick)) { //Create new private chat window _privateChatWindows[nick] = CreatePrivateChatWindow(userCard); //Set initial state as minimized _privateChatWindows[nick].WindowState = WindowState.Minimized; //Flash the window button on taskbar to inform user WindowsHelper.FlashWindow(new WindowInteropHelper(_privateChatWindows[nick]).Handle, WindowsHelper.FlashWindowFlags.FLASHW_ALL, 2, 1000); } _privateChatWindows[nick].MessageReceived(message); } /// /// Creates a new PrivateChatWindow from a user card. /// /// User card to use while creating window /// Created window private PrivateChatWindow CreatePrivateChatWindow(UserCardControl userCard) { var window = new PrivateChatWindow(_controller) { CurrentUserNick = txtNick.Text, CurrentUserStatus = GetCurrentUserStatus(), CurrentUserAvatar = imgCurrentUserAvatar.Source, RemoteUserNick = userCard.UserNick, RemoteUserStatus = userCard.UserStatus, RemoteUserAvatar = userCard.AvatarImageSource }; window.Closed += PrivateChatWindow_Closed; window.Show(); return window; } /// /// Adds user to user list in right area of the window. /// /// New user informations private void AddUserToListInternal(UserInfo userInfo) { //Do not add the current user (that is using the application) to user list if (userInfo.Nick == CurrentUserInfo.Nick) { return; } //Do not add user to list if it is already exists. if (FindUserInList(userInfo.Nick) != null) { return; } //Find correct order (by name) to insert the user var orderedIndex = 0; foreach (UserCardControl userCardControl in spUsers.Children) { if (userInfo.Nick.CompareTo(userCardControl.UserNick) < 0) { break; } orderedIndex++; } //Create user control var userCard = new UserCardControl { UserNick = userInfo.Nick, UserStatus = userInfo.Status, AvatarBytes = userInfo.AvatarBytes, Height = 60 }; userCard.MouseDoubleClick += UserCard_MouseDoubleClick; //Insert user to user list spUsers.Children.Insert( orderedIndex, userCard ); //Enable private messaging window if any open with that user if (_privateChatWindows.ContainsKey(userInfo.Nick)) { _privateChatWindows[userInfo.Nick].UserLoggedIn(); _privateChatWindows[userInfo.Nick].RemoteUserStatus = userInfo.Status; _privateChatWindows[userInfo.Nick].RemoteUserAvatar = userCard.AvatarImageSource; } } /// /// Removes an existing user from user list. /// /// private void RemoveUserFromListInternal(string nick) { //Enable private messaging window is any open with that user if (_privateChatWindows.ContainsKey(nick)) { _privateChatWindows[nick].UserLoggedOut(); } //Find user in list var userCard = FindUserInList(nick); //Remove if found if (userCard != null) { spUsers.Children.Remove(userCard); userCard.MouseDoubleClick -= UserCard_MouseDoubleClick; } } /// /// Changes status of a user in user list. /// /// Nick of the user /// New status of the user public void OnUserStatusChangeInternal(string nick, UserStatus newStatus) { //Find user in list var userCard = FindUserInList(nick); //Change status of user if found if (userCard != null) { userCard.UserStatus = newStatus; } //Change status of user if any private chat window is open if (_privateChatWindows.ContainsKey(nick)) { _privateChatWindows[nick].RemoteUserStatus = newStatus; } } /// /// Searches a user (by nick) in user list and gets user card control of user. /// /// Nick to search /// Found user card of user private UserCardControl FindUserInList(string nick) { return spUsers.Children.Cast().FirstOrDefault(userCardControl => userCardControl.UserNick == nick); } /// /// Changes avatar of the current user by a file. /// /// File path of new avatar private void ChangeCurrentUserAvatar(string avatarPath) { imgLoginAvatar.Source = new BitmapImage(new Uri(avatarPath)); imgCurrentUserAvatar.Source = imgLoginAvatar.Source; _userPreferences.AvatarFile = avatarPath; } /// /// Gets status of the current user from combobox. /// /// Status of current user private UserStatus GetCurrentUserStatus() { switch (cmbCurrentUserStatus.SelectedIndex) { case 0: return UserStatus.Available; case 1: return UserStatus.Busy; default: return UserStatus.Out; } } /// /// Gets bytes of current user avatar. /// /// Bytes of user avatar file private byte[] GetBytesOfCurrentUserAvatar() { if (string.IsNullOrEmpty(_userPreferences.AvatarFile)) { return null; } try { if (!File.Exists(_userPreferences.AvatarFile)) { return null; } return File.ReadAllBytes(_userPreferences.AvatarFile); } catch (Exception) { return null; } } #endregion } }