From 29ca70a6d534e1cd79963718c793ae740318cff1 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 10 Mar 2017 19:34:56 +0100 Subject: Initial plugin system --- CMakeLists.txt | 6 +- client/CMakeLists.txt | 148 ------- client/data/add_conversation/add_contact_dialog.ui | 150 ------- .../data/add_conversation/add_groupchat_dialog.ui | 224 ---------- .../conference_details_fragment.ui | 227 ---------- client/data/add_conversation/list_row.ui | 61 --- .../data/add_conversation/select_jid_fragment.ui | 109 ----- client/data/chat_input.ui | 23 - client/data/conversation_list_titlebar.ui | 41 -- .../data/conversation_selector/chat_row_tooltip.ui | 23 - .../data/conversation_selector/conversation_row.ui | 146 ------- client/data/conversation_selector/view.ui | 33 -- client/data/conversation_summary/message_item.ui | 98 ----- client/data/conversation_summary/view.ui | 33 -- client/data/conversation_titlebar.ui | 63 --- client/data/gschemas.compiled | Bin 316 -> 0 bytes client/data/img/double_tick.svg | 190 --------- client/data/img/send.svg | 1 - client/data/img/status_away.svg | 73 ---- client/data/img/status_chat.svg | 85 ---- client/data/img/status_dnd.svg | 73 ---- client/data/img/status_online.svg | 67 --- client/data/img/tick.svg | 184 -------- client/data/manage_accounts/account_row.ui | 30 -- client/data/manage_accounts/add_account_dialog.ui | 137 ------ client/data/manage_accounts/dialog.ui | 358 ---------------- client/data/menu_add.ui | 16 - client/data/menu_app.ui | 20 - client/data/menu_conversation.ui | 9 - client/data/menu_encryption.ui | 49 --- client/data/occupant_list.ui | 43 -- client/data/occupant_list_item.ui | 44 -- client/data/settings.gschema.xml | 15 - client/data/settings_dialog.ui | 51 --- client/data/style.css | 3 - client/data/unified_window.ui | 178 -------- client/src/dbus/login1.vala | 18 - client/src/dbus/networkmanager.vala | 22 - client/src/dbus/upower.vala | 19 - client/src/entity/account.vala | 40 -- client/src/entity/conversation.vala | 52 --- client/src/entity/jid.vala | 89 ---- client/src/entity/message.vala | 91 ---- client/src/main.vala | 12 - client/src/service/avatar_manager.vala | 134 ------ client/src/service/avatar_storage.vala | 34 -- client/src/service/chat_interaction.vala | 146 ------- client/src/service/connection_manager.vala | 222 ---------- client/src/service/conversation_manager.vala | 103 ----- .../service/counterpart_interaction_manager.vala | 99 ----- client/src/service/database.vala | 466 --------------------- .../src/service/entity_capabilities_storage.vala | 23 - client/src/service/message_manager.vala | 193 --------- client/src/service/module_manager.vala | 96 ----- client/src/service/muc_manager.vala | 224 ---------- client/src/service/pgp_manager.vala | 54 --- client/src/service/presence_manager.vala | 150 ------- client/src/service/roster_manager.vala | 82 ---- client/src/service/stream_interactor.vala | 70 ---- client/src/settings.vala | 28 -- .../add_conversation/chat/add_contact_dialog.vala | 67 --- client/src/ui/add_conversation/chat/dialog.vala | 82 ---- .../src/ui/add_conversation/chat/roster_list.vala | 77 ---- .../conference/add_groupchat_dialog.vala | 107 ----- .../conference/conference_details_fragment.vala | 175 -------- .../conference/conference_list.vala | 105 ----- .../src/ui/add_conversation/conference/dialog.vala | 166 -------- client/src/ui/add_conversation/list_row.vala | 43 -- .../ui/add_conversation/select_jid_fragment.vala | 124 ------ client/src/ui/application.vala | 112 ----- client/src/ui/avatar_generator.vala | 233 ----------- client/src/ui/chat_input.vala | 123 ------ client/src/ui/conversation_list_titlebar.vala | 47 --- client/src/ui/conversation_selector/chat_row.vala | 88 ---- .../ui/conversation_selector/conversation_row.vala | 175 -------- .../ui/conversation_selector/groupchat_row.vala | 33 -- client/src/ui/conversation_selector/list.vala | 173 -------- client/src/ui/conversation_selector/view.vala | 56 --- .../conversation_summary/merged_message_item.vala | 170 -------- .../conversation_summary/merged_status_item.vala | 30 -- .../src/ui/conversation_summary/status_item.vala | 29 -- client/src/ui/conversation_summary/view.vala | 222 ---------- client/src/ui/conversation_titlebar.vala | 124 ------ client/src/ui/manage_accounts/account_row.vala | 24 -- .../src/ui/manage_accounts/add_account_dialog.vala | 70 ---- client/src/ui/manage_accounts/dialog.vala | 193 --------- client/src/ui/notifications.vala | 55 --- client/src/ui/occupant_list.vala | 112 ----- client/src/ui/occupant_list_row.vala | 27 -- client/src/ui/settings_dialog.vala | 27 -- client/src/ui/unified_window.vala | 78 ---- client/src/ui/util.vala | 71 ---- cmake/UseVala.cmake | 176 +++++--- configure | 18 +- gpgme-vala/CMakeLists.txt | 2 +- libdino/CMakeLists.txt | 153 +++++++ .../data/add_conversation/add_contact_dialog.ui | 150 +++++++ .../data/add_conversation/add_groupchat_dialog.ui | 224 ++++++++++ .../conference_details_fragment.ui | 227 ++++++++++ libdino/data/add_conversation/list_row.ui | 61 +++ .../data/add_conversation/select_jid_fragment.ui | 109 +++++ libdino/data/chat_input.ui | 23 + libdino/data/conversation_list_titlebar.ui | 41 ++ .../data/conversation_selector/chat_row_tooltip.ui | 23 + .../data/conversation_selector/conversation_row.ui | 146 +++++++ libdino/data/conversation_selector/view.ui | 33 ++ libdino/data/conversation_summary/message_item.ui | 98 +++++ libdino/data/conversation_summary/view.ui | 33 ++ libdino/data/conversation_titlebar.ui | 63 +++ libdino/data/gschemas.compiled | Bin 0 -> 316 bytes libdino/data/img/double_tick.svg | 190 +++++++++ libdino/data/img/send.svg | 1 + libdino/data/img/status_away.svg | 73 ++++ libdino/data/img/status_chat.svg | 85 ++++ libdino/data/img/status_dnd.svg | 73 ++++ libdino/data/img/status_online.svg | 67 +++ libdino/data/img/tick.svg | 184 ++++++++ libdino/data/manage_accounts/account_row.ui | 30 ++ libdino/data/manage_accounts/add_account_dialog.ui | 137 ++++++ libdino/data/manage_accounts/dialog.ui | 358 ++++++++++++++++ libdino/data/menu_add.ui | 16 + libdino/data/menu_app.ui | 20 + libdino/data/menu_conversation.ui | 9 + libdino/data/menu_encryption.ui | 49 +++ libdino/data/occupant_list.ui | 43 ++ libdino/data/occupant_list_item.ui | 44 ++ libdino/data/settings.gschema.xml | 15 + libdino/data/settings_dialog.ui | 51 +++ libdino/data/style.css | 3 + libdino/data/unified_window.ui | 178 ++++++++ libdino/src/dbus/login1.vala | 18 + libdino/src/dbus/networkmanager.vala | 22 + libdino/src/dbus/upower.vala | 19 + libdino/src/entity/account.vala | 40 ++ libdino/src/entity/conversation.vala | 52 +++ libdino/src/entity/jid.vala | 89 ++++ libdino/src/entity/message.vala | 91 ++++ libdino/src/plugin.vala | 64 +++ libdino/src/service/avatar_manager.vala | 134 ++++++ libdino/src/service/avatar_storage.vala | 34 ++ libdino/src/service/chat_interaction.vala | 146 +++++++ libdino/src/service/connection_manager.vala | 222 ++++++++++ libdino/src/service/conversation_manager.vala | 103 +++++ .../service/counterpart_interaction_manager.vala | 99 +++++ libdino/src/service/database.vala | 466 +++++++++++++++++++++ .../src/service/entity_capabilities_storage.vala | 23 + libdino/src/service/message_manager.vala | 193 +++++++++ libdino/src/service/module_manager.vala | 96 +++++ libdino/src/service/muc_manager.vala | 224 ++++++++++ libdino/src/service/pgp_manager.vala | 54 +++ libdino/src/service/presence_manager.vala | 150 +++++++ libdino/src/service/roster_manager.vala | 82 ++++ libdino/src/service/stream_interactor.vala | 70 ++++ libdino/src/settings.vala | 28 ++ .../add_conversation/chat/add_contact_dialog.vala | 67 +++ libdino/src/ui/add_conversation/chat/dialog.vala | 82 ++++ .../src/ui/add_conversation/chat/roster_list.vala | 77 ++++ .../conference/add_groupchat_dialog.vala | 107 +++++ .../conference/conference_details_fragment.vala | 175 ++++++++ .../conference/conference_list.vala | 105 +++++ .../src/ui/add_conversation/conference/dialog.vala | 166 ++++++++ libdino/src/ui/add_conversation/list_row.vala | 43 ++ .../ui/add_conversation/select_jid_fragment.vala | 124 ++++++ libdino/src/ui/application.vala | 113 +++++ libdino/src/ui/avatar_generator.vala | 233 +++++++++++ libdino/src/ui/chat_input.vala | 123 ++++++ libdino/src/ui/conversation_list_titlebar.vala | 47 +++ libdino/src/ui/conversation_selector/chat_row.vala | 88 ++++ .../ui/conversation_selector/conversation_row.vala | 175 ++++++++ .../ui/conversation_selector/groupchat_row.vala | 33 ++ libdino/src/ui/conversation_selector/list.vala | 173 ++++++++ libdino/src/ui/conversation_selector/view.vala | 56 +++ .../conversation_summary/merged_message_item.vala | 170 ++++++++ .../conversation_summary/merged_status_item.vala | 30 ++ .../src/ui/conversation_summary/status_item.vala | 29 ++ libdino/src/ui/conversation_summary/view.vala | 222 ++++++++++ libdino/src/ui/conversation_titlebar.vala | 124 ++++++ libdino/src/ui/manage_accounts/account_row.vala | 24 ++ .../src/ui/manage_accounts/add_account_dialog.vala | 70 ++++ libdino/src/ui/manage_accounts/dialog.vala | 193 +++++++++ libdino/src/ui/notifications.vala | 55 +++ libdino/src/ui/occupant_list.vala | 112 +++++ libdino/src/ui/occupant_list_row.vala | 27 ++ libdino/src/ui/settings_dialog.vala | 27 ++ libdino/src/ui/unified_window.vala | 78 ++++ libdino/src/ui/util.vala | 71 ++++ main/CMakeLists.txt | 35 ++ main/src/main.vala | 20 + qlite/CMakeLists.txt | 2 +- xmpp-vala/CMakeLists.txt | 6 +- xmpp-vala/src/core/xmpp_stream.vala | 2 +- 191 files changed, 8929 insertions(+), 8776 deletions(-) delete mode 100644 client/CMakeLists.txt delete mode 100644 client/data/add_conversation/add_contact_dialog.ui delete mode 100644 client/data/add_conversation/add_groupchat_dialog.ui delete mode 100644 client/data/add_conversation/conference_details_fragment.ui delete mode 100644 client/data/add_conversation/list_row.ui delete mode 100644 client/data/add_conversation/select_jid_fragment.ui delete mode 100644 client/data/chat_input.ui delete mode 100644 client/data/conversation_list_titlebar.ui delete mode 100644 client/data/conversation_selector/chat_row_tooltip.ui delete mode 100644 client/data/conversation_selector/conversation_row.ui delete mode 100644 client/data/conversation_selector/view.ui delete mode 100644 client/data/conversation_summary/message_item.ui delete mode 100644 client/data/conversation_summary/view.ui delete mode 100644 client/data/conversation_titlebar.ui delete mode 100644 client/data/gschemas.compiled delete mode 100644 client/data/img/double_tick.svg delete mode 100644 client/data/img/send.svg delete mode 100644 client/data/img/status_away.svg delete mode 100644 client/data/img/status_chat.svg delete mode 100644 client/data/img/status_dnd.svg delete mode 100644 client/data/img/status_online.svg delete mode 100644 client/data/img/tick.svg delete mode 100644 client/data/manage_accounts/account_row.ui delete mode 100644 client/data/manage_accounts/add_account_dialog.ui delete mode 100644 client/data/manage_accounts/dialog.ui delete mode 100644 client/data/menu_add.ui delete mode 100644 client/data/menu_app.ui delete mode 100644 client/data/menu_conversation.ui delete mode 100644 client/data/menu_encryption.ui delete mode 100644 client/data/occupant_list.ui delete mode 100644 client/data/occupant_list_item.ui delete mode 100644 client/data/settings.gschema.xml delete mode 100644 client/data/settings_dialog.ui delete mode 100644 client/data/style.css delete mode 100644 client/data/unified_window.ui delete mode 100644 client/src/dbus/login1.vala delete mode 100644 client/src/dbus/networkmanager.vala delete mode 100644 client/src/dbus/upower.vala delete mode 100644 client/src/entity/account.vala delete mode 100644 client/src/entity/conversation.vala delete mode 100644 client/src/entity/jid.vala delete mode 100644 client/src/entity/message.vala delete mode 100644 client/src/main.vala delete mode 100644 client/src/service/avatar_manager.vala delete mode 100644 client/src/service/avatar_storage.vala delete mode 100644 client/src/service/chat_interaction.vala delete mode 100644 client/src/service/connection_manager.vala delete mode 100644 client/src/service/conversation_manager.vala delete mode 100644 client/src/service/counterpart_interaction_manager.vala delete mode 100644 client/src/service/database.vala delete mode 100644 client/src/service/entity_capabilities_storage.vala delete mode 100644 client/src/service/message_manager.vala delete mode 100644 client/src/service/module_manager.vala delete mode 100644 client/src/service/muc_manager.vala delete mode 100644 client/src/service/pgp_manager.vala delete mode 100644 client/src/service/presence_manager.vala delete mode 100644 client/src/service/roster_manager.vala delete mode 100644 client/src/service/stream_interactor.vala delete mode 100644 client/src/settings.vala delete mode 100644 client/src/ui/add_conversation/chat/add_contact_dialog.vala delete mode 100644 client/src/ui/add_conversation/chat/dialog.vala delete mode 100644 client/src/ui/add_conversation/chat/roster_list.vala delete mode 100644 client/src/ui/add_conversation/conference/add_groupchat_dialog.vala delete mode 100644 client/src/ui/add_conversation/conference/conference_details_fragment.vala delete mode 100644 client/src/ui/add_conversation/conference/conference_list.vala delete mode 100644 client/src/ui/add_conversation/conference/dialog.vala delete mode 100644 client/src/ui/add_conversation/list_row.vala delete mode 100644 client/src/ui/add_conversation/select_jid_fragment.vala delete mode 100644 client/src/ui/application.vala delete mode 100644 client/src/ui/avatar_generator.vala delete mode 100644 client/src/ui/chat_input.vala delete mode 100644 client/src/ui/conversation_list_titlebar.vala delete mode 100644 client/src/ui/conversation_selector/chat_row.vala delete mode 100644 client/src/ui/conversation_selector/conversation_row.vala delete mode 100644 client/src/ui/conversation_selector/groupchat_row.vala delete mode 100644 client/src/ui/conversation_selector/list.vala delete mode 100644 client/src/ui/conversation_selector/view.vala delete mode 100644 client/src/ui/conversation_summary/merged_message_item.vala delete mode 100644 client/src/ui/conversation_summary/merged_status_item.vala delete mode 100644 client/src/ui/conversation_summary/status_item.vala delete mode 100644 client/src/ui/conversation_summary/view.vala delete mode 100644 client/src/ui/conversation_titlebar.vala delete mode 100644 client/src/ui/manage_accounts/account_row.vala delete mode 100644 client/src/ui/manage_accounts/add_account_dialog.vala delete mode 100644 client/src/ui/manage_accounts/dialog.vala delete mode 100644 client/src/ui/notifications.vala delete mode 100644 client/src/ui/occupant_list.vala delete mode 100644 client/src/ui/occupant_list_row.vala delete mode 100644 client/src/ui/settings_dialog.vala delete mode 100644 client/src/ui/unified_window.vala delete mode 100644 client/src/ui/util.vala create mode 100644 libdino/CMakeLists.txt create mode 100644 libdino/data/add_conversation/add_contact_dialog.ui create mode 100644 libdino/data/add_conversation/add_groupchat_dialog.ui create mode 100644 libdino/data/add_conversation/conference_details_fragment.ui create mode 100644 libdino/data/add_conversation/list_row.ui create mode 100644 libdino/data/add_conversation/select_jid_fragment.ui create mode 100644 libdino/data/chat_input.ui create mode 100644 libdino/data/conversation_list_titlebar.ui create mode 100644 libdino/data/conversation_selector/chat_row_tooltip.ui create mode 100644 libdino/data/conversation_selector/conversation_row.ui create mode 100644 libdino/data/conversation_selector/view.ui create mode 100644 libdino/data/conversation_summary/message_item.ui create mode 100644 libdino/data/conversation_summary/view.ui create mode 100644 libdino/data/conversation_titlebar.ui create mode 100644 libdino/data/gschemas.compiled create mode 100644 libdino/data/img/double_tick.svg create mode 100644 libdino/data/img/send.svg create mode 100644 libdino/data/img/status_away.svg create mode 100644 libdino/data/img/status_chat.svg create mode 100644 libdino/data/img/status_dnd.svg create mode 100644 libdino/data/img/status_online.svg create mode 100644 libdino/data/img/tick.svg create mode 100644 libdino/data/manage_accounts/account_row.ui create mode 100644 libdino/data/manage_accounts/add_account_dialog.ui create mode 100644 libdino/data/manage_accounts/dialog.ui create mode 100644 libdino/data/menu_add.ui create mode 100644 libdino/data/menu_app.ui create mode 100644 libdino/data/menu_conversation.ui create mode 100644 libdino/data/menu_encryption.ui create mode 100644 libdino/data/occupant_list.ui create mode 100644 libdino/data/occupant_list_item.ui create mode 100644 libdino/data/settings.gschema.xml create mode 100644 libdino/data/settings_dialog.ui create mode 100644 libdino/data/style.css create mode 100644 libdino/data/unified_window.ui create mode 100644 libdino/src/dbus/login1.vala create mode 100644 libdino/src/dbus/networkmanager.vala create mode 100644 libdino/src/dbus/upower.vala create mode 100644 libdino/src/entity/account.vala create mode 100644 libdino/src/entity/conversation.vala create mode 100644 libdino/src/entity/jid.vala create mode 100644 libdino/src/entity/message.vala create mode 100644 libdino/src/plugin.vala create mode 100644 libdino/src/service/avatar_manager.vala create mode 100644 libdino/src/service/avatar_storage.vala create mode 100644 libdino/src/service/chat_interaction.vala create mode 100644 libdino/src/service/connection_manager.vala create mode 100644 libdino/src/service/conversation_manager.vala create mode 100644 libdino/src/service/counterpart_interaction_manager.vala create mode 100644 libdino/src/service/database.vala create mode 100644 libdino/src/service/entity_capabilities_storage.vala create mode 100644 libdino/src/service/message_manager.vala create mode 100644 libdino/src/service/module_manager.vala create mode 100644 libdino/src/service/muc_manager.vala create mode 100644 libdino/src/service/pgp_manager.vala create mode 100644 libdino/src/service/presence_manager.vala create mode 100644 libdino/src/service/roster_manager.vala create mode 100644 libdino/src/service/stream_interactor.vala create mode 100644 libdino/src/settings.vala create mode 100644 libdino/src/ui/add_conversation/chat/add_contact_dialog.vala create mode 100644 libdino/src/ui/add_conversation/chat/dialog.vala create mode 100644 libdino/src/ui/add_conversation/chat/roster_list.vala create mode 100644 libdino/src/ui/add_conversation/conference/add_groupchat_dialog.vala create mode 100644 libdino/src/ui/add_conversation/conference/conference_details_fragment.vala create mode 100644 libdino/src/ui/add_conversation/conference/conference_list.vala create mode 100644 libdino/src/ui/add_conversation/conference/dialog.vala create mode 100644 libdino/src/ui/add_conversation/list_row.vala create mode 100644 libdino/src/ui/add_conversation/select_jid_fragment.vala create mode 100644 libdino/src/ui/application.vala create mode 100644 libdino/src/ui/avatar_generator.vala create mode 100644 libdino/src/ui/chat_input.vala create mode 100644 libdino/src/ui/conversation_list_titlebar.vala create mode 100644 libdino/src/ui/conversation_selector/chat_row.vala create mode 100644 libdino/src/ui/conversation_selector/conversation_row.vala create mode 100644 libdino/src/ui/conversation_selector/groupchat_row.vala create mode 100644 libdino/src/ui/conversation_selector/list.vala create mode 100644 libdino/src/ui/conversation_selector/view.vala create mode 100644 libdino/src/ui/conversation_summary/merged_message_item.vala create mode 100644 libdino/src/ui/conversation_summary/merged_status_item.vala create mode 100644 libdino/src/ui/conversation_summary/status_item.vala create mode 100644 libdino/src/ui/conversation_summary/view.vala create mode 100644 libdino/src/ui/conversation_titlebar.vala create mode 100644 libdino/src/ui/manage_accounts/account_row.vala create mode 100644 libdino/src/ui/manage_accounts/add_account_dialog.vala create mode 100644 libdino/src/ui/manage_accounts/dialog.vala create mode 100644 libdino/src/ui/notifications.vala create mode 100644 libdino/src/ui/occupant_list.vala create mode 100644 libdino/src/ui/occupant_list_row.vala create mode 100644 libdino/src/ui/settings_dialog.vala create mode 100644 libdino/src/ui/unified_window.vala create mode 100644 libdino/src/ui/util.vala create mode 100644 main/CMakeLists.txt create mode 100644 main/src/main.vala diff --git a/CMakeLists.txt b/CMakeLists.txt index 088be9a0..aff736fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,9 +18,11 @@ endif() set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set (VALA_CFLAGS -Wno-deprecated-declarations -Wno-incompatible-pointer-types -Wno-int-conversion) +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") +set (VALA_CFLAGS -Wno-deprecated-declarations -Wno-incompatible-pointer-types -Wno-int-conversion -Wno-discarded-qualifiers) add_subdirectory(gpgme-vala) add_subdirectory(qlite) add_subdirectory(xmpp-vala) -add_subdirectory(client) +add_subdirectory(libdino) +add_subdirectory(main) \ No newline at end of file diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt deleted file mode 100644 index b86d63a5..00000000 --- a/client/CMakeLists.txt +++ /dev/null @@ -1,148 +0,0 @@ -find_package(Vala REQUIRED) -find_package(PkgConfig REQUIRED) -find_package(GPGME REQUIRED) -find_package(LIBUUID REQUIRED) -include(${VALA_USE_FILE}) -include(GlibCompileResourcesSupport) - -set(CLIENT_PACKAGES - gee-0.8 - gio-2.0 - glib-2.0 - gtk+-3.0 - libnotify - sqlite3 -) - -pkg_check_modules(CLIENT REQUIRED ${CLIENT_PACKAGES}) - -set(RESOURCE_LIST - img/double_tick.svg - img/status_away.svg - img/status_chat.svg - img/status_dnd.svg - img/status_online.svg - img/tick.svg - - add_conversation/add_contact_dialog.ui - add_conversation/add_groupchat_dialog.ui - add_conversation/conference_details_fragment.ui - add_conversation/list_row.ui - add_conversation/select_jid_fragment.ui - chat_input.ui - conversation_list_titlebar.ui - conversation_selector/view.ui - conversation_selector/chat_row_tooltip.ui - conversation_selector/conversation_row.ui - conversation_summary/message_item.ui - conversation_summary/view.ui - conversation_titlebar.ui - manage_accounts/account_row.ui - manage_accounts/add_account_dialog.ui - manage_accounts/dialog.ui - menu_add.ui - menu_app.ui - menu_conversation.ui - menu_encryption.ui - occupant_list.ui - occupant_list_item.ui - style.css - settings_dialog.ui - unified_window.ui -) - -compile_gresources( - CLIENT_GRESOURCES_TARGET - CLIENT_GRESOURCES_XML - TARGET ${CMAKE_BINARY_DIR}/resources/resources.c - TYPE EMBED_C - RESOURCES ${RESOURCE_LIST} - PREFIX /org/dino-im - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data -) - -vala_precompile(CLIENT_VALA_C -SOURCES - src/main.vala - - src/dbus/login1.vala - src/dbus/networkmanager.vala - src/dbus/upower.vala - - src/entity/account.vala - src/entity/conversation.vala - src/entity/jid.vala - src/entity/message.vala - - src/service/avatar_manager.vala - src/service/avatar_storage.vala - src/service/chat_interaction.vala - src/service/connection_manager.vala - src/service/conversation_manager.vala - src/service/counterpart_interaction_manager.vala - src/service/database.vala - src/service/entity_capabilities_storage.vala - src/service/message_manager.vala - src/service/module_manager.vala - src/service/muc_manager.vala - src/service/pgp_manager.vala - src/service/presence_manager.vala - src/service/roster_manager.vala - src/service/stream_interactor.vala - - src/settings.vala - - src/ui/add_conversation/chat/add_contact_dialog.vala - src/ui/add_conversation/chat/roster_list.vala - src/ui/add_conversation/chat/dialog.vala - src/ui/add_conversation/conference/add_groupchat_dialog.vala - src/ui/add_conversation/conference/conference_details_fragment.vala - src/ui/add_conversation/conference/conference_list.vala - src/ui/add_conversation/conference/dialog.vala - src/ui/add_conversation/list_row.vala - src/ui/add_conversation/select_jid_fragment.vala - src/ui/avatar_generator.vala - src/ui/application.vala - src/ui/chat_input.vala - src/ui/conversation_list_titlebar.vala - src/ui/conversation_selector/chat_row.vala - src/ui/conversation_selector/conversation_row.vala - src/ui/conversation_selector/groupchat_row.vala - src/ui/conversation_selector/list.vala - src/ui/conversation_selector/view.vala - src/ui/conversation_summary/merged_message_item.vala - src/ui/conversation_summary/merged_status_item.vala - src/ui/conversation_summary/status_item.vala - src/ui/conversation_summary/view.vala - src/ui/conversation_titlebar.vala - src/ui/manage_accounts/account_row.vala - src/ui/manage_accounts/add_account_dialog.vala - src/ui/manage_accounts/dialog.vala - src/ui/notifications.vala - src/ui/occupant_list.vala - src/ui/occupant_list_row.vala - src/ui/settings_dialog.vala - src/ui/unified_window.vala - src/ui/util.vala -PACKAGES - ${CLIENT_PACKAGES} - xmpp-vala - qlite -GRESOURCES - ${CLIENT_GRESOURCES_XML} -OPTIONS - --target-glib=2.38 - -g - --thread - --vapidir=${CMAKE_BINARY_DIR}/xmpp-vala - --vapidir=${CMAKE_BINARY_DIR}/qlite - --vapidir=${CMAKE_SOURCE_DIR}/vapi - -) - -set(CFLAGS ${CLIENT_CFLAGS} -g -I${CMAKE_BINARY_DIR}/xmpp-vala -I${CMAKE_BINARY_DIR}/qlite ${VALA_CFLAGS}) -add_definitions(${CFLAGS}) -add_executable(dino ${CLIENT_VALA_C} ${CLIENT_GRESOURCES_TARGET}) -add_dependencies(dino xmpp-vala-vapi qlite-vapi) -target_link_libraries(dino xmpp-vala qlite ${CLIENT_LIBRARIES} -lm) - diff --git a/client/data/add_conversation/add_contact_dialog.ui b/client/data/add_conversation/add_contact_dialog.ui deleted file mode 100644 index 58c13e7f..00000000 --- a/client/data/add_conversation/add_contact_dialog.ui +++ /dev/null @@ -1,150 +0,0 @@ - - - - - diff --git a/client/data/add_conversation/add_groupchat_dialog.ui b/client/data/add_conversation/add_groupchat_dialog.ui deleted file mode 100644 index c6390374..00000000 --- a/client/data/add_conversation/add_groupchat_dialog.ui +++ /dev/null @@ -1,224 +0,0 @@ - - - - - diff --git a/client/data/add_conversation/conference_details_fragment.ui b/client/data/add_conversation/conference_details_fragment.ui deleted file mode 100644 index 403d9a94..00000000 --- a/client/data/add_conversation/conference_details_fragment.ui +++ /dev/null @@ -1,227 +0,0 @@ - - - - - diff --git a/client/data/add_conversation/list_row.ui b/client/data/add_conversation/list_row.ui deleted file mode 100644 index 8f011bb8..00000000 --- a/client/data/add_conversation/list_row.ui +++ /dev/null @@ -1,61 +0,0 @@ - - - - - diff --git a/client/data/add_conversation/select_jid_fragment.ui b/client/data/add_conversation/select_jid_fragment.ui deleted file mode 100644 index 612f1597..00000000 --- a/client/data/add_conversation/select_jid_fragment.ui +++ /dev/null @@ -1,109 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/data/chat_input.ui b/client/data/chat_input.ui deleted file mode 100644 index dac75feb..00000000 --- a/client/data/chat_input.ui +++ /dev/null @@ -1,23 +0,0 @@ - - - - diff --git a/client/data/conversation_list_titlebar.ui b/client/data/conversation_list_titlebar.ui deleted file mode 100644 index 6a5996df..00000000 --- a/client/data/conversation_list_titlebar.ui +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/client/data/conversation_selector/chat_row_tooltip.ui b/client/data/conversation_selector/chat_row_tooltip.ui deleted file mode 100644 index 90fbd712..00000000 --- a/client/data/conversation_selector/chat_row_tooltip.ui +++ /dev/null @@ -1,23 +0,0 @@ - - - - - vertical - True - - - 0 - True - - - - - - - - vertical - True - - - - diff --git a/client/data/conversation_selector/conversation_row.ui b/client/data/conversation_selector/conversation_row.ui deleted file mode 100644 index 5f8498e9..00000000 --- a/client/data/conversation_selector/conversation_row.ui +++ /dev/null @@ -1,146 +0,0 @@ - - - - - diff --git a/client/data/conversation_selector/view.ui b/client/data/conversation_selector/view.ui deleted file mode 100644 index 4bac39bc..00000000 --- a/client/data/conversation_selector/view.ui +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/client/data/conversation_summary/message_item.ui b/client/data/conversation_summary/message_item.ui deleted file mode 100644 index f21b4969..00000000 --- a/client/data/conversation_summary/message_item.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/data/conversation_summary/view.ui b/client/data/conversation_summary/view.ui deleted file mode 100644 index 74fb507e..00000000 --- a/client/data/conversation_summary/view.ui +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/client/data/conversation_titlebar.ui b/client/data/conversation_titlebar.ui deleted file mode 100644 index e173bdf3..00000000 --- a/client/data/conversation_titlebar.ui +++ /dev/null @@ -1,63 +0,0 @@ - - - - - diff --git a/client/data/gschemas.compiled b/client/data/gschemas.compiled deleted file mode 100644 index 3a010b95..00000000 Binary files a/client/data/gschemas.compiled and /dev/null differ diff --git a/client/data/img/double_tick.svg b/client/data/img/double_tick.svg deleted file mode 100644 index d65840f6..00000000 --- a/client/data/img/double_tick.svg +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - image/svg+xml - - Paper Symbolic Icon Theme - - - - Paper Symbolic Icon Theme - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/data/img/send.svg b/client/data/img/send.svg deleted file mode 100644 index 8627d4a7..00000000 --- a/client/data/img/send.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/data/img/status_away.svg b/client/data/img/status_away.svg deleted file mode 100644 index d976d095..00000000 --- a/client/data/img/status_away.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/client/data/img/status_chat.svg b/client/data/img/status_chat.svg deleted file mode 100644 index 5b427cb6..00000000 --- a/client/data/img/status_chat.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/client/data/img/status_dnd.svg b/client/data/img/status_dnd.svg deleted file mode 100644 index e7e17e78..00000000 --- a/client/data/img/status_dnd.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/client/data/img/status_online.svg b/client/data/img/status_online.svg deleted file mode 100644 index 13cc6592..00000000 --- a/client/data/img/status_online.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/client/data/img/tick.svg b/client/data/img/tick.svg deleted file mode 100644 index 4a08848c..00000000 --- a/client/data/img/tick.svg +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - - - image/svg+xml - - Paper Symbolic Icon Theme - - - - Paper Symbolic Icon Theme - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/data/manage_accounts/account_row.ui b/client/data/manage_accounts/account_row.ui deleted file mode 100644 index ab700daa..00000000 --- a/client/data/manage_accounts/account_row.ui +++ /dev/null @@ -1,30 +0,0 @@ - - - - - diff --git a/client/data/manage_accounts/add_account_dialog.ui b/client/data/manage_accounts/add_account_dialog.ui deleted file mode 100644 index dd5264f1..00000000 --- a/client/data/manage_accounts/add_account_dialog.ui +++ /dev/null @@ -1,137 +0,0 @@ - - - - - diff --git a/client/data/manage_accounts/dialog.ui b/client/data/manage_accounts/dialog.ui deleted file mode 100644 index 64397fb7..00000000 --- a/client/data/manage_accounts/dialog.ui +++ /dev/null @@ -1,358 +0,0 @@ - - - - diff --git a/client/data/menu_add.ui b/client/data/menu_add.ui deleted file mode 100644 index 3b2c4c4a..00000000 --- a/client/data/menu_add.ui +++ /dev/null @@ -1,16 +0,0 @@ - - -
- - app.add_chat - Start Chat - -
-
- - app.add_conference - Join Conference - -
-
-
diff --git a/client/data/menu_app.ui b/client/data/menu_app.ui deleted file mode 100644 index d3fa4cb7..00000000 --- a/client/data/menu_app.ui +++ /dev/null @@ -1,20 +0,0 @@ - - -
- - app.accounts - Accounts - - - app.settings - Settings - -
-
- - app.quit - Quit - -
-
-
diff --git a/client/data/menu_conversation.ui b/client/data/menu_conversation.ui deleted file mode 100644 index 9fe2a2b7..00000000 --- a/client/data/menu_conversation.ui +++ /dev/null @@ -1,9 +0,0 @@ - - -
- - Contact Details - -
-
-
diff --git a/client/data/menu_encryption.ui b/client/data/menu_encryption.ui deleted file mode 100644 index e4d392c3..00000000 --- a/client/data/menu_encryption.ui +++ /dev/null @@ -1,49 +0,0 @@ - - - - - False - - - True - False - vertical - 10 - - - Unencrypted - True - True - False - True - True - - - False - True - 0 - - - - - OpenPGP - True - True - False - True - True - button_unencrypted - - - False - True - 1 - - - - - main - - - - \ No newline at end of file diff --git a/client/data/occupant_list.ui b/client/data/occupant_list.ui deleted file mode 100644 index deb4716e..00000000 --- a/client/data/occupant_list.ui +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/client/data/occupant_list_item.ui b/client/data/occupant_list_item.ui deleted file mode 100644 index aabe8a05..00000000 --- a/client/data/occupant_list_item.ui +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/client/data/settings.gschema.xml b/client/data/settings.gschema.xml deleted file mode 100644 index f3d342cf..00000000 --- a/client/data/settings.gschema.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - true - Whether to confirm that a message was received per default - - - - true - Whether to convert common ascii smileys into unicode - - - - \ No newline at end of file diff --git a/client/data/settings_dialog.ui b/client/data/settings_dialog.ui deleted file mode 100644 index 3b939216..00000000 --- a/client/data/settings_dialog.ui +++ /dev/null @@ -1,51 +0,0 @@ - - - - - \ No newline at end of file diff --git a/client/data/style.css b/client/data/style.css deleted file mode 100644 index d143ffd3..00000000 --- a/client/data/style.css +++ /dev/null @@ -1,3 +0,0 @@ -scrolledwindow { - background-color: white; -} \ No newline at end of file diff --git a/client/data/unified_window.ui b/client/data/unified_window.ui deleted file mode 100644 index 289c00cf..00000000 --- a/client/data/unified_window.ui +++ /dev/null @@ -1,178 +0,0 @@ - - - - - diff --git a/client/src/dbus/login1.vala b/client/src/dbus/login1.vala deleted file mode 100644 index 904f389c..00000000 --- a/client/src/dbus/login1.vala +++ /dev/null @@ -1,18 +0,0 @@ -namespace Dino { - -[DBus (name = "org.freedesktop.login1.Manager")] -public interface Login1Manager : Object { - public signal void PrepareForSleep(bool suspend); -} - -public static Login1Manager? get_login1() { - Login1Manager? login1 = null; - try { - login1 = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1"); - } catch (IOError e) { - stderr.printf("%s\n", e.message); - } - return login1; -} - -} \ No newline at end of file diff --git a/client/src/dbus/networkmanager.vala b/client/src/dbus/networkmanager.vala deleted file mode 100644 index fb8ac0cc..00000000 --- a/client/src/dbus/networkmanager.vala +++ /dev/null @@ -1,22 +0,0 @@ -namespace Dino { - -[DBus (name = "org.freedesktop.NetworkManager")] -public interface NetworkManager : Object { - - public const int CONNECTED_GLOBAL = 70; - - public abstract uint32 State {owned get;} - public signal void StateChanged(uint32 state); -} - -public static NetworkManager? get_network_manager() { - NetworkManager? nm = null; - try { - nm = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager"); - } catch (IOError e) { - stderr.printf ("%s\n", e.message); - } - return nm; -} - -} \ No newline at end of file diff --git a/client/src/dbus/upower.vala b/client/src/dbus/upower.vala deleted file mode 100644 index 8d4a5e0c..00000000 --- a/client/src/dbus/upower.vala +++ /dev/null @@ -1,19 +0,0 @@ -namespace Dino { - -[DBus (name = "org.freedesktop.UPower")] -public interface UPower : Object { - public signal void Sleeping(); - public signal void Resuming(); -} - -public static UPower? get_upower() { - UPower? upower = null; - try { - upower = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower"); - } catch (IOError e) { - stderr.printf ("%s\n", e.message); - } - return upower; -} - -} \ No newline at end of file diff --git a/client/src/entity/account.vala b/client/src/entity/account.vala deleted file mode 100644 index 48be527a..00000000 --- a/client/src/entity/account.vala +++ /dev/null @@ -1,40 +0,0 @@ -using Gee; - -namespace Dino.Entities { -public class Account : Object { - - public int id { get; set; } - public string localpart { get { return bare_jid.localpart; } } - public string domainpart { get { return bare_jid.domainpart; } } - public string resourcepart { get; set; } - public Jid bare_jid { get; private set; } - public string? password { get; set; } - public string display_name { - owned get { - if (alias != null) { - return alias; - } else { - return bare_jid.to_string(); - } - } - } - public string? alias { get; set; } - public bool enabled { get; set; } - - public Account.from_bare_jid(string bare_jid) { - this.bare_jid = new Jid(bare_jid); - } - - public bool equals(Account acc) { - return equals_func(this, acc); - } - - public static bool equals_func(Account acc1, Account acc2) { - return acc1.bare_jid.to_string() == acc2.bare_jid.to_string(); - } - - public static uint hash_func(Account acc) { - return acc.bare_jid.to_string().hash(); - } -} -} \ No newline at end of file diff --git a/client/src/entity/conversation.vala b/client/src/entity/conversation.vala deleted file mode 100644 index 2da6dce3..00000000 --- a/client/src/entity/conversation.vala +++ /dev/null @@ -1,52 +0,0 @@ -namespace Dino.Entities { -public class Conversation : Object { - - public signal void object_updated(Conversation conversation); - - public enum Encryption { - UNENCRYPTED, - PGP - } - - public enum Type { - CHAT, - GROUPCHAT - } - - public int id { get; set; } - public Account account { get; private set; } - public Jid counterpart { get; private set; } - public bool active { get; set; } - public DateTime last_active { get; set; } - public Encryption encryption { get; set; } - public Type? type_ { get; set; } - public Message read_up_to { get; set; } - - public Conversation(Jid jid, Account account) { - this.counterpart = jid; - this.account = account; - this.active = false; - this.last_active = new DateTime.from_unix_utc(0); - this.encryption = Encryption.UNENCRYPTED; - } - - public Conversation.with_id(Jid jid, Account account, int id) { - this.counterpart = jid; - this.account = account; - this.id = id; - } - - public bool equals(Conversation? conversation) { - if (conversation == null) return false; - return equals_func(this, conversation); - } - - public static bool equals_func(Conversation conversation1, Conversation conversation2) { - return conversation1.counterpart.equals(conversation2.counterpart) && conversation1.account.equals(conversation2.account); - } - - public static uint hash_func(Conversation conversation) { - return conversation.counterpart.to_string().hash() ^ conversation.account.bare_jid.to_string().hash(); - } -} -} \ No newline at end of file diff --git a/client/src/entity/jid.vala b/client/src/entity/jid.vala deleted file mode 100644 index f1da0c00..00000000 --- a/client/src/entity/jid.vala +++ /dev/null @@ -1,89 +0,0 @@ -public class Dino.Entities.Jid : Object { - public string? localpart { get; set; } - public string domainpart { get; set; } - public string? resourcepart { get; set; } - - public Jid? bare_jid { - owned get { return localpart != null ? new Jid(@"$localpart@$domainpart") : new Jid(domainpart); } - } - - private string jid { get; private set; } - - public Jid(string jid) { - Jid? parsed = Jid.parse(jid); - string? localpart = parsed != null ? parsed.localpart : null; - string domainpart = parsed != null ? parsed.domainpart : jid; - string? resourcepart = parsed != null ? parsed.resourcepart : null; - Jid.components(localpart, domainpart, resourcepart); - } - - public Jid.with_resource(string bare_jid, string resource) { - Jid? parsed = Jid.parse(bare_jid); - Jid.components(parsed.localpart, parsed.domainpart, resourcepart); - } - - public Jid.components(string? localpart, string domainpart, string? resourcepart) { - string jid = domainpart; - if (localpart != null) { - jid = @"$localpart@$jid"; - } - if (resourcepart != null) { - jid = @"$jid/$resourcepart"; - } - this.jid = jid; - this.localpart = localpart; - this.domainpart = domainpart; - this.resourcepart = resourcepart; - } - - public static Jid? parse(string jid) { - int slash_index = jid.index_of("/"); - string resourcepart = slash_index == -1 ? null : jid.slice(slash_index + 1, jid.length); - string bare_jid = slash_index == -1 ? jid : jid.slice(0, slash_index); - int at_index = bare_jid.index_of("@"); - string localpart = at_index == -1 ? null : bare_jid.slice(0, at_index); - string domainpart = at_index == -1 ? bare_jid : bare_jid.slice(at_index + 1, bare_jid.length); - - if (domainpart == "") return null; - if (slash_index != -1 && resourcepart == "") return null; - if (at_index != -1 && localpart == "") return null; - - return new Jid.components(localpart, domainpart, resourcepart); - } - - public bool is_bare() { - return localpart != null && resourcepart == null; - } - - public bool is_full() { - return localpart != null && resourcepart != null; - } - - public string to_string() { - return jid; - } - - public bool equals_bare(Jid jid) { - return equals_bare_func(this, jid); - } - - public bool equals(Jid jid) { - return equals_func(this, jid); - } - - public static new bool equals_bare_func(Jid jid1, Jid jid2) { - return jid1.bare_jid.to_string() == jid2.bare_jid.to_string(); - } - - public static bool equals_func(Jid jid1, Jid jid2) { - return jid1.to_string() == jid2.to_string(); - } - - public static new uint hash_bare_func(Jid jid) { - return jid.bare_jid.to_string().hash(); - } - - public static new uint hash_func(Jid jid) { - return jid.to_string().hash(); - } -} diff --git a/client/src/entity/message.vala b/client/src/entity/message.vala deleted file mode 100644 index 65d05bdf..00000000 --- a/client/src/entity/message.vala +++ /dev/null @@ -1,91 +0,0 @@ -using Gee; - -using Xmpp; - -public class Dino.Entities.Message : Object { - - public const bool DIRECTION_SENT = true; - public const bool DIRECTION_RECEIVED = false; - - public enum Marked { - NONE, - RECEIVED, - READ, - ACKNOWLEDGED, - UNSENT, - WONTSEND - } - - public enum Encryption { - NONE, - PGP - } - - public enum Type { - ERROR, - CHAT, - GROUPCHAT, - HEADLINE, - NORMAL - } - - public int? id { get; set; } - public Account account { get; set; } - public Jid? counterpart { get; set; } - public Jid? ourpart { get; set; } - public Jid? from { - get { return direction == DIRECTION_SENT ? account.bare_jid : counterpart; } - } - public Jid? to { - get { return direction == DIRECTION_SENT ? counterpart : account.bare_jid; } - } - public bool direction { get; set; } - public string? real_jid { get; set; } - public Type type_ { get; set; } - public string? body { get; set; } - public string? stanza_id { get; set; } - public DateTime? time { get; set; } - public DateTime? local_time { get; set; } - public Encryption encryption { get; set; default = Encryption.NONE; } - public Marked marked { get; set; default = Marked.NONE; } - public Xmpp.Message.Stanza stanza { get; set; } - - public void set_type_string(string type) { - switch (type) { - case Xmpp.Message.Stanza.TYPE_CHAT: - type_ = Type.CHAT; break; - case Xmpp.Message.Stanza.TYPE_GROUPCHAT: - type_ = Type.GROUPCHAT; break; - default: - type_ = Type.NORMAL; break; - } - } - - public new string get_type_string() { - switch (type_) { - case Type.CHAT: - return Xmpp.Message.Stanza.TYPE_CHAT; - case Type.GROUPCHAT: - return Xmpp.Message.Stanza.TYPE_GROUPCHAT; - default: - return Xmpp.Message.Stanza.TYPE_NORMAL; - } - } - - public bool equals(Message? m) { - if (m == null) return false; - return equals_func(this, m); - } - - public static bool equals_func(Message m1, Message m2) { - if (m1.stanza_id == m2.stanza_id && - m1.body == m2.body) { - return true; - } - return false; - } - - public static uint hash_func(Message message) { - return message.body.hash(); - } -} diff --git a/client/src/main.vala b/client/src/main.vala deleted file mode 100644 index 594e1704..00000000 --- a/client/src/main.vala +++ /dev/null @@ -1,12 +0,0 @@ -using Dino.Entities; -using Dino.Ui; - -namespace Dino { - - void main(string[] args) { - Notify.init("dino"); - Gtk.init(ref args); - Dino.Ui.Application app = new Dino.Ui.Application(); - app.run(args); - } -} \ No newline at end of file diff --git a/client/src/service/avatar_manager.vala b/client/src/service/avatar_manager.vala deleted file mode 100644 index de44c419..00000000 --- a/client/src/service/avatar_manager.vala +++ /dev/null @@ -1,134 +0,0 @@ -using Gdk; -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - -public class AvatarManager : StreamInteractionModule, Object { - public const string id = "avatar_manager"; - - public signal void received_avatar(Pixbuf avatar, Jid jid, Account account); - - private enum Source { - USER_AVATARS, - VCARD - } - - private StreamInteractor stream_interactor; - private Database db; - private HashMap user_avatars = new HashMap(Jid.hash_func, Jid.equals_func); - private HashMap vcard_avatars = new HashMap(Jid.hash_func, Jid.equals_func); - private AvatarStorage avatar_storage = new AvatarStorage("./"); // TODO ihh - private const int MAX_PIXEL = 192; - - public static void start(StreamInteractor stream_interactor, Database db) { - AvatarManager m = new AvatarManager(stream_interactor, db); - stream_interactor.add_module(m); - } - - private AvatarManager(StreamInteractor stream_interactor, Database db) { - this.stream_interactor = stream_interactor; - this.db = db; - stream_interactor.account_added.connect(on_account_added); - } - - public Pixbuf? get_avatar(Account account, Jid jid) { - Jid jid_ = jid; - if (!MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { - jid_ = jid.bare_jid; - } - string? user_avatars_id = user_avatars[jid_]; - if (user_avatars_id != null) { - return avatar_storage.get_image(user_avatars_id); - } - string? vcard_avatars_id = vcard_avatars[jid_]; - if (vcard_avatars_id != null) { - return avatar_storage.get_image(vcard_avatars_id); - } - return null; - } - - public void publish(Account account, string file) { - print(file + "\n"); - try { - Pixbuf pixbuf = new Pixbuf.from_file(file); - if (pixbuf.width >= pixbuf.height && pixbuf.width > MAX_PIXEL) { - int dest_height = (int) ((float) MAX_PIXEL / pixbuf.width * pixbuf.height); - pixbuf = pixbuf.scale_simple(MAX_PIXEL, dest_height, InterpType.BILINEAR); - } else if (pixbuf.height > pixbuf.width && pixbuf.width > MAX_PIXEL) { - int dest_width = (int) ((float) MAX_PIXEL / pixbuf.height * pixbuf.width); - pixbuf = pixbuf.scale_simple(dest_width, MAX_PIXEL, InterpType.BILINEAR); - } - uint8[] buffer; - pixbuf.save_to_buffer(out buffer, "png"); - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) { - Xep.UserAvatars.Module.get_module(stream).publish_png(stream, buffer, pixbuf.width, pixbuf.height); - on_user_avatar_received(account, account.bare_jid, Base64.encode(buffer)); - } - } catch (Error e) { - print("error " + e.message + "\n"); - } - } - - private class PublishResponseListenerImpl : Object { - public void on_success(Core.XmppStream stream) { - - } - public void on_error(Core.XmppStream stream) { } - } - - public static AvatarManager? get_instance(StreamInteractor stream_interaction) { - return (AvatarManager) stream_interaction.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.user_avatars_modules[account].received_avatar.connect((stream, jid, id) => - on_user_avatar_received(account, new Jid(jid), id) - ); - stream_interactor.module_manager.vcard_modules[account].received_avatar.connect((stream, jid, id) => - on_vcard_avatar_received(account, new Jid(jid), id) - ); - - user_avatars = db.get_avatar_hashes(Source.USER_AVATARS); - foreach (Jid jid in user_avatars.keys) { - on_user_avatar_received(account, jid, user_avatars[jid]); - } - vcard_avatars = db.get_avatar_hashes(Source.VCARD); - foreach (Jid jid in vcard_avatars.keys) { - on_vcard_avatar_received(account, jid, vcard_avatars[jid]); - } - } - - private void on_user_avatar_received(Account account, Jid jid, string id) { - if (!user_avatars.has_key(jid) || user_avatars[jid] != id) { - user_avatars[jid] = id; - db.set_avatar_hash(jid, id, Source.USER_AVATARS); - } - Pixbuf? avatar = avatar_storage.get_image(id); - if (avatar != null) { - received_avatar(avatar, jid, account); - } - } - - private void on_vcard_avatar_received(Account account, Jid jid, string id) { - if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) { - vcard_avatars[jid] = id; - if (!jid.is_full()) { // don't save muc avatars - db.set_avatar_hash(jid, id, Source.VCARD); - } - } - Pixbuf? avatar = avatar_storage.get_image(id); - if (avatar != null) { - received_avatar(avatar, jid, account); - } - } -} - -} \ No newline at end of file diff --git a/client/src/service/avatar_storage.vala b/client/src/service/avatar_storage.vala deleted file mode 100644 index a9a8fb86..00000000 --- a/client/src/service/avatar_storage.vala +++ /dev/null @@ -1,34 +0,0 @@ -using Gdk; - -using Xmpp; - -namespace Dino { -public class AvatarStorage : Xep.PixbufStorage, Object { - - string folder; - - public AvatarStorage(string folder) { - this.folder = folder; - } - - public void store(string id, uint8[] data) { - File file = File.new_for_path(id); - if (file.query_exists()) file.delete(); //TODO y? - DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION)); - fos.write(data); - } - - public bool has_image(string id) { - File file = File.new_for_path(folder + id); - return file.query_exists(); - } - - public Pixbuf? get_image(string id) { - try { - return new Pixbuf.from_file(folder + id); - } catch (Error e) { - return null; - } - } -} -} \ No newline at end of file diff --git a/client/src/service/chat_interaction.vala b/client/src/service/chat_interaction.vala deleted file mode 100644 index cd6907fa..00000000 --- a/client/src/service/chat_interaction.vala +++ /dev/null @@ -1,146 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { -public class ChatInteraction : StreamInteractionModule, Object { - private const string id = "chat_interaction"; - - public signal void conversation_read(Conversation conversation); - public signal void conversation_unread(Conversation conversation); - - private StreamInteractor stream_interactor; - private Conversation? selected_conversation; - - private HashMap last_input_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); - private HashMap last_interface_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); - private bool focus_in = false; - - public static void start(StreamInteractor stream_interactor) { - ChatInteraction m = new ChatInteraction(stream_interactor); - stream_interactor.add_module(m); - } - - private ChatInteraction(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - Timeout.add_seconds(30, update_interactions); - MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); - MessageManager.get_instance(stream_interactor).message_sent.connect(on_message_sent); - } - - public bool is_active_focus(Conversation? conversation = null) { - if (conversation != null) { - return focus_in && conversation.equals(this.selected_conversation); - } else { - return focus_in; - } - } - - public void window_focus_in(Conversation? conversation) { - on_conversation_selected(selected_conversation); - } - - public void window_focus_out(Conversation? conversation) { - focus_in = false; - } - - public void on_message_entered(Conversation conversation) { - if (Settings.instance().send_read) { - if (!last_input_interaction.has_key(conversation) && conversation.type_ != Conversation.Type.GROUPCHAT) { - send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_COMPOSING); - } - } - last_input_interaction[conversation] = new DateTime.now_utc(); - last_interface_interaction[conversation] = new DateTime.now_utc(); - } - - public void on_message_cleared(Conversation conversation) { - if (last_input_interaction.has_key(conversation)) { - last_input_interaction.unset(conversation); - last_interface_interaction.unset(conversation); - send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_ACTIVE); - } - } - - public void on_conversation_selected(Conversation? conversation) { - selected_conversation = conversation; - focus_in = true; - if (conversation != null) { - conversation_read(selected_conversation); - check_send_read(); - selected_conversation.read_up_to = MessageManager.get_instance(stream_interactor).get_last_message(conversation); - } - } - - internal string get_id() { - return id; - } - - public static ChatInteraction? get_instance(StreamInteractor stream_interactor) { - return (ChatInteraction) stream_interactor.get_module(id); - } - - private void check_send_read() { - if (selected_conversation == null || selected_conversation.type_ == Conversation.Type.GROUPCHAT) return; - Entities.Message? message = MessageManager.get_instance(stream_interactor).get_last_message(selected_conversation); - if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED && - message.stanza != null && !message.equals(selected_conversation.read_up_to)) { - selected_conversation.read_up_to = message; - send_chat_marker(selected_conversation, message, Xep.ChatMarkers.MARKER_DISPLAYED); - } - } - - private bool update_interactions() { - ArrayList remove_input = new ArrayList(Conversation.equals_func); - ArrayList remove_interface = new ArrayList(Conversation.equals_func); - foreach (Conversation conversation in last_input_interaction.keys) { - if (last_input_interaction.has_key(conversation) && - (new DateTime.now_utc()).difference(last_input_interaction[conversation]) >= 15 * TimeSpan.SECOND) { - remove_input.add(conversation); - send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); - } - } - foreach (Conversation conversation in last_interface_interaction.keys) { - if (last_interface_interaction.has_key(conversation) && - (new DateTime.now_utc()).difference(last_interface_interaction[conversation]) >= 1.5 * TimeSpan.MINUTE) { - remove_interface.add(conversation); - send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_GONE); - } - } - foreach (Conversation conversation in remove_input) last_input_interaction.unset(conversation); - foreach (Conversation conversation in remove_interface) last_interface_interaction.unset(conversation); - return true; - } - - private void on_message_received(Entities.Message message, Conversation conversation) { - if (is_active_focus(conversation)) { - check_send_read(); - conversation.read_up_to = message; - send_chat_marker(conversation, message, Xep.ChatMarkers.MARKER_DISPLAYED); - } else { - conversation_unread(conversation); - } - } - - private void on_message_sent(Entities.Message message, Conversation conversation) { - last_input_interaction.unset(conversation); - last_interface_interaction.unset(conversation); - conversation.read_up_to = message; - } - - private void send_chat_marker(Conversation conversation, Entities.Message message, string marker) { - Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - if (stream != null && Settings.instance().send_read && Xep.ChatMarkers.Module.requests_marking(message.stanza)) { - Xep.ChatMarkers.Module.get_module(stream).send_marker(stream, message.stanza.from, message.stanza_id, message.get_type_string(), marker); - } - } - - private void send_chat_state_notification(Conversation conversation, string state) { - Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - if (stream != null && Settings.instance().send_read) { - Xep.ChatStateNotifications.Module.get_module(stream).send_state(stream, conversation.counterpart.to_string(), state); - } - } -} -} \ No newline at end of file diff --git a/client/src/service/connection_manager.vala b/client/src/service/connection_manager.vala deleted file mode 100644 index 92827296..00000000 --- a/client/src/service/connection_manager.vala +++ /dev/null @@ -1,222 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - -public class ConnectionManager { - - public signal void stream_opened(Account account, Core.XmppStream stream); - public signal void connection_state_changed(Account account, ConnectionState state); - - public enum ConnectionState { - CONNECTED, - CONNECTING, - DISCONNECTED - } - - private ArrayList connection_todo = new ArrayList(Account.equals_func); - private HashMap stream_states = new HashMap(Account.hash_func, Account.equals_func); - private NetworkManager? network_manager; - private Login1Manager? login1; - private ModuleManager module_manager; - - private class Connection { - public Core.XmppStream stream { get; set; } - public ConnectionState connection_state { get; set; default = ConnectionState.DISCONNECTED; } - public DateTime established { get; set; } - public class Connection(Core.XmppStream stream, DateTime established) { - this.stream = stream; - this.established = established; - } - } - - public ConnectionManager(ModuleManager module_manager) { - this.module_manager = module_manager; - network_manager = get_network_manager(); - if (network_manager != null) { - network_manager.StateChanged.connect(on_nm_state_changed); - } - login1 = get_login1(); - if (login1 != null) { - login1.PrepareForSleep.connect(on_prepare_for_sleep); - } - } - - public Core.XmppStream? get_stream(Account account) { - if (get_connection_state(account) == ConnectionState.CONNECTED) { - return stream_states[account].stream; - } - return null; - } - - public ConnectionState get_connection_state(Account account) { - if (stream_states.has_key(account)){ - return stream_states[account].connection_state; - } - return ConnectionState.DISCONNECTED; - } - - public ArrayList get_managed_accounts() { - return connection_todo; - } - - public Core.XmppStream? connect(Account account) { - if (!connection_todo.contains(account)) connection_todo.add(account); - if (!stream_states.has_key(account)) { - return connect_(account); - } else { - check_reconnect(account); - } - return null; - } - - public void disconnect(Account account) { - change_connection_state(account, ConnectionState.DISCONNECTED); - if (stream_states.has_key(account)) { - try { - stream_states[account].stream.disconnect(); - } catch (Error e) { } - } - connection_todo.remove(account); - } - - private Core.XmppStream? connect_(Account account, string? resource = null) { - if (resource == null) resource = account.resourcepart; - if (stream_states.has_key(account)) { - stream_states[account].stream.remove_modules(); - } - - Core.XmppStream stream = new Core.XmppStream(); - foreach (Core.XmppStreamModule module in module_manager.get_modules(account, resource)) { - stream.add_module(module); - } - stream.debug = false; - - Connection connection = new Connection(stream, new DateTime.now_local()); - stream_states[account] = connection; - change_connection_state(account, ConnectionState.CONNECTING); - stream.stream_negotiated.connect((stream) => { - change_connection_state(account, ConnectionState.CONNECTED); - }); - new Thread (null, () => { - try { - stream.connect(account.domainpart); - } catch (Error e) { - stderr.printf("Stream Error: %s\n", e.message); - change_connection_state(account, ConnectionState.DISCONNECTED); - interpret_reconnect_flags(account, StreamError.Flag.get_flag(stream) ?? - new StreamError.Flag() { reconnection_recomendation = StreamError.Flag.Reconnect.NOW }); - } - return null; - }); - stream_opened(account, stream); - - return stream; - } - - private void interpret_reconnect_flags(Account account, StreamError.Flag stream_error_flag) { - if (!connection_todo.contains(account)) return; - int wait_sec = 10; - if (network_manager != null && network_manager.State != NetworkManager.CONNECTED_GLOBAL) { - wait_sec = 60; - } - switch (stream_error_flag.reconnection_recomendation) { - case StreamError.Flag.Reconnect.NOW: - wait_sec = 10; - break; - case StreamError.Flag.Reconnect.LATER: - case StreamError.Flag.Reconnect.UNKNOWN: - wait_sec = 60; - break; - case StreamError.Flag.Reconnect.NEVER: - return; - } - print(@"recovering in $wait_sec\n"); - Timeout.add_seconds(wait_sec, () => { - if (stream_error_flag.resource_rejected) { - connect_(account, account.resourcepart + "-" + random_uuid()); - } else { - connect_(account); - } - return false; - }); - } - - private void check_reconnects() { - foreach (Account account in connection_todo) { - check_reconnect(account); - } - } - - private void check_reconnect(Account account) { - PingResponseListenerImpl ping_response_listener = new PingResponseListenerImpl(this, account); - Core.XmppStream stream = stream_states[account].stream; - Xep.Ping.Module.get_module(stream).send_ping(stream, account.domainpart, ping_response_listener); - - Timeout.add_seconds(5, () => { - if (stream_states[account].stream != stream) return false; - if (ping_response_listener.acked) return false; - - change_connection_state(account, ConnectionState.DISCONNECTED); - try { - stream_states[account].stream.disconnect(); - } catch (Error e) { } - return false; - }); - } - - private class PingResponseListenerImpl : Xep.Ping.ResponseListener, Object { - public bool acked = false; - ConnectionManager outer; - Account account; - public PingResponseListenerImpl(ConnectionManager outer, Account account) { - this.outer = outer; - this.account = account; - } - public void on_result(Core.XmppStream stream) { - print("ping ok\n"); - acked = true; - outer.change_connection_state(account, ConnectionState.CONNECTED); - } - } - - private void on_nm_state_changed(uint32 state) { - print("nm " + state.to_string() + "\n"); - if (state == NetworkManager.CONNECTED_GLOBAL) { - check_reconnects(); - } else { - foreach (Account account in connection_todo) { - change_connection_state(account, ConnectionState.DISCONNECTED); - } - } - } - - private void on_prepare_for_sleep(bool suspend) { - foreach (Account account in connection_todo) { - change_connection_state(account, ConnectionState.DISCONNECTED); - } - if (suspend) { - print("suspend\n"); - foreach (Account account in connection_todo) { - Xmpp.Presence.Stanza presence = new Xmpp.Presence.Stanza(); - presence.type_ = Xmpp.Presence.Stanza.TYPE_UNAVAILABLE; - try { - Presence.Module.get_module(stream_states[account].stream).send_presence(stream_states[account].stream, presence); - stream_states[account].stream.disconnect(); - } catch (Error e) { print(@"on_prepare_for_sleep error $(e.message)\n"); } - } - } else { - print("un-suspend\n"); - check_reconnects(); - } - } - - private void change_connection_state(Account account, ConnectionState state) { - stream_states[account].connection_state = state; - connection_state_changed(account, state); - } -} - -} diff --git a/client/src/service/conversation_manager.vala b/client/src/service/conversation_manager.vala deleted file mode 100644 index 716c9b39..00000000 --- a/client/src/service/conversation_manager.vala +++ /dev/null @@ -1,103 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { -public class ConversationManager : StreamInteractionModule, Object { - - public const string id = "conversation_manager"; - - public signal void conversation_activated(Conversation conversation); - - private StreamInteractor stream_interactor; - private Database db; - - private HashMap> conversations = new HashMap>(Account.hash_func, Account.equals_func); - - public static void start(StreamInteractor stream_interactor, Database db) { - ConversationManager m = new ConversationManager(stream_interactor, db); - stream_interactor.add_module(m); - } - - private ConversationManager(StreamInteractor stream_interactor, Database db) { - this.db = db; - this.stream_interactor = stream_interactor; - stream_interactor.add_module(this); - stream_interactor.account_added.connect(on_account_added); - MucManager.get_instance(stream_interactor).groupchat_joined.connect(on_groupchat_joined); - MessageManager.get_instance(stream_interactor).pre_message_received.connect(on_message_received); - MessageManager.get_instance(stream_interactor).message_sent.connect(on_message_sent); - } - - public Conversation? get_conversation(Jid jid, Account account) { - if (conversations.has_key(account)) { - return conversations[account][jid]; - } - return null; - } - - public Conversation get_add_conversation(Jid jid, Account account) { - ensure_add_conversation(jid, account, Conversation.Type.CHAT); - return get_conversation(jid, account); - } - - public void ensure_start_conversation(Jid jid, Account account) { - ensure_add_conversation(jid, account, Conversation.Type.CHAT); - Conversation? conversation = get_conversation(jid, account); - if (conversation != null) { - conversation.last_active = new DateTime.now_utc(); - if (!conversation.active) { - conversation.active = true; - conversation_activated(conversation); - } - } - - } - - public string get_id() { - return id; - } - - public static ConversationManager? get_instance(StreamInteractor stream_interaction) { - return (ConversationManager) stream_interaction.get_module(id); - } - - private void on_account_added(Account account) { - conversations[account] = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); - foreach (Conversation conversation in db.get_conversations(account)) { - add_conversation(conversation); - } - } - - private void on_message_received(Entities.Message message, Conversation conversation) { - ensure_start_conversation(conversation.counterpart, conversation.account); - } - - private void on_message_sent(Entities.Message message, Conversation conversation) { - conversation.last_active = message.time; - } - - private void on_groupchat_joined(Account account, Jid jid, string nick) { - ensure_add_conversation(jid, account, Conversation.Type.GROUPCHAT); - ensure_start_conversation(jid, account); - } - - private void ensure_add_conversation(Jid jid, Account account, Conversation.Type type) { - if (conversations.has_key(account) && !conversations[account].has_key(jid)) { - Conversation conversation = new Conversation(jid, account); - conversation.type_ = type; - add_conversation(conversation); - db.add_conversation(conversation); - } - } - - private void add_conversation(Conversation conversation) { - conversations[conversation.account][conversation.counterpart] = conversation; - if (conversation.active) { - conversation_activated(conversation); - } - } -} - -} \ No newline at end of file diff --git a/client/src/service/counterpart_interaction_manager.vala b/client/src/service/counterpart_interaction_manager.vala deleted file mode 100644 index 8ea8ba15..00000000 --- a/client/src/service/counterpart_interaction_manager.vala +++ /dev/null @@ -1,99 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { -public class CounterpartInteractionManager : StreamInteractionModule, Object { - public const string id = "counterpart_interaction_manager"; - - public signal void received_state(Account account, Jid jid, string state); - public signal void received_marker(Account account, Jid jid, Entities.Message message, string marker); - public signal void received_message_received(Account account, Jid jid, Entities.Message message); - public signal void received_message_displayed(Account account, Jid jid, Entities.Message message); - - private StreamInteractor stream_interactor; - private HashMap last_read = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); - private HashMap chat_states = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); - - public static void start(StreamInteractor stream_interactor) { - CounterpartInteractionManager m = new CounterpartInteractionManager(stream_interactor); - stream_interactor.add_module(m); - } - - private CounterpartInteractionManager(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - stream_interactor.account_added.connect(on_account_added); - MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); - } - - public string? get_chat_state(Account account, Jid jid) { - return chat_states[jid]; - } - - public Entities.Message? get_last_read(Account account, Jid jid) { - return last_read[jid]; - } - - public static CounterpartInteractionManager? get_instance(StreamInteractor stream_interactor) { - return (CounterpartInteractionManager) stream_interactor.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.chat_markers_modules[account].marker_received.connect( (stream, jid, marker, id) => { - on_chat_marker_received(account, new Jid(jid), marker, id); - }); - stream_interactor.module_manager.message_delivery_receipts_modules[account].receipt_received.connect((stream, jid, id) => { - on_receipt_received(account, new Jid(jid), id); - }); - stream_interactor.module_manager.chat_state_notifications_modules[account].chat_state_received.connect((stream, jid, state) => { - on_chat_state_received(account, new Jid(jid), state); - }); - } - - private void on_chat_state_received(Account account, Jid jid, string state) { - chat_states[jid] = state; - received_state(account, jid, state); - } - - private void on_chat_marker_received(Account account, Jid jid, string marker, string stanza_id) { - Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); - if (conversation != null) { - Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); - if (messages != null) { // TODO not here - foreach (Entities.Message message in messages) { - if (message.stanza_id == stanza_id) { - switch (marker) { - case Xep.ChatMarkers.MARKER_RECEIVED: - received_message_received(account, jid, message); - message.marked = Entities.Message.Marked.RECEIVED; - break; - case Xep.ChatMarkers.MARKER_DISPLAYED: - last_read[jid] = message; - received_message_displayed(account, jid, message); - foreach (Entities.Message m in messages) { - if (m.equals(message)) break; - if (m.marked == Entities.Message.Marked.RECEIVED) m.marked = Entities.Message.Marked.READ; - } - message.marked = Entities.Message.Marked.READ; - break; - } - } - } - } - } - } - - private void on_message_received(Entities.Message message, Conversation conversation) { - on_chat_state_received(conversation.account, conversation.counterpart, Xep.ChatStateNotifications.STATE_ACTIVE); - } - - private void on_receipt_received(Account account, Jid jid, string id) { - on_chat_marker_received(account, jid, Xep.ChatMarkers.MARKER_RECEIVED, id); - } -} -} \ No newline at end of file diff --git a/client/src/service/database.vala b/client/src/service/database.vala deleted file mode 100644 index 13be6222..00000000 --- a/client/src/service/database.vala +++ /dev/null @@ -1,466 +0,0 @@ -using Gee; -using Sqlite; -using Qlite; - -using Dino.Entities; - -namespace Dino { - -public class Database : Qlite.Database { - private const int VERSION = 0; - - public class AccountTable : Table { - public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; - public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; - public Column resourcepart = new Column.Text("resourcepart"); - public Column password = new Column.Text("password"); - public Column alias = new Column.Text("alias"); - public Column enabled = new Column.BoolInt("enabled"); - - protected AccountTable(Database db) { - base(db, "account"); - init({id, bare_jid, resourcepart, password, alias, enabled}); - } - } - - public class JidTable : Table { - public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; - public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; - - protected JidTable(Database db) { - base(db, "jid"); - init({id, bare_jid}); - } - } - - public class MessageTable : Table { - public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; - public Column stanza_id = new Column.Text("stanza_id"); - public Column account_id = new Column.Integer("account_id") { not_null = true }; - public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; - public Column counterpart_resource = new Column.Text("counterpart_resource"); - public Column our_resource = new Column.Text("our_resource"); - public Column direction = new Column.BoolInt("direction") { not_null = true }; - public Column type_ = new Column.Integer("type"); - public Column time = new Column.Long("time"); - public Column local_time = new Column.Long("local_time"); - public Column body = new Column.Text("body"); - public Column encryption = new Column.Integer("encryption"); - public Column marked = new Column.Integer("marked"); - - protected MessageTable(Database db) { - base(db, "message"); - init({id, stanza_id, account_id, counterpart_id, our_resource, counterpart_resource, direction, - type_, time, local_time, body, encryption, marked}); - } - } - - public class RealJidTable : Table { - public Column message_id = new Column.Integer("message_id") { primary_key = true }; - public Column real_jid = new Column.Text("real_jid"); - - protected RealJidTable(Database db) { - base(db, "real_jid"); - init({message_id, real_jid}); - } - } - - public class UndecryptedTable : Table { - public Column message_id = new Column.Integer("message_id"); - public Column type_ = new Column.Integer("type"); - public Column data = new Column.Text("data"); - - protected UndecryptedTable(Database db) { - base(db, "undecrypted"); - init({message_id, type_, data}); - } - } - - public class ConversationTable : Table { - public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; - public Column account_id = new Column.Integer("account_id") { not_null = true }; - public Column jid_id = new Column.Integer("jid_id") { not_null = true }; - public Column active = new Column.BoolInt("active"); - public Column last_active = new Column.Long("last_active"); - public Column type_ = new Column.Integer("type"); - public Column encryption = new Column.Integer("encryption"); - public Column read_up_to = new Column.Integer("read_up_to"); - - protected ConversationTable(Database db) { - base(db, "conversation"); - init({id, account_id, jid_id, active, last_active, type_, encryption, read_up_to}); - } - } - - public class AvatarTable : Table { - public Column jid = new Column.Text("jid"); - public Column hash = new Column.Text("hash"); - public Column type_ = new Column.Integer("type"); - - protected AvatarTable(Database db) { - base(db, "avatar"); - init({jid, hash, type_}); - } - } - - public class PgpTable : Table { - public Column jid = new Column.Text("jid") { primary_key = true }; - public Column key = new Column.Text("key") { not_null = true }; - - protected PgpTable(Database db) { - base(db, "pgp"); - init({jid, key}); - } - } - - public class EntityFeatureTable : Table { - public Column entity = new Column.Text("entity"); - public Column feature = new Column.Text("feature"); - - protected EntityFeatureTable(Database db) { - base(db, "entity_feature"); - init({entity, feature}); - } - } - - public AccountTable account { get; private set; } - public JidTable jid { get; private set; } - public MessageTable message { get; private set; } - public RealJidTable real_jid { get; private set; } - public ConversationTable conversation { get; private set; } - public AvatarTable avatar { get; private set; } - public PgpTable pgp { get; private set; } - public EntityFeatureTable entity_feature { get; private set; } - - public Database(string fileName) { - base(fileName, VERSION); - account = new AccountTable(this); - jid = new JidTable(this); - message = new MessageTable(this); - real_jid = new RealJidTable(this); - conversation = new ConversationTable(this); - avatar = new AvatarTable(this); - pgp = new PgpTable(this); - entity_feature = new EntityFeatureTable(this); - init({ account, jid, message, real_jid, conversation, avatar, pgp, entity_feature }); - } - - public override void migrate(long oldVersion) { - // new table columns are added, outdated columns are still present - } - - public void add_account(Account new_account) { - new_account.id = (int) account.insert() - .value(account.bare_jid, new_account.bare_jid.to_string()) - .value(account.resourcepart, new_account.resourcepart) - .value(account.password, new_account.password) - .value(account.alias, new_account.alias) - .value(account.enabled, new_account.enabled) - .perform(); - new_account.notify.connect(on_account_update); - } - - private void on_account_update(Object o, ParamSpec sp) { - Account changed_account = (Account) o; - account.update().with(account.id, "=", changed_account.id) - .set(account.bare_jid, changed_account.bare_jid.to_string()) - .set(account.resourcepart, changed_account.resourcepart) - .set(account.password, changed_account.password) - .set(account.alias, changed_account.alias) - .set(account.enabled, changed_account.enabled) - .perform(); - } - - public void remove_account(Account to_delete) { - account.delete().with(account.bare_jid, "=", to_delete.bare_jid.to_string()).perform(); - } - - public ArrayList get_accounts() { - ArrayList ret = new ArrayList(); - foreach(Row row in account.select()) { - Account account = get_account_from_row(row); - account.notify.connect(on_account_update); - ret.add(account); - } - return ret; - } - - private Account? get_account_by_id(int id) { - Row? row = account.row_with(account.id, id); - if (row != null) { - return get_account_from_row(row); - } - return null; - } - - private Account get_account_from_row(Row row) { - Account new_account = new Account.from_bare_jid(row[account.bare_jid]); - - new_account.id = row[account.id]; - new_account.resourcepart = row[account.resourcepart]; - new_account.password = row[account.password]; - new_account.alias = row[account.alias]; - new_account.enabled = row[account.enabled]; - return new_account; - } - - public void add_message(Message new_message, Account account) { - InsertBuilder builder = message.insert() - .value(message.account_id, new_message.account.id) - .value(message.counterpart_id, get_jid_id(new_message.counterpart)) - .value(message.counterpart_resource, new_message.counterpart.resourcepart) - .value(message.our_resource, new_message.ourpart.resourcepart) - .value(message.direction, new_message.direction) - .value(message.type_, new_message.type_) - .value(message.time, (long) new_message.time.to_unix()) - .value(message.local_time, (long) new_message.local_time.to_unix()) - .value(message.body, new_message.body) - .value(message.encryption, new_message.encryption) - .value(message.marked, new_message.marked); - if (new_message.stanza_id != null) builder.value(message.stanza_id, new_message.stanza_id); - new_message.id = (int) builder.perform(); - - if (new_message.real_jid != null) { - real_jid.insert() - .value(real_jid.message_id, new_message.id) - .value(real_jid.real_jid, new_message.real_jid) - .perform(); - } - new_message.notify.connect(on_message_update); - } - - private void on_message_update(Object o, ParamSpec sp) { - Message changed_message = (Message) o; - UpdateBuilder update_builder = message.update().with(message.id, "=", changed_message.id); - switch (sp.get_name()) { - case "stanza_id": - update_builder.set(message.stanza_id, changed_message.stanza_id); break; - case "counterpart": - update_builder.set(message.counterpart_id, get_jid_id(changed_message.counterpart)); - update_builder.set(message.counterpart_resource, changed_message.counterpart.resourcepart); break; - case "ourpart": - update_builder.set(message.our_resource, changed_message.ourpart.resourcepart); break; - case "direction": - update_builder.set(message.direction, changed_message.direction); break; - case "type_": - update_builder.set(message.type_, changed_message.type_); break; - case "time": - update_builder.set(message.time, (long) changed_message.time.to_unix()); break; - case "local_time": - update_builder.set(message.local_time, (long) changed_message.local_time.to_unix()); break; - case "body": - update_builder.set(message.body, changed_message.body); break; - case "encryption": - update_builder.set(message.encryption, changed_message.encryption); break; - case "marked": - update_builder.set(message.marked, changed_message.marked); break; - } - update_builder.perform(); - - if (sp.get_name() == "real_jid") { - real_jid.insert() - .value(real_jid.message_id, changed_message.id) - .value(real_jid.real_jid, changed_message.real_jid) - .perform(); - } - } - - public Gee.List get_messages(Jid jid, Account account, int count, Message? before) { - string jid_id = get_jid_id(jid).to_string(); - - QueryBuilder select = message.select() - .with(message.counterpart_id, "=", get_jid_id(jid)) - .with(message.account_id, "=", account.id) - .order_by(message.id, "DESC") - .limit(count); - if (before != null) { - select.with(message.time, "<", (long) before.time.to_unix()); - } - - LinkedList ret = new LinkedList(); - foreach (Row row in select) { - ret.insert(0, get_message_from_row(row)); - } - return ret; - } - - public Gee.List get_unsend_messages(Account account) { - Gee.List ret = new ArrayList(); - foreach (Row row in message.select().with(message.marked, "=", (int) Message.Marked.UNSENT)) { - ret.add(get_message_from_row(row)); - } - return ret; - } - - public bool contains_message(Message query_message, Account account) { - int jid_id = get_jid_id(query_message.counterpart); - return message.select() - .with(message.account_id, "=", account.id) - .with(message.stanza_id, "=", query_message.stanza_id) - .with(message.counterpart_id, "=", jid_id) - .with(message.counterpart_resource, "=", query_message.counterpart.resourcepart) - .with(message.body, "=", query_message.body) - .with(message.time, "<", (long) query_message.time.add_minutes(1).to_unix()) - .with(message.time, ">", (long) query_message.time.add_minutes(-1).to_unix()) - .count() > 0; - } - - public bool contains_message_by_stanza_id(string stanza_id) { - return message.select() - .with(message.stanza_id, "=", stanza_id) - .count() > 0; - } - - public Message? get_message_by_id(int id) { - Row? row = message.row_with(message.id, id); - if (row != null) { - return get_message_from_row(row); - } - return null; - } - - public Message get_message_from_row(Row row) { - Message new_message = new Message(); - - new_message.id = row[message.id]; - new_message.stanza_id = row[message.stanza_id]; - string from = get_jid_by_id(row[message.counterpart_id]); - string from_resource = row[message.counterpart_resource]; - if (from_resource != null) { - new_message.counterpart = new Jid(from + "/" + from_resource); - } else { - new_message.counterpart = new Jid(from); - } - new_message.direction = row[message.direction]; - new_message.type_ = (Message.Type) row[message.type_]; - new_message.time = new DateTime.from_unix_utc(row[message.time]); - new_message.body = row[message.body]; - new_message.account = get_account_by_id(row[message.account_id]); // TODO dont have to generate acc new - new_message.marked = (Message.Marked) row[message.marked]; - new_message.encryption = (Message.Encryption) row[message.encryption]; - new_message.real_jid = get_real_jid_for_message(new_message); - - new_message.notify.connect(on_message_update); - return new_message; - } - - public string? get_real_jid_for_message(Message message) { - return real_jid.select({real_jid.real_jid}).with(real_jid.message_id, "=", message.id)[real_jid.real_jid]; - } - - public void add_conversation(Conversation new_conversation) { - var insert = conversation.insert() - .value(conversation.jid_id, get_jid_id(new_conversation.counterpart)) - .value(conversation.account_id, new_conversation.account.id) - .value(conversation.type_, new_conversation.type_) - .value(conversation.encryption, new_conversation.encryption) - //.value(conversation.read_up_to, new_conversation.read_up_to) - .value(conversation.active, new_conversation.active); - if (new_conversation.last_active != null) { - insert.value(conversation.last_active, (long) new_conversation.last_active.to_unix()); - } else { - insert.value_null(conversation.last_active); - } - new_conversation.id = (int) insert.perform(); - new_conversation.notify.connect(on_conversation_update); - } - - public ArrayList get_conversations(Account account) { - ArrayList ret = new ArrayList(); - foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) { - ret.add(get_conversation_from_row(row)); - } - return ret; - } - - private void on_conversation_update(Object o, ParamSpec sp) { - Conversation changed_conversation = (Conversation) o; - var update = conversation.update().with(conversation.jid_id, "=", get_jid_id(changed_conversation.counterpart)).with(conversation.account_id, "=", changed_conversation.account.id) - .set(conversation.type_, changed_conversation.type_) - .set(conversation.encryption, changed_conversation.encryption) - //.set(conversation.read_up_to, changed_conversation.read_up_to) - .set(conversation.active, changed_conversation.active); - if (changed_conversation.last_active != null) { - update.set(conversation.last_active, (long) changed_conversation.last_active.to_unix()); - } else { - update.set_null(conversation.last_active); - } - update.perform(); - } - - private Conversation get_conversation_from_row(Row row) { - Conversation new_conversation = new Conversation(new Jid(get_jid_by_id(row[conversation.jid_id])), get_account_by_id(row[conversation.account_id])); - - new_conversation.id = row[conversation.id]; - new_conversation.active = row[conversation.active]; - int64? last_active = row[conversation.last_active]; - if (last_active != null) new_conversation.last_active = new DateTime.from_unix_utc(last_active); - new_conversation.type_ = (Conversation.Type) row[conversation.type_]; - new_conversation.encryption = (Conversation.Encryption) row[conversation.encryption]; - int? read_up_to = row[conversation.read_up_to]; - if (read_up_to != null) new_conversation.read_up_to = get_message_by_id(read_up_to); - - new_conversation.notify.connect(on_conversation_update); - return new_conversation; - } - - public void set_avatar_hash(Jid jid, string hash, int type) { - avatar.insert().or("REPLACE") - .value(avatar.jid, jid.to_string()) - .value(avatar.hash, hash) - .value(avatar.type_, type) - .perform(); - } - - public HashMap get_avatar_hashes(int type) { - HashMap ret = new HashMap(Jid.hash_func, Jid.equals_func); - foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) { - ret[new Jid(row[avatar.jid])] = row[avatar.hash]; - } - return ret; - } - - public void set_pgp_key(Jid jid, string key) { - pgp.insert().or("REPLACE") - .value(pgp.jid, jid.to_string()) - .value(pgp.key, key) - .perform(); - } - - public string? get_pgp_key(Jid jid) { - return pgp.select({pgp.key}).with(pgp.jid, "=", jid.to_string())[pgp.key]; - } - - public void add_entity_features(string entity, ArrayList features) { - foreach (string feature in features) { - entity_feature.insert() - .value(entity_feature.entity, entity) - .value(entity_feature.feature, feature) - .perform(); - } - } - - public ArrayList get_entity_features(string entity) { - ArrayList ret = new ArrayList(); - foreach (Row row in entity_feature.select({entity_feature.feature}).with(entity_feature.entity, "=", entity)) { - ret.add(row[entity_feature.feature]); - } - return ret; - } - - - private int get_jid_id(Jid jid_obj) { - Row? row = jid.row_with(jid.bare_jid, jid_obj.bare_jid.to_string()); - return row != null ? row[jid.id] : add_jid(jid_obj); - } - - private string? get_jid_by_id(int id) { - return jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid]; - } - - private int add_jid(Jid jid_obj) { - return (int) jid.insert().value(jid.bare_jid, jid_obj.bare_jid.to_string()).perform(); - } -} - -} \ No newline at end of file diff --git a/client/src/service/entity_capabilities_storage.vala b/client/src/service/entity_capabilities_storage.vala deleted file mode 100644 index 9774739a..00000000 --- a/client/src/service/entity_capabilities_storage.vala +++ /dev/null @@ -1,23 +0,0 @@ -using Gee; - -using Xmpp; - -namespace Dino { - -public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object { - - private Database db; - - public EntityCapabilitiesStorage(Database db) { - this.db = db; - } - - public void store_features(string entity, ArrayList features) { - db.add_entity_features(entity, features); - } - - public ArrayList get_features(string entitiy) { - return db.get_entity_features(entitiy); - } -} -} \ No newline at end of file diff --git a/client/src/service/message_manager.vala b/client/src/service/message_manager.vala deleted file mode 100644 index ec7a35c8..00000000 --- a/client/src/service/message_manager.vala +++ /dev/null @@ -1,193 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - -public class MessageManager : StreamInteractionModule, Object { - public const string ID = "message_manager"; - - public signal void pre_message_received(Entities.Message message, Conversation conversation); - public signal void message_received(Entities.Message message, Conversation conversation); - public signal void message_sent(Entities.Message message, Conversation conversation); - - private StreamInteractor stream_interactor; - private Database db; - private HashMap> messages = new HashMap>(Conversation.hash_func, Conversation.equals_func); - - public static void start(StreamInteractor stream_interactor, Database db) { - MessageManager m = new MessageManager(stream_interactor, db); - stream_interactor.add_module(m); - } - - private MessageManager(StreamInteractor stream_interactor, Database db) { - this.stream_interactor = stream_interactor; - this.db = db; - stream_interactor.account_added.connect(on_account_added); - stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { - if (state == ConnectionManager.ConnectionState.CONNECTED) send_unsent_messages(account); - }); - } - - public void send_message(string text, Conversation conversation) { - Entities.Message message = create_out_message(text, conversation); - add_message(message, conversation); - db.add_message(message, conversation.account); - send_xmpp_message(message, conversation); - message_sent(message, conversation); - } - - public Gee.List? get_messages(Conversation conversation) { - if (messages.has_key(conversation) && messages[conversation].size > 0) { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, messages[conversation][0]); - db_messages.add_all(messages[conversation]); - return db_messages; - } else { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, null); - return db_messages; - } - } - - public Entities.Message? get_last_message(Conversation conversation) { - if (messages.has_key(conversation) && messages[conversation].size > 0) { - return messages[conversation][messages[conversation].size - 1]; - } else { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 1, null); - if (db_messages.size >= 1) { - return db_messages[0]; - } - } - return null; - } - - public Gee.List? get_messages_before(Conversation? conversation, Entities.Message before) { - Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 20, before); - return db_messages; - } - - public string get_id() { - return ID; - } - - public static MessageManager? get_instance(StreamInteractor stream_interactor) { - return (MessageManager) stream_interactor.get_module(ID); - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.message_modules[account].received_message.connect( (stream, message) => { - on_message_received(account, message); - }); - stream_interactor.stream_negotiated.connect(send_unsent_messages); - } - - private void send_unsent_messages(Account account) { - Gee.List unsend_messages = db.get_unsend_messages(account); - foreach (Entities.Message message in unsend_messages) { - Conversation conversation = ConversationManager.get_instance(stream_interactor).get_conversation(message.counterpart, account); - send_xmpp_message(message, conversation, true); - } - } - - private void on_message_received(Account account, Xmpp.Message.Stanza message) { - if (message.body == null) return; - - Entities.Message new_message = new Entities.Message(); - new_message.account = account; - new_message.stanza_id = message.id; - Jid from_jid = new Jid(message.from); - if (!account.bare_jid.equals_bare(from_jid) || - MucManager.get_instance(stream_interactor).get_nick(from_jid.bare_jid, account) == from_jid.resourcepart) { - new_message.direction = Entities.Message.DIRECTION_RECEIVED; - } else { - new_message.direction = Entities.Message.DIRECTION_SENT; - } - new_message.counterpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.to) : new Jid(message.from); - new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.from) : new Jid(message.to); - new_message.body = message.body; - new_message.stanza = message; - new_message.set_type_string(message.type_); - Xep.DelayedDelivery.MessageFlag? deleyed_delivery_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); - new_message.time = deleyed_delivery_flag != null ? deleyed_delivery_flag.datetime : new DateTime.now_utc(); - new_message.local_time = new DateTime.now_utc(); - if (Xep.Pgp.MessageFlag.get_flag(message) != null) { - new_message.encryption = Entities.Message.Encryption.PGP; - } - Conversation conversation = ConversationManager.get_instance(stream_interactor).get_add_conversation(new_message.counterpart, account); - pre_message_received(new_message, conversation); - - bool is_uuid = new_message.stanza_id != null && Regex.match_simple("""[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}""", new_message.stanza_id); - if ((is_uuid && !db.contains_message_by_stanza_id(new_message.stanza_id)) || - (!is_uuid && !db.contains_message(new_message, conversation.account))) { - db.add_message(new_message, conversation.account); - add_message(new_message, conversation); - if (new_message.time.difference(conversation.last_active) > 0) { - conversation.last_active = new_message.time; - } - if (new_message.direction == Entities.Message.DIRECTION_SENT) { - message_sent(new_message, conversation); - } else { - message_received(new_message, conversation); - } - } - } - - private void add_message(Entities.Message message, Conversation conversation) { - if (!messages.has_key(conversation)) { - messages[conversation] = new ArrayList(Entities.Message.equals_func); - } - messages[conversation].add(message); - } - - private Entities.Message create_out_message(string text, Conversation conversation) { - Entities.Message message = new Entities.Message(); - message.stanza_id = random_uuid(); - message.account = conversation.account; - message.body = text; - message.time = new DateTime.now_utc(); - message.local_time = new DateTime.now_utc(); - message.direction = Entities.Message.DIRECTION_SENT; - message.counterpart = conversation.counterpart; - message.ourpart = new Jid(conversation.account.bare_jid.to_string() + "/" + conversation.account.resourcepart); - - if (conversation.encryption == Conversation.Encryption.PGP) { - message.encryption = Entities.Message.Encryption.PGP; - } - return message; - } - - private void send_xmpp_message(Entities.Message message, Conversation conversation, bool delayed = false) { - Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - message.marked = Entities.Message.Marked.NONE; - if (stream != null) { - Xmpp.Message.Stanza new_message = new Xmpp.Message.Stanza(message.stanza_id); - new_message.to = message.counterpart.to_string(); - new_message.body = message.body; - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - new_message.type_ = Xmpp.Message.Stanza.TYPE_GROUPCHAT; - } else { - new_message.type_ = Xmpp.Message.Stanza.TYPE_CHAT; - } - if (message.encryption == Entities.Message.Encryption.PGP) { - string? key_id = PgpManager.get_instance(stream_interactor).get_key_id(conversation.account, message.counterpart); - if (key_id != null) { - bool encrypted = Xep.Pgp.Module.get_module(stream).encrypt(new_message, key_id); - if (!encrypted) { - message.marked = Entities.Message.Marked.WONTSEND; - return; - } - } - } - if (delayed) { - Xmpp.Xep.DelayedDelivery.Module.get_module(stream).set_message_delay(new_message, message.time); - } - Xmpp.Message.Module.get_module(stream).send_message(stream, new_message); - message.stanza_id = new_message.id; - message.stanza = new_message; - } else { - message.marked = Entities.Message.Marked.UNSENT; - } - } -} - -} \ No newline at end of file diff --git a/client/src/service/module_manager.vala b/client/src/service/module_manager.vala deleted file mode 100644 index 5ef93da8..00000000 --- a/client/src/service/module_manager.vala +++ /dev/null @@ -1,96 +0,0 @@ -using Gee; - -using Dino.Entities; -using Xmpp; - -namespace Dino { - -public class ModuleManager { - - public HashMap tls_modules = new HashMap(); - public HashMap plain_sasl_modules = new HashMap(); - public HashMap bind_modules = new HashMap(); - public HashMap roster_modules = new HashMap(); - public HashMap service_discovery_modules = new HashMap(); - public HashMap private_xmp_storage_modules = new HashMap(); - public HashMap bookmarks_module = new HashMap(); - public HashMap presence_modules = new HashMap(); - public HashMap message_modules = new HashMap(); - public HashMap message_carbons_modules = new HashMap(); - public HashMap muc_modules = new HashMap(); - public HashMap pgp_modules = new HashMap(); - public HashMap pubsub_modules = new HashMap(); - public HashMap entity_capabilities_modules = new HashMap(); - public HashMap user_avatars_modules = new HashMap(); - public HashMap vcard_modules = new HashMap(); - public HashMap message_delivery_receipts_modules = new HashMap(); - public HashMap chat_state_notifications_modules = new HashMap(); - public HashMap chat_markers_modules = new HashMap(); - public HashMap ping_modules = new HashMap(); - public HashMap delayed_delivery_module = new HashMap(); - public HashMap stream_error_modules = new HashMap(); - - private AvatarStorage avatar_storage = new AvatarStorage("./"); - private EntityCapabilitiesStorage entity_capabilities_storage; - - public ModuleManager(Database db) { - entity_capabilities_storage = new EntityCapabilitiesStorage(db); - } - - public ArrayList get_modules(Account account, string? resource = null) { - ArrayList modules = new ArrayList(); - - if (!tls_modules.has_key(account)) add_account(account); - - modules.add(tls_modules[account]); - modules.add(plain_sasl_modules[account]); - modules.add(new Bind.Module(resource == null ? account.resourcepart : resource)); - modules.add(roster_modules[account]); - modules.add(service_discovery_modules[account]); - modules.add(private_xmp_storage_modules[account]); - modules.add(bookmarks_module[account]); - modules.add(presence_modules[account]); - modules.add(message_modules[account]); - modules.add(message_carbons_modules[account]); - modules.add(muc_modules[account]); - modules.add(pgp_modules[account]); - modules.add(pubsub_modules[account]); - modules.add(entity_capabilities_modules[account]); - modules.add(user_avatars_modules[account]); - modules.add(vcard_modules[account]); - modules.add(message_delivery_receipts_modules[account]); - modules.add(chat_state_notifications_modules[account]); - modules.add(chat_markers_modules[account]); - modules.add(ping_modules[account]); - modules.add(delayed_delivery_module[account]); - modules.add(stream_error_modules[account]); - return modules; - } - - public void add_account(Account account) { - tls_modules[account] = new Tls.Module(); - plain_sasl_modules[account] = new PlainSasl.Module(account.bare_jid.to_string(), account.password); - bind_modules[account] = new Bind.Module(account.resourcepart); - roster_modules[account] = new Roster.Module(); - service_discovery_modules[account] = new Xep.ServiceDiscovery.Module.with_identity("client", "pc"); - private_xmp_storage_modules[account] = new Xep.PrivateXmlStorage.Module(); - bookmarks_module[account] = new Xep.Bookmarks.Module(); - presence_modules[account] = new Presence.Module(); - message_modules[account] = new Xmpp.Message.Module(); - message_carbons_modules[account] = new Xep.MessageCarbons.Module(); - muc_modules[account] = new Xep.Muc.Module(); - pgp_modules[account] = new Xep.Pgp.Module(); - pubsub_modules[account] = new Xep.Pubsub.Module(); - entity_capabilities_modules[account] = new Xep.EntityCapabilities.Module(entity_capabilities_storage); - user_avatars_modules[account] = new Xep.UserAvatars.Module(avatar_storage); - vcard_modules[account] = new Xep.VCard.Module(avatar_storage); - message_delivery_receipts_modules[account] = new Xep.MessageDeliveryReceipts.Module(); - chat_state_notifications_modules[account] = new Xep.ChatStateNotifications.Module(); - chat_markers_modules[account] = new Xep.ChatMarkers.Module(); - ping_modules[account] = new Xep.Ping.Module(); - delayed_delivery_module[account] = new Xep.DelayedDelivery.Module(); - stream_error_modules[account] = new StreamError.Module(); - } -} - -} \ No newline at end of file diff --git a/client/src/service/muc_manager.vala b/client/src/service/muc_manager.vala deleted file mode 100644 index be23d391..00000000 --- a/client/src/service/muc_manager.vala +++ /dev/null @@ -1,224 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { -public class MucManager : StreamInteractionModule, Object { - public const string id = "muc_manager"; - - public signal void groupchat_joined(Account account, Jid jid, string nick); - public signal void groupchat_subject_set(Account account, Jid jid, string subject); - public signal void bookmarks_updated(Account account, ArrayList conferences); - - private StreamInteractor stream_interactor; - protected HashMap conference_bookmarks = new HashMap(); - - public static void start(StreamInteractor stream_interactor) { - MucManager m = new MucManager(stream_interactor); - stream_interactor.add_module(m); - } - - private MucManager(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - stream_interactor.account_added.connect(on_account_added); - stream_interactor.stream_negotiated.connect(on_stream_negotiated); - MessageManager.get_instance(stream_interactor).pre_message_received.connect(on_pre_message_received); - } - - public void join(Account account, Jid jid, string nick, string? password = null) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Muc.Module.get_module(stream).enter(stream, jid.bare_jid.to_string(), nick, password, new MucEnterListenerImpl(this, jid, nick, account)); - } - - public void part(Account account, Jid jid) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Muc.Module.get_module(stream).exit(stream, jid.bare_jid.to_string()); - } - - public void change_subject(Account account, Jid jid, string subject) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Muc.Module.get_module(stream).change_subject(stream, jid.bare_jid.to_string(), subject); - } - - public void change_nick(Account account, Jid jid, string new_nick) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Muc.Module.get_module(stream).change_nick(stream, jid.bare_jid.to_string(), new_nick); - } - - public void kick(Account account, Jid jid, string nick) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Muc.Module.get_module(stream).kick(stream, jid.bare_jid.to_string(), nick); - } - - public ArrayList? get_occupants(Jid jid, Account account) { - return PresenceManager.get_instance(stream_interactor).get_full_jids(jid, account); - } - - public ArrayList? get_other_occupants(Jid jid, Account account) { - ArrayList? occupants = get_occupants(jid, account); - string? nick = get_nick(jid, account); - if (occupants != null && nick != null) { - occupants.remove(new Jid(@"$(jid.bare_jid)/$nick")); - } - return occupants; - } - - public bool is_groupchat(Jid jid, Account account) { - Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); - return !jid.is_full() && conversation != null && conversation.type_ == Conversation.Type.GROUPCHAT; - } - - public bool is_groupchat_occupant(Jid jid, Account account) { - return is_groupchat(jid.bare_jid, account) && jid.is_full(); - } - - public void get_bookmarks(Account account, Xep.Bookmarks.ConferencesRetrieveResponseListener listener) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Xep.Bookmarks.Module.get_module(stream).get_conferences(stream, listener); - } - } - - public void add_bookmark(Account account, Xep.Bookmarks.Conference conference) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Xep.Bookmarks.Module.get_module(stream).add_conference(stream, conference); - } - } - - public void replace_bookmark(Account account, Xep.Bookmarks.Conference was, Xep.Bookmarks.Conference replace) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Xep.Bookmarks.Module.get_module(stream).replace_conference(stream, was, replace); - } - } - - public void remove_bookmark(Account account, Xep.Bookmarks.Conference conference) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Xep.Bookmarks.Module.get_module(stream).remove_conference(stream, conference); - } - } - - public string? get_groupchat_subject(Jid jid, Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - return Xep.Muc.Flag.get_flag(stream).get_muc_subject(jid.bare_jid.to_string()); - } - return null; - } - - public Jid? get_real_jid(Jid jid, Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - string? real_jid = Xep.Muc.Flag.get_flag(stream).get_real_jid(jid.to_string()); - if (real_jid != null) { - return new Jid(real_jid); - } - } - return null; - } - - public Jid? get_message_real_jid(Entities.Message message) { - if (message.real_jid != null) { - return new Jid(message.real_jid); - } - return null; - } - - public string? get_nick(Jid jid, Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - return Xep.Muc.Flag.get_flag(stream).get_muc_nick(jid.bare_jid.to_string()); - } - return null; - } - - public static MucManager? get_instance(StreamInteractor stream_interactor) { - return (MucManager) stream_interactor.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.muc_modules[account].subject_set.connect( (stream, subject, jid) => { - on_subject_set(account, new Jid(jid), subject); - }); - stream_interactor.module_manager.bookmarks_module[account].conferences_updated.connect( (stream, conferences) => { - bookmarks_updated(account, conferences); - }); - } - - private void on_subject_set(Account account, Jid sender_jid, string subject) { - groupchat_subject_set(account, sender_jid, subject); - } - - private void on_stream_negotiated(Account account) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xep.Bookmarks.Module.get_module(stream).get_conferences(stream, new BookmarksRetrieveResponseListener(this, account)); - } - - private void on_pre_message_received(Entities.Message message, Conversation conversation) { - if (conversation.type_ != Conversation.Type.GROUPCHAT) return; - Core.XmppStream stream = stream_interactor.get_stream(conversation.account); - if (stream == null) return; - if (Xep.DelayedDelivery.MessageFlag.get_flag(message.stanza) == null) { - string? real_jid = Xep.Muc.Flag.get_flag(stream).get_real_jid(message.counterpart.to_string()); - if (real_jid != null && real_jid != message.counterpart.to_string()) { - message.real_jid = real_jid; - } - } - string muc_nick = Xep.Muc.Flag.get_flag(stream).get_muc_nick(conversation.counterpart.bare_jid.to_string()); - if (message.from.equals(new Jid(@"$(message.from.bare_jid)/$muc_nick"))) { // TODO better from own - Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); - if (messages != null) { // TODO not here - foreach (Entities.Message m in messages) { - if (m.equals(message)) { - m.marked = Entities.Message.Marked.RECEIVED; - } - } - } - } - } - - private class BookmarksRetrieveResponseListener : Xep.Bookmarks.ConferencesRetrieveResponseListener, Object { - MucManager outer = null; - Account account = null; - - public BookmarksRetrieveResponseListener(MucManager outer, Account account) { - this.outer = outer; - this.account = account; - } - - public void on_result(Core.XmppStream stream, ArrayList conferences) { - foreach (Xep.Bookmarks.Conference bookmark in conferences) { - Jid jid = new Jid(bookmark.jid); - outer.conference_bookmarks[jid] = bookmark; - if (bookmark.autojoin) { - outer.join(account, jid, bookmark.nick); - } - } - } - } - - private class MucEnterListenerImpl : Xep.Muc.MucEnterListener, Object { // TODO - private MucManager outer; - private Jid jid; - private string nick; - private Account account; - public MucEnterListenerImpl(MucManager outer, Jid jid, string nick, Account account) { - this.outer = outer; - this.jid = jid; - this.nick = nick; - this.account = account; - } - public void on_success() { - outer.groupchat_joined(account, jid, nick); - } - public void on_error(Xep.Muc.MucEnterError error) { } - } -} -} \ No newline at end of file diff --git a/client/src/service/pgp_manager.vala b/client/src/service/pgp_manager.vala deleted file mode 100644 index 6f3b63d7..00000000 --- a/client/src/service/pgp_manager.vala +++ /dev/null @@ -1,54 +0,0 @@ -using Gee; - -using Dino.Entities; - -namespace Dino { - public class PgpManager : StreamInteractionModule, Object { - public const string id = "pgp_manager"; - - public const string MESSAGE_ENCRYPTED = "pgp"; - - private StreamInteractor stream_interactor; - private Database db; - private HashMap pgp_key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); - - public static void start(StreamInteractor stream_interactor, Database db) { - PgpManager m = new PgpManager(stream_interactor, db); - stream_interactor.add_module(m); - } - - private PgpManager(StreamInteractor stream_interactor, Database db) { - this.stream_interactor = stream_interactor; - this.db = db; - - stream_interactor.account_added.connect(on_account_added); - } - - public string? get_key_id(Account account, Jid jid) { - return db.get_pgp_key(jid); - } - - public static PgpManager? get_instance(StreamInteractor stream_interactor) { - return (PgpManager) stream_interactor.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.pgp_modules[account].received_jid_key_id.connect((stream, jid, key_id) => { - on_jid_key_received(account, new Jid(jid), key_id); - }); - } - - private void on_jid_key_received(Account account, Jid jid, string key_id) { - if (!pgp_key_ids.has_key(jid) || pgp_key_ids[jid] != key_id) { - if (!MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { - db.set_pgp_key(jid.bare_jid, key_id); - } - } - pgp_key_ids[jid] = key_id; - } - } -} \ No newline at end of file diff --git a/client/src/service/presence_manager.vala b/client/src/service/presence_manager.vala deleted file mode 100644 index b89c6570..00000000 --- a/client/src/service/presence_manager.vala +++ /dev/null @@ -1,150 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { -public class PresenceManager : StreamInteractionModule, Object { - public const string id = "presence_manager"; - - public signal void show_received(Show show, Jid jid, Account account); - public signal void received_subscription_request(Jid jid, Account account); - - private StreamInteractor stream_interactor; - private HashMap>> shows = new HashMap>>(Jid.hash_bare_func, Jid.equals_bare_func); - private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); - - public static void start(StreamInteractor stream_interactor) { - PresenceManager m = new PresenceManager(stream_interactor); - stream_interactor.add_module(m); - } - - private PresenceManager(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - stream_interactor.account_added.connect(on_account_added); - } - - public Show get_last_show(Jid jid, Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Xmpp.Presence.Stanza? presence = Xmpp.Presence.Flag.get_flag(stream).get_presence(jid.to_string()); - if (presence != null) { - return new Show(jid, presence.show, new DateTime.now_local()); - } - } - return new Show(jid, Show.OFFLINE, new DateTime.now_local()); - } - - public HashMap>? get_shows(Jid jid, Account account) { - return shows[jid]; - } - - public ArrayList? get_full_jids(Jid jid, Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - Gee.List resources = Xmpp.Presence.Flag.get_flag(stream).get_resources(jid.bare_jid.to_string()); - if (resources == null) { - return null; - } - ArrayList ret = new ArrayList(Jid.equals_func); - foreach (string resource in resources) { - ret.add(new Jid(resource)); - } - return ret; - } - return null; - } - - public void request_subscription(Account account, Jid jid) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xmpp.Presence.Module.get_module(stream).request_subscription(stream, jid.bare_jid.to_string()); - } - - public void approve_subscription(Account account, Jid jid) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xmpp.Presence.Module.get_module(stream).approve_subscription(stream, jid.bare_jid.to_string()); - } - - public void deny_subscription(Account account, Jid jid) { - Core.XmppStream stream = stream_interactor.get_stream(account); - if (stream != null) Xmpp.Presence.Module.get_module(stream).deny_subscription(stream, jid.bare_jid.to_string()); - } - - public static PresenceManager? get_instance(StreamInteractor stream_interactor) { - return (PresenceManager) stream_interactor.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.presence_modules[account].received_available_show.connect((stream, jid, show) => - on_received_available_show(account, new Jid(jid), show) - ); - stream_interactor.module_manager.presence_modules[account].received_unavailable.connect((stream, jid) => - on_received_unavailable(account, new Jid(jid)) - ); - stream_interactor.module_manager.presence_modules[account].received_subscription_request.connect((stream, jid) => - received_subscription_request(new Jid(jid), account) - ); - } - - private void on_received_available_show(Account account, Jid jid, string show) { - lock (resources) { - if (!resources.has_key(jid)){ - resources[jid] = new ArrayList(Jid.equals_func); - } - if (!resources[jid].contains(jid)) { - resources[jid].add(jid); - } - } - add_show(account, jid, show); - } - - private void on_received_unavailable(Account account, Jid jid) { - lock (resources) { - if (resources.has_key(jid)) { - resources[jid].remove(jid); - if (resources[jid].size == 0 || jid.is_bare()) { - resources.unset(jid); - } - } - } - add_show(account, jid, Show.OFFLINE); - } - - private void add_show(Account account, Jid jid, string s) { - Show show = new Show(jid, s, new DateTime.now_local()); - lock (shows) { - if (!shows.has_key(jid)) { - shows[jid] = new HashMap>(); - } - if (!shows[jid].has_key(jid)) { - shows[jid][jid] = new ArrayList(); - } - shows[jid][jid].add(show); - } - show_received(show, jid, account); - } -} - -public class Show : Object { - public const string ONLINE = Xmpp.Presence.Stanza.SHOW_ONLINE; - public const string AWAY = Xmpp.Presence.Stanza.SHOW_AWAY; - public const string CHAT = Xmpp.Presence.Stanza.SHOW_CHAT; - public const string DND = Xmpp.Presence.Stanza.SHOW_DND; - public const string XA = Xmpp.Presence.Stanza.SHOW_XA; - public const string OFFLINE = "offline"; - - public Jid jid; - public string as; - public DateTime datetime; - - public Show(Jid jid, string show, DateTime datetime) { - this.jid = jid; - this.as = show; - this.datetime = datetime; - } -} -} \ No newline at end of file diff --git a/client/src/service/roster_manager.vala b/client/src/service/roster_manager.vala deleted file mode 100644 index 106405e2..00000000 --- a/client/src/service/roster_manager.vala +++ /dev/null @@ -1,82 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - public class RosterManager : StreamInteractionModule, Object { - public const string id = "roster_manager"; - - public signal void removed_roster_item(Account account, Jid jid, Roster.Item roster_item); - public signal void updated_roster_item(Account account, Jid jid, Roster.Item roster_item); - - private StreamInteractor stream_interactor; - - public static void start(StreamInteractor stream_interactor) { - RosterManager m = new RosterManager(stream_interactor); - stream_interactor.add_module(m); - } - - public RosterManager(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - stream_interactor.account_added.connect(on_account_added); - } - - public ArrayList get_roster(Account account) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - ArrayList ret = new ArrayList(); - if (stream != null) { - ret.add_all(Xmpp.Roster.Flag.get_flag(stream).get_roster()); - } - return ret; - } - - public Roster.Item? get_roster_item(Account account, Jid jid) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) { - return Xmpp.Roster.Flag.get_flag(stream).get_item(jid.bare_jid.to_string()); - } - return null; - } - - public void remove_jid(Account account, Jid jid) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) Xmpp.Roster.Module.get_module(stream).remove_jid(stream, jid.bare_jid.to_string()); - } - - public void add_jid(Account account, Jid jid, string? handle) { - Core.XmppStream? stream = stream_interactor.get_stream(account); - if (stream != null) Xmpp.Roster.Module.get_module(stream).add_jid(stream, jid.bare_jid.to_string(), handle); - } - - public static RosterManager? get_instance(StreamInteractor stream_interactor) { - return (RosterManager) stream_interactor.get_module(id); - } - - internal string get_id() { - return id; - } - - private void on_account_added(Account account) { - stream_interactor.module_manager.roster_modules[account].received_roster.connect( (stream, roster) => { - on_roster_received(account, roster); - }); - stream_interactor.module_manager.roster_modules[account].item_removed.connect( (stream, roster_item) => { - removed_roster_item(account, new Jid(roster_item.jid), roster_item); - }); - stream_interactor.module_manager.roster_modules[account].item_updated.connect( (stream, roster_item) => { - on_roster_item_updated(account, roster_item); - }); - } - - private void on_roster_received(Account account, Collection roster_items) { - foreach (Roster.Item roster_item in roster_items) { - on_roster_item_updated(account, roster_item); - } - } - - private void on_roster_item_updated(Account account, Roster.Item roster_item) { - updated_roster_item(account, new Jid(roster_item.jid), roster_item); - } - } -} \ No newline at end of file diff --git a/client/src/service/stream_interactor.vala b/client/src/service/stream_interactor.vala deleted file mode 100644 index f3859e3b..00000000 --- a/client/src/service/stream_interactor.vala +++ /dev/null @@ -1,70 +0,0 @@ -using Gee; - -using Xmpp; -using Dino.Entities; - -namespace Dino { - -public class StreamInteractor { - - public signal void account_added(Account account); - public signal void stream_negotiated(Account account); - - public ModuleManager module_manager; - public ConnectionManager connection_manager; - private ArrayList interaction_modules = new ArrayList(); - - public StreamInteractor(Database db) { - module_manager = new ModuleManager(db); - connection_manager = new ConnectionManager(module_manager); - - connection_manager.stream_opened.connect(on_stream_opened); - } - - public void connect(Account account) { - module_manager.add_account(account); - account_added(account); - connection_manager.connect(account); - } - - public void disconnect(Account account) { - connection_manager.disconnect(account); - } - - public ArrayList get_accounts() { - ArrayList ret = new ArrayList(Account.equals_func); - foreach (Account account in connection_manager.get_managed_accounts()) { - ret.add(account); - } - return ret; - } - - public Core.XmppStream? get_stream(Account account) { - return connection_manager.get_stream(account); - } - - public void add_module(StreamInteractionModule module) { - interaction_modules.add(module); - } - - public StreamInteractionModule? get_module(string id) { - foreach (StreamInteractionModule module in interaction_modules) { - if (module.get_id() == id) { - return module; - } - } - return null; - } - - private void on_stream_opened(Account account, Core.XmppStream stream) { - stream.stream_negotiated.connect( (stream) => { - stream_negotiated(account); - }); - } -} - -public interface StreamInteractionModule : Object { - internal abstract string get_id(); -} - -} \ No newline at end of file diff --git a/client/src/settings.vala b/client/src/settings.vala deleted file mode 100644 index 17177232..00000000 --- a/client/src/settings.vala +++ /dev/null @@ -1,28 +0,0 @@ -namespace Dino { - -public class Settings { - - private GLib.Settings gsettings; - - public bool send_read { - get { return gsettings.get_boolean("send-read"); } - set { gsettings.set_boolean("send-read", value); } - } - - public bool convert_utf8_smileys { - get { return gsettings.get_boolean("convert-utf8-smileys"); } - set { gsettings.set_boolean("convert-utf8-smileys", value); } - } - - public Settings(GLib.Settings gsettings) { - this.gsettings = gsettings; - } - - public static Settings instance() { - SettingsSchemaSource sss = SettingsSchemaSource.get_default(); - SettingsSchema schema = sss.lookup("org.dino-im", false); - return new Settings(new GLib.Settings.full(schema, null, null)); - } -} - -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/chat/add_contact_dialog.vala b/client/src/ui/add_conversation/chat/add_contact_dialog.vala deleted file mode 100644 index 1be0225b..00000000 --- a/client/src/ui/add_conversation/chat/add_contact_dialog.vala +++ /dev/null @@ -1,67 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Chat { - -[GtkTemplate (ui = "/org/dino-im/add_conversation/add_contact_dialog.ui")] -protected class AddContactDialog : Gtk.Dialog { - - [GtkChild] - private ComboBoxText accounts_comboboxtext; - - [GtkChild] - private Button ok_button; - - [GtkChild] - private Button cancel_button; - - [GtkChild] - private Entry jid_entry; - - [GtkChild] - private Entry alias_entry; - - [GtkChild] - private CheckButton subscribe_checkbutton; - - private StreamInteractor stream_interactor; - - public AddContactDialog(StreamInteractor stream_interactor) { - Object(use_header_bar : 1); - this.stream_interactor = stream_interactor; - - foreach (Account account in stream_interactor.get_accounts()) { - accounts_comboboxtext.append_text(account.bare_jid.to_string()); - } - accounts_comboboxtext.set_active(0); - - cancel_button.clicked.connect(() => { close(); }); - ok_button.clicked.connect(on_ok_button_clicked); - jid_entry.changed.connect(on_jid_entry_changed); - } - - private void on_ok_button_clicked() { - string? alias = alias_entry.text == "" ? null : alias_entry.text; - Account? account = null; - Jid jid = new Jid(jid_entry.text); - foreach (Account account2 in stream_interactor.get_accounts()) { - print(account2.bare_jid.to_string() + "\n"); - if (accounts_comboboxtext.get_active_text() == account2.bare_jid.to_string()) { - account = account2; - } - } - RosterManager.get_instance(stream_interactor).add_jid(account, jid, alias); - if (subscribe_checkbutton.active) { - PresenceManager.get_instance(stream_interactor).request_subscription(account, jid); - } - close(); - } - - private void on_jid_entry_changed() { - Jid parsed_jid = Jid.parse(jid_entry.text); - ok_button.set_sensitive(parsed_jid != null && parsed_jid.resourcepart == null); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/chat/dialog.vala b/client/src/ui/add_conversation/chat/dialog.vala deleted file mode 100644 index 80dac68e..00000000 --- a/client/src/ui/add_conversation/chat/dialog.vala +++ /dev/null @@ -1,82 +0,0 @@ -using Gee; -using Gdk; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Chat { - -public class Dialog : Gtk.Dialog { - - public signal void conversation_opened(Conversation conversation); - - private Button ok_button; - - private RosterList roster_list; - private SelectJidFragment select_jid_fragment; - private StreamInteractor stream_interactor; - - public Dialog(StreamInteractor stream_interactor) { - Object(use_header_bar : 1); - this.title = "Start Chat"; - this.modal = true; - this.stream_interactor = stream_interactor; - - setup_headerbar(); - setup_view(); - } - - private void setup_headerbar() { - HeaderBar header_bar = get_header_bar() as HeaderBar; - header_bar.show_close_button = false; - - Button cancel_button = new Button(); - cancel_button.set_label("Cancel"); - cancel_button.visible = true; - header_bar.pack_start(cancel_button); - - ok_button = new Button(); - ok_button.get_style_context().add_class("suggested-action"); - ok_button.label = "Start"; - ok_button.sensitive = false; - ok_button.visible = true; - header_bar.pack_end(ok_button); - - cancel_button.clicked.connect(() => { close(); }); - ok_button.clicked.connect(on_ok_button_clicked); - } - - private void setup_view() { - roster_list = new RosterList(stream_interactor); - roster_list.row_activated.connect(() => { ok_button.clicked(); }); - select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list); - select_jid_fragment.add_jid.connect((row) => { - AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); - add_contact_dialog.set_transient_for(this); - add_contact_dialog.show(); - }); - select_jid_fragment.edit_jid.connect(() => { - - }); - select_jid_fragment.remove_jid.connect((row) => { - ListRow list_row = roster_list.get_selected_row() as ListRow; - RosterManager.get_instance(stream_interactor).remove_jid(list_row.account, list_row.jid); - }); - select_jid_fragment.notify["done"].connect(() => { - ok_button.sensitive = select_jid_fragment.done; - }); - get_content_area().add(select_jid_fragment); - } - - protected void on_ok_button_clicked() { - ListRow? selected_row = roster_list.get_selected_row() as ListRow; - if (selected_row != null) { - // TODO move in list to front immediately - ConversationManager.get_instance(stream_interactor).ensure_start_conversation(selected_row.jid, selected_row.account); - Conversation conversation = ConversationManager.get_instance(stream_interactor).get_conversation(selected_row.jid, selected_row.account); - conversation_opened(conversation); - } - close(); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/chat/roster_list.vala b/client/src/ui/add_conversation/chat/roster_list.vala deleted file mode 100644 index 9e970d8c..00000000 --- a/client/src/ui/add_conversation/chat/roster_list.vala +++ /dev/null @@ -1,77 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui.AddConversation.Chat { -protected class RosterList : FilterableList { - - public signal void conversation_selected(Conversation? conversation); - private StreamInteractor stream_interactor; - - private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); - - public RosterList(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - - set_filter_func(filter); - set_header_func(header); - set_sort_func(sort); - - RosterManager.get_instance(stream_interactor).removed_roster_item.connect( (account, jid, roster_item) => { - Idle.add(() => { on_removed_roster_item(account, jid, roster_item); return false;});}); - RosterManager.get_instance(stream_interactor).updated_roster_item.connect( (account, jid, roster_item) => { - Idle.add(() => { on_updated_roster_item(account, jid, roster_item); return false;});}); - - foreach (Account account in stream_interactor.get_accounts()) { - foreach (Roster.Item roster_item in RosterManager.get_instance(stream_interactor).get_roster(account)) { - on_updated_roster_item(account, new Jid(roster_item.jid), roster_item); - } - } - } - - private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { - if (rows.has_key(jid)) { - remove(rows[jid]); - rows.unset(jid); - } - } - - private void on_updated_roster_item(Account account, Jid jid, Roster.Item roster_item) { - on_removed_roster_item(account, jid, roster_item); - ListRow row = new ListRow.from_jid(stream_interactor, new Jid(roster_item.jid), account); - rows[jid] = row; - add(row); - invalidate_sort(); - invalidate_filter(); - } - - private void header(ListBoxRow row, ListBoxRow? before_row) { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - } - - private bool filter(ListBoxRow r) { - if (r.get_type().is_a(typeof(ListRow))) { - ListRow row = r as ListRow; - if (filter_values != null) { - foreach (string filter in filter_values) { - if (!(row.name_label.label.down().contains(filter.down()) || - row.jid.to_string().down().contains(filter.down()))) { - return false; - } - } - } - } - return true; - } - - public override int sort(ListBoxRow row1, ListBoxRow row2) { - ListRow c1 = (row1 as ListRow); - ListRow c2 = (row2 as ListRow); - return c1.name_label.label.collate(c2.name_label.label); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/conference/add_groupchat_dialog.vala b/client/src/ui/add_conversation/conference/add_groupchat_dialog.vala deleted file mode 100644 index 05589fe1..00000000 --- a/client/src/ui/add_conversation/conference/add_groupchat_dialog.vala +++ /dev/null @@ -1,107 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Conference { - -[GtkTemplate (ui = "/org/dino-im/add_conversation/add_groupchat_dialog.ui")] -protected class AddGroupchatDialog : Gtk.Dialog { - - [GtkChild] - private Stack accounts_stack; - - [GtkChild] - private ComboBoxText accounts_comboboxtext; - - [GtkChild] - private Label account_label; - - [GtkChild] - private Button ok_button; - - [GtkChild] - private Button cancel_button; - - [GtkChild] - private Entry jid_entry; - - [GtkChild] - private Entry alias_entry; - - [GtkChild] - private Entry nick_entry; - - [GtkChild] - private CheckButton autojoin_checkbutton; - - private StreamInteractor stream_interactor; - private Xmpp.Xep.Bookmarks.Conference? edit_confrence = null; - private bool alias_entry_changed = false; - - public AddGroupchatDialog(StreamInteractor stream_interactor) { - Object(use_header_bar : 1); - this.stream_interactor = stream_interactor; - ok_button.label = "Add"; - ok_button.get_style_context().add_class("suggested-action"); // TODO why doesn't it work in XML - accounts_stack.set_visible_child_name("combobox"); - foreach (Account account in stream_interactor.get_accounts()) { - accounts_comboboxtext.append_text(account.bare_jid.to_string()); - } - accounts_comboboxtext.set_active(0); - - cancel_button.clicked.connect(() => { close(); }); - ok_button.clicked.connect(on_ok_button_clicked); - jid_entry.key_release_event.connect(on_jid_key_release); - nick_entry.key_release_event.connect(check_ok); - } - - public AddGroupchatDialog.for_conference(StreamInteractor stream_interactor, Account account, Xmpp.Xep.Bookmarks.Conference conference) { - this(stream_interactor); - edit_confrence = conference; - ok_button.label = "Save"; - ok_button.sensitive = true; - accounts_stack.set_visible_child_name("label"); - account_label.label = account.bare_jid.to_string(); - jid_entry.text = conference.jid; - nick_entry.text = conference.nick; - autojoin_checkbutton.active = conference.autojoin; - alias_entry.text = conference.name; - } - - private bool on_jid_key_release() { - check_ok(); - if (!alias_entry_changed) { - Jid? parsed_jid = Jid.parse(jid_entry.text); - alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text; - } - return false; - } - - private bool check_ok() { - Jid? parsed_jid = Jid.parse(jid_entry.text); - ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null && - nick_entry.text != "" && alias_entry.text != null; - return false; - } - - private void on_ok_button_clicked() { - Account? account = null; - foreach (Account account2 in stream_interactor.get_accounts()) { - if (accounts_comboboxtext.get_active_text() == account2.bare_jid.to_string()) { - account = account2; - } - } - Xmpp.Xep.Bookmarks.Conference conference = new Xmpp.Xep.Bookmarks.Conference(jid_entry.text); - conference.nick = nick_entry.text; - conference.name = alias_entry.text; - conference.autojoin = autojoin_checkbutton.active; - if (edit_confrence == null) { - MucManager.get_instance(stream_interactor).add_bookmark(account, conference); - } else { - MucManager.get_instance(stream_interactor).replace_bookmark(account, edit_confrence, conference); - } - close(); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/conference/conference_details_fragment.vala b/client/src/ui/add_conversation/conference/conference_details_fragment.vala deleted file mode 100644 index 324c133d..00000000 --- a/client/src/ui/add_conversation/conference/conference_details_fragment.vala +++ /dev/null @@ -1,175 +0,0 @@ -using Gdk; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Conference { - -[GtkTemplate (ui = "/org/dino-im/add_conversation/conference_details_fragment.ui")] -protected class ConferenceDetailsFragment : Box { - - public bool done { - get { - Jid? parsed_jid = Jid.parse(jid); - return parsed_jid != null && parsed_jid.localpart != null && - parsed_jid.resourcepart == null && nick != ""; - } - private set {} - } - - public Account account { - owned get { - foreach (Account account in stream_interactor.get_accounts()) { - if (accounts_comboboxtext.get_active_text() == account.bare_jid.to_string()) { - return account; - } - } - return null; - } - set { - accounts_label.label = value.bare_jid.to_string(); - accounts_comboboxtext.set_active_id(value.bare_jid.to_string()); - } - } - public string jid { - get { return jid_entry.text; } - set { - jid_label.label = value; - jid_entry.text = value; - } - } - public string nick { - get { return nick_entry.text; } - set { - nick_label.label = value; - nick_entry.text = value; - } - } - public string password { - get { return password_entry.text == "" ? null : password_entry.text; } - set { - password_label.label = value; - password_entry.text = value; - } - } - - [GtkChild] - private Stack accounts_stack; - - [GtkChild] - private Stack jid_stack; - - [GtkChild] - private Stack nick_stack; - - [GtkChild] - private Stack password_stack; - - [GtkChild] - private Button accounts_button; - - [GtkChild] - private Button jid_button; - - [GtkChild] - private Button nick_button; - - [GtkChild] - private Button password_button; - - [GtkChild] - private Label accounts_label; - - [GtkChild] - private Label jid_label; - - [GtkChild] - private Label nick_label; - - [GtkChild] - private Label password_label; - - [GtkChild] - private ComboBoxText accounts_comboboxtext; - - [GtkChild] - private Entry jid_entry; - - [GtkChild] - private Entry nick_entry; - - [GtkChild] - private Entry password_entry; - - private StreamInteractor stream_interactor; - - public ConferenceDetailsFragment(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - - accounts_stack.set_visible_child_name("label"); - jid_stack.set_visible_child_name("label"); - nick_stack.set_visible_child_name("label"); - password_stack.set_visible_child_name("label"); - - accounts_button.clicked.connect(() => { set_active_stack(accounts_stack); }); - jid_button.clicked.connect(() => { set_active_stack(jid_stack); }); - nick_button.clicked.connect(() => { set_active_stack(nick_stack); }); - password_button.clicked.connect(() => { set_active_stack(password_stack); }); - - accounts_comboboxtext.changed.connect(() => {accounts_label.label = accounts_comboboxtext.get_active_text(); }); - jid_entry.key_release_event.connect(on_jid_key_release_event); - nick_entry.key_release_event.connect(on_nick_key_release_event); - password_entry.key_release_event.connect(on_password_key_release_event); - - jid_entry.key_release_event.connect(() => { done = true; return false; }); // just for notifying - nick_entry.key_release_event.connect(() => { done = true; return false; }); - - foreach (Account account in stream_interactor.get_accounts()) { - accounts_comboboxtext.append_text(account.bare_jid.to_string()); - } - accounts_comboboxtext.set_active(0); - } - - public void set_editable() { - accounts_stack.set_visible_child_name("entry"); - nick_stack.set_visible_child_name("entry"); - password_stack.set_visible_child_name("entry"); - } - - public void clear() { - jid = ""; - nick = ""; - password = ""; - } - - private bool on_jid_key_release_event(EventKey event) { - jid_label.label = jid_entry.text; - if (event.keyval == Key.Return) jid_stack.set_visible_child_name("label"); - return false; - } - - private bool on_nick_key_release_event(EventKey event) { - nick_label.label = nick_entry.text; - if (event.keyval == Key.Return) nick_stack.set_visible_child_name("label"); - return false; - } - - private bool on_password_key_release_event(EventKey event) { - string filler = ""; - for (int i = 0; i < password_entry.text.length; i++) filler += password_entry.get_invisible_char().to_string(); - password_label.label = filler; - if (event.keyval == Key.Return) password_stack.set_visible_child_name("label"); - return false; - } - - private void set_active_stack(Stack stack) { - stack.set_visible_child_name("entry"); - if (stack != accounts_stack) accounts_stack.set_visible_child_name("label"); - if (stack != jid_stack) jid_stack.set_visible_child_name("label"); - if (stack != nick_stack) nick_stack.set_visible_child_name("label"); - if (stack != password_stack) password_stack.set_visible_child_name("label"); - } - -} - -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/conference/conference_list.vala b/client/src/ui/add_conversation/conference/conference_list.vala deleted file mode 100644 index 2e461472..00000000 --- a/client/src/ui/add_conversation/conference/conference_list.vala +++ /dev/null @@ -1,105 +0,0 @@ -using Gee; -using Gtk; - -using Xmpp; -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Conference { -protected class ConferenceList : FilterableList { - - public signal void conversation_selected(Conversation? conversation); - - private StreamInteractor stream_interactor; - private HashMap> lists = new HashMap>(); - - public ConferenceList(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - - set_filter_func(filter); - set_header_func(header); - set_sort_func(sort); - - MucManager.get_instance(stream_interactor).bookmarks_updated.connect((account, conferences) => { - Idle.add(() => { - lists[account] = conferences; - refresh_conferences(); - return false; - }); - }); - - foreach (Account account in stream_interactor.get_accounts()) { - MucManager.get_instance(stream_interactor).get_bookmarks(account, new BookmarksListener(this, stream_interactor, account)); - } - } - - public void refresh_conferences() { - @foreach((widget) => { remove(widget); }); - foreach (Account account in lists.keys) { - foreach (Xep.Bookmarks.Conference conference in lists[account]) { - add(new ConferenceListRow(stream_interactor, conference, account)); - } - } - } - - private class BookmarksListener : Xep.Bookmarks.ConferencesRetrieveResponseListener, Object { - ConferenceList outer; - Account account; - public BookmarksListener(ConferenceList outer, StreamInteractor stream_interactor, Account account) { - this.outer = outer; - this.account = account; - } - - public void on_result(Core.XmppStream stream, ArrayList conferences) { - outer.lists[account] = conferences; - Idle.add(() => { outer.refresh_conferences(); return false; }); - } - } - - private void header(ListBoxRow row, ListBoxRow? before_row) { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - } - - private bool filter(ListBoxRow r) { - if (r.get_type().is_a(typeof(ListRow))) { - ListRow row = r as ListRow; - if (filter_values != null) { - foreach (string filter in filter_values) { - if (!(row.name_label.label.down().contains(filter.down()) || - row.jid.to_string().down().contains(filter.down()))) { - return false; - } - } - } - } - return true; - } - - public override int sort(ListBoxRow row1, ListBoxRow row2) { - ListRow c1 = (row1 as ListRow); - ListRow c2 = (row2 as ListRow); - return c1.name_label.label.collate(c2.name_label.label); - } -} - -internal class ConferenceListRow : ListRow { - - public Xep.Bookmarks.Conference bookmark; - - public ConferenceListRow(StreamInteractor stream_interactor, Xep.Bookmarks.Conference bookmark, Account account) { - this.jid = new Jid(bookmark.jid); - this.account = account; - this.bookmark = bookmark; - - if (bookmark.name != "" && bookmark.name != bookmark.jid) { - name_label.label = bookmark.name; - via_label.label = bookmark.jid; - } else { - name_label.label = bookmark.jid; - via_label.visible = false; - } - image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_stateless(true).draw_jid(stream_interactor, jid, account)); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/conference/dialog.vala b/client/src/ui/add_conversation/conference/dialog.vala deleted file mode 100644 index ff548699..00000000 --- a/client/src/ui/add_conversation/conference/dialog.vala +++ /dev/null @@ -1,166 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation.Conference { - -public class Dialog : Gtk.Dialog { - - public signal void conversation_opened(Conversation conversation); - - private Stack stack = new Stack(); - private Button cancel_button; - private Button ok_button; - private Label cancel_label = new Label("Cancel") {visible=true}; - private Image cancel_image = new Image.from_icon_name("go-previous-symbolic", IconSize.MENU) {visible=true}; - - private SelectJidFragment select_fragment; - private ConferenceDetailsFragment details_fragment; - private ConferenceList conference_list; - - private StreamInteractor stream_interactor; - - public Dialog(StreamInteractor stream_interactor) { - Object(use_header_bar : 1); - this.title = "Join Conference"; - this.modal = true; - this.stream_interactor = stream_interactor; - - stack.visible = true; - stack.vhomogeneous = false; - get_content_area().add(stack); - - setup_headerbar(); - setup_jid_add_view(); - setup_conference_details_view(); - show_jid_add_view(); - } - - private void show_jid_add_view() { - cancel_button.remove(cancel_image); - cancel_button.add(cancel_label); - cancel_button.clicked.disconnect(show_jid_add_view); - cancel_button.clicked.connect(close); - ok_button.label = "Next"; - ok_button.sensitive = select_fragment.done; - ok_button.clicked.disconnect(on_ok_button_clicked); - ok_button.clicked.connect(on_next_button_clicked); - details_fragment.notify["done"].disconnect(set_ok_sensitive_from_details); - select_fragment.notify["done"].connect(set_ok_sensitive_from_select); - stack.transition_type = StackTransitionType.SLIDE_RIGHT; - stack.set_visible_child_name("select"); - } - - private void show_conference_details_view() { - cancel_button.remove(cancel_label); - cancel_button.add(cancel_image); - cancel_button.clicked.disconnect(close); - cancel_button.clicked.connect(show_jid_add_view); - ok_button.label = "Join"; - ok_button.sensitive = details_fragment.done; - ok_button.clicked.disconnect(on_next_button_clicked); - ok_button.clicked.connect(on_ok_button_clicked); - select_fragment.notify["done"].disconnect(set_ok_sensitive_from_select); - details_fragment.notify["done"].connect(set_ok_sensitive_from_details); - stack.transition_type = StackTransitionType.SLIDE_LEFT; - stack.set_visible_child_name("details"); - animate_window_resize(); - } - - private void setup_headerbar() { - HeaderBar header_bar = get_header_bar() as HeaderBar; - header_bar.show_close_button = false; - - cancel_button = new Button(); - header_bar.pack_start(cancel_button); - cancel_button.visible = true; - - ok_button = new Button(); - header_bar.pack_end(ok_button); - ok_button.get_style_context().add_class("suggested-action"); - ok_button.visible = true; - ok_button.can_focus = true; - ok_button.can_default = true; - ok_button.has_default = true; - } - - private void setup_jid_add_view() { - conference_list = new ConferenceList(stream_interactor); - conference_list.row_activated.connect(() => { ok_button.clicked(); }); - select_fragment = new SelectJidFragment(stream_interactor, conference_list); - select_fragment.add_jid.connect((row) => { - AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); - dialog.set_transient_for(this); - dialog.show(); - }); - select_fragment.edit_jid.connect((row) => { - ConferenceListRow conference_row = row as ConferenceListRow; - AddGroupchatDialog dialog = new AddGroupchatDialog.for_conference(stream_interactor, conference_row.account, conference_row.bookmark); - dialog.set_transient_for(this); - dialog.show(); - }); - select_fragment.remove_jid.connect((row) => { - ConferenceListRow conference_row = row as ConferenceListRow; - MucManager.get_instance(stream_interactor).remove_bookmark(conference_row.account, conference_row.bookmark); - }); - stack.add_named(select_fragment, "select"); - } - - private void setup_conference_details_view() { - details_fragment = new ConferenceDetailsFragment(stream_interactor); - stack.add_named(details_fragment, "details"); - } - - private void set_ok_sensitive_from_select() { - ok_button.sensitive = select_fragment.done; - } - - private void set_ok_sensitive_from_details() { - ok_button.sensitive = select_fragment.done; - } - - private void on_next_button_clicked() { - details_fragment.clear(); - ListRow? row = conference_list.get_selected_row() as ListRow; - ConferenceListRow? conference_row = conference_list.get_selected_row() as ConferenceListRow; - if (conference_row != null) { - details_fragment.jid = conference_row.bookmark.jid; - details_fragment.nick = conference_row.bookmark.nick; - if (conference_row.bookmark.password != null) details_fragment.password = conference_row.bookmark.password; - ok_button.grab_focus(); - } else if (row != null) { - details_fragment.jid = row.jid.to_string(); - details_fragment.set_editable(); - } - show_conference_details_view(); - } - - private void on_ok_button_clicked() { - MucManager.get_instance(stream_interactor).join(details_fragment.account, new Jid(details_fragment.jid), details_fragment.nick, details_fragment.password); - close(); - } - - private void close() { - base.close(); - } - - private void animate_window_resize() { - int def_height, curr_width, curr_height; - get_size(out curr_width, out curr_height); - stack.get_preferred_height(null, out def_height); - int difference = def_height - curr_height; - Timer timer = new Timer(); - Timeout.add((int) (stack.transition_duration / 30), - () => { - ulong microsec; - timer.elapsed(out microsec); - ulong millisec = microsec / 1000; - double partial = double.min(1, (double) millisec / stack.transition_duration); - resize(curr_width, (int) (curr_height + difference * partial)); - return millisec < stack.transition_duration; - }); - } -} - -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/list_row.vala b/client/src/ui/add_conversation/list_row.vala deleted file mode 100644 index 5c2eff97..00000000 --- a/client/src/ui/add_conversation/list_row.vala +++ /dev/null @@ -1,43 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation { - -[GtkTemplate (ui = "/org/dino-im/add_conversation/list_row.ui")] -public class ListRow : ListBoxRow { - - [GtkChild] - public Image image; - - [GtkChild] - public Label name_label; - - [GtkChild] - public Label via_label; - - public Jid? jid; - public Account? account; - - public ListRow() {} - - public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account) { - this.jid = jid; - this.account = account; - - string display_name = Util.get_display_name(stream_interactor, jid, account); - if (stream_interactor.get_accounts().size > 1) { - via_label.label = @"via $(account.bare_jid)"; - this.has_tooltip = true; - set_tooltip_text(jid.to_string()); - } else if (display_name != jid.bare_jid.to_string()){ - via_label.label = jid.bare_jid.to_string(); - } else { - via_label.visible = false; - } - name_label.label = display_name; - image.set_from_pixbuf((new AvatarGenerator(35, 35)).draw_jid(stream_interactor, jid, account)); - } -} -} \ No newline at end of file diff --git a/client/src/ui/add_conversation/select_jid_fragment.vala b/client/src/ui/add_conversation/select_jid_fragment.vala deleted file mode 100644 index 847a9ecb..00000000 --- a/client/src/ui/add_conversation/select_jid_fragment.vala +++ /dev/null @@ -1,124 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.AddConversation { - -[GtkTemplate (ui = "/org/dino-im/add_conversation/select_jid_fragment.ui")] -public class SelectJidFragment : Gtk.Box { - - public signal void add_jid(); - public signal void edit_jid(ListRow row); - public signal void remove_jid(ListRow row); - public bool done { - get { - return filterable_list.get_selected_row() != null; - } - private set {} } - - [GtkChild] - private Entry entry; - - [GtkChild] - private Box box; - - [GtkChild] - private Button add_button; - - [GtkChild] - private Button edit_button; - - [GtkChild] - private Button remove_button; - - private FilterableList filterable_list; - private ArrayList added_rows = new ArrayList(); - private StreamInteractor stream_interactor; - - public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list) { - this.stream_interactor = stream_interactor; - this.filterable_list = filterable_list; - - filterable_list.visible = true; - filterable_list.activate_on_single_click = false; - filterable_list.vexpand = true; - box.add(filterable_list); - - filterable_list.set_sort_func(sort); - filterable_list.row_selected.connect(check_buttons_active); - filterable_list.row_selected.connect(() => { done = true; }); // just for notifying - entry.changed.connect(on_entry_changed); - add_button.clicked.connect(() => { add_jid(); }); - remove_button.clicked.connect(() => { remove_jid(filterable_list.get_selected_row() as ListRow); }); - edit_button.clicked.connect(() => { edit_jid(filterable_list.get_selected_row() as ListRow); }); - } - - private void on_entry_changed() { - foreach (AddListRow row in added_rows) { - filterable_list.remove(row); - } - added_rows.clear(); - - string[] ? values; - string str = entry.get_text(); - values = str == "" ? null : str.split(" "); - filterable_list.set_filter_values(values); - Jid? parsed_jid = Jid.parse(str); - if (parsed_jid != null && parsed_jid.localpart != null) { - foreach (Account account in stream_interactor.get_accounts()) { - AddListRow row = new AddListRow(stream_interactor, str, account); - filterable_list.add(row); - added_rows.add(row); - } - } - } - - private void check_buttons_active() { - ListBoxRow? row = filterable_list.get_selected_row(); - bool active = row != null && !row.get_type().is_a(typeof(AddListRow)); - edit_button.sensitive = active; - remove_button.sensitive = active; - } - - private int sort(ListBoxRow row1, ListBoxRow row2) { - AddListRow al1 = (row1 as AddListRow); - AddListRow al2 = (row2 as AddListRow); - if (al1 != null && al2 == null) { - return -1; - } else if (al2 != null && al1 == null) { - return 1; - } - return filterable_list.sort(row1, row2); - } - - private class AddListRow : ListRow { - - public AddListRow(StreamInteractor stream_interactor, string jid, Account account) { - this.account = account; - this.jid = new Jid(jid); - - name_label.label = jid; - if (stream_interactor.get_accounts().size > 1) { - via_label.label = account.bare_jid.to_string(); - } else { - via_label.visible = false; - } - image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_greyscale(true).draw_text("?")); - } - } -} - -public abstract class FilterableList : Gtk.ListBox { - public string[]? filter_values; - - public void set_filter_values(string[] values) { - if (filter_values == values) return; - filter_values = values; - invalidate_filter(); - } - - public abstract int sort(ListBoxRow row1, ListBoxRow row2); -} - -} \ No newline at end of file diff --git a/client/src/ui/application.vala b/client/src/ui/application.vala deleted file mode 100644 index c3f0e302..00000000 --- a/client/src/ui/application.vala +++ /dev/null @@ -1,112 +0,0 @@ -using Gtk; - -using Dino.Entities; - -public class Dino.Ui.Application : Gtk.Application { - - private Database db; - private StreamInteractor stream_interaction; - - private Notifications notifications; - private UnifiedWindow? window; - private ConversationSelector.View? filterable_conversation_list; - private ConversationSelector.List? conversation_list; - private ConversationSummary.View? conversation_frame; - private ChatInput? chat_input; - - public Application() { - this.db = new Database("store.sqlite3"); - this.stream_interaction = new StreamInteractor(db); - - AvatarManager.start(stream_interaction, db); - MessageManager.start(stream_interaction, db); - CounterpartInteractionManager.start(stream_interaction); - PresenceManager.start(stream_interaction); - MucManager.start(stream_interaction); - PgpManager.start(stream_interaction, db); - RosterManager.start(stream_interaction); - ConversationManager.start(stream_interaction, db); - ChatInteraction.start(stream_interaction); - - notifications = new Notifications(stream_interaction); - notifications.start(); - - load_css(); - } - - public override void activate() { - create_set_app_menu(); - create_window(); - window.show_all(); - restore(); - } - - private void create_window() { - window = new UnifiedWindow(this, stream_interaction); - - filterable_conversation_list = window.filterable_conversation_list; - conversation_list = window.filterable_conversation_list.conversation_list; - conversation_frame = window.conversation_frame; - chat_input = window.chat_input; - } - - private void show_accounts_window() { - ManageAccounts.Dialog dialog = new ManageAccounts.Dialog(stream_interaction, db); - dialog.set_transient_for(window); - dialog.account_enabled.connect(add_connection); - dialog.account_disabled.connect(remove_connection); - dialog.show(); - } - - private void show_settings_window() { - SettingsDialog dialog = new SettingsDialog(); - dialog.set_transient_for(window); - dialog.show(); - } - - private void create_set_app_menu() { - SimpleAction accounts_action = new SimpleAction("accounts", null); - accounts_action.activate.connect(show_accounts_window); - add_action(accounts_action); - - SimpleAction settings_action = new SimpleAction("settings", null); - settings_action.activate.connect(show_settings_window); - add_action(settings_action); - - SimpleAction quit_action = new SimpleAction("quit", null); - quit_action.activate.connect(quit); - add_action(quit_action); - add_accelerator("Q", "app.quit", null); - - Builder builder = new Builder.from_resource("/org/dino-im/menu_app.ui"); - MenuModel menu = builder.get_object("menu_app") as MenuModel; - - set_app_menu(menu); - } - - private void restore() { - foreach (Account account in db.get_accounts()) { - if (account.enabled) add_connection(account); - } - } - - private void add_connection(Account account) { - stream_interaction.connect(account); - } - - private void remove_connection(Account account) { - stream_interaction.disconnect(account); - } - - private void load_css() { - var css_provider = new Gtk.CssProvider (); - try { - var file = File.new_for_uri("resource:///org/dino-im/style.css"); - css_provider.load_from_file (file); - } catch (GLib.Error e) { - warning ("loading css: %s", e.message); - } - Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - } -} - diff --git a/client/src/ui/avatar_generator.vala b/client/src/ui/avatar_generator.vala deleted file mode 100644 index e168c4a4..00000000 --- a/client/src/ui/avatar_generator.vala +++ /dev/null @@ -1,233 +0,0 @@ -using Cairo; -using Gee; -using Gdk; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui { -public class AvatarGenerator { - - private const string COLOR_GREY = "E0E0E0"; - private const string GROUPCHAT_ICON = "system-users-symbolic"; - - StreamInteractor? stream_interactor; - bool greyscale = false; - bool stateless = false; - int width; - int height; - int scale_factor; - - public AvatarGenerator(int width, int height, int scale_factor = 1) { - this.width = width; - this.height = height; - this.scale_factor = scale_factor; - } - - public Pixbuf draw_jid(StreamInteractor stream_interactor, Jid jid, Account account) { - this.stream_interactor = stream_interactor; - return crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor)); - } - - public Pixbuf draw_message(StreamInteractor stream_interactor, Message message) { - Jid? real_jid = MucManager.get_instance(stream_interactor).get_message_real_jid(message); - return draw_jid(stream_interactor, real_jid != null ? real_jid : message.from, message.account); - } - - public Pixbuf draw_conversation(StreamInteractor stream_interactor, Conversation conversation) { - return draw_jid(stream_interactor, conversation.counterpart, conversation.account); - } - - public Pixbuf draw_account(StreamInteractor stream_interactor, Account account) { - return draw_jid(stream_interactor, account.bare_jid, account); - } - - public Pixbuf draw_text(string text) { - string color = greyscale ? COLOR_GREY : Util.get_avatar_hex_color(text); - Pixbuf pixbuf = draw_colored_rectangle_text(color, text, width, height); - return crop_corners(pixbuf); - } - - public AvatarGenerator set_greyscale(bool greyscale) { - this.greyscale = greyscale; - return this; - } - - public AvatarGenerator set_stateless(bool stateless) { - this.stateless = stateless; - return this; - } - - private int get_left_border() { - return (int)Math.floor(scale_factor/2.0); - } - - private int get_right_border() { - return (int)Math.ceil(scale_factor/2.0); - } - - private void add_tile_to_pixbuf(Pixbuf pixbuf, Jid jid, Account account, int width, int height, int x, int y) { - Pixbuf tile = draw_chat_tile(jid, account, width, height); - tile.copy_area(0, 0, width, height, pixbuf, x, y); - } - - private Pixbuf draw_tile(Jid jid, Account account, int width, int height) { - if (MucManager.get_instance(stream_interactor).is_groupchat(jid, account)) { - return draw_groupchat_tile(jid, account, width, height); - } else { - return draw_chat_tile(jid, account, width, height); - } - } - - private Pixbuf draw_chat_tile(Jid jid, Account account, int width, int height) { - if (MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { - Jid? real_jid = MucManager.get_instance(stream_interactor).get_real_jid(jid, account); - if (real_jid != null) { - return draw_tile(real_jid, account, width, height); - } - } - Pixbuf? avatar = AvatarManager.get_instance(stream_interactor).get_avatar(account, jid); - if (avatar != null) { - double desired_ratio = (double) width / height; - double avatar_ratio = (double) avatar.width / avatar.height; - if (avatar_ratio > desired_ratio) { - int comp_width = width * avatar.height / height; - avatar = new Pixbuf.subpixbuf(avatar, avatar.width / 2 - comp_width / 2, 0, comp_width, avatar.height); - } else if (avatar_ratio < desired_ratio) { - int comp_height = height * avatar.width / width; - avatar = new Pixbuf.subpixbuf(avatar, 0, avatar.height / 2 - comp_height / 2, avatar.width, comp_height); - } - avatar = avatar.scale_simple(width, height, InterpType.BILINEAR); - if (greyscale) avatar = convert_to_greyscale(avatar); - return avatar; - } else { - string display_name = Util.get_display_name(stream_interactor, jid, account); - string color = greyscale ? COLOR_GREY : Util.get_avatar_hex_color(display_name); - return draw_colored_rectangle_text(color, display_name.get_char(0).toupper().to_string(), width, height); - } - } - - private Pixbuf draw_groupchat_tile(Jid jid, Account account, int width, int height) { - ArrayList? occupants = MucManager.get_instance(stream_interactor).get_other_occupants(jid, account); - if (stateless || occupants == null || occupants.size == 0) { - return draw_chat_tile(jid, account, width, height); - } - Pixbuf pixbuf = initialize_pixbuf(width, height); - if (occupants.size == 1 || occupants.size == 2 || occupants.size == 3) { - add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height, 0, 0); - if (occupants.size == 1) { - add_tile_to_pixbuf(pixbuf, account.bare_jid, account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); - } else if (occupants.size == 2) { - add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); - } else if (occupants.size == 3) { - add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); - add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); - } - } else if (occupants.size >= 4) { - add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height / 2 - get_right_border(), 0, 0); - add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); - add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_right_border(), height / 2 - get_left_border(), 0, height / 2 + get_left_border()); - if (occupants.size == 4) { - add_tile_to_pixbuf(pixbuf, occupants[3], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); - } else if (occupants.size > 4) { - Pixbuf plus_pixbuf = draw_colored_rectangle_text("555753", "+", width / 2 - get_left_border(), height / 2 - get_left_border()); - if (greyscale) plus_pixbuf = convert_to_greyscale(plus_pixbuf); - plus_pixbuf.copy_area(0, 0, width / 2 - get_left_border(), height / 2 - get_left_border(), pixbuf, width / 2 + get_left_border(), height / 2 + get_left_border()); - } - } - return pixbuf; - } - - public Pixbuf draw_colored_icon(string hex_color, string icon, int width, int height) { - int ICON_SIZE = width > 20 * scale_factor ? 17 * scale_factor : 14 * scale_factor; - - Context rectancle_context = new Context(new ImageSurface(Format.ARGB32, width, height)); - draw_colored_rectangle(rectancle_context, hex_color, width, height); - - Pixbuf icon_pixbuf = IconTheme.get_default().load_icon(icon, ICON_SIZE, IconLookupFlags.FORCE_SIZE); - Surface icon_surface = cairo_surface_create_from_pixbuf(icon_pixbuf, 1, null); - Context context = new Context(icon_surface); - context.set_operator(Operator.IN); - context.set_source_rgba(1, 1, 1, 1); - context.rectangle(0, 0, width, height); - context.fill(); - - rectancle_context.set_source_surface(icon_surface, width / 2 - ICON_SIZE / 2, height / 2 - ICON_SIZE / 2); - rectancle_context.paint(); - - return pixbuf_get_from_surface(rectancle_context.get_target(), 0, 0, width, height); - } - - public Pixbuf draw_colored_rectangle_text(string hex_color, string text, int width, int height) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); - draw_colored_rectangle(ctx, hex_color, width, height); - draw_center_text(ctx, text, width < 40 * scale_factor ? 17 * scale_factor : 25 * scale_factor, width, height); - return pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); - } - - private static void draw_center_text(Context ctx, string text, int fontsize, int width, int height) { - ctx.select_font_face("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); - ctx.set_font_size(fontsize); - Cairo.TextExtents extents; - ctx.text_extents(text, out extents); - double x_pos = width/2 - (extents.width/2 + extents.x_bearing); - double y_pos = height/2 - (extents.height/2 + extents.y_bearing); - ctx.move_to(x_pos, y_pos); - ctx.set_source_rgba(1, 1, 1, 1); - ctx.show_text(text); - } - - private static void draw_colored_rectangle(Context ctx, string hex_color, int width, int height) { - set_source_hex_color(ctx, hex_color); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - } - - private static Pixbuf convert_to_greyscale(Pixbuf pixbuf) { - Surface surface = cairo_surface_create_from_pixbuf(pixbuf, 1, null); - Context context = new Context(surface); - // convert to greyscale - context.set_operator(Operator.HSL_COLOR); - context.set_source_rgb(1, 1, 1); - context.rectangle(0, 0, pixbuf.width, pixbuf.height); - context.fill(); - // make the visible part more light - context.set_operator(Operator.ATOP); - context.set_source_rgba(1, 1, 1, 0.7); - context.rectangle(0, 0, pixbuf.width, pixbuf.height); - context.fill(); - return pixbuf_get_from_surface(context.get_target(), 0, 0, pixbuf.width, pixbuf.height); - } - - private Pixbuf crop_corners(Pixbuf pixbuf, double radius = 3) { - radius *= scale_factor; - Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); - cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); - double degrees = Math.PI / 180.0; - ctx.new_sub_path(); - ctx.arc(pixbuf.width - radius, radius, radius, -90 * degrees, 0 * degrees); - ctx.arc(pixbuf.width - radius, pixbuf.height - radius, radius, 0 * degrees, 90 * degrees); - ctx.arc(radius, pixbuf.height - radius, radius, 90 * degrees, 180 * degrees); - ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); - ctx.close_path(); - ctx.clip(); - ctx.paint(); - return pixbuf_get_from_surface(ctx.get_target(), 0, 0, pixbuf.width, pixbuf.height); - } - - private static Pixbuf initialize_pixbuf(int width, int height) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); - ctx.set_source_rgba(1, 1, 1, 0); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - return pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); - } - - private static void set_source_hex_color(Context ctx, string hex_color) { - ctx.set_source_rgba((double) hex_color.substring(0, 2).to_long(null, 16) / 255, - (double) hex_color.substring(2, 2).to_long(null, 16) / 255, - (double) hex_color.substring(4, 2).to_long(null, 16) / 255, - hex_color.length > 6 ? (double) hex_color.substring(6, 2).to_long(null, 16) / 255 : 1); - } -} -} diff --git a/client/src/ui/chat_input.vala b/client/src/ui/chat_input.vala deleted file mode 100644 index d2f9c562..00000000 --- a/client/src/ui/chat_input.vala +++ /dev/null @@ -1,123 +0,0 @@ -using Gdk; -using Gee; -using Gtk; - -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui { -[GtkTemplate (ui = "/org/dino-im/chat_input.ui")] -public class ChatInput : Grid { - - [GtkChild] - private TextView text_input; - - private Conversation? conversation; - private StreamInteractor stream_interactor; - private HashMap entry_cache = new HashMap(Conversation.hash_func, Conversation.equals_func); - private static HashMap smiley_translations = new HashMap(); - - static construct { - smiley_translations[":)"] = "🙂"; - smiley_translations[":D"] = "😀"; - smiley_translations[";)"] = "😉"; - smiley_translations["O:)"] = "😇"; - smiley_translations["]:>"] = "😈"; - smiley_translations[":o"] = "😮"; - smiley_translations[":P"] = "😛"; - smiley_translations[";P"] = "😜"; - smiley_translations[":("] = "🙁"; - smiley_translations[":'("] = "😢"; - smiley_translations[":/"] = "😕"; - smiley_translations["-.-"] = "😑"; - } - - public ChatInput(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public void initialize_for_conversation(Conversation conversation) { - if (this.conversation != null) { - if (text_input.buffer.text != "") { - entry_cache[this.conversation] = text_input.buffer.text; - } else { - entry_cache.unset(this.conversation); - } - } - this.conversation = conversation; - text_input.buffer.text = ""; - if (entry_cache.has_key(conversation)) { - text_input.buffer.text = entry_cache[conversation]; - } - text_input.key_press_event.connect(on_text_input_key_press); - text_input.key_release_event.connect(on_text_input_key_release); - text_input.grab_focus(); - } - - private void send_text() { - string text = text_input.buffer.text; - if (text.has_prefix("/")) { - string[] token = text.split(" ", 2); - switch(token[0]) { - case "/kick": - MucManager.get_instance(stream_interactor).kick(conversation.account, conversation.counterpart, token[1]); - break; - case "/me": - MessageManager.get_instance(stream_interactor).send_message(text, conversation); - break; - case "/nick": - MucManager.get_instance(stream_interactor).change_nick(conversation.account, conversation.counterpart, token[1]); - break; - case "/ping": // TODO remove this - Xep.Ping.Module.get_module(stream_interactor.get_stream(conversation.account)) - .send_ping(stream_interactor.get_stream(conversation.account), @"$(conversation.counterpart.bare_jid)/$(token[1])"); - Xep.Ping.Module.get_module(stream_interactor.get_stream(conversation.account)).get_id(); - break; - case "/topic": - MucManager.get_instance(stream_interactor).change_subject(conversation.account, conversation.counterpart, token[1]); - break; - } - } else { - MessageManager.get_instance(stream_interactor).send_message(text, conversation); - } - text_input.buffer.text = ""; - } - - private bool on_text_input_key_press(EventKey event) { - if (event.keyval == Key.space || event.keyval == Key.Return) { - check_convert_smiley(); - } - if (event.keyval == Key.Return) { - if (event.state == ModifierType.SHIFT_MASK) { - text_input.buffer.insert_at_cursor("\n", 1); - } else if (text_input.buffer.text != ""){ - send_text(); - } - return true; - } - return false; - } - - private void check_convert_smiley() { - if (Dino.Settings.instance().convert_utf8_smileys) { - foreach (string smiley in smiley_translations.keys) { - if (text_input.buffer.text.has_suffix(smiley)) { - if (text_input.buffer.text.length == smiley.length || - text_input.buffer.text[text_input.buffer.text.length - smiley.length - 1] == ' ') { - text_input.buffer.text = text_input.buffer.text.substring(0, text_input.buffer.text.length - smiley.length) + smiley_translations[smiley]; - } - } - } - } - } - - private bool on_text_input_key_release(EventKey event) { - if (text_input.buffer.text != "") { - ChatInteraction.get_instance(stream_interactor).on_message_entered(conversation); - } else { - ChatInteraction.get_instance(stream_interactor).on_message_cleared(conversation); - } - return false; - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_list_titlebar.vala b/client/src/ui/conversation_list_titlebar.vala deleted file mode 100644 index 4bcc9c71..00000000 --- a/client/src/ui/conversation_list_titlebar.vala +++ /dev/null @@ -1,47 +0,0 @@ -using Gtk; - -using Dino.Entities; - -[GtkTemplate (ui = "/org/dino-im/conversation_list_titlebar.ui")] -public class Dino.Ui.ConversationListTitlebar : Gtk.HeaderBar { - - public signal void conversation_opened(Conversation conversation); - - [GtkChild] - private MenuButton add_button; - - [GtkChild] - public ToggleButton search_button; - - private StreamInteractor stream_interactor; - - public ConversationListTitlebar(Window application, StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - create_add_menu(application); - } - - private void create_add_menu(Window window) { - SimpleAction contacts_action = new SimpleAction("add_chat", null); - contacts_action.activate.connect(() => { - AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor); - add_chat_dialog.set_transient_for((Window) get_toplevel()); - add_chat_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); - add_chat_dialog.show(); - }); - window.get_application().add_action(contacts_action); - - SimpleAction conference_action = new SimpleAction("add_conference", null); - conference_action.activate.connect(() => { - AddConversation.Conference.Dialog add_conference_dialog = new AddConversation.Conference.Dialog(stream_interactor); - add_conference_dialog.set_transient_for((Window) get_toplevel()); - add_conference_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); - add_conference_dialog.show(); - }); - window.get_application().add_action(conference_action); - - Builder builder = new Builder.from_resource("/org/dino-im/menu_add.ui"); - MenuModel menu = builder.get_object("menu_add") as MenuModel; - add_button.set_menu_model(menu); - } -} - diff --git a/client/src/ui/conversation_selector/chat_row.vala b/client/src/ui/conversation_selector/chat_row.vala deleted file mode 100644 index 1613b404..00000000 --- a/client/src/ui/conversation_selector/chat_row.vala +++ /dev/null @@ -1,88 +0,0 @@ -using Gdk; -using Gee; -using Gtk; - -using Xmpp; -using Dino.Entities; - -namespace Dino.Ui.ConversationSelector { -public class ChatRow : ConversationRow { - - public ChatRow(StreamInteractor stream_interactor, Conversation conversation) { - base(stream_interactor, conversation); - has_tooltip = true; - query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => { - tooltip.set_custom(generate_tooltip()); - return true; - }); - update_avatar(); - } - - public override void on_show_received(Show show) { - update_avatar(); - } - - public override void network_connection(bool connected) { - if (!connected) { - set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)).set_greyscale(true).draw_conversation(stream_interactor, conversation), image.scale_factor); - } else { - update_avatar(); - } - } - - public void on_updated_roster_item(Roster.Item roster_item) { - if (roster_item.name != null) { - display_name = roster_item.name; - update_name(); - } - update_avatar(); - } - - public void update_avatar() { - ArrayList full_jids = PresenceManager.get_instance(stream_interactor).get_full_jids(conversation.counterpart, conversation.account); - set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) - .set_greyscale(full_jids == null) - .draw_conversation(stream_interactor, conversation), image.scale_factor); - } - - private Widget generate_tooltip() { - Builder builder = new Builder.from_resource("/org/dino-im/conversation_selector/chat_row_tooltip.ui"); - Box main_box = builder.get_object("main_box") as Box; - Box inner_box = builder.get_object("inner_box") as Box; - Label jid_label = builder.get_object("jid_label") as Label; - - jid_label.label = conversation.counterpart.to_string(); - - ArrayList? full_jids = PresenceManager.get_instance(stream_interactor).get_full_jids(conversation.counterpart, conversation.account); - if (full_jids != null) { - for (int i = 0; i < full_jids.size; i++) { - Box box = new Box(Orientation.HORIZONTAL, 5); - - Show show = PresenceManager.get_instance(stream_interactor).get_last_show(full_jids[i], conversation.account); - Image image = new Image(); - Pixbuf pixbuf; - int icon_size = 13 * image.scale_factor; - if (show.as == Show.AWAY) { - pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_away.svg", icon_size, icon_size, true); - } else if (show.as == Show.XA || show.as == Show.DND) { - pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_dnd.svg", icon_size, icon_size, true); - } else if (show.as == Show.CHAT) { - pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_chat.svg", icon_size, icon_size, true); - } else { - pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_online.svg", icon_size, icon_size, true); - } - Util.image_set_from_scaled_pixbuf(image, pixbuf); - box.add(image); - - Label resource = new Label(full_jids[i].resourcepart); - resource.xalign = 0; - box.add(resource); - box.show_all(); - - inner_box.add(box); - } - } - return main_box; - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_selector/conversation_row.vala b/client/src/ui/conversation_selector/conversation_row.vala deleted file mode 100644 index e641cab2..00000000 --- a/client/src/ui/conversation_selector/conversation_row.vala +++ /dev/null @@ -1,175 +0,0 @@ -using Gee; -using Gdk; -using Gtk; -using Pango; - -using Xmpp; -using Dino.Entities; - -namespace Dino.Ui.ConversationSelector { - -[GtkTemplate (ui = "/org/dino-im/conversation_selector/conversation_row.ui")] -public abstract class ConversationRow : ListBoxRow { - - [GtkChild] - protected Image image; - - [GtkChild] - private Label name_label; - - [GtkChild] - private Label time_label; - - [GtkChild] - private Label message_label; - - [GtkChild] - protected Button x_button; - - [GtkChild] - private Revealer time_revealer; - - [GtkChild] - private Revealer xbutton_revealer; - - [GtkChild] - public Revealer main_revealer; - - public Conversation conversation { get; private set; } - - protected const int AVATAR_SIZE = 40; - - protected string display_name; - protected string message; - protected DateTime time; - protected bool read = true; - - - protected StreamInteractor stream_interactor; - - construct { - name_label.attributes = new AttrList(); - } - - public ConversationRow(StreamInteractor stream_interactor, Conversation conversation) { - this.conversation = conversation; - this.stream_interactor = stream_interactor; - - x_button.clicked.connect(on_x_button_clicked); - - update_name(Util.get_conversation_display_name(stream_interactor, conversation)); - Entities.Message message = MessageManager.get_instance(stream_interactor).get_last_message(conversation); - if (message != null) { - message_received(message); - } - } - - public void update() { - update_time(); - } - - public void message_received(Entities.Message message) { - update_message(message.body.replace("\n", " ")); - update_time(message.time.to_local()); - } - - public void set_avatar(Pixbuf pixbuf, int scale_factor = 1) { - Util.image_set_from_scaled_pixbuf(image, pixbuf, scale_factor); - image.queue_draw(); - } - - public void mark_read() { - update_read(true); - } - - public void mark_unread() { - update_read(false); - } - - public abstract void on_show_received(Show presence); - public abstract void network_connection(bool connected); - - protected void update_name(string? new_name = null) { - if (new_name != null) { - display_name = new_name; - } - name_label.label = display_name; - } - - protected void update_time(DateTime? new_time = null) { - time_label.visible = true; - if (new_time != null) { - time = new_time; - } - if (time != null) { - time_label.label = get_relative_time(time); - } - } - - protected void update_message(string? new_message = null) { - if (new_message != null) { - message = new_message; - } - if (message != null) { - message_label.visible = true; - message_label.label = message; - } - } - - protected void update_read(bool read) { - this.read = read; - if (read) { - name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); - time_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); - message_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); - } else { - name_label.attributes.insert(attr_weight_new(Weight.BOLD)); - time_label.attributes.insert(attr_weight_new(Weight.BOLD)); - message_label.attributes.insert(attr_weight_new(Weight.BOLD)); - } - name_label.label = name_label.label; // TODO initializes redrawing, which would otherwise not happen. nicer? - time_label.label = time_label.label; - message_label.label = message_label.label; - } - - private void on_x_button_clicked() { - main_revealer.set_transition_type(RevealerTransitionType.SLIDE_UP); - main_revealer.set_reveal_child(false); - main_revealer.notify["child-revealed"].connect(() => { - conversation.active = false; - }); - } - - public override void state_flags_changed(StateFlags flags) { - StateFlags curr_flags = get_state_flags(); - if ((curr_flags & StateFlags.PRELIGHT) != 0) { - time_revealer.set_reveal_child(false); - xbutton_revealer.set_reveal_child(true); - } else { - time_revealer.set_reveal_child(true); - xbutton_revealer.set_reveal_child(false); - } - } - - private static string get_relative_time(DateTime datetime) { - DateTime now = new DateTime.now_local(); - TimeSpan timespan = now.difference(datetime); - if (timespan > 365 * TimeSpan.DAY) { - return datetime.get_year().to_string(); - } else if (timespan > 7 * TimeSpan.DAY) { - return datetime.format("%d.%m"); - } else if (timespan > 2 * TimeSpan.DAY) { - return datetime.format("%a"); - } else if (timespan > 1 * TimeSpan.DAY) { - return "Yesterday"; - } else if (timespan > 9 * TimeSpan.MINUTE) { - return datetime.format("%H:%M"); - } else if (timespan > 1 * TimeSpan.MINUTE) { - return (timespan / TimeSpan.MINUTE).to_string() + " min ago"; - } else { - return "Just now"; - } - } - -} -} diff --git a/client/src/ui/conversation_selector/groupchat_row.vala b/client/src/ui/conversation_selector/groupchat_row.vala deleted file mode 100644 index bec2181e..00000000 --- a/client/src/ui/conversation_selector/groupchat_row.vala +++ /dev/null @@ -1,33 +0,0 @@ -using Dino.Entities; - -namespace Dino.Ui.ConversationSelector { -public class GroupchatRow : ConversationRow { - - public GroupchatRow(StreamInteractor stream_interactor, Conversation conversation) { - base(stream_interactor, conversation); - has_tooltip = true; - set_tooltip_text(conversation.counterpart.bare_jid.to_string()); - set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) - .set_greyscale(true) - .draw_conversation(stream_interactor, conversation), image.scale_factor); - x_button.clicked.connect(on_x_button_clicked); - } - - - public override void on_show_received(Show show) { - set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) - .draw_conversation(stream_interactor, conversation), image.scale_factor); - } - - public override void network_connection(bool connected) { - set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) - .set_greyscale(!connected || - MucManager.get_instance(stream_interactor).get_nick(conversation.counterpart, conversation.account) == null) // TODO better currently joined - .draw_conversation(stream_interactor, conversation), image.scale_factor); - } - - private void on_x_button_clicked() { - MucManager.get_instance(stream_interactor).part(conversation.account, conversation.counterpart); - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_selector/list.vala b/client/src/ui/conversation_selector/list.vala deleted file mode 100644 index e6a5231c..00000000 --- a/client/src/ui/conversation_selector/list.vala +++ /dev/null @@ -1,173 +0,0 @@ -using Gee; -using Gtk; - -using Xmpp; -using Dino.Entities; - -namespace Dino.Ui.ConversationSelector { -public class List : ListBox { - - public signal void conversation_selected(Conversation conversation); - - private StreamInteractor stream_interactor; - private string[]? filter_values; - private HashMap rows = new HashMap(Conversation.hash_func, Conversation.equals_func); - - public List(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - - get_style_context().add_class("sidebar"); - set_filter_func(filter); - set_header_func(header); - set_sort_func(sort); - - ChatInteraction.get_instance(stream_interactor).conversation_read.connect((conversation) => { - Idle.add(() => {rows[conversation].mark_read(); return false;}); - }); - ChatInteraction.get_instance(stream_interactor).conversation_unread.connect((conversation) => { - Idle.add(() => {rows[conversation].mark_unread(); return false;}); - }); - ConversationManager.get_instance(stream_interactor).conversation_activated.connect((conversation) => { - Idle.add(() => {add_conversation(conversation); return false;}); - }); - MessageManager.get_instance(stream_interactor).message_received.connect((message, conversation) => { - Idle.add(() => {message_received(message, conversation); return false;}); - }); - MessageManager.get_instance(stream_interactor).message_sent.connect((message, conversation) => { - Idle.add(() => {message_received(message, conversation); return false;}); - }); - PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { - Idle.add(() => { - Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); - if (conversation != null && rows.has_key(conversation)) rows[conversation].on_show_received(show); - return false; - }); - }); - RosterManager.get_instance(stream_interactor).updated_roster_item.connect((account, jid, roster_item) => { - Idle.add(() => { - Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); - if (conversation != null && rows.has_key(conversation)) { - ChatRow row = rows[conversation] as ChatRow; - if (row != null) row.on_updated_roster_item(roster_item); - } - return false; - }); - }); - AvatarManager.get_instance(stream_interactor).received_avatar.connect((avatar, jid, account) => { - Idle.add(() => { - Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); - if (conversation != null && rows.has_key(conversation)) { - ChatRow row = rows[conversation] as ChatRow; - if (row != null) row.update_avatar(); - } - return false; - }); - }); - stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { - Idle.add(() => { - foreach (ConversationRow row in rows.values) { - if (row.conversation.account.equals(account)) row.network_connection(state == ConnectionManager.ConnectionState.CONNECTED); - } - return false; - }); - }); - Timeout.add_seconds(60, () => { - foreach (ConversationRow row in rows.values) row.update(); - return true; - }); - } - - public override void row_activated(ListBoxRow r) { - if (r.get_type().is_a(typeof(ConversationRow))) { - ConversationRow row = r as ConversationRow; - conversation_selected(row.conversation); - } - } - - public void set_filter_values(string[]? values) { - if (filter_values == values) { - return; - } - filter_values = values; - invalidate_filter(); - } - - public void add_conversation(Conversation conversation) { - ConversationRow row; - if (!rows.has_key(conversation)) { - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - row = new GroupchatRow(stream_interactor, conversation); - } else { - row = new ChatRow(stream_interactor, conversation); - } - rows[conversation] = row; - add(row); - row.main_revealer.set_reveal_child(true); - conversation.notify["active"].connect((s, p) => { - if (rows.has_key(conversation) && !conversation.active) { - remove_conversation(conversation); - } - }); - } - invalidate_sort(); - queue_draw(); - } - - public void remove_conversation(Conversation conversation) { - remove(rows[conversation]); - rows.unset(conversation); - } - - public void on_conversation_selected(Conversation conversation) { - if (!rows.has_key(conversation)) { - add_conversation(conversation); - } - this.select_row(rows[conversation]); - } - - private void message_received(Entities.Message message, Conversation conversation) { - if (rows.has_key(conversation)) { - rows[conversation].message_received(message); - invalidate_sort(); - } - } - - private void header(ListBoxRow row, ListBoxRow? before_row) { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - } - - private bool filter(ListBoxRow r) { - if (r.get_type().is_a(typeof(ConversationRow))) { - ConversationRow row = r as ConversationRow; - if (filter_values != null && filter_values.length != 0) { - foreach (string filter in filter_values) { - if (!(Util.get_conversation_display_name(stream_interactor, row.conversation).down().contains(filter.down()) || - row.conversation.counterpart.to_string().down().contains(filter.down()))) { - return false; - } - } - } - } - return true; - } - - private int sort(ListBoxRow row1, ListBoxRow row2) { - ConversationRow cr1 = row1 as ConversationRow; - ConversationRow cr2 = row2 as ConversationRow; - if (cr1 != null && cr2 != null) { - Conversation c1 = cr1.conversation; - Conversation c2 = cr2.conversation; - int comp = c2.last_active.compare(c1.last_active); - if (comp == 0) { - return Util.get_conversation_display_name(stream_interactor, c1) - .collate(Util.get_conversation_display_name(stream_interactor, c2)); - } else { - return comp; - } - } - return 0; - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_selector/view.vala b/client/src/ui/conversation_selector/view.vala deleted file mode 100644 index c551d258..00000000 --- a/client/src/ui/conversation_selector/view.vala +++ /dev/null @@ -1,56 +0,0 @@ -using Gee; -using Gtk; -using Gdk; - -using Dino.Entities; - -namespace Dino.Ui.ConversationSelector { - -[GtkTemplate (ui = "/org/dino-im/conversation_selector/view.ui")] -public class View : Grid { - public List conversation_list; - - [GtkChild] - public SearchEntry search_entry; - - [GtkChild] - public SearchBar search_bar; - - [GtkChild] - private ScrolledWindow scrolled; - - public View(StreamInteractor stream_interactor) { - conversation_list = new List(stream_interactor); - scrolled.add(conversation_list); - search_entry.key_release_event.connect(search_key_release_event); - search_entry.search_changed.connect(search_changed); - } - - public void conversation_selected(Conversation? conversation) { - search_entry.set_text(""); - } - - private void refilter() { - string[]? values = null; - string str = search_entry.get_text (); - if (str != "") values = str.split(" "); - conversation_list.set_filter_values(values); - } - - private void search_changed(Editable editable) { - refilter(); - } - - private bool search_key_release_event(EventKey event) { - conversation_list.select_row(conversation_list.get_row_at_y(0)); - if (event.keyval == Key.Down) { - ConversationRow? row = (ConversationRow) conversation_list.get_row_at_index(0); - if (row != null) { - conversation_list.select_row(row); - row.grab_focus(); - } - } - return false; - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_summary/merged_message_item.vala b/client/src/ui/conversation_summary/merged_message_item.vala deleted file mode 100644 index 3647d082..00000000 --- a/client/src/ui/conversation_summary/merged_message_item.vala +++ /dev/null @@ -1,170 +0,0 @@ -using Gee; -using Gdk; -using Gtk; -using Markup; - -using Dino.Entities; - -namespace Dino.Ui.ConversationSummary { - -[GtkTemplate (ui = "/org/dino-im/conversation_summary/message_item.ui")] -public class MergedMessageItem : Grid { - - public Conversation conversation { get; set; } - public Jid from { get; private set; } - public DateTime initial_time { get; private set; } - public ArrayList messages = new ArrayList(Message.equals_func); - - [GtkChild] - private Image image; - - [GtkChild] - private Label time_label; - - [GtkChild] - private Label name_label; - - [GtkChild] - private Image encryption_image; - - [GtkChild] - private Image received_image; - - [GtkChild] - private TextView message_text_view; - - public MergedMessageItem(StreamInteractor stream_interactor, Conversation conversation, Message message) { - this.conversation = conversation; - this.from = message.from; - this.initial_time = message.time; - setup_tags(); - add_message(message); - - time_label.label = get_relative_time(initial_time.to_local()); - string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account); - name_label.set_markup(@"$display_name"); - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_message(stream_interactor, message)); - if (message.encryption == Entities.Message.Encryption.PGP) { - encryption_image.visible = true; - encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.SMALL_TOOLBAR); - } - } - - public void update() { - time_label.label = get_relative_time(initial_time.to_local()); - } - - public void add_message(Message message) { - TextIter end; - message_text_view.buffer.get_end_iter(out end); - if (messages.size > 0) { - message_text_view.buffer.insert(ref end, "\n", -1); - } - message_text_view.buffer.insert(ref end, message.body, -1); - format_suffix_urls(message.body); - messages.add(message); - message.notify["marked"].connect_after(update_received); // TODO other thread? not main? css error? gtk main? - update_received(); - } - - private void update_received() { - bool all_received = true; - bool all_read = true; - foreach (Message message in messages) { - if (message.marked == Message.Marked.WONTSEND) { - received_image.visible = true; - Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default(); - Gtk.IconInfo? icon_info = icon_theme.lookup_icon("dialog-warning-symbolic", IconSize.SMALL_TOOLBAR, 0); - received_image.set_from_pixbuf(icon_info.load_symbolic({1,0,0,1})); - return; - } else if (message.marked != Message.Marked.READ) { - all_read = false; - if (message.marked != Message.Marked.RECEIVED) { - all_received = false; - } - } - } - if (all_read) { - received_image.visible = true; - received_image.set_from_resource("/org/dino-im/img/double_tick.svg"); - } else if (all_received) { - received_image.visible = true; - received_image.set_from_resource("/org/dino-im/img/tick.svg"); - } else if (received_image.visible) { - received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR); - } - } - - private void format_suffix_urls(string text) { - int absolute_start = message_text_view.buffer.text.length - text.length; - - Regex url_regex = new Regex("""(?i)\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))"""); - MatchInfo match_info; - url_regex.match(text, 0, out match_info); - for (; match_info.matches(); match_info.next()) { - string? url = match_info.fetch(0); - int start; - int end; - match_info.fetch_pos(0, out start, out end); - TextIter start_iter; - TextIter end_iter; - message_text_view.buffer.get_iter_at_offset(out start_iter, absolute_start + start); - message_text_view.buffer.get_iter_at_offset(out end_iter, absolute_start + end); - message_text_view.buffer.apply_tag_by_name("url", start_iter, end_iter); - } - } - - private void setup_tags() { - message_text_view.buffer.create_tag("url", underline: Pango.Underline.SINGLE, foreground: "blue"); - message_text_view.button_release_event.connect(open_url); - message_text_view.motion_notify_event.connect(change_cursor_over_url); - } - - private bool open_url(EventButton event_button) { - int buffer_x, buffer_y; - message_text_view.window_to_buffer_coords(TextWindowType.TEXT, (int) event_button.x, (int) event_button.y, out buffer_x, out buffer_y); - TextIter iter; - message_text_view.get_iter_at_location(out iter, buffer_x, buffer_y); - TextIter start_iter = iter, end_iter = iter; - if (start_iter.backward_to_tag_toggle(null) && end_iter.forward_to_tag_toggle(null)) { - string url = start_iter.get_text(end_iter); - try{ - AppInfo.launch_default_for_uri(url, null); - } catch (Error err) { - print("Tryed to open " + url); - } - } - return false; - } - - private bool change_cursor_over_url(EventMotion event_motion) { - TextIter iter; - message_text_view.get_iter_at_location(out iter, (int) event_motion.x, (int) event_motion.y); - if (iter.has_tag(message_text_view.buffer.tag_table.lookup("url"))) { - event_motion.window.set_cursor(new Cursor.for_display(get_display(), CursorType.HAND2)); - } else { - event_motion.window.set_cursor(new Cursor.for_display(get_display(), CursorType.XTERM)); - } - return false; - } - - private static string get_relative_time(DateTime datetime) { - DateTime now = new DateTime.now_local(); - TimeSpan timespan = now.difference(datetime); - if (timespan > 365 * TimeSpan.DAY) { - return datetime.format("%d.%m.%Y %H:%M"); - } else if (timespan > 7 * TimeSpan.DAY) { - return datetime.format("%d.%m %H:%M"); - } else if (timespan > 1 * TimeSpan.DAY) { - return datetime.format("%a, %H:%M"); - } else if (timespan > 9 * TimeSpan.MINUTE) { - return datetime.format("%H:%M"); - } else if (timespan > TimeSpan.MINUTE) { - return (timespan / TimeSpan.MINUTE).to_string() + " min ago"; - } else { - return "Just now"; - } - } -} - -} diff --git a/client/src/ui/conversation_summary/merged_status_item.vala b/client/src/ui/conversation_summary/merged_status_item.vala deleted file mode 100644 index 78b156e9..00000000 --- a/client/src/ui/conversation_summary/merged_status_item.vala +++ /dev/null @@ -1,30 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.ConversationSummary { - -private class MergedStatusItem : Expander { - - private StreamInteractor stream_interactor; - private Conversation conversation; - private ArrayList statuses = new ArrayList(); - - public MergedStatusItem(StreamInteractor stream_interactor, Conversation conversation, Show show) { - set_hexpand(true); - add_status(show); - } - - public void add_status(Show show) { - statuses.add(show); - StatusItem status_item = new StatusItem(stream_interactor, conversation, @"is $(show.as)"); - if (statuses.size == 1) { - label = show.as; - } else { - label = @"changed their status $(statuses.size) times"; - add(new Label(show.as)); - } - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_summary/status_item.vala b/client/src/ui/conversation_summary/status_item.vala deleted file mode 100644 index 5918d008..00000000 --- a/client/src/ui/conversation_summary/status_item.vala +++ /dev/null @@ -1,29 +0,0 @@ -using Gtk; -using Markup; - -using Dino.Entities; - -namespace Dino.Ui.ConversationSummary { - -private class StatusItem : Grid { - - private Image image = new Image(); - private Label label = new Label(""); - - private StreamInteractor stream_interactor; - private Conversation conversation; - - public StatusItem(StreamInteractor stream_interactor, Conversation conversation, string? text) { - Object(column_spacing : 7); - set_hexpand(true); - this.stream_interactor = stream_interactor; - this.conversation = conversation; - image.set_from_pixbuf((new AvatarGenerator(30, 30)).set_greyscale(true).draw_conversation(stream_interactor, conversation)); - attach(image, 0, 0, 1, 1); - attach(label, 1, 0, 1, 1); - string display_name = Util.get_display_name(stream_interactor, conversation.counterpart, conversation.account); - label.set_markup(@" $(escape_text(display_name)) $text "); - show_all(); - } -} -} \ No newline at end of file diff --git a/client/src/ui/conversation_summary/view.vala b/client/src/ui/conversation_summary/view.vala deleted file mode 100644 index 59cf88aa..00000000 --- a/client/src/ui/conversation_summary/view.vala +++ /dev/null @@ -1,222 +0,0 @@ -using Gee; -using Gtk; -using Pango; - -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui.ConversationSummary { - -[GtkTemplate (ui = "/org/dino-im/conversation_summary/view.ui")] -public class View : Box { - - public Conversation? conversation { get; private set; } - public HashMap message_items = new HashMap(Entities.Message.hash_func, Entities.Message.equals_func); - - [GtkChild] - private ScrolledWindow scrolled; - - [GtkChild] - private Box main; - - private StreamInteractor stream_interactor; - private MergedMessageItem? last_message_item; - private StatusItem typing_status; - private Entities.Message? earliest_message; - double? was_value; - double? was_upper; - double? was_page_size; - Object reloading_lock = new Object(); - bool reloading = false; - - public View(StreamInteractor stream_interactor) { - Object(homogeneous : false, spacing : 0); - this.stream_interactor = stream_interactor; - scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); - scrolled.vadjustment.notify["value"].connect(on_value_notify); - - CounterpartInteractionManager.get_instance(stream_interactor).received_state.connect((account, jid, state) => { - Idle.add(() => { on_received_state(account, jid, state); return false; }); - }); - MessageManager.get_instance(stream_interactor).message_received.connect((message, conversation) => { - Idle.add(() => { show_message(message, conversation, true); return false; }); - }); - MessageManager.get_instance(stream_interactor).message_sent.connect((message, conversation) => { - Idle.add(() => { show_message(message, conversation, true); return false; }); - }); - PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { - Idle.add(() => { on_show_received(show, jid, account); return false; }); - }); - Timeout.add_seconds(60, () => { - foreach (MergedMessageItem message_item in message_items.values) { - message_item.update(); - } - return true; - }); - } - - public void initialize_for_conversation(Conversation? conversation) { - this.conversation = conversation; - clear(); - message_items.clear(); - was_upper = null; - was_page_size = null; - last_message_item = null; - - ArrayList objects = new ArrayList(); - Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); - if (messages != null && messages.size > 0) { - earliest_message = messages[0]; - objects.add_all(messages); - } - HashMap>? shows = PresenceManager.get_instance(stream_interactor).get_shows(conversation.counterpart, conversation.account); - if (shows != null) { - foreach (Jid jid in shows.keys) objects.add_all(shows[jid]); - } - objects.sort((a, b) => { - DateTime? dt1 = null; - DateTime? dt2 = null; - Entities.Message m1 = a as Entities.Message; - if (m1 != null) dt1 = m1.time; - Show s1 = a as Show; - if (s1 != null) dt1 = s1.datetime; - Entities.Message m2 = b as Entities.Message; - if (m2 != null) dt2 = m2.time; - Show s2 = b as Show; - if (s2 != null) dt2 = s2.datetime; - return dt1.compare(dt2); - }); - foreach (Object o in objects) { - Entities.Message message = o as Entities.Message; - Show show = o as Show; - if (message != null) { - show_message(message, conversation); - } else if (show != null) { - on_show_received(show, conversation.counterpart, conversation.account); - } - } - update_chat_state(); - } - - private void on_received_state(Account account, Jid jid, string state) { - if (conversation != null && conversation.account.equals(account) && conversation.counterpart.equals_bare(jid)) { - update_chat_state(state); - } - } - - private void update_chat_state(string? state = null) { - string? state_ = state; - if (state_ == null) { - state_ = CounterpartInteractionManager.get_instance(stream_interactor).get_chat_state(conversation.account, conversation.counterpart); - } - if (typing_status != null) { - main.remove(typing_status); - } - if (state_ != null) { - if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING || state_ == Xep.ChatStateNotifications.STATE_PAUSED) { - if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING) { - typing_status = new StatusItem(stream_interactor, conversation, "is typing..."); - } else if (state_ == Xep.ChatStateNotifications.STATE_PAUSED) { - typing_status = new StatusItem(stream_interactor, conversation, "has stoped typing"); - } - main.add(typing_status); - } - } - } - - private void on_show_received(Show show, Jid jid, Account account) { - - } - - private void on_upper_notify() { - if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1 || - scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size - scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down - } else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1){ - scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content - } - was_upper = scrolled.vadjustment.upper; - was_page_size = scrolled.vadjustment.page_size; - lock(reloading_lock) { - reloading = false; - } - } - - private void on_value_notify() { - if (scrolled.vadjustment.value < 200) { - load_earlier_messages(); - } - } - - private void load_earlier_messages() { - was_value = scrolled.vadjustment.value; - lock(reloading_lock) { - if(reloading) return; - reloading = true; - } - Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages_before(conversation, earliest_message); - if (messages != null && messages.size > 0) { - earliest_message = messages[0]; - MergedMessageItem? current_item = null; - int items_added = 0; - for (int i = 0; i < messages.size; i++) { - if (current_item != null && should_merge_message(current_item, messages[i])) { - current_item.add_message(messages[i]); - } else { - current_item = new MergedMessageItem(stream_interactor, conversation, messages[i]); - force_alloc_width(current_item, main.get_allocated_width()); - main.add(current_item); - message_items[messages[i]] = current_item; - main.reorder_child(current_item, items_added); - items_added++; - } - } - return; - } - reloading = false; - } - - private void show_message(Entities.Message message, Conversation conversation, bool animate = false) { - if (this.conversation != null && this.conversation.equals(conversation)) { - if (should_merge_message(last_message_item, message)) { - last_message_item.add_message(message); - } else { - MergedMessageItem message_item = new MergedMessageItem(stream_interactor, conversation, message); - if (animate) { - Revealer revealer = new Revealer() {transition_duration = 200, transition_type = RevealerTransitionType.SLIDE_UP, visible = true}; - revealer.add(message_item); - force_alloc_width(revealer, main.get_allocated_width()); - main.add(revealer); - revealer.set_reveal_child(true); - } else { - force_alloc_width(message_item, main.get_allocated_width()); - main.add(message_item); - } - last_message_item = message_item; - } - message_items[message] = last_message_item; - update_chat_state(); - } - } - - private bool should_merge_message(MergedMessageItem? message_item, Entities.Message message) { - return message_item != null && - message_item.from.equals(message.from) && - message_item.messages.get(0).encryption == message.encryption && - message.time.difference(message_item.initial_time) < TimeSpan.MINUTE && - (message_item.messages.get(0).marked == Entities.Message.Marked.WONTSEND) == (message.marked == Entities.Message.Marked.WONTSEND); - } - - private void force_alloc_width(Widget widget, int width) { - Allocation alloc = Allocation(); - widget.get_preferred_width(out alloc.width, null); - widget.get_preferred_height(out alloc.height, null); - alloc.width = width; - widget.size_allocate(alloc); - } - - private void clear() { - main.@foreach((widget) => { main.remove(widget); }); - } -} -} diff --git a/client/src/ui/conversation_titlebar.vala b/client/src/ui/conversation_titlebar.vala deleted file mode 100644 index 25304e1a..00000000 --- a/client/src/ui/conversation_titlebar.vala +++ /dev/null @@ -1,124 +0,0 @@ -using Gtk; - -using Dino.Entities; - -[GtkTemplate (ui = "/org/dino-im/conversation_titlebar.ui")] -public class Dino.Ui.ConversationTitlebar : Gtk.HeaderBar { - - [GtkChild] - private MenuButton menu_button; - - [GtkChild] - private MenuButton encryption_button; - private RadioButton? button_unencrypted; - private RadioButton? button_pgp; - - [GtkChild] - private MenuButton groupchat_button; - - private StreamInteractor stream_interactor; - private Conversation? conversation; - - public ConversationTitlebar(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - MucManager.get_instance(stream_interactor).groupchat_subject_set.connect((account, jid, subject) => { - Idle.add(() => { on_groupchat_subject_set(account, jid, subject); return false; }); - }); - create_conversation_menu(); - create_encryption_menu(); - } - - public void initialize_for_conversation(Conversation conversation) { - this.conversation = conversation; - update_encryption_menu_state(); - update_encryption_menu_icon(); - update_groupchat_menu(); - update_title(); - update_subtitle(); - } - - private void update_encryption_menu_state() { - string? pgp_id = PgpManager.get_instance(stream_interactor).get_key_id(conversation.account, conversation.counterpart); - button_pgp.set_sensitive(pgp_id != null); - switch (conversation.encryption) { - case Conversation.Encryption.UNENCRYPTED: - button_unencrypted.set_active(true); - break; - case Conversation.Encryption.PGP: - button_pgp.set_active(true); - break; - } - } - - private void update_encryption_menu_icon() { - encryption_button.visible = (conversation.type_ == Conversation.Type.CHAT); - if (conversation.type_ == Conversation.Type.CHAT) { - if (conversation.encryption == Conversation.Encryption.UNENCRYPTED) { - encryption_button.set_image(new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON)); - } else { - encryption_button.set_image(new Image.from_icon_name("changes-prevent-symbolic", IconSize.BUTTON)); - } - } - } - - private void update_groupchat_menu() { - groupchat_button.visible = conversation.type_ == Conversation.Type.GROUPCHAT; - if (conversation.type_ == Conversation.Type.GROUPCHAT) { - groupchat_button.set_use_popover(true); - Popover popover = new Popover(null); - OccupantList occupant_list = new OccupantList(stream_interactor, conversation); - popover.add(occupant_list); - occupant_list.show_all(); - groupchat_button.set_popover(popover); - } - } - - private void update_title() { - set_title(Util.get_conversation_display_name(stream_interactor, conversation)); - } - - private void update_subtitle(string? subtitle = null) { - if (subtitle != null) { - set_subtitle(subtitle); - } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { - string subject = MucManager.get_instance(stream_interactor).get_groupchat_subject(conversation.counterpart, conversation.account); - set_subtitle(subject != "" ? subject : null); - } else { - set_subtitle(null); - } - } - - private void create_conversation_menu() { - Builder builder = new Builder.from_resource("/org/dino-im/menu_conversation.ui"); - MenuModel menu = builder.get_object("menu_conversation") as MenuModel; - menu_button.set_menu_model(menu); - } - - private void create_encryption_menu() { - Builder builder = new Builder.from_resource("/org/dino-im/menu_encryption.ui"); - PopoverMenu menu = builder.get_object("menu_encryption") as PopoverMenu; - button_unencrypted = builder.get_object("button_unencrypted") as RadioButton; - button_pgp = builder.get_object("button_pgp") as RadioButton; - encryption_button.set_use_popover(true); - encryption_button.set_popover(menu); - encryption_button.set_image(new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON)); - - button_unencrypted.toggled.connect(() => { - if (conversation != null) { - if (button_unencrypted.get_active()) { - conversation.encryption = Conversation.Encryption.UNENCRYPTED; - } else if (button_pgp.get_active()) { - conversation.encryption = Conversation.Encryption.PGP; - } - update_encryption_menu_icon(); - } - }); - } - - private void on_groupchat_subject_set(Account account, Jid jid, string subject) { - if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { - update_subtitle(subject); - } - } -} - diff --git a/client/src/ui/manage_accounts/account_row.vala b/client/src/ui/manage_accounts/account_row.vala deleted file mode 100644 index 6ca4daf6..00000000 --- a/client/src/ui/manage_accounts/account_row.vala +++ /dev/null @@ -1,24 +0,0 @@ -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.ManageAccounts { - -[GtkTemplate (ui = "/org/dino-im/manage_accounts/account_row.ui")] -public class AccountRow : Gtk.ListBoxRow { - - [GtkChild] - public Image image; - - [GtkChild] - public Label jid_label; - - public Account account; - - public AccountRow(StreamInteractor stream_interactor, Account account) { - this.account = account; - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(40, 40, image.scale_factor)).draw_account(stream_interactor, account)); - jid_label.set_label(account.bare_jid.to_string()); - } -} -} \ No newline at end of file diff --git a/client/src/ui/manage_accounts/add_account_dialog.vala b/client/src/ui/manage_accounts/add_account_dialog.vala deleted file mode 100644 index b22fca3a..00000000 --- a/client/src/ui/manage_accounts/add_account_dialog.vala +++ /dev/null @@ -1,70 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.ManageAccounts { - -[GtkTemplate (ui = "/org/dino-im/manage_accounts/add_account_dialog.ui")] -public class AddAccountDialog : Gtk.Dialog { - - public signal void added(Account account); - - [GtkChild] - private Button cancel_button; - - [GtkChild] - private Button ok_button; - - [GtkChild] - private Entry alias_entry; - - [GtkChild] - private Entry jid_entry; - - [GtkChild] - private Entry password_entry; - - public AddAccountDialog(StreamInteractor stream_interactor) { - Object(use_header_bar : 1); - this.title = "Add Account"; - - cancel_button.clicked.connect(() => { close(); }); - ok_button.clicked.connect(on_ok_button_clicked); - jid_entry.changed.connect(on_jid_entry_changed); - jid_entry.focus_out_event.connect(on_jid_entry_focus_out_event); - } - - private void on_jid_entry_changed() { - Jid? jid = Jid.parse(jid_entry.text); - if (jid != null && jid.localpart != null && jid.resourcepart == null) { - ok_button.set_sensitive(true); - jid_entry.secondary_icon_name = null; - } else { - ok_button.set_sensitive(false); - } - } - - private bool on_jid_entry_focus_out_event() { - Jid? jid = Jid.parse(jid_entry.text); - if (jid == null || jid.localpart == null || jid.resourcepart != null) { - jid_entry.secondary_icon_name = "dialog-warning-symbolic"; - // TODO why doesn't the tooltip work - jid_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY, "JID should be of the form \"user@example.com\""); - } else { - jid_entry.secondary_icon_name = null; - } - return false; - } - - private void on_ok_button_clicked() { - Account account = new Account.from_bare_jid(jid_entry.get_text()); - account.resourcepart = "dino"; - account.alias = alias_entry.get_text(); - account.enabled = false; - account.password = password_entry.get_text(); - added(account); - close(); - } -} -} diff --git a/client/src/ui/manage_accounts/dialog.vala b/client/src/ui/manage_accounts/dialog.vala deleted file mode 100644 index 5d18cb30..00000000 --- a/client/src/ui/manage_accounts/dialog.vala +++ /dev/null @@ -1,193 +0,0 @@ -using Gdk; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui.ManageAccounts { - -[GtkTemplate (ui = "/org/dino-im/manage_accounts/dialog.ui")] -public class Dialog : Gtk.Window { - - public signal void account_enabled(Account account); - public signal void account_disabled(Account account); - - [GtkChild] public Stack main_stack; - [GtkChild] public ListBox account_list; - [GtkChild] public Button no_accounts_add; - [GtkChild] public ToolButton add_button; - [GtkChild] public ToolButton remove_button; - [GtkChild] public Image image; - [GtkChild] public Button image_button; - [GtkChild] public Label jid_label; - [GtkChild] public Switch active_switch; - [GtkChild] public Stack password_stack; - [GtkChild] public Label password_label; - [GtkChild] public Button password_button; - [GtkChild] public Entry password_entry; - [GtkChild] public Stack alias_stack; - [GtkChild] public Label alias_label; - [GtkChild] public Button alias_button; - [GtkChild] public Entry alias_entry; - [GtkChild] public Stack pgp_stack; - [GtkChild] public Label pgp_label; - [GtkChild] public Button pgp_button; - [GtkChild] public ComboBoxText pgp_combobox; - - - private Database db; - private StreamInteractor stream_interactor; - - construct { - account_list.row_selected.connect(account_list_row_selected); - add_button.clicked.connect(add_button_clicked); - no_accounts_add.clicked.connect(add_button_clicked); - remove_button.clicked.connect(remove_button_clicked); - password_entry.key_release_event.connect(on_password_key_release_event); - alias_entry.key_release_event.connect(on_alias_key_release_event); - image_button.clicked.connect(on_image_button_clicked); - - main_stack.set_visible_child_name("no_accounts"); - } - - public Dialog(StreamInteractor stream_interactor, Database db) { - this.db = db; - this.stream_interactor = stream_interactor; - foreach (Account account in db.get_accounts()) { - add_account(account); - } - - AvatarManager.get_instance(stream_interactor).received_avatar.connect((pixbuf, jid, account) => { - Idle.add(() => { - on_received_avatar(pixbuf, jid, account); - return false; - });}); - - if (account_list.get_row_at_index(0) != null) account_list.select_row(account_list.get_row_at_index(0)); - } - - public AccountRow add_account(Account account) { - AccountRow account_item = new AccountRow (stream_interactor, account); - account_list.add(account_item); - main_stack.set_visible_child_name("accounts_exist"); - return account_item; - } - - private void add_button_clicked() { - AddAccountDialog add_account_dialog = new AddAccountDialog(stream_interactor); - add_account_dialog.set_transient_for(this); - add_account_dialog.added.connect((account) => { - db.add_account(account); - AccountRow account_item = add_account(account); - account_list.select_row(account_item); - account_list.queue_draw(); - }); - add_account_dialog.show(); - } - - private void remove_button_clicked() { - AccountRow account_item = account_list.get_selected_row() as AccountRow; - if (account_item != null) { - account_list.remove(account_item); - account_list.queue_draw(); - if (account_item.account.enabled) account_disabled(account_item.account); - db.remove_account(account_item.account); - if (account_list.get_row_at_index(0) != null) { - account_list.select_row(account_list.get_row_at_index(0)); - } else { - main_stack.set_visible_child_name("no_accounts"); - } - } - } - - private void account_list_row_selected(ListBoxRow? row) { - AccountRow? account_item = row as AccountRow; - if (account_item != null) populate_grid_data(account_item.account); - } - - private void populate_grid_data(Account account) { - active_switch.state_set.disconnect(on_active_switch_state_changed); - - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); - active_switch.set_active(account.enabled); - jid_label.label = account.bare_jid.to_string(); - - string filler = ""; - for (int i = 0; i < account.password.length; i++) filler += password_entry.get_invisible_char().to_string(); - password_label.label = filler; - password_stack.set_visible_child_name("label"); - password_entry.text = account.password; - - alias_stack.set_visible_child_name("label"); - alias_label.label = account.alias; - alias_entry.text = account.alias; - - password_button.clicked.connect(() => { set_active_stack(password_stack); }); - alias_button.clicked.connect(() => { set_active_stack(alias_stack); }); - active_switch.state_set.connect(on_active_switch_state_changed); - } - - private void on_image_button_clicked() { - FileChooserDialog chooser = new FileChooserDialog ( - "Select avatar", this, FileChooserAction.OPEN, - "Cancel", ResponseType.CANCEL, - "Select", ResponseType.ACCEPT); - FileFilter filter = new FileFilter(); - filter.add_mime_type("image/*"); - chooser.set_filter(filter); - if (chooser.run() == Gtk.ResponseType.ACCEPT) { - string uri = chooser.get_filename(); - Account account = (account_list.get_selected_row() as AccountRow).account; - AvatarManager.get_instance(stream_interactor).publish(account, uri); - } - chooser.close(); - } - - private bool on_active_switch_state_changed(bool state) { - Account account = (account_list.get_selected_row() as AccountRow).account; - account.enabled = state; - if (state) { - account_enabled(account); - } else { - account_disabled(account); - } - return false; - } - - private bool on_password_key_release_event(EventKey event) { - Account account = (account_list.get_selected_row() as AccountRow).account; - account.password = password_entry.text; - string filler = ""; - for (int i = 0; i < account.password.length; i++) filler += password_entry.get_invisible_char().to_string(); - password_label.label = filler; - if (event.keyval == Key.Return) { - password_stack.set_visible_child_name("label"); - } - return false; - } - - private bool on_alias_key_release_event(EventKey event) { - Account account = (account_list.get_selected_row() as AccountRow).account; - account.alias = alias_entry.text; - alias_label.label = alias_entry.text; - if (event.keyval == Key.Return) { - alias_stack.set_visible_child_name("label"); - } - return false; - } - - private void on_received_avatar(Pixbuf pixbuf, Jid jid, Account account) { - Account curr_account = (account_list.get_selected_row() as AccountRow).account; - if (curr_account.equals(account) && jid.equals(account.bare_jid)) { - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); - } - } - - private void set_active_stack(Stack stack) { - stack.set_visible_child_name("entry"); - if (stack != password_stack) password_stack.set_visible_child_name("label"); - if (stack != alias_stack) alias_stack.set_visible_child_name("label"); - if (stack != pgp_stack) pgp_stack.set_visible_child_name("label"); - } -} -} - diff --git a/client/src/ui/notifications.vala b/client/src/ui/notifications.vala deleted file mode 100644 index 46bc6bf5..00000000 --- a/client/src/ui/notifications.vala +++ /dev/null @@ -1,55 +0,0 @@ -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui { -public class Notifications : GLib.Object { - - private StreamInteractor stream_interactor; - private Notify.Notification notification = new Notify.Notification("", null, null); - - public Notifications(StreamInteractor stream_interactor) { - this.stream_interactor = stream_interactor; - } - - public void start() { - MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); - PresenceManager.get_instance(stream_interactor).received_subscription_request.connect(on_received_subscription_request); - } - - private void on_message_received(Entities.Message message, Conversation conversation) { - if (!ChatInteraction.get_instance(stream_interactor).is_active_focus()) { - string display_name = Util.get_conversation_display_name(stream_interactor, conversation); - if (MucManager.get_instance(stream_interactor).is_groupchat(conversation.counterpart, conversation.account)) { - string muc_occupant = Util.get_display_name(stream_interactor, message.from, conversation.account); - display_name = muc_occupant + " in " + display_name; - } - notification.update(display_name, message.body, null); - notification.set_image_from_pixbuf((new AvatarGenerator(40, 40)).draw_conversation(stream_interactor, conversation)); - notification.set_timeout(3); - try { - notification.show(); - } catch (Error error) { } - } - } - - private void on_received_subscription_request(Jid jid, Account account) { - Notify.Notification notification = new Notify.Notification("Subscription request", jid.bare_jid.to_string(), null); - notification.set_image_from_pixbuf((new AvatarGenerator(40, 40)).draw_jid(stream_interactor, jid, account)); - notification.add_action("accept", "Accept", () => { - PresenceManager.get_instance(stream_interactor).approve_subscription(account, jid); - try { - notification.close(); - } catch (Error error) { } - }); - notification.add_action("deny", "Deny", () => { - PresenceManager.get_instance(stream_interactor).deny_subscription(account, jid); - try { - notification.close(); - } catch (Error error) { } - }); - try { - notification.show(); - } catch (Error error) { } - } -} -} \ No newline at end of file diff --git a/client/src/ui/occupant_list.vala b/client/src/ui/occupant_list.vala deleted file mode 100644 index 921f7e70..00000000 --- a/client/src/ui/occupant_list.vala +++ /dev/null @@ -1,112 +0,0 @@ -using Gee; -using Gtk; - -using Dino.Entities; - -namespace Dino.Ui{ -[GtkTemplate (ui = "/org/dino-im/occupant_list.ui")] -public class OccupantList : Box { - - public signal void conversation_selected(Conversation? conversation); - private StreamInteractor stream_interactor; - - [GtkChild] - private ListBox list_box; - - [GtkChild] - private SearchEntry search_entry; - - private Conversation? conversation; - private string[]? filter_values; - private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); - - public OccupantList(StreamInteractor stream_interactor, Conversation conversation) { - this.stream_interactor = stream_interactor; - list_box.set_header_func(header); - list_box.set_sort_func(sort); - list_box.set_filter_func(filter); - search_entry.search_changed.connect(search_changed); - - PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { - Idle.add(() => { on_show_received(show, jid, account); return false; }); - }); - RosterManager.get_instance(stream_interactor).updated_roster_item.connect(on_updated_roster_item); - - initialize_for_conversation(conversation); - } - - public void initialize_for_conversation(Conversation conversation) { - this.conversation = conversation; - ArrayList? occupants = MucManager.get_instance(stream_interactor).get_occupants(conversation.counterpart, conversation.account); - if (occupants != null) { - foreach (Jid occupant in occupants) { - add_occupant(occupant); - } - } - } - - private void refilter() { - string[]? values = null; - string str = search_entry.get_text (); - if (str != "") values = str.split(" "); - if (filter_values == values) return; - filter_values = values; - list_box.invalidate_filter(); - } - - private void search_changed(Editable editable) { - refilter(); - } - - public void add_occupant(Jid jid) { - rows[jid] = new OccupantListRow(stream_interactor, conversation.account, jid); - list_box.add(rows[jid]); - list_box.invalidate_filter(); - list_box.invalidate_sort(); - } - - public void remove_occupant(Jid jid) { - list_box.remove(rows[jid]); - rows.unset(jid); - } - - private void on_updated_roster_item(Account account, Jid jid, Xmpp.Roster.Item roster_item) { - - } - - private void on_show_received(Show show, Jid jid, Account account) { - if (conversation != null && conversation.counterpart.equals_bare(jid)) { - if (show.as == Show.OFFLINE && rows.has_key(jid)) { - remove_occupant(jid); - } else if (show.as != Show.OFFLINE && !rows.has_key(jid)) { - add_occupant(jid); - } - } - } - - private void header(ListBoxRow row, ListBoxRow? before_row) { - if (row.get_header() == null && before_row != null) { - row.set_header(new Separator(Orientation.HORIZONTAL)); - } - } - - private bool filter(ListBoxRow r) { - if (r.get_type().is_a(typeof(OccupantListRow))) { - OccupantListRow row = r as OccupantListRow; - foreach (string filter in filter_values) { - return row.name_label.label.down().contains(filter.down()); - } - } - return true; - } - - private int sort(ListBoxRow row1, ListBoxRow row2) { - if (row1.get_type().is_a(typeof(OccupantListRow)) && row2.get_type().is_a(typeof(OccupantListRow))) { - OccupantListRow c1 = row1 as OccupantListRow; - OccupantListRow c2 = row2 as OccupantListRow; - return c1.name_label.label.collate(c2.name_label.label); - } - return 0; - } -} -} \ No newline at end of file diff --git a/client/src/ui/occupant_list_row.vala b/client/src/ui/occupant_list_row.vala deleted file mode 100644 index 067455b5..00000000 --- a/client/src/ui/occupant_list_row.vala +++ /dev/null @@ -1,27 +0,0 @@ -using Gtk; - -using Dino.Entities; -using Xmpp; - -namespace Dino.Ui { - -[GtkTemplate (ui = "/org/dino-im/occupant_list_item.ui")] -public class OccupantListRow : ListBoxRow { - - [GtkChild] - private Image image; - - [GtkChild] - public Label name_label; - - public OccupantListRow(StreamInteractor stream_interactor, Account account, Jid jid) { - name_label.label = Util.get_display_name(stream_interactor, jid, account); - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_jid(stream_interactor, jid, account)); - //has_tooltip = true; - } - - public void on_presence_received(Presence.Stanza presence) { - - } -} -} \ No newline at end of file diff --git a/client/src/ui/settings_dialog.vala b/client/src/ui/settings_dialog.vala deleted file mode 100644 index 600ec873..00000000 --- a/client/src/ui/settings_dialog.vala +++ /dev/null @@ -1,27 +0,0 @@ -using Gtk; - -namespace Dino.Ui { - -[GtkTemplate (ui = "/org/dino-im/settings_dialog.ui")] -class SettingsDialog : Dialog { - - [GtkChild] - private CheckButton marker_checkbutton; - - [GtkChild] - private CheckButton emoji_checkbutton; - - Dino.Settings settings = Dino.Settings.instance(); - - public SettingsDialog() { - Object(use_header_bar : 1); - - marker_checkbutton.active = settings.send_read; - emoji_checkbutton.active = settings.convert_utf8_smileys; - - marker_checkbutton.toggled.connect(() => { settings.send_read = marker_checkbutton.active; }); - emoji_checkbutton.toggled.connect(() => { settings.convert_utf8_smileys = emoji_checkbutton.active; }); - } -} - -} \ No newline at end of file diff --git a/client/src/ui/unified_window.vala b/client/src/ui/unified_window.vala deleted file mode 100644 index 8a9500e9..00000000 --- a/client/src/ui/unified_window.vala +++ /dev/null @@ -1,78 +0,0 @@ -using Gtk; - -using Dino.Entities; - -public class Dino.Ui.UnifiedWindow : Window { - public ChatInput chat_input; - public ConversationListTitlebar conversation_list_titlebar; - public ConversationSelector.View filterable_conversation_list; - public ConversationSummary.View conversation_frame; - public ConversationTitlebar conversation_titlebar; - public Paned paned; - - private StreamInteractor stream_interactor; - private Conversation? conversation; - - public UnifiedWindow(Application application, StreamInteractor stream_interactor) { - Object(application : application); - this.stream_interactor = stream_interactor; - focus_in_event.connect(on_focus_in_event); - focus_out_event.connect(on_focus_out_event); - - default_width = 1200; - default_height = 700; - - chat_input = new ChatInput(stream_interactor); - conversation_frame = new ConversationSummary.View(stream_interactor); - conversation_titlebar = new ConversationTitlebar(stream_interactor); - paned = new Paned(Orientation.HORIZONTAL); - paned.set_position(300); - filterable_conversation_list = new ConversationSelector.View(stream_interactor); - conversation_list_titlebar = new ConversationListTitlebar(this, stream_interactor); - conversation_list_titlebar.search_button.bind_property("active", filterable_conversation_list.search_bar, "search-mode-enabled", - BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - Grid grid = new Grid(); - grid.orientation = Orientation.VERTICAL; - Paned toolbar_paned = new Paned(Orientation.HORIZONTAL); - - add(paned); - paned.add1(filterable_conversation_list); - paned.add2(grid); - - grid.add(conversation_frame); - grid.add(new Separator(Orientation.HORIZONTAL)); - grid.add(chat_input); - - conversation_frame.show_all(); - - toolbar_paned.add1(conversation_list_titlebar); - toolbar_paned.add2(conversation_titlebar); - paned.bind_property("position", toolbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - set_titlebar(toolbar_paned); - - filterable_conversation_list.conversation_list.conversation_selected.connect(on_conversation_selected); - conversation_list_titlebar.conversation_opened.connect(on_conversation_selected); - } - - private void on_conversation_selected(Conversation conversation) { - this.conversation = conversation; - ChatInteraction.get_instance(stream_interactor).on_conversation_selected(conversation); - conversation.active = true; // only for conversation_selected - filterable_conversation_list.conversation_list.on_conversation_selected(conversation); // only for conversation_opened - - chat_input.initialize_for_conversation(conversation); - conversation_frame.initialize_for_conversation(conversation); - conversation_titlebar.initialize_for_conversation(conversation); - } - - private bool on_focus_in_event() { - ChatInteraction.get_instance(stream_interactor).window_focus_in(conversation); - return false; - } - - private bool on_focus_out_event() { - ChatInteraction.get_instance(stream_interactor).window_focus_out(conversation); - return false; - } -} - diff --git a/client/src/ui/util.vala b/client/src/ui/util.vala deleted file mode 100644 index d06afe67..00000000 --- a/client/src/ui/util.vala +++ /dev/null @@ -1,71 +0,0 @@ -using Gtk; - -using Dino.Entities; -using Xmpp; - -public class Dino.Ui.Util : GLib.Object { - - private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; - private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; - private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFEB3B", "FFC107", "FF9800", "FF5722", "795548"}; - private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFF176", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; - private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFF59D", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; - - public static string get_avatar_hex_color(string name) { - return material_colors_300[name.hash() % material_colors_300.length]; -// return tango_colors_light[name.hash() % tango_colors_light.length]; - } - - public static string get_name_hex_color(string name) { - return material_colors_500[name.hash() % material_colors_500.length]; -// return tango_colors_medium[name.hash() % tango_colors_medium.length]; - } - - public static string color_for_show(string show) { - switch(show) { - case "online": return "#9CCC65"; - case "away": return "#FFCA28"; - case "chat": return "#66BB6A"; - case "xa": return "#EF5350"; - case "dnd": return "#EF5350"; - default: return "#BDBDBD"; - } - } - - public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { - return get_display_name(stream_interactor, conversation.counterpart, conversation.account); - } - - public static string get_display_name(StreamInteractor stream_interactor, Jid jid, Account account) { - if (MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { - return jid.resourcepart; - } else { - if (jid.bare_jid.equals(account.bare_jid.bare_jid)) { - if (account.alias == null || account.alias == "") { - return account.bare_jid.to_string(); - } else { - return account.alias; - } - } - Roster.Item roster_item = RosterManager.get_instance(stream_interactor).get_roster_item(account, jid); - if (roster_item != null && roster_item.name != null) { - return roster_item.name; - } - return jid.bare_jid.to_string(); - } - } - - public static string get_message_display_name(StreamInteractor stream_interactor, Entities.Message message, Account account) { - Jid? real_jid = MucManager.get_instance(stream_interactor).get_message_real_jid(message); - if (real_jid != null) { - return get_display_name(stream_interactor, real_jid, account); - } else { - return get_display_name(stream_interactor, message.from, account); - } - } - - public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0) { - if (scale == 0) scale = image.get_scale_factor(); - image.set_from_surface(Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window())); - } -} diff --git a/cmake/UseVala.cmake b/cmake/UseVala.cmake index ae6a5db1..ae090bb1 100644 --- a/cmake/UseVala.cmake +++ b/cmake/UseVala.cmake @@ -115,9 +115,11 @@ function(_vala_mkdir_for_file file) endfunction() function(vala_precompile output) - cmake_parse_arguments(ARGS "" "DIRECTORY;GENERATE_HEADER;GENERATE_VAPI" + cmake_parse_arguments(ARGS "FAST_VAPI" "DIRECTORY;GENERATE_HEADER;GENERATE_VAPI" "SOURCES;PACKAGES;OPTIONS;DEFINITIONS;CUSTOM_VAPIS;GRESOURCES" ${ARGN}) + set(ARGS_FAST_VAPI true) + if(ARGS_DIRECTORY) get_filename_component(DIRECTORY ${ARGS_DIRECTORY} ABSOLUTE) else(ARGS_DIRECTORY) @@ -159,7 +161,8 @@ function(vala_precompile output) set(vapi_arguments "") if(ARGS_GENERATE_VAPI) list(APPEND out_extra_files "${DIRECTORY}/${ARGS_GENERATE_VAPI}.vapi") - set(vapi_arguments "--vapi=${ARGS_GENERATE_VAPI}.vapi") + list(APPEND out_extra_files "${DIRECTORY}/${ARGS_GENERATE_VAPI}-internal.vapi") + set(vapi_arguments "--vapi=${ARGS_GENERATE_VAPI}.vapi" "--internal-vapi=${ARGS_GENERATE_VAPI}-internal.vapi") # Header and internal header is needed to generate internal vapi if (NOT ARGS_GENERATE_HEADER) @@ -175,80 +178,115 @@ function(vala_precompile output) list(APPEND header_arguments "--internal-header=${DIRECTORY}/${ARGS_GENERATE_HEADER}_internal.h") endif(ARGS_GENERATE_HEADER) - foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) - set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") - list(APPEND in_files "${in_file}") - string(REPLACE ".vala" ".c" src ${src}) - string(REPLACE ".gs" ".c" src ${src}) - string(REPLACE ".c" ".vapi" fast_vapi ${src}) - set(fast_vapi_file "${DIRECTORY}/${fast_vapi}") - list(APPEND fast_vapi_files "${fast_vapi_file}") - list(APPEND out_files "${DIRECTORY}/${src}") + if(ARGS_FAST_VAPI) + foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) + set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") + list(APPEND in_files "${in_file}") + string(REPLACE ".vala" ".c" src ${src}) + string(REPLACE ".gs" ".c" src ${src}) + string(REPLACE ".c" ".vapi" fast_vapi ${src}) + set(fast_vapi_file "${DIRECTORY}/${fast_vapi}") + list(APPEND fast_vapi_files "${fast_vapi_file}") + list(APPEND out_files "${DIRECTORY}/${src}") - _vala_mkdir_for_file("${fast_vapi_file}") + _vala_mkdir_for_file("${fast_vapi_file}") - add_custom_command(OUTPUT ${fast_vapi_file} - COMMAND - ${VALA_EXECUTABLE} - ARGS - --fast-vapi ${fast_vapi_file} - ${ARGS_OPTIONS} - ${in_file} - DEPENDS - ${in_file} - COMMENT - "Generating fast VAPI ${fast_vapi}" - ) - endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) + add_custom_command(OUTPUT ${fast_vapi_file} + COMMAND + ${VALA_EXECUTABLE} + ARGS + --fast-vapi ${fast_vapi_file} + ${ARGS_OPTIONS} + ${in_file} + DEPENDS + ${in_file} + COMMENT + "Generating fast VAPI ${fast_vapi}" + ) + endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) - foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) - set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") - string(REPLACE ".vala" ".c" c_code ${src}) - string(REPLACE ".gs" ".c" c_code ${c_code}) - string(REPLACE ".c" ".vapi" fast_vapi ${c_code}) - set(my_fast_vapi_file "${DIRECTORY}/${fast_vapi}") - set(c_code_file "${DIRECTORY}/${c_code}") - set(fast_vapi_flags "") - set(fast_vapi_stamp "") - foreach(fast_vapi_file ${fast_vapi_files}) - if(NOT "${fast_vapi_file}" STREQUAL "${my_fast_vapi_file}") - list(APPEND fast_vapi_flags --use-fast-vapi "${fast_vapi_file}") - list(APPEND fast_vapi_stamp "${fast_vapi_file}") - endif() - endforeach(fast_vapi_file) + foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) + set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") + string(REPLACE ".vala" ".c" c_code ${src}) + string(REPLACE ".gs" ".c" c_code ${c_code}) + string(REPLACE ".c" ".vapi" fast_vapi ${c_code}) + set(my_fast_vapi_file "${DIRECTORY}/${fast_vapi}") + set(c_code_file "${DIRECTORY}/${c_code}") + set(fast_vapi_flags "") + set(fast_vapi_stamp "") + foreach(fast_vapi_file ${fast_vapi_files}) + if(NOT "${fast_vapi_file}" STREQUAL "${my_fast_vapi_file}") + list(APPEND fast_vapi_flags --use-fast-vapi "${fast_vapi_file}") + list(APPEND fast_vapi_stamp "${fast_vapi_file}") + endif() + endforeach(fast_vapi_file) - _vala_mkdir_for_file("${fast_vapi_file}") - get_filename_component(dir "${c_code_file}" DIRECTORY) + _vala_mkdir_for_file("${fast_vapi_file}") + get_filename_component(dir "${c_code_file}" DIRECTORY) - add_custom_command(OUTPUT ${c_code_file} - COMMAND - ${VALA_EXECUTABLE} - ARGS - "-C" - "-d" ${dir} - ${vala_pkg_opts} - ${vala_define_opts} - ${gresources_args} - ${ARGS_OPTIONS} - ${fast_vapi_flags} - ${in_file} - ${custom_vapi_arguments} - DEPENDS - ${fast_vapi_stamp} - ${in_file} - ${ARGS_CUSTOM_VAPIS} - ${ARGS_GRESOURCES} - COMMENT - "Generating C source ${c_code}" - ) - endforeach(src) + add_custom_command(OUTPUT ${c_code_file} + COMMAND + ${VALA_EXECUTABLE} + ARGS + "-C" + "-d" ${dir} + ${vala_pkg_opts} + ${vala_define_opts} + ${gresources_args} + ${ARGS_OPTIONS} + ${fast_vapi_flags} + ${in_file} + ${custom_vapi_arguments} + DEPENDS + ${fast_vapi_stamp} + ${in_file} + ${ARGS_CUSTOM_VAPIS} + ${ARGS_GRESOURCES} + COMMENT + "Generating C source ${c_code}" + ) + endforeach(src) + + if(NOT "${out_extra_files}" STREQUAL "") + add_custom_command(OUTPUT ${out_extra_files} + COMMAND + ${VALA_EXECUTABLE} + ARGS + -C -q --disable-warnings + ${header_arguments} + ${vapi_arguments} + "-b" ${CMAKE_CURRENT_SOURCE_DIR} + "-d" ${DIRECTORY} + ${vala_pkg_opts} + ${vala_define_opts} + ${gresources_args} + ${ARGS_OPTIONS} + ${in_files} + ${custom_vapi_arguments} + DEPENDS + ${in_files} + ${ARGS_CUSTOM_VAPIS} + ${ARGS_GRESOURCES} + COMMENT + "Generating VAPI and headers for linking" + ) + endif() + else(ARGS_FAST_VAPI) + foreach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) + set(in_file "${CMAKE_CURRENT_SOURCE_DIR}/${src}") + list(APPEND in_files "${in_file}") + string(REPLACE ".vala" ".c" src ${src}) + string(REPLACE ".gs" ".c" src ${src}) + list(APPEND out_files "${DIRECTORY}/${src}") + + _vala_mkdir_for_file("${fast_vapi_file}") + endforeach(src ${ARGS_SOURCES} ${ARGS_UNPARSED_ARGUMENTS}) - if(NOT "${out_extra_files}" STREQUAL "") - add_custom_command(OUTPUT ${out_extra_files} + add_custom_command(OUTPUT ${out_files} ${out_extra_files} COMMAND ${VALA_EXECUTABLE} ARGS - -C -q --disable-warnings + -C ${header_arguments} ${vapi_arguments} "-b" ${CMAKE_CURRENT_SOURCE_DIR} @@ -264,8 +302,8 @@ function(vala_precompile output) ${ARGS_CUSTOM_VAPIS} ${ARGS_GRESOURCES} COMMENT - "Generating VAPI and headers for linking" + "Generating C code for target ${output}" ) - endif() + endif(ARGS_FAST_VAPI) set(${output} ${out_files} PARENT_SCOPE) endfunction(vala_precompile) diff --git a/configure b/configure index e98db6ec..4940f9bd 100755 --- a/configure +++ b/configure @@ -15,27 +15,27 @@ then fi if [ -x "$(which ninja 2>/dev/null)" ]; then - echo "Using Ninja ($(which ninja))" + echo "-- Found Ninja: $(which ninja)" cmake_type="Ninja" exec_bin="ninja" elif [ -x "$(which ninja-build 2>/dev/null)" ]; then - echo "Using Ninja ($(which ninja-build))" + echo "-- Found Ninja: $(which ninja-build)" cmake_type="Ninja" exec_bin="ninja-build" elif [ -x "$(which make 2>/dev/null)" ]; then - echo "Using Make ($(which make))" + echo "-- Found Make: $(which make)" cmake_type="Unix Makefiles" - exec_bin="make" - printf "Using Ninja improves build experience, continue with Make? [y/N] " + exec_bin="make -j4" + echo "-- Using Ninja might improve build experience." cont else - echo "No compatible build system (Ninja, Make) found." + echo "-!- No compatible build system (Ninja, Make) found." exit 4 fi if [ -f ./build ] then - echo "./build file exists. ./configure can't continue" + echo "-!- ./build file exists. ./configure can't continue" exit 2 fi @@ -43,13 +43,13 @@ if [ -d build ] then if [ ! -f "build/.cmake_type" ] then - printf "./build exists but was not created by ./configure script, continue? [y/N] " + printf "-!- ./build exists but was not created by ./configure script, continue? [y/N] " cont fi last_type=`cat build/.cmake_type` if [ "$cmake_type" != "$last_type" ] then - echo "Using different build system, cleaning build system files" + echo "-- Using different build system, cleaning build system files" cd build rm -r CMakeCache.txt CMakeFiles cd .. diff --git a/gpgme-vala/CMakeLists.txt b/gpgme-vala/CMakeLists.txt index 322b4e37..519b4ce7 100644 --- a/gpgme-vala/CMakeLists.txt +++ b/gpgme-vala/CMakeLists.txt @@ -28,7 +28,7 @@ OPTIONS --thread ) -set(CFLAGS ${GPGME_VALA_CFLAGS} ${GPGME_CFLAGS} -g ${VALA_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) +set(CFLAGS ${VALA_CFLAGS} ${GPGME_VALA_CFLAGS} ${GPGME_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/src) add_definitions(${CFLAGS}) add_library(gpgme-vala SHARED ${GPGME_VALA_C} src/fix.c) target_link_libraries(gpgme-vala ${GPGME_VALA_LIBRARIES} ${GPGME_LIBRARIES}) diff --git a/libdino/CMakeLists.txt b/libdino/CMakeLists.txt new file mode 100644 index 00000000..faee24e5 --- /dev/null +++ b/libdino/CMakeLists.txt @@ -0,0 +1,153 @@ +find_package(Vala REQUIRED) +find_package(PkgConfig REQUIRED) +include(${VALA_USE_FILE}) +include(GlibCompileResourcesSupport) + +set(LIBDINO_PACKAGES + gee-0.8 + gio-2.0 + glib-2.0 + gtk+-3.0 + gmodule-2.0 + libnotify + sqlite3 +) + +pkg_check_modules(LIBDINO REQUIRED ${LIBDINO_PACKAGES}) + +set(RESOURCE_LIST + img/double_tick.svg + img/status_away.svg + img/status_chat.svg + img/status_dnd.svg + img/status_online.svg + img/tick.svg + + add_conversation/add_contact_dialog.ui + add_conversation/add_groupchat_dialog.ui + add_conversation/conference_details_fragment.ui + add_conversation/list_row.ui + add_conversation/select_jid_fragment.ui + chat_input.ui + conversation_list_titlebar.ui + conversation_selector/view.ui + conversation_selector/chat_row_tooltip.ui + conversation_selector/conversation_row.ui + conversation_summary/message_item.ui + conversation_summary/view.ui + conversation_titlebar.ui + manage_accounts/account_row.ui + manage_accounts/add_account_dialog.ui + manage_accounts/dialog.ui + menu_add.ui + menu_app.ui + menu_conversation.ui + menu_encryption.ui + occupant_list.ui + occupant_list_item.ui + style.css + settings_dialog.ui + unified_window.ui +) + +compile_gresources( + LIBDINO_GRESOURCES_TARGET + LIBDINO_GRESOURCES_XML + TARGET ${CMAKE_BINARY_DIR}/resources/resources.c + TYPE EMBED_C + RESOURCES ${RESOURCE_LIST} + PREFIX /org/dino-im + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data +) + +vala_precompile(LIBDINO_VALA_C +SOURCES + src/plugin.vala + + src/dbus/login1.vala + src/dbus/networkmanager.vala + src/dbus/upower.vala + + src/entity/account.vala + src/entity/conversation.vala + src/entity/jid.vala + src/entity/message.vala + + src/service/avatar_manager.vala + src/service/avatar_storage.vala + src/service/chat_interaction.vala + src/service/connection_manager.vala + src/service/conversation_manager.vala + src/service/counterpart_interaction_manager.vala + src/service/database.vala + src/service/entity_capabilities_storage.vala + src/service/message_manager.vala + src/service/module_manager.vala + src/service/muc_manager.vala + src/service/pgp_manager.vala + src/service/presence_manager.vala + src/service/roster_manager.vala + src/service/stream_interactor.vala + + src/settings.vala + + src/ui/add_conversation/chat/add_contact_dialog.vala + src/ui/add_conversation/chat/roster_list.vala + src/ui/add_conversation/chat/dialog.vala + src/ui/add_conversation/conference/add_groupchat_dialog.vala + src/ui/add_conversation/conference/conference_details_fragment.vala + src/ui/add_conversation/conference/conference_list.vala + src/ui/add_conversation/conference/dialog.vala + src/ui/add_conversation/list_row.vala + src/ui/add_conversation/select_jid_fragment.vala + src/ui/avatar_generator.vala + src/ui/application.vala + src/ui/chat_input.vala + src/ui/conversation_list_titlebar.vala + src/ui/conversation_selector/chat_row.vala + src/ui/conversation_selector/conversation_row.vala + src/ui/conversation_selector/groupchat_row.vala + src/ui/conversation_selector/list.vala + src/ui/conversation_selector/view.vala + src/ui/conversation_summary/merged_message_item.vala + src/ui/conversation_summary/merged_status_item.vala + src/ui/conversation_summary/status_item.vala + src/ui/conversation_summary/view.vala + src/ui/conversation_titlebar.vala + src/ui/manage_accounts/account_row.vala + src/ui/manage_accounts/add_account_dialog.vala + src/ui/manage_accounts/dialog.vala + src/ui/notifications.vala + src/ui/occupant_list.vala + src/ui/occupant_list_row.vala + src/ui/settings_dialog.vala + src/ui/unified_window.vala + src/ui/util.vala +CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/xmpp-vala/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/qlite/qlite.vapi +PACKAGES + ${LIBDINO_PACKAGES} +GENERATE_VAPI + dino +GENERATE_HEADER + dino +GRESOURCES + ${LIBDINO_GRESOURCES_XML} +OPTIONS + --target-glib=2.38 + -g + --thread +) + +set(CFLAGS ${VALA_CFLAGS} ${LIBDINO_CFLAGS} -I${CMAKE_BINARY_DIR}/xmpp-vala -I${CMAKE_BINARY_DIR}/qlite) +add_definitions(${CFLAGS}) +add_library(libdino SHARED ${LIBDINO_VALA_C} ${LIBDINO_GRESOURCES_TARGET}) +add_dependencies(libdino xmpp-vala-vapi qlite-vapi) +target_link_libraries(libdino xmpp-vala qlite ${LIBDINO_LIBRARIES} -lm) +set_target_properties(libdino PROPERTIES PREFIX "") + +add_custom_target(dino-vapi +DEPENDS + ${CMAKE_BINARY_DIR}/libdino/dino.vapi +) diff --git a/libdino/data/add_conversation/add_contact_dialog.ui b/libdino/data/add_conversation/add_contact_dialog.ui new file mode 100644 index 00000000..58c13e7f --- /dev/null +++ b/libdino/data/add_conversation/add_contact_dialog.ui @@ -0,0 +1,150 @@ + + + + + diff --git a/libdino/data/add_conversation/add_groupchat_dialog.ui b/libdino/data/add_conversation/add_groupchat_dialog.ui new file mode 100644 index 00000000..c6390374 --- /dev/null +++ b/libdino/data/add_conversation/add_groupchat_dialog.ui @@ -0,0 +1,224 @@ + + + + + diff --git a/libdino/data/add_conversation/conference_details_fragment.ui b/libdino/data/add_conversation/conference_details_fragment.ui new file mode 100644 index 00000000..403d9a94 --- /dev/null +++ b/libdino/data/add_conversation/conference_details_fragment.ui @@ -0,0 +1,227 @@ + + + + + diff --git a/libdino/data/add_conversation/list_row.ui b/libdino/data/add_conversation/list_row.ui new file mode 100644 index 00000000..8f011bb8 --- /dev/null +++ b/libdino/data/add_conversation/list_row.ui @@ -0,0 +1,61 @@ + + + + + diff --git a/libdino/data/add_conversation/select_jid_fragment.ui b/libdino/data/add_conversation/select_jid_fragment.ui new file mode 100644 index 00000000..612f1597 --- /dev/null +++ b/libdino/data/add_conversation/select_jid_fragment.ui @@ -0,0 +1,109 @@ + + + + + \ No newline at end of file diff --git a/libdino/data/chat_input.ui b/libdino/data/chat_input.ui new file mode 100644 index 00000000..dac75feb --- /dev/null +++ b/libdino/data/chat_input.ui @@ -0,0 +1,23 @@ + + + + diff --git a/libdino/data/conversation_list_titlebar.ui b/libdino/data/conversation_list_titlebar.ui new file mode 100644 index 00000000..6a5996df --- /dev/null +++ b/libdino/data/conversation_list_titlebar.ui @@ -0,0 +1,41 @@ + + + + + diff --git a/libdino/data/conversation_selector/chat_row_tooltip.ui b/libdino/data/conversation_selector/chat_row_tooltip.ui new file mode 100644 index 00000000..90fbd712 --- /dev/null +++ b/libdino/data/conversation_selector/chat_row_tooltip.ui @@ -0,0 +1,23 @@ + + + + + vertical + True + + + 0 + True + + + + + + + + vertical + True + + + + diff --git a/libdino/data/conversation_selector/conversation_row.ui b/libdino/data/conversation_selector/conversation_row.ui new file mode 100644 index 00000000..5f8498e9 --- /dev/null +++ b/libdino/data/conversation_selector/conversation_row.ui @@ -0,0 +1,146 @@ + + + + + diff --git a/libdino/data/conversation_selector/view.ui b/libdino/data/conversation_selector/view.ui new file mode 100644 index 00000000..4bac39bc --- /dev/null +++ b/libdino/data/conversation_selector/view.ui @@ -0,0 +1,33 @@ + + + + + diff --git a/libdino/data/conversation_summary/message_item.ui b/libdino/data/conversation_summary/message_item.ui new file mode 100644 index 00000000..f21b4969 --- /dev/null +++ b/libdino/data/conversation_summary/message_item.ui @@ -0,0 +1,98 @@ + + + + + \ No newline at end of file diff --git a/libdino/data/conversation_summary/view.ui b/libdino/data/conversation_summary/view.ui new file mode 100644 index 00000000..74fb507e --- /dev/null +++ b/libdino/data/conversation_summary/view.ui @@ -0,0 +1,33 @@ + + + + + diff --git a/libdino/data/conversation_titlebar.ui b/libdino/data/conversation_titlebar.ui new file mode 100644 index 00000000..e173bdf3 --- /dev/null +++ b/libdino/data/conversation_titlebar.ui @@ -0,0 +1,63 @@ + + + + + diff --git a/libdino/data/gschemas.compiled b/libdino/data/gschemas.compiled new file mode 100644 index 00000000..3a010b95 Binary files /dev/null and b/libdino/data/gschemas.compiled differ diff --git a/libdino/data/img/double_tick.svg b/libdino/data/img/double_tick.svg new file mode 100644 index 00000000..d65840f6 --- /dev/null +++ b/libdino/data/img/double_tick.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + image/svg+xml + + Paper Symbolic Icon Theme + + + + Paper Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libdino/data/img/send.svg b/libdino/data/img/send.svg new file mode 100644 index 00000000..8627d4a7 --- /dev/null +++ b/libdino/data/img/send.svg @@ -0,0 +1 @@ + diff --git a/libdino/data/img/status_away.svg b/libdino/data/img/status_away.svg new file mode 100644 index 00000000..d976d095 --- /dev/null +++ b/libdino/data/img/status_away.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/libdino/data/img/status_chat.svg b/libdino/data/img/status_chat.svg new file mode 100644 index 00000000..5b427cb6 --- /dev/null +++ b/libdino/data/img/status_chat.svg @@ -0,0 +1,85 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/libdino/data/img/status_dnd.svg b/libdino/data/img/status_dnd.svg new file mode 100644 index 00000000..e7e17e78 --- /dev/null +++ b/libdino/data/img/status_dnd.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/libdino/data/img/status_online.svg b/libdino/data/img/status_online.svg new file mode 100644 index 00000000..13cc6592 --- /dev/null +++ b/libdino/data/img/status_online.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/libdino/data/img/tick.svg b/libdino/data/img/tick.svg new file mode 100644 index 00000000..4a08848c --- /dev/null +++ b/libdino/data/img/tick.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + image/svg+xml + + Paper Symbolic Icon Theme + + + + Paper Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + diff --git a/libdino/data/manage_accounts/account_row.ui b/libdino/data/manage_accounts/account_row.ui new file mode 100644 index 00000000..ab700daa --- /dev/null +++ b/libdino/data/manage_accounts/account_row.ui @@ -0,0 +1,30 @@ + + + + + diff --git a/libdino/data/manage_accounts/add_account_dialog.ui b/libdino/data/manage_accounts/add_account_dialog.ui new file mode 100644 index 00000000..dd5264f1 --- /dev/null +++ b/libdino/data/manage_accounts/add_account_dialog.ui @@ -0,0 +1,137 @@ + + + + + diff --git a/libdino/data/manage_accounts/dialog.ui b/libdino/data/manage_accounts/dialog.ui new file mode 100644 index 00000000..64397fb7 --- /dev/null +++ b/libdino/data/manage_accounts/dialog.ui @@ -0,0 +1,358 @@ + + + + diff --git a/libdino/data/menu_add.ui b/libdino/data/menu_add.ui new file mode 100644 index 00000000..3b2c4c4a --- /dev/null +++ b/libdino/data/menu_add.ui @@ -0,0 +1,16 @@ + + +
+ + app.add_chat + Start Chat + +
+
+ + app.add_conference + Join Conference + +
+
+
diff --git a/libdino/data/menu_app.ui b/libdino/data/menu_app.ui new file mode 100644 index 00000000..d3fa4cb7 --- /dev/null +++ b/libdino/data/menu_app.ui @@ -0,0 +1,20 @@ + + +
+ + app.accounts + Accounts + + + app.settings + Settings + +
+
+ + app.quit + Quit + +
+
+
diff --git a/libdino/data/menu_conversation.ui b/libdino/data/menu_conversation.ui new file mode 100644 index 00000000..9fe2a2b7 --- /dev/null +++ b/libdino/data/menu_conversation.ui @@ -0,0 +1,9 @@ + + +
+ + Contact Details + +
+
+
diff --git a/libdino/data/menu_encryption.ui b/libdino/data/menu_encryption.ui new file mode 100644 index 00000000..e4d392c3 --- /dev/null +++ b/libdino/data/menu_encryption.ui @@ -0,0 +1,49 @@ + + + + + False + + + True + False + vertical + 10 + + + Unencrypted + True + True + False + True + True + + + False + True + 0 + + + + + OpenPGP + True + True + False + True + True + button_unencrypted + + + False + True + 1 + + + + + main + + + + \ No newline at end of file diff --git a/libdino/data/occupant_list.ui b/libdino/data/occupant_list.ui new file mode 100644 index 00000000..deb4716e --- /dev/null +++ b/libdino/data/occupant_list.ui @@ -0,0 +1,43 @@ + + + + + diff --git a/libdino/data/occupant_list_item.ui b/libdino/data/occupant_list_item.ui new file mode 100644 index 00000000..aabe8a05 --- /dev/null +++ b/libdino/data/occupant_list_item.ui @@ -0,0 +1,44 @@ + + + + + diff --git a/libdino/data/settings.gschema.xml b/libdino/data/settings.gschema.xml new file mode 100644 index 00000000..f3d342cf --- /dev/null +++ b/libdino/data/settings.gschema.xml @@ -0,0 +1,15 @@ + + + + + true + Whether to confirm that a message was received per default + + + + true + Whether to convert common ascii smileys into unicode + + + + \ No newline at end of file diff --git a/libdino/data/settings_dialog.ui b/libdino/data/settings_dialog.ui new file mode 100644 index 00000000..3b939216 --- /dev/null +++ b/libdino/data/settings_dialog.ui @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/libdino/data/style.css b/libdino/data/style.css new file mode 100644 index 00000000..d143ffd3 --- /dev/null +++ b/libdino/data/style.css @@ -0,0 +1,3 @@ +scrolledwindow { + background-color: white; +} \ No newline at end of file diff --git a/libdino/data/unified_window.ui b/libdino/data/unified_window.ui new file mode 100644 index 00000000..289c00cf --- /dev/null +++ b/libdino/data/unified_window.ui @@ -0,0 +1,178 @@ + + + + + diff --git a/libdino/src/dbus/login1.vala b/libdino/src/dbus/login1.vala new file mode 100644 index 00000000..904f389c --- /dev/null +++ b/libdino/src/dbus/login1.vala @@ -0,0 +1,18 @@ +namespace Dino { + +[DBus (name = "org.freedesktop.login1.Manager")] +public interface Login1Manager : Object { + public signal void PrepareForSleep(bool suspend); +} + +public static Login1Manager? get_login1() { + Login1Manager? login1 = null; + try { + login1 = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1"); + } catch (IOError e) { + stderr.printf("%s\n", e.message); + } + return login1; +} + +} \ No newline at end of file diff --git a/libdino/src/dbus/networkmanager.vala b/libdino/src/dbus/networkmanager.vala new file mode 100644 index 00000000..fb8ac0cc --- /dev/null +++ b/libdino/src/dbus/networkmanager.vala @@ -0,0 +1,22 @@ +namespace Dino { + +[DBus (name = "org.freedesktop.NetworkManager")] +public interface NetworkManager : Object { + + public const int CONNECTED_GLOBAL = 70; + + public abstract uint32 State {owned get;} + public signal void StateChanged(uint32 state); +} + +public static NetworkManager? get_network_manager() { + NetworkManager? nm = null; + try { + nm = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager"); + } catch (IOError e) { + stderr.printf ("%s\n", e.message); + } + return nm; +} + +} \ No newline at end of file diff --git a/libdino/src/dbus/upower.vala b/libdino/src/dbus/upower.vala new file mode 100644 index 00000000..8d4a5e0c --- /dev/null +++ b/libdino/src/dbus/upower.vala @@ -0,0 +1,19 @@ +namespace Dino { + +[DBus (name = "org.freedesktop.UPower")] +public interface UPower : Object { + public signal void Sleeping(); + public signal void Resuming(); +} + +public static UPower? get_upower() { + UPower? upower = null; + try { + upower = Bus.get_proxy_sync(BusType.SYSTEM, "org.freedesktop.UPower", "/org/freedesktop/UPower"); + } catch (IOError e) { + stderr.printf ("%s\n", e.message); + } + return upower; +} + +} \ No newline at end of file diff --git a/libdino/src/entity/account.vala b/libdino/src/entity/account.vala new file mode 100644 index 00000000..48be527a --- /dev/null +++ b/libdino/src/entity/account.vala @@ -0,0 +1,40 @@ +using Gee; + +namespace Dino.Entities { +public class Account : Object { + + public int id { get; set; } + public string localpart { get { return bare_jid.localpart; } } + public string domainpart { get { return bare_jid.domainpart; } } + public string resourcepart { get; set; } + public Jid bare_jid { get; private set; } + public string? password { get; set; } + public string display_name { + owned get { + if (alias != null) { + return alias; + } else { + return bare_jid.to_string(); + } + } + } + public string? alias { get; set; } + public bool enabled { get; set; } + + public Account.from_bare_jid(string bare_jid) { + this.bare_jid = new Jid(bare_jid); + } + + public bool equals(Account acc) { + return equals_func(this, acc); + } + + public static bool equals_func(Account acc1, Account acc2) { + return acc1.bare_jid.to_string() == acc2.bare_jid.to_string(); + } + + public static uint hash_func(Account acc) { + return acc.bare_jid.to_string().hash(); + } +} +} \ No newline at end of file diff --git a/libdino/src/entity/conversation.vala b/libdino/src/entity/conversation.vala new file mode 100644 index 00000000..2da6dce3 --- /dev/null +++ b/libdino/src/entity/conversation.vala @@ -0,0 +1,52 @@ +namespace Dino.Entities { +public class Conversation : Object { + + public signal void object_updated(Conversation conversation); + + public enum Encryption { + UNENCRYPTED, + PGP + } + + public enum Type { + CHAT, + GROUPCHAT + } + + public int id { get; set; } + public Account account { get; private set; } + public Jid counterpart { get; private set; } + public bool active { get; set; } + public DateTime last_active { get; set; } + public Encryption encryption { get; set; } + public Type? type_ { get; set; } + public Message read_up_to { get; set; } + + public Conversation(Jid jid, Account account) { + this.counterpart = jid; + this.account = account; + this.active = false; + this.last_active = new DateTime.from_unix_utc(0); + this.encryption = Encryption.UNENCRYPTED; + } + + public Conversation.with_id(Jid jid, Account account, int id) { + this.counterpart = jid; + this.account = account; + this.id = id; + } + + public bool equals(Conversation? conversation) { + if (conversation == null) return false; + return equals_func(this, conversation); + } + + public static bool equals_func(Conversation conversation1, Conversation conversation2) { + return conversation1.counterpart.equals(conversation2.counterpart) && conversation1.account.equals(conversation2.account); + } + + public static uint hash_func(Conversation conversation) { + return conversation.counterpart.to_string().hash() ^ conversation.account.bare_jid.to_string().hash(); + } +} +} \ No newline at end of file diff --git a/libdino/src/entity/jid.vala b/libdino/src/entity/jid.vala new file mode 100644 index 00000000..f1da0c00 --- /dev/null +++ b/libdino/src/entity/jid.vala @@ -0,0 +1,89 @@ +public class Dino.Entities.Jid : Object { + public string? localpart { get; set; } + public string domainpart { get; set; } + public string? resourcepart { get; set; } + + public Jid? bare_jid { + owned get { return localpart != null ? new Jid(@"$localpart@$domainpart") : new Jid(domainpart); } + } + + private string jid { get; private set; } + + public Jid(string jid) { + Jid? parsed = Jid.parse(jid); + string? localpart = parsed != null ? parsed.localpart : null; + string domainpart = parsed != null ? parsed.domainpart : jid; + string? resourcepart = parsed != null ? parsed.resourcepart : null; + Jid.components(localpart, domainpart, resourcepart); + } + + public Jid.with_resource(string bare_jid, string resource) { + Jid? parsed = Jid.parse(bare_jid); + Jid.components(parsed.localpart, parsed.domainpart, resourcepart); + } + + public Jid.components(string? localpart, string domainpart, string? resourcepart) { + string jid = domainpart; + if (localpart != null) { + jid = @"$localpart@$jid"; + } + if (resourcepart != null) { + jid = @"$jid/$resourcepart"; + } + this.jid = jid; + this.localpart = localpart; + this.domainpart = domainpart; + this.resourcepart = resourcepart; + } + + public static Jid? parse(string jid) { + int slash_index = jid.index_of("/"); + string resourcepart = slash_index == -1 ? null : jid.slice(slash_index + 1, jid.length); + string bare_jid = slash_index == -1 ? jid : jid.slice(0, slash_index); + int at_index = bare_jid.index_of("@"); + string localpart = at_index == -1 ? null : bare_jid.slice(0, at_index); + string domainpart = at_index == -1 ? bare_jid : bare_jid.slice(at_index + 1, bare_jid.length); + + if (domainpart == "") return null; + if (slash_index != -1 && resourcepart == "") return null; + if (at_index != -1 && localpart == "") return null; + + return new Jid.components(localpart, domainpart, resourcepart); + } + + public bool is_bare() { + return localpart != null && resourcepart == null; + } + + public bool is_full() { + return localpart != null && resourcepart != null; + } + + public string to_string() { + return jid; + } + + public bool equals_bare(Jid jid) { + return equals_bare_func(this, jid); + } + + public bool equals(Jid jid) { + return equals_func(this, jid); + } + + public static new bool equals_bare_func(Jid jid1, Jid jid2) { + return jid1.bare_jid.to_string() == jid2.bare_jid.to_string(); + } + + public static bool equals_func(Jid jid1, Jid jid2) { + return jid1.to_string() == jid2.to_string(); + } + + public static new uint hash_bare_func(Jid jid) { + return jid.bare_jid.to_string().hash(); + } + + public static new uint hash_func(Jid jid) { + return jid.to_string().hash(); + } +} diff --git a/libdino/src/entity/message.vala b/libdino/src/entity/message.vala new file mode 100644 index 00000000..65d05bdf --- /dev/null +++ b/libdino/src/entity/message.vala @@ -0,0 +1,91 @@ +using Gee; + +using Xmpp; + +public class Dino.Entities.Message : Object { + + public const bool DIRECTION_SENT = true; + public const bool DIRECTION_RECEIVED = false; + + public enum Marked { + NONE, + RECEIVED, + READ, + ACKNOWLEDGED, + UNSENT, + WONTSEND + } + + public enum Encryption { + NONE, + PGP + } + + public enum Type { + ERROR, + CHAT, + GROUPCHAT, + HEADLINE, + NORMAL + } + + public int? id { get; set; } + public Account account { get; set; } + public Jid? counterpart { get; set; } + public Jid? ourpart { get; set; } + public Jid? from { + get { return direction == DIRECTION_SENT ? account.bare_jid : counterpart; } + } + public Jid? to { + get { return direction == DIRECTION_SENT ? counterpart : account.bare_jid; } + } + public bool direction { get; set; } + public string? real_jid { get; set; } + public Type type_ { get; set; } + public string? body { get; set; } + public string? stanza_id { get; set; } + public DateTime? time { get; set; } + public DateTime? local_time { get; set; } + public Encryption encryption { get; set; default = Encryption.NONE; } + public Marked marked { get; set; default = Marked.NONE; } + public Xmpp.Message.Stanza stanza { get; set; } + + public void set_type_string(string type) { + switch (type) { + case Xmpp.Message.Stanza.TYPE_CHAT: + type_ = Type.CHAT; break; + case Xmpp.Message.Stanza.TYPE_GROUPCHAT: + type_ = Type.GROUPCHAT; break; + default: + type_ = Type.NORMAL; break; + } + } + + public new string get_type_string() { + switch (type_) { + case Type.CHAT: + return Xmpp.Message.Stanza.TYPE_CHAT; + case Type.GROUPCHAT: + return Xmpp.Message.Stanza.TYPE_GROUPCHAT; + default: + return Xmpp.Message.Stanza.TYPE_NORMAL; + } + } + + public bool equals(Message? m) { + if (m == null) return false; + return equals_func(this, m); + } + + public static bool equals_func(Message m1, Message m2) { + if (m1.stanza_id == m2.stanza_id && + m1.body == m2.body) { + return true; + } + return false; + } + + public static uint hash_func(Message message) { + return message.body.hash(); + } +} diff --git a/libdino/src/plugin.vala b/libdino/src/plugin.vala new file mode 100644 index 00000000..60a99bc2 --- /dev/null +++ b/libdino/src/plugin.vala @@ -0,0 +1,64 @@ +namespace Dino { + +public errordomain PluginError { + NOT_SUPPORTED, + UNEXPECTED_TYPE, + NO_REGISTRATION_FUNCTION, + FAILED +} + +public interface PluginIface : Object { + public abstract void registered(Dino.Ui.Application app); +} + +private class PluginInfo : Object { + public Module module; + public Type gtype; + + public PluginInfo(Type type, owned Module module) { + this.module = (owned) module; + this.gtype = type; + } +} + +public class PluginLoader : Object { + [CCode (has_target = false)] + private delegate Type RegisterPluginFunction (Module module); + + private PluginIface[] plugins = new PluginIface[0]; + private PluginInfo[] infos = new PluginInfo[0]; + + public PluginIface load(string name, Dino.Ui.Application app) throws PluginError { + if (Module.supported () == false) { + throw new PluginError.NOT_SUPPORTED ("Plugins are not supported"); + } + + Module module = Module.open ("plugins/" + name, ModuleFlags.BIND_LAZY); + if (module == null) { + throw new PluginError.FAILED (Module.error ()); + } + + void* function; + module.symbol ("register_plugin", out function); + if (function == null) { + throw new PluginError.NO_REGISTRATION_FUNCTION ("register_plugin () not found"); + } + + RegisterPluginFunction register_plugin = (RegisterPluginFunction) function; + Type type = register_plugin (module); + if (type.is_a (typeof (PluginIface)) == false) { + throw new PluginError.UNEXPECTED_TYPE ("Unexpected type"); + } + + PluginInfo info = new PluginInfo (type, (owned) module); + infos += info; + + PluginIface plugin = (PluginIface) Object.new (type); + plugins += plugin; + plugin.registered (app); + + return plugin; + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala new file mode 100644 index 00000000..de44c419 --- /dev/null +++ b/libdino/src/service/avatar_manager.vala @@ -0,0 +1,134 @@ +using Gdk; +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class AvatarManager : StreamInteractionModule, Object { + public const string id = "avatar_manager"; + + public signal void received_avatar(Pixbuf avatar, Jid jid, Account account); + + private enum Source { + USER_AVATARS, + VCARD + } + + private StreamInteractor stream_interactor; + private Database db; + private HashMap user_avatars = new HashMap(Jid.hash_func, Jid.equals_func); + private HashMap vcard_avatars = new HashMap(Jid.hash_func, Jid.equals_func); + private AvatarStorage avatar_storage = new AvatarStorage("./"); // TODO ihh + private const int MAX_PIXEL = 192; + + public static void start(StreamInteractor stream_interactor, Database db) { + AvatarManager m = new AvatarManager(stream_interactor, db); + stream_interactor.add_module(m); + } + + private AvatarManager(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + stream_interactor.account_added.connect(on_account_added); + } + + public Pixbuf? get_avatar(Account account, Jid jid) { + Jid jid_ = jid; + if (!MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { + jid_ = jid.bare_jid; + } + string? user_avatars_id = user_avatars[jid_]; + if (user_avatars_id != null) { + return avatar_storage.get_image(user_avatars_id); + } + string? vcard_avatars_id = vcard_avatars[jid_]; + if (vcard_avatars_id != null) { + return avatar_storage.get_image(vcard_avatars_id); + } + return null; + } + + public void publish(Account account, string file) { + print(file + "\n"); + try { + Pixbuf pixbuf = new Pixbuf.from_file(file); + if (pixbuf.width >= pixbuf.height && pixbuf.width > MAX_PIXEL) { + int dest_height = (int) ((float) MAX_PIXEL / pixbuf.width * pixbuf.height); + pixbuf = pixbuf.scale_simple(MAX_PIXEL, dest_height, InterpType.BILINEAR); + } else if (pixbuf.height > pixbuf.width && pixbuf.width > MAX_PIXEL) { + int dest_width = (int) ((float) MAX_PIXEL / pixbuf.height * pixbuf.width); + pixbuf = pixbuf.scale_simple(dest_width, MAX_PIXEL, InterpType.BILINEAR); + } + uint8[] buffer; + pixbuf.save_to_buffer(out buffer, "png"); + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) { + Xep.UserAvatars.Module.get_module(stream).publish_png(stream, buffer, pixbuf.width, pixbuf.height); + on_user_avatar_received(account, account.bare_jid, Base64.encode(buffer)); + } + } catch (Error e) { + print("error " + e.message + "\n"); + } + } + + private class PublishResponseListenerImpl : Object { + public void on_success(Core.XmppStream stream) { + + } + public void on_error(Core.XmppStream stream) { } + } + + public static AvatarManager? get_instance(StreamInteractor stream_interaction) { + return (AvatarManager) stream_interaction.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.user_avatars_modules[account].received_avatar.connect((stream, jid, id) => + on_user_avatar_received(account, new Jid(jid), id) + ); + stream_interactor.module_manager.vcard_modules[account].received_avatar.connect((stream, jid, id) => + on_vcard_avatar_received(account, new Jid(jid), id) + ); + + user_avatars = db.get_avatar_hashes(Source.USER_AVATARS); + foreach (Jid jid in user_avatars.keys) { + on_user_avatar_received(account, jid, user_avatars[jid]); + } + vcard_avatars = db.get_avatar_hashes(Source.VCARD); + foreach (Jid jid in vcard_avatars.keys) { + on_vcard_avatar_received(account, jid, vcard_avatars[jid]); + } + } + + private void on_user_avatar_received(Account account, Jid jid, string id) { + if (!user_avatars.has_key(jid) || user_avatars[jid] != id) { + user_avatars[jid] = id; + db.set_avatar_hash(jid, id, Source.USER_AVATARS); + } + Pixbuf? avatar = avatar_storage.get_image(id); + if (avatar != null) { + received_avatar(avatar, jid, account); + } + } + + private void on_vcard_avatar_received(Account account, Jid jid, string id) { + if (!vcard_avatars.has_key(jid) || vcard_avatars[jid] != id) { + vcard_avatars[jid] = id; + if (!jid.is_full()) { // don't save muc avatars + db.set_avatar_hash(jid, id, Source.VCARD); + } + } + Pixbuf? avatar = avatar_storage.get_image(id); + if (avatar != null) { + received_avatar(avatar, jid, account); + } + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/avatar_storage.vala b/libdino/src/service/avatar_storage.vala new file mode 100644 index 00000000..a9a8fb86 --- /dev/null +++ b/libdino/src/service/avatar_storage.vala @@ -0,0 +1,34 @@ +using Gdk; + +using Xmpp; + +namespace Dino { +public class AvatarStorage : Xep.PixbufStorage, Object { + + string folder; + + public AvatarStorage(string folder) { + this.folder = folder; + } + + public void store(string id, uint8[] data) { + File file = File.new_for_path(id); + if (file.query_exists()) file.delete(); //TODO y? + DataOutputStream fos = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION)); + fos.write(data); + } + + public bool has_image(string id) { + File file = File.new_for_path(folder + id); + return file.query_exists(); + } + + public Pixbuf? get_image(string id) { + try { + return new Pixbuf.from_file(folder + id); + } catch (Error e) { + return null; + } + } +} +} \ No newline at end of file diff --git a/libdino/src/service/chat_interaction.vala b/libdino/src/service/chat_interaction.vala new file mode 100644 index 00000000..cd6907fa --- /dev/null +++ b/libdino/src/service/chat_interaction.vala @@ -0,0 +1,146 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { +public class ChatInteraction : StreamInteractionModule, Object { + private const string id = "chat_interaction"; + + public signal void conversation_read(Conversation conversation); + public signal void conversation_unread(Conversation conversation); + + private StreamInteractor stream_interactor; + private Conversation? selected_conversation; + + private HashMap last_input_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); + private HashMap last_interface_interaction = new HashMap(Conversation.hash_func, Conversation.equals_func); + private bool focus_in = false; + + public static void start(StreamInteractor stream_interactor) { + ChatInteraction m = new ChatInteraction(stream_interactor); + stream_interactor.add_module(m); + } + + private ChatInteraction(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + Timeout.add_seconds(30, update_interactions); + MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); + MessageManager.get_instance(stream_interactor).message_sent.connect(on_message_sent); + } + + public bool is_active_focus(Conversation? conversation = null) { + if (conversation != null) { + return focus_in && conversation.equals(this.selected_conversation); + } else { + return focus_in; + } + } + + public void window_focus_in(Conversation? conversation) { + on_conversation_selected(selected_conversation); + } + + public void window_focus_out(Conversation? conversation) { + focus_in = false; + } + + public void on_message_entered(Conversation conversation) { + if (Settings.instance().send_read) { + if (!last_input_interaction.has_key(conversation) && conversation.type_ != Conversation.Type.GROUPCHAT) { + send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_COMPOSING); + } + } + last_input_interaction[conversation] = new DateTime.now_utc(); + last_interface_interaction[conversation] = new DateTime.now_utc(); + } + + public void on_message_cleared(Conversation conversation) { + if (last_input_interaction.has_key(conversation)) { + last_input_interaction.unset(conversation); + last_interface_interaction.unset(conversation); + send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_ACTIVE); + } + } + + public void on_conversation_selected(Conversation? conversation) { + selected_conversation = conversation; + focus_in = true; + if (conversation != null) { + conversation_read(selected_conversation); + check_send_read(); + selected_conversation.read_up_to = MessageManager.get_instance(stream_interactor).get_last_message(conversation); + } + } + + internal string get_id() { + return id; + } + + public static ChatInteraction? get_instance(StreamInteractor stream_interactor) { + return (ChatInteraction) stream_interactor.get_module(id); + } + + private void check_send_read() { + if (selected_conversation == null || selected_conversation.type_ == Conversation.Type.GROUPCHAT) return; + Entities.Message? message = MessageManager.get_instance(stream_interactor).get_last_message(selected_conversation); + if (message != null && message.direction == Entities.Message.DIRECTION_RECEIVED && + message.stanza != null && !message.equals(selected_conversation.read_up_to)) { + selected_conversation.read_up_to = message; + send_chat_marker(selected_conversation, message, Xep.ChatMarkers.MARKER_DISPLAYED); + } + } + + private bool update_interactions() { + ArrayList remove_input = new ArrayList(Conversation.equals_func); + ArrayList remove_interface = new ArrayList(Conversation.equals_func); + foreach (Conversation conversation in last_input_interaction.keys) { + if (last_input_interaction.has_key(conversation) && + (new DateTime.now_utc()).difference(last_input_interaction[conversation]) >= 15 * TimeSpan.SECOND) { + remove_input.add(conversation); + send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_PAUSED); + } + } + foreach (Conversation conversation in last_interface_interaction.keys) { + if (last_interface_interaction.has_key(conversation) && + (new DateTime.now_utc()).difference(last_interface_interaction[conversation]) >= 1.5 * TimeSpan.MINUTE) { + remove_interface.add(conversation); + send_chat_state_notification(conversation, Xep.ChatStateNotifications.STATE_GONE); + } + } + foreach (Conversation conversation in remove_input) last_input_interaction.unset(conversation); + foreach (Conversation conversation in remove_interface) last_interface_interaction.unset(conversation); + return true; + } + + private void on_message_received(Entities.Message message, Conversation conversation) { + if (is_active_focus(conversation)) { + check_send_read(); + conversation.read_up_to = message; + send_chat_marker(conversation, message, Xep.ChatMarkers.MARKER_DISPLAYED); + } else { + conversation_unread(conversation); + } + } + + private void on_message_sent(Entities.Message message, Conversation conversation) { + last_input_interaction.unset(conversation); + last_interface_interaction.unset(conversation); + conversation.read_up_to = message; + } + + private void send_chat_marker(Conversation conversation, Entities.Message message, string marker) { + Core.XmppStream stream = stream_interactor.get_stream(conversation.account); + if (stream != null && Settings.instance().send_read && Xep.ChatMarkers.Module.requests_marking(message.stanza)) { + Xep.ChatMarkers.Module.get_module(stream).send_marker(stream, message.stanza.from, message.stanza_id, message.get_type_string(), marker); + } + } + + private void send_chat_state_notification(Conversation conversation, string state) { + Core.XmppStream stream = stream_interactor.get_stream(conversation.account); + if (stream != null && Settings.instance().send_read) { + Xep.ChatStateNotifications.Module.get_module(stream).send_state(stream, conversation.counterpart.to_string(), state); + } + } +} +} \ No newline at end of file diff --git a/libdino/src/service/connection_manager.vala b/libdino/src/service/connection_manager.vala new file mode 100644 index 00000000..92827296 --- /dev/null +++ b/libdino/src/service/connection_manager.vala @@ -0,0 +1,222 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class ConnectionManager { + + public signal void stream_opened(Account account, Core.XmppStream stream); + public signal void connection_state_changed(Account account, ConnectionState state); + + public enum ConnectionState { + CONNECTED, + CONNECTING, + DISCONNECTED + } + + private ArrayList connection_todo = new ArrayList(Account.equals_func); + private HashMap stream_states = new HashMap(Account.hash_func, Account.equals_func); + private NetworkManager? network_manager; + private Login1Manager? login1; + private ModuleManager module_manager; + + private class Connection { + public Core.XmppStream stream { get; set; } + public ConnectionState connection_state { get; set; default = ConnectionState.DISCONNECTED; } + public DateTime established { get; set; } + public class Connection(Core.XmppStream stream, DateTime established) { + this.stream = stream; + this.established = established; + } + } + + public ConnectionManager(ModuleManager module_manager) { + this.module_manager = module_manager; + network_manager = get_network_manager(); + if (network_manager != null) { + network_manager.StateChanged.connect(on_nm_state_changed); + } + login1 = get_login1(); + if (login1 != null) { + login1.PrepareForSleep.connect(on_prepare_for_sleep); + } + } + + public Core.XmppStream? get_stream(Account account) { + if (get_connection_state(account) == ConnectionState.CONNECTED) { + return stream_states[account].stream; + } + return null; + } + + public ConnectionState get_connection_state(Account account) { + if (stream_states.has_key(account)){ + return stream_states[account].connection_state; + } + return ConnectionState.DISCONNECTED; + } + + public ArrayList get_managed_accounts() { + return connection_todo; + } + + public Core.XmppStream? connect(Account account) { + if (!connection_todo.contains(account)) connection_todo.add(account); + if (!stream_states.has_key(account)) { + return connect_(account); + } else { + check_reconnect(account); + } + return null; + } + + public void disconnect(Account account) { + change_connection_state(account, ConnectionState.DISCONNECTED); + if (stream_states.has_key(account)) { + try { + stream_states[account].stream.disconnect(); + } catch (Error e) { } + } + connection_todo.remove(account); + } + + private Core.XmppStream? connect_(Account account, string? resource = null) { + if (resource == null) resource = account.resourcepart; + if (stream_states.has_key(account)) { + stream_states[account].stream.remove_modules(); + } + + Core.XmppStream stream = new Core.XmppStream(); + foreach (Core.XmppStreamModule module in module_manager.get_modules(account, resource)) { + stream.add_module(module); + } + stream.debug = false; + + Connection connection = new Connection(stream, new DateTime.now_local()); + stream_states[account] = connection; + change_connection_state(account, ConnectionState.CONNECTING); + stream.stream_negotiated.connect((stream) => { + change_connection_state(account, ConnectionState.CONNECTED); + }); + new Thread (null, () => { + try { + stream.connect(account.domainpart); + } catch (Error e) { + stderr.printf("Stream Error: %s\n", e.message); + change_connection_state(account, ConnectionState.DISCONNECTED); + interpret_reconnect_flags(account, StreamError.Flag.get_flag(stream) ?? + new StreamError.Flag() { reconnection_recomendation = StreamError.Flag.Reconnect.NOW }); + } + return null; + }); + stream_opened(account, stream); + + return stream; + } + + private void interpret_reconnect_flags(Account account, StreamError.Flag stream_error_flag) { + if (!connection_todo.contains(account)) return; + int wait_sec = 10; + if (network_manager != null && network_manager.State != NetworkManager.CONNECTED_GLOBAL) { + wait_sec = 60; + } + switch (stream_error_flag.reconnection_recomendation) { + case StreamError.Flag.Reconnect.NOW: + wait_sec = 10; + break; + case StreamError.Flag.Reconnect.LATER: + case StreamError.Flag.Reconnect.UNKNOWN: + wait_sec = 60; + break; + case StreamError.Flag.Reconnect.NEVER: + return; + } + print(@"recovering in $wait_sec\n"); + Timeout.add_seconds(wait_sec, () => { + if (stream_error_flag.resource_rejected) { + connect_(account, account.resourcepart + "-" + random_uuid()); + } else { + connect_(account); + } + return false; + }); + } + + private void check_reconnects() { + foreach (Account account in connection_todo) { + check_reconnect(account); + } + } + + private void check_reconnect(Account account) { + PingResponseListenerImpl ping_response_listener = new PingResponseListenerImpl(this, account); + Core.XmppStream stream = stream_states[account].stream; + Xep.Ping.Module.get_module(stream).send_ping(stream, account.domainpart, ping_response_listener); + + Timeout.add_seconds(5, () => { + if (stream_states[account].stream != stream) return false; + if (ping_response_listener.acked) return false; + + change_connection_state(account, ConnectionState.DISCONNECTED); + try { + stream_states[account].stream.disconnect(); + } catch (Error e) { } + return false; + }); + } + + private class PingResponseListenerImpl : Xep.Ping.ResponseListener, Object { + public bool acked = false; + ConnectionManager outer; + Account account; + public PingResponseListenerImpl(ConnectionManager outer, Account account) { + this.outer = outer; + this.account = account; + } + public void on_result(Core.XmppStream stream) { + print("ping ok\n"); + acked = true; + outer.change_connection_state(account, ConnectionState.CONNECTED); + } + } + + private void on_nm_state_changed(uint32 state) { + print("nm " + state.to_string() + "\n"); + if (state == NetworkManager.CONNECTED_GLOBAL) { + check_reconnects(); + } else { + foreach (Account account in connection_todo) { + change_connection_state(account, ConnectionState.DISCONNECTED); + } + } + } + + private void on_prepare_for_sleep(bool suspend) { + foreach (Account account in connection_todo) { + change_connection_state(account, ConnectionState.DISCONNECTED); + } + if (suspend) { + print("suspend\n"); + foreach (Account account in connection_todo) { + Xmpp.Presence.Stanza presence = new Xmpp.Presence.Stanza(); + presence.type_ = Xmpp.Presence.Stanza.TYPE_UNAVAILABLE; + try { + Presence.Module.get_module(stream_states[account].stream).send_presence(stream_states[account].stream, presence); + stream_states[account].stream.disconnect(); + } catch (Error e) { print(@"on_prepare_for_sleep error $(e.message)\n"); } + } + } else { + print("un-suspend\n"); + check_reconnects(); + } + } + + private void change_connection_state(Account account, ConnectionState state) { + stream_states[account].connection_state = state; + connection_state_changed(account, state); + } +} + +} diff --git a/libdino/src/service/conversation_manager.vala b/libdino/src/service/conversation_manager.vala new file mode 100644 index 00000000..716c9b39 --- /dev/null +++ b/libdino/src/service/conversation_manager.vala @@ -0,0 +1,103 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { +public class ConversationManager : StreamInteractionModule, Object { + + public const string id = "conversation_manager"; + + public signal void conversation_activated(Conversation conversation); + + private StreamInteractor stream_interactor; + private Database db; + + private HashMap> conversations = new HashMap>(Account.hash_func, Account.equals_func); + + public static void start(StreamInteractor stream_interactor, Database db) { + ConversationManager m = new ConversationManager(stream_interactor, db); + stream_interactor.add_module(m); + } + + private ConversationManager(StreamInteractor stream_interactor, Database db) { + this.db = db; + this.stream_interactor = stream_interactor; + stream_interactor.add_module(this); + stream_interactor.account_added.connect(on_account_added); + MucManager.get_instance(stream_interactor).groupchat_joined.connect(on_groupchat_joined); + MessageManager.get_instance(stream_interactor).pre_message_received.connect(on_message_received); + MessageManager.get_instance(stream_interactor).message_sent.connect(on_message_sent); + } + + public Conversation? get_conversation(Jid jid, Account account) { + if (conversations.has_key(account)) { + return conversations[account][jid]; + } + return null; + } + + public Conversation get_add_conversation(Jid jid, Account account) { + ensure_add_conversation(jid, account, Conversation.Type.CHAT); + return get_conversation(jid, account); + } + + public void ensure_start_conversation(Jid jid, Account account) { + ensure_add_conversation(jid, account, Conversation.Type.CHAT); + Conversation? conversation = get_conversation(jid, account); + if (conversation != null) { + conversation.last_active = new DateTime.now_utc(); + if (!conversation.active) { + conversation.active = true; + conversation_activated(conversation); + } + } + + } + + public string get_id() { + return id; + } + + public static ConversationManager? get_instance(StreamInteractor stream_interaction) { + return (ConversationManager) stream_interaction.get_module(id); + } + + private void on_account_added(Account account) { + conversations[account] = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); + foreach (Conversation conversation in db.get_conversations(account)) { + add_conversation(conversation); + } + } + + private void on_message_received(Entities.Message message, Conversation conversation) { + ensure_start_conversation(conversation.counterpart, conversation.account); + } + + private void on_message_sent(Entities.Message message, Conversation conversation) { + conversation.last_active = message.time; + } + + private void on_groupchat_joined(Account account, Jid jid, string nick) { + ensure_add_conversation(jid, account, Conversation.Type.GROUPCHAT); + ensure_start_conversation(jid, account); + } + + private void ensure_add_conversation(Jid jid, Account account, Conversation.Type type) { + if (conversations.has_key(account) && !conversations[account].has_key(jid)) { + Conversation conversation = new Conversation(jid, account); + conversation.type_ = type; + add_conversation(conversation); + db.add_conversation(conversation); + } + } + + private void add_conversation(Conversation conversation) { + conversations[conversation.account][conversation.counterpart] = conversation; + if (conversation.active) { + conversation_activated(conversation); + } + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/counterpart_interaction_manager.vala b/libdino/src/service/counterpart_interaction_manager.vala new file mode 100644 index 00000000..8ea8ba15 --- /dev/null +++ b/libdino/src/service/counterpart_interaction_manager.vala @@ -0,0 +1,99 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { +public class CounterpartInteractionManager : StreamInteractionModule, Object { + public const string id = "counterpart_interaction_manager"; + + public signal void received_state(Account account, Jid jid, string state); + public signal void received_marker(Account account, Jid jid, Entities.Message message, string marker); + public signal void received_message_received(Account account, Jid jid, Entities.Message message); + public signal void received_message_displayed(Account account, Jid jid, Entities.Message message); + + private StreamInteractor stream_interactor; + private HashMap last_read = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); + private HashMap chat_states = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); + + public static void start(StreamInteractor stream_interactor) { + CounterpartInteractionManager m = new CounterpartInteractionManager(stream_interactor); + stream_interactor.add_module(m); + } + + private CounterpartInteractionManager(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + stream_interactor.account_added.connect(on_account_added); + MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); + } + + public string? get_chat_state(Account account, Jid jid) { + return chat_states[jid]; + } + + public Entities.Message? get_last_read(Account account, Jid jid) { + return last_read[jid]; + } + + public static CounterpartInteractionManager? get_instance(StreamInteractor stream_interactor) { + return (CounterpartInteractionManager) stream_interactor.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.chat_markers_modules[account].marker_received.connect( (stream, jid, marker, id) => { + on_chat_marker_received(account, new Jid(jid), marker, id); + }); + stream_interactor.module_manager.message_delivery_receipts_modules[account].receipt_received.connect((stream, jid, id) => { + on_receipt_received(account, new Jid(jid), id); + }); + stream_interactor.module_manager.chat_state_notifications_modules[account].chat_state_received.connect((stream, jid, state) => { + on_chat_state_received(account, new Jid(jid), state); + }); + } + + private void on_chat_state_received(Account account, Jid jid, string state) { + chat_states[jid] = state; + received_state(account, jid, state); + } + + private void on_chat_marker_received(Account account, Jid jid, string marker, string stanza_id) { + Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); + if (conversation != null) { + Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); + if (messages != null) { // TODO not here + foreach (Entities.Message message in messages) { + if (message.stanza_id == stanza_id) { + switch (marker) { + case Xep.ChatMarkers.MARKER_RECEIVED: + received_message_received(account, jid, message); + message.marked = Entities.Message.Marked.RECEIVED; + break; + case Xep.ChatMarkers.MARKER_DISPLAYED: + last_read[jid] = message; + received_message_displayed(account, jid, message); + foreach (Entities.Message m in messages) { + if (m.equals(message)) break; + if (m.marked == Entities.Message.Marked.RECEIVED) m.marked = Entities.Message.Marked.READ; + } + message.marked = Entities.Message.Marked.READ; + break; + } + } + } + } + } + } + + private void on_message_received(Entities.Message message, Conversation conversation) { + on_chat_state_received(conversation.account, conversation.counterpart, Xep.ChatStateNotifications.STATE_ACTIVE); + } + + private void on_receipt_received(Account account, Jid jid, string id) { + on_chat_marker_received(account, jid, Xep.ChatMarkers.MARKER_RECEIVED, id); + } +} +} \ No newline at end of file diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala new file mode 100644 index 00000000..13be6222 --- /dev/null +++ b/libdino/src/service/database.vala @@ -0,0 +1,466 @@ +using Gee; +using Sqlite; +using Qlite; + +using Dino.Entities; + +namespace Dino { + +public class Database : Qlite.Database { + private const int VERSION = 0; + + public class AccountTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; + public Column resourcepart = new Column.Text("resourcepart"); + public Column password = new Column.Text("password"); + public Column alias = new Column.Text("alias"); + public Column enabled = new Column.BoolInt("enabled"); + + protected AccountTable(Database db) { + base(db, "account"); + init({id, bare_jid, resourcepart, password, alias, enabled}); + } + } + + public class JidTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column bare_jid = new Column.Text("bare_jid") { unique = true, not_null = true }; + + protected JidTable(Database db) { + base(db, "jid"); + init({id, bare_jid}); + } + } + + public class MessageTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column stanza_id = new Column.Text("stanza_id"); + public Column account_id = new Column.Integer("account_id") { not_null = true }; + public Column counterpart_id = new Column.Integer("counterpart_id") { not_null = true }; + public Column counterpart_resource = new Column.Text("counterpart_resource"); + public Column our_resource = new Column.Text("our_resource"); + public Column direction = new Column.BoolInt("direction") { not_null = true }; + public Column type_ = new Column.Integer("type"); + public Column time = new Column.Long("time"); + public Column local_time = new Column.Long("local_time"); + public Column body = new Column.Text("body"); + public Column encryption = new Column.Integer("encryption"); + public Column marked = new Column.Integer("marked"); + + protected MessageTable(Database db) { + base(db, "message"); + init({id, stanza_id, account_id, counterpart_id, our_resource, counterpart_resource, direction, + type_, time, local_time, body, encryption, marked}); + } + } + + public class RealJidTable : Table { + public Column message_id = new Column.Integer("message_id") { primary_key = true }; + public Column real_jid = new Column.Text("real_jid"); + + protected RealJidTable(Database db) { + base(db, "real_jid"); + init({message_id, real_jid}); + } + } + + public class UndecryptedTable : Table { + public Column message_id = new Column.Integer("message_id"); + public Column type_ = new Column.Integer("type"); + public Column data = new Column.Text("data"); + + protected UndecryptedTable(Database db) { + base(db, "undecrypted"); + init({message_id, type_, data}); + } + } + + public class ConversationTable : Table { + public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; + public Column account_id = new Column.Integer("account_id") { not_null = true }; + public Column jid_id = new Column.Integer("jid_id") { not_null = true }; + public Column active = new Column.BoolInt("active"); + public Column last_active = new Column.Long("last_active"); + public Column type_ = new Column.Integer("type"); + public Column encryption = new Column.Integer("encryption"); + public Column read_up_to = new Column.Integer("read_up_to"); + + protected ConversationTable(Database db) { + base(db, "conversation"); + init({id, account_id, jid_id, active, last_active, type_, encryption, read_up_to}); + } + } + + public class AvatarTable : Table { + public Column jid = new Column.Text("jid"); + public Column hash = new Column.Text("hash"); + public Column type_ = new Column.Integer("type"); + + protected AvatarTable(Database db) { + base(db, "avatar"); + init({jid, hash, type_}); + } + } + + public class PgpTable : Table { + public Column jid = new Column.Text("jid") { primary_key = true }; + public Column key = new Column.Text("key") { not_null = true }; + + protected PgpTable(Database db) { + base(db, "pgp"); + init({jid, key}); + } + } + + public class EntityFeatureTable : Table { + public Column entity = new Column.Text("entity"); + public Column feature = new Column.Text("feature"); + + protected EntityFeatureTable(Database db) { + base(db, "entity_feature"); + init({entity, feature}); + } + } + + public AccountTable account { get; private set; } + public JidTable jid { get; private set; } + public MessageTable message { get; private set; } + public RealJidTable real_jid { get; private set; } + public ConversationTable conversation { get; private set; } + public AvatarTable avatar { get; private set; } + public PgpTable pgp { get; private set; } + public EntityFeatureTable entity_feature { get; private set; } + + public Database(string fileName) { + base(fileName, VERSION); + account = new AccountTable(this); + jid = new JidTable(this); + message = new MessageTable(this); + real_jid = new RealJidTable(this); + conversation = new ConversationTable(this); + avatar = new AvatarTable(this); + pgp = new PgpTable(this); + entity_feature = new EntityFeatureTable(this); + init({ account, jid, message, real_jid, conversation, avatar, pgp, entity_feature }); + } + + public override void migrate(long oldVersion) { + // new table columns are added, outdated columns are still present + } + + public void add_account(Account new_account) { + new_account.id = (int) account.insert() + .value(account.bare_jid, new_account.bare_jid.to_string()) + .value(account.resourcepart, new_account.resourcepart) + .value(account.password, new_account.password) + .value(account.alias, new_account.alias) + .value(account.enabled, new_account.enabled) + .perform(); + new_account.notify.connect(on_account_update); + } + + private void on_account_update(Object o, ParamSpec sp) { + Account changed_account = (Account) o; + account.update().with(account.id, "=", changed_account.id) + .set(account.bare_jid, changed_account.bare_jid.to_string()) + .set(account.resourcepart, changed_account.resourcepart) + .set(account.password, changed_account.password) + .set(account.alias, changed_account.alias) + .set(account.enabled, changed_account.enabled) + .perform(); + } + + public void remove_account(Account to_delete) { + account.delete().with(account.bare_jid, "=", to_delete.bare_jid.to_string()).perform(); + } + + public ArrayList get_accounts() { + ArrayList ret = new ArrayList(); + foreach(Row row in account.select()) { + Account account = get_account_from_row(row); + account.notify.connect(on_account_update); + ret.add(account); + } + return ret; + } + + private Account? get_account_by_id(int id) { + Row? row = account.row_with(account.id, id); + if (row != null) { + return get_account_from_row(row); + } + return null; + } + + private Account get_account_from_row(Row row) { + Account new_account = new Account.from_bare_jid(row[account.bare_jid]); + + new_account.id = row[account.id]; + new_account.resourcepart = row[account.resourcepart]; + new_account.password = row[account.password]; + new_account.alias = row[account.alias]; + new_account.enabled = row[account.enabled]; + return new_account; + } + + public void add_message(Message new_message, Account account) { + InsertBuilder builder = message.insert() + .value(message.account_id, new_message.account.id) + .value(message.counterpart_id, get_jid_id(new_message.counterpart)) + .value(message.counterpart_resource, new_message.counterpart.resourcepart) + .value(message.our_resource, new_message.ourpart.resourcepart) + .value(message.direction, new_message.direction) + .value(message.type_, new_message.type_) + .value(message.time, (long) new_message.time.to_unix()) + .value(message.local_time, (long) new_message.local_time.to_unix()) + .value(message.body, new_message.body) + .value(message.encryption, new_message.encryption) + .value(message.marked, new_message.marked); + if (new_message.stanza_id != null) builder.value(message.stanza_id, new_message.stanza_id); + new_message.id = (int) builder.perform(); + + if (new_message.real_jid != null) { + real_jid.insert() + .value(real_jid.message_id, new_message.id) + .value(real_jid.real_jid, new_message.real_jid) + .perform(); + } + new_message.notify.connect(on_message_update); + } + + private void on_message_update(Object o, ParamSpec sp) { + Message changed_message = (Message) o; + UpdateBuilder update_builder = message.update().with(message.id, "=", changed_message.id); + switch (sp.get_name()) { + case "stanza_id": + update_builder.set(message.stanza_id, changed_message.stanza_id); break; + case "counterpart": + update_builder.set(message.counterpart_id, get_jid_id(changed_message.counterpart)); + update_builder.set(message.counterpart_resource, changed_message.counterpart.resourcepart); break; + case "ourpart": + update_builder.set(message.our_resource, changed_message.ourpart.resourcepart); break; + case "direction": + update_builder.set(message.direction, changed_message.direction); break; + case "type_": + update_builder.set(message.type_, changed_message.type_); break; + case "time": + update_builder.set(message.time, (long) changed_message.time.to_unix()); break; + case "local_time": + update_builder.set(message.local_time, (long) changed_message.local_time.to_unix()); break; + case "body": + update_builder.set(message.body, changed_message.body); break; + case "encryption": + update_builder.set(message.encryption, changed_message.encryption); break; + case "marked": + update_builder.set(message.marked, changed_message.marked); break; + } + update_builder.perform(); + + if (sp.get_name() == "real_jid") { + real_jid.insert() + .value(real_jid.message_id, changed_message.id) + .value(real_jid.real_jid, changed_message.real_jid) + .perform(); + } + } + + public Gee.List get_messages(Jid jid, Account account, int count, Message? before) { + string jid_id = get_jid_id(jid).to_string(); + + QueryBuilder select = message.select() + .with(message.counterpart_id, "=", get_jid_id(jid)) + .with(message.account_id, "=", account.id) + .order_by(message.id, "DESC") + .limit(count); + if (before != null) { + select.with(message.time, "<", (long) before.time.to_unix()); + } + + LinkedList ret = new LinkedList(); + foreach (Row row in select) { + ret.insert(0, get_message_from_row(row)); + } + return ret; + } + + public Gee.List get_unsend_messages(Account account) { + Gee.List ret = new ArrayList(); + foreach (Row row in message.select().with(message.marked, "=", (int) Message.Marked.UNSENT)) { + ret.add(get_message_from_row(row)); + } + return ret; + } + + public bool contains_message(Message query_message, Account account) { + int jid_id = get_jid_id(query_message.counterpart); + return message.select() + .with(message.account_id, "=", account.id) + .with(message.stanza_id, "=", query_message.stanza_id) + .with(message.counterpart_id, "=", jid_id) + .with(message.counterpart_resource, "=", query_message.counterpart.resourcepart) + .with(message.body, "=", query_message.body) + .with(message.time, "<", (long) query_message.time.add_minutes(1).to_unix()) + .with(message.time, ">", (long) query_message.time.add_minutes(-1).to_unix()) + .count() > 0; + } + + public bool contains_message_by_stanza_id(string stanza_id) { + return message.select() + .with(message.stanza_id, "=", stanza_id) + .count() > 0; + } + + public Message? get_message_by_id(int id) { + Row? row = message.row_with(message.id, id); + if (row != null) { + return get_message_from_row(row); + } + return null; + } + + public Message get_message_from_row(Row row) { + Message new_message = new Message(); + + new_message.id = row[message.id]; + new_message.stanza_id = row[message.stanza_id]; + string from = get_jid_by_id(row[message.counterpart_id]); + string from_resource = row[message.counterpart_resource]; + if (from_resource != null) { + new_message.counterpart = new Jid(from + "/" + from_resource); + } else { + new_message.counterpart = new Jid(from); + } + new_message.direction = row[message.direction]; + new_message.type_ = (Message.Type) row[message.type_]; + new_message.time = new DateTime.from_unix_utc(row[message.time]); + new_message.body = row[message.body]; + new_message.account = get_account_by_id(row[message.account_id]); // TODO dont have to generate acc new + new_message.marked = (Message.Marked) row[message.marked]; + new_message.encryption = (Message.Encryption) row[message.encryption]; + new_message.real_jid = get_real_jid_for_message(new_message); + + new_message.notify.connect(on_message_update); + return new_message; + } + + public string? get_real_jid_for_message(Message message) { + return real_jid.select({real_jid.real_jid}).with(real_jid.message_id, "=", message.id)[real_jid.real_jid]; + } + + public void add_conversation(Conversation new_conversation) { + var insert = conversation.insert() + .value(conversation.jid_id, get_jid_id(new_conversation.counterpart)) + .value(conversation.account_id, new_conversation.account.id) + .value(conversation.type_, new_conversation.type_) + .value(conversation.encryption, new_conversation.encryption) + //.value(conversation.read_up_to, new_conversation.read_up_to) + .value(conversation.active, new_conversation.active); + if (new_conversation.last_active != null) { + insert.value(conversation.last_active, (long) new_conversation.last_active.to_unix()); + } else { + insert.value_null(conversation.last_active); + } + new_conversation.id = (int) insert.perform(); + new_conversation.notify.connect(on_conversation_update); + } + + public ArrayList get_conversations(Account account) { + ArrayList ret = new ArrayList(); + foreach (Row row in conversation.select().with(conversation.account_id, "=", account.id)) { + ret.add(get_conversation_from_row(row)); + } + return ret; + } + + private void on_conversation_update(Object o, ParamSpec sp) { + Conversation changed_conversation = (Conversation) o; + var update = conversation.update().with(conversation.jid_id, "=", get_jid_id(changed_conversation.counterpart)).with(conversation.account_id, "=", changed_conversation.account.id) + .set(conversation.type_, changed_conversation.type_) + .set(conversation.encryption, changed_conversation.encryption) + //.set(conversation.read_up_to, changed_conversation.read_up_to) + .set(conversation.active, changed_conversation.active); + if (changed_conversation.last_active != null) { + update.set(conversation.last_active, (long) changed_conversation.last_active.to_unix()); + } else { + update.set_null(conversation.last_active); + } + update.perform(); + } + + private Conversation get_conversation_from_row(Row row) { + Conversation new_conversation = new Conversation(new Jid(get_jid_by_id(row[conversation.jid_id])), get_account_by_id(row[conversation.account_id])); + + new_conversation.id = row[conversation.id]; + new_conversation.active = row[conversation.active]; + int64? last_active = row[conversation.last_active]; + if (last_active != null) new_conversation.last_active = new DateTime.from_unix_utc(last_active); + new_conversation.type_ = (Conversation.Type) row[conversation.type_]; + new_conversation.encryption = (Conversation.Encryption) row[conversation.encryption]; + int? read_up_to = row[conversation.read_up_to]; + if (read_up_to != null) new_conversation.read_up_to = get_message_by_id(read_up_to); + + new_conversation.notify.connect(on_conversation_update); + return new_conversation; + } + + public void set_avatar_hash(Jid jid, string hash, int type) { + avatar.insert().or("REPLACE") + .value(avatar.jid, jid.to_string()) + .value(avatar.hash, hash) + .value(avatar.type_, type) + .perform(); + } + + public HashMap get_avatar_hashes(int type) { + HashMap ret = new HashMap(Jid.hash_func, Jid.equals_func); + foreach (Row row in avatar.select({avatar.jid, avatar.hash}).with(avatar.type_, "=", type)) { + ret[new Jid(row[avatar.jid])] = row[avatar.hash]; + } + return ret; + } + + public void set_pgp_key(Jid jid, string key) { + pgp.insert().or("REPLACE") + .value(pgp.jid, jid.to_string()) + .value(pgp.key, key) + .perform(); + } + + public string? get_pgp_key(Jid jid) { + return pgp.select({pgp.key}).with(pgp.jid, "=", jid.to_string())[pgp.key]; + } + + public void add_entity_features(string entity, ArrayList features) { + foreach (string feature in features) { + entity_feature.insert() + .value(entity_feature.entity, entity) + .value(entity_feature.feature, feature) + .perform(); + } + } + + public ArrayList get_entity_features(string entity) { + ArrayList ret = new ArrayList(); + foreach (Row row in entity_feature.select({entity_feature.feature}).with(entity_feature.entity, "=", entity)) { + ret.add(row[entity_feature.feature]); + } + return ret; + } + + + private int get_jid_id(Jid jid_obj) { + Row? row = jid.row_with(jid.bare_jid, jid_obj.bare_jid.to_string()); + return row != null ? row[jid.id] : add_jid(jid_obj); + } + + private string? get_jid_by_id(int id) { + return jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid]; + } + + private int add_jid(Jid jid_obj) { + return (int) jid.insert().value(jid.bare_jid, jid_obj.bare_jid.to_string()).perform(); + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/entity_capabilities_storage.vala b/libdino/src/service/entity_capabilities_storage.vala new file mode 100644 index 00000000..9774739a --- /dev/null +++ b/libdino/src/service/entity_capabilities_storage.vala @@ -0,0 +1,23 @@ +using Gee; + +using Xmpp; + +namespace Dino { + +public class EntityCapabilitiesStorage : Xep.EntityCapabilities.Storage, Object { + + private Database db; + + public EntityCapabilitiesStorage(Database db) { + this.db = db; + } + + public void store_features(string entity, ArrayList features) { + db.add_entity_features(entity, features); + } + + public ArrayList get_features(string entitiy) { + return db.get_entity_features(entitiy); + } +} +} \ No newline at end of file diff --git a/libdino/src/service/message_manager.vala b/libdino/src/service/message_manager.vala new file mode 100644 index 00000000..ec7a35c8 --- /dev/null +++ b/libdino/src/service/message_manager.vala @@ -0,0 +1,193 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class MessageManager : StreamInteractionModule, Object { + public const string ID = "message_manager"; + + public signal void pre_message_received(Entities.Message message, Conversation conversation); + public signal void message_received(Entities.Message message, Conversation conversation); + public signal void message_sent(Entities.Message message, Conversation conversation); + + private StreamInteractor stream_interactor; + private Database db; + private HashMap> messages = new HashMap>(Conversation.hash_func, Conversation.equals_func); + + public static void start(StreamInteractor stream_interactor, Database db) { + MessageManager m = new MessageManager(stream_interactor, db); + stream_interactor.add_module(m); + } + + private MessageManager(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + stream_interactor.account_added.connect(on_account_added); + stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { + if (state == ConnectionManager.ConnectionState.CONNECTED) send_unsent_messages(account); + }); + } + + public void send_message(string text, Conversation conversation) { + Entities.Message message = create_out_message(text, conversation); + add_message(message, conversation); + db.add_message(message, conversation.account); + send_xmpp_message(message, conversation); + message_sent(message, conversation); + } + + public Gee.List? get_messages(Conversation conversation) { + if (messages.has_key(conversation) && messages[conversation].size > 0) { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, messages[conversation][0]); + db_messages.add_all(messages[conversation]); + return db_messages; + } else { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 50, null); + return db_messages; + } + } + + public Entities.Message? get_last_message(Conversation conversation) { + if (messages.has_key(conversation) && messages[conversation].size > 0) { + return messages[conversation][messages[conversation].size - 1]; + } else { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 1, null); + if (db_messages.size >= 1) { + return db_messages[0]; + } + } + return null; + } + + public Gee.List? get_messages_before(Conversation? conversation, Entities.Message before) { + Gee.List db_messages = db.get_messages(conversation.counterpart, conversation.account, 20, before); + return db_messages; + } + + public string get_id() { + return ID; + } + + public static MessageManager? get_instance(StreamInteractor stream_interactor) { + return (MessageManager) stream_interactor.get_module(ID); + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.message_modules[account].received_message.connect( (stream, message) => { + on_message_received(account, message); + }); + stream_interactor.stream_negotiated.connect(send_unsent_messages); + } + + private void send_unsent_messages(Account account) { + Gee.List unsend_messages = db.get_unsend_messages(account); + foreach (Entities.Message message in unsend_messages) { + Conversation conversation = ConversationManager.get_instance(stream_interactor).get_conversation(message.counterpart, account); + send_xmpp_message(message, conversation, true); + } + } + + private void on_message_received(Account account, Xmpp.Message.Stanza message) { + if (message.body == null) return; + + Entities.Message new_message = new Entities.Message(); + new_message.account = account; + new_message.stanza_id = message.id; + Jid from_jid = new Jid(message.from); + if (!account.bare_jid.equals_bare(from_jid) || + MucManager.get_instance(stream_interactor).get_nick(from_jid.bare_jid, account) == from_jid.resourcepart) { + new_message.direction = Entities.Message.DIRECTION_RECEIVED; + } else { + new_message.direction = Entities.Message.DIRECTION_SENT; + } + new_message.counterpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.to) : new Jid(message.from); + new_message.ourpart = new_message.direction == Entities.Message.DIRECTION_SENT ? new Jid(message.from) : new Jid(message.to); + new_message.body = message.body; + new_message.stanza = message; + new_message.set_type_string(message.type_); + Xep.DelayedDelivery.MessageFlag? deleyed_delivery_flag = Xep.DelayedDelivery.MessageFlag.get_flag(message); + new_message.time = deleyed_delivery_flag != null ? deleyed_delivery_flag.datetime : new DateTime.now_utc(); + new_message.local_time = new DateTime.now_utc(); + if (Xep.Pgp.MessageFlag.get_flag(message) != null) { + new_message.encryption = Entities.Message.Encryption.PGP; + } + Conversation conversation = ConversationManager.get_instance(stream_interactor).get_add_conversation(new_message.counterpart, account); + pre_message_received(new_message, conversation); + + bool is_uuid = new_message.stanza_id != null && Regex.match_simple("""[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}""", new_message.stanza_id); + if ((is_uuid && !db.contains_message_by_stanza_id(new_message.stanza_id)) || + (!is_uuid && !db.contains_message(new_message, conversation.account))) { + db.add_message(new_message, conversation.account); + add_message(new_message, conversation); + if (new_message.time.difference(conversation.last_active) > 0) { + conversation.last_active = new_message.time; + } + if (new_message.direction == Entities.Message.DIRECTION_SENT) { + message_sent(new_message, conversation); + } else { + message_received(new_message, conversation); + } + } + } + + private void add_message(Entities.Message message, Conversation conversation) { + if (!messages.has_key(conversation)) { + messages[conversation] = new ArrayList(Entities.Message.equals_func); + } + messages[conversation].add(message); + } + + private Entities.Message create_out_message(string text, Conversation conversation) { + Entities.Message message = new Entities.Message(); + message.stanza_id = random_uuid(); + message.account = conversation.account; + message.body = text; + message.time = new DateTime.now_utc(); + message.local_time = new DateTime.now_utc(); + message.direction = Entities.Message.DIRECTION_SENT; + message.counterpart = conversation.counterpart; + message.ourpart = new Jid(conversation.account.bare_jid.to_string() + "/" + conversation.account.resourcepart); + + if (conversation.encryption == Conversation.Encryption.PGP) { + message.encryption = Entities.Message.Encryption.PGP; + } + return message; + } + + private void send_xmpp_message(Entities.Message message, Conversation conversation, bool delayed = false) { + Core.XmppStream stream = stream_interactor.get_stream(conversation.account); + message.marked = Entities.Message.Marked.NONE; + if (stream != null) { + Xmpp.Message.Stanza new_message = new Xmpp.Message.Stanza(message.stanza_id); + new_message.to = message.counterpart.to_string(); + new_message.body = message.body; + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + new_message.type_ = Xmpp.Message.Stanza.TYPE_GROUPCHAT; + } else { + new_message.type_ = Xmpp.Message.Stanza.TYPE_CHAT; + } + if (message.encryption == Entities.Message.Encryption.PGP) { + string? key_id = PgpManager.get_instance(stream_interactor).get_key_id(conversation.account, message.counterpart); + if (key_id != null) { + bool encrypted = Xep.Pgp.Module.get_module(stream).encrypt(new_message, key_id); + if (!encrypted) { + message.marked = Entities.Message.Marked.WONTSEND; + return; + } + } + } + if (delayed) { + Xmpp.Xep.DelayedDelivery.Module.get_module(stream).set_message_delay(new_message, message.time); + } + Xmpp.Message.Module.get_module(stream).send_message(stream, new_message); + message.stanza_id = new_message.id; + message.stanza = new_message; + } else { + message.marked = Entities.Message.Marked.UNSENT; + } + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/module_manager.vala b/libdino/src/service/module_manager.vala new file mode 100644 index 00000000..5ef93da8 --- /dev/null +++ b/libdino/src/service/module_manager.vala @@ -0,0 +1,96 @@ +using Gee; + +using Dino.Entities; +using Xmpp; + +namespace Dino { + +public class ModuleManager { + + public HashMap tls_modules = new HashMap(); + public HashMap plain_sasl_modules = new HashMap(); + public HashMap bind_modules = new HashMap(); + public HashMap roster_modules = new HashMap(); + public HashMap service_discovery_modules = new HashMap(); + public HashMap private_xmp_storage_modules = new HashMap(); + public HashMap bookmarks_module = new HashMap(); + public HashMap presence_modules = new HashMap(); + public HashMap message_modules = new HashMap(); + public HashMap message_carbons_modules = new HashMap(); + public HashMap muc_modules = new HashMap(); + public HashMap pgp_modules = new HashMap(); + public HashMap pubsub_modules = new HashMap(); + public HashMap entity_capabilities_modules = new HashMap(); + public HashMap user_avatars_modules = new HashMap(); + public HashMap vcard_modules = new HashMap(); + public HashMap message_delivery_receipts_modules = new HashMap(); + public HashMap chat_state_notifications_modules = new HashMap(); + public HashMap chat_markers_modules = new HashMap(); + public HashMap ping_modules = new HashMap(); + public HashMap delayed_delivery_module = new HashMap(); + public HashMap stream_error_modules = new HashMap(); + + private AvatarStorage avatar_storage = new AvatarStorage("./"); + private EntityCapabilitiesStorage entity_capabilities_storage; + + public ModuleManager(Database db) { + entity_capabilities_storage = new EntityCapabilitiesStorage(db); + } + + public ArrayList get_modules(Account account, string? resource = null) { + ArrayList modules = new ArrayList(); + + if (!tls_modules.has_key(account)) add_account(account); + + modules.add(tls_modules[account]); + modules.add(plain_sasl_modules[account]); + modules.add(new Bind.Module(resource == null ? account.resourcepart : resource)); + modules.add(roster_modules[account]); + modules.add(service_discovery_modules[account]); + modules.add(private_xmp_storage_modules[account]); + modules.add(bookmarks_module[account]); + modules.add(presence_modules[account]); + modules.add(message_modules[account]); + modules.add(message_carbons_modules[account]); + modules.add(muc_modules[account]); + modules.add(pgp_modules[account]); + modules.add(pubsub_modules[account]); + modules.add(entity_capabilities_modules[account]); + modules.add(user_avatars_modules[account]); + modules.add(vcard_modules[account]); + modules.add(message_delivery_receipts_modules[account]); + modules.add(chat_state_notifications_modules[account]); + modules.add(chat_markers_modules[account]); + modules.add(ping_modules[account]); + modules.add(delayed_delivery_module[account]); + modules.add(stream_error_modules[account]); + return modules; + } + + public void add_account(Account account) { + tls_modules[account] = new Tls.Module(); + plain_sasl_modules[account] = new PlainSasl.Module(account.bare_jid.to_string(), account.password); + bind_modules[account] = new Bind.Module(account.resourcepart); + roster_modules[account] = new Roster.Module(); + service_discovery_modules[account] = new Xep.ServiceDiscovery.Module.with_identity("client", "pc"); + private_xmp_storage_modules[account] = new Xep.PrivateXmlStorage.Module(); + bookmarks_module[account] = new Xep.Bookmarks.Module(); + presence_modules[account] = new Presence.Module(); + message_modules[account] = new Xmpp.Message.Module(); + message_carbons_modules[account] = new Xep.MessageCarbons.Module(); + muc_modules[account] = new Xep.Muc.Module(); + pgp_modules[account] = new Xep.Pgp.Module(); + pubsub_modules[account] = new Xep.Pubsub.Module(); + entity_capabilities_modules[account] = new Xep.EntityCapabilities.Module(entity_capabilities_storage); + user_avatars_modules[account] = new Xep.UserAvatars.Module(avatar_storage); + vcard_modules[account] = new Xep.VCard.Module(avatar_storage); + message_delivery_receipts_modules[account] = new Xep.MessageDeliveryReceipts.Module(); + chat_state_notifications_modules[account] = new Xep.ChatStateNotifications.Module(); + chat_markers_modules[account] = new Xep.ChatMarkers.Module(); + ping_modules[account] = new Xep.Ping.Module(); + delayed_delivery_module[account] = new Xep.DelayedDelivery.Module(); + stream_error_modules[account] = new StreamError.Module(); + } +} + +} \ No newline at end of file diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala new file mode 100644 index 00000000..be23d391 --- /dev/null +++ b/libdino/src/service/muc_manager.vala @@ -0,0 +1,224 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { +public class MucManager : StreamInteractionModule, Object { + public const string id = "muc_manager"; + + public signal void groupchat_joined(Account account, Jid jid, string nick); + public signal void groupchat_subject_set(Account account, Jid jid, string subject); + public signal void bookmarks_updated(Account account, ArrayList conferences); + + private StreamInteractor stream_interactor; + protected HashMap conference_bookmarks = new HashMap(); + + public static void start(StreamInteractor stream_interactor) { + MucManager m = new MucManager(stream_interactor); + stream_interactor.add_module(m); + } + + private MucManager(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + stream_interactor.account_added.connect(on_account_added); + stream_interactor.stream_negotiated.connect(on_stream_negotiated); + MessageManager.get_instance(stream_interactor).pre_message_received.connect(on_pre_message_received); + } + + public void join(Account account, Jid jid, string nick, string? password = null) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Muc.Module.get_module(stream).enter(stream, jid.bare_jid.to_string(), nick, password, new MucEnterListenerImpl(this, jid, nick, account)); + } + + public void part(Account account, Jid jid) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Muc.Module.get_module(stream).exit(stream, jid.bare_jid.to_string()); + } + + public void change_subject(Account account, Jid jid, string subject) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Muc.Module.get_module(stream).change_subject(stream, jid.bare_jid.to_string(), subject); + } + + public void change_nick(Account account, Jid jid, string new_nick) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Muc.Module.get_module(stream).change_nick(stream, jid.bare_jid.to_string(), new_nick); + } + + public void kick(Account account, Jid jid, string nick) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Muc.Module.get_module(stream).kick(stream, jid.bare_jid.to_string(), nick); + } + + public ArrayList? get_occupants(Jid jid, Account account) { + return PresenceManager.get_instance(stream_interactor).get_full_jids(jid, account); + } + + public ArrayList? get_other_occupants(Jid jid, Account account) { + ArrayList? occupants = get_occupants(jid, account); + string? nick = get_nick(jid, account); + if (occupants != null && nick != null) { + occupants.remove(new Jid(@"$(jid.bare_jid)/$nick")); + } + return occupants; + } + + public bool is_groupchat(Jid jid, Account account) { + Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); + return !jid.is_full() && conversation != null && conversation.type_ == Conversation.Type.GROUPCHAT; + } + + public bool is_groupchat_occupant(Jid jid, Account account) { + return is_groupchat(jid.bare_jid, account) && jid.is_full(); + } + + public void get_bookmarks(Account account, Xep.Bookmarks.ConferencesRetrieveResponseListener listener) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Xep.Bookmarks.Module.get_module(stream).get_conferences(stream, listener); + } + } + + public void add_bookmark(Account account, Xep.Bookmarks.Conference conference) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Xep.Bookmarks.Module.get_module(stream).add_conference(stream, conference); + } + } + + public void replace_bookmark(Account account, Xep.Bookmarks.Conference was, Xep.Bookmarks.Conference replace) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Xep.Bookmarks.Module.get_module(stream).replace_conference(stream, was, replace); + } + } + + public void remove_bookmark(Account account, Xep.Bookmarks.Conference conference) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Xep.Bookmarks.Module.get_module(stream).remove_conference(stream, conference); + } + } + + public string? get_groupchat_subject(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + return Xep.Muc.Flag.get_flag(stream).get_muc_subject(jid.bare_jid.to_string()); + } + return null; + } + + public Jid? get_real_jid(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + string? real_jid = Xep.Muc.Flag.get_flag(stream).get_real_jid(jid.to_string()); + if (real_jid != null) { + return new Jid(real_jid); + } + } + return null; + } + + public Jid? get_message_real_jid(Entities.Message message) { + if (message.real_jid != null) { + return new Jid(message.real_jid); + } + return null; + } + + public string? get_nick(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + return Xep.Muc.Flag.get_flag(stream).get_muc_nick(jid.bare_jid.to_string()); + } + return null; + } + + public static MucManager? get_instance(StreamInteractor stream_interactor) { + return (MucManager) stream_interactor.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.muc_modules[account].subject_set.connect( (stream, subject, jid) => { + on_subject_set(account, new Jid(jid), subject); + }); + stream_interactor.module_manager.bookmarks_module[account].conferences_updated.connect( (stream, conferences) => { + bookmarks_updated(account, conferences); + }); + } + + private void on_subject_set(Account account, Jid sender_jid, string subject) { + groupchat_subject_set(account, sender_jid, subject); + } + + private void on_stream_negotiated(Account account) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xep.Bookmarks.Module.get_module(stream).get_conferences(stream, new BookmarksRetrieveResponseListener(this, account)); + } + + private void on_pre_message_received(Entities.Message message, Conversation conversation) { + if (conversation.type_ != Conversation.Type.GROUPCHAT) return; + Core.XmppStream stream = stream_interactor.get_stream(conversation.account); + if (stream == null) return; + if (Xep.DelayedDelivery.MessageFlag.get_flag(message.stanza) == null) { + string? real_jid = Xep.Muc.Flag.get_flag(stream).get_real_jid(message.counterpart.to_string()); + if (real_jid != null && real_jid != message.counterpart.to_string()) { + message.real_jid = real_jid; + } + } + string muc_nick = Xep.Muc.Flag.get_flag(stream).get_muc_nick(conversation.counterpart.bare_jid.to_string()); + if (message.from.equals(new Jid(@"$(message.from.bare_jid)/$muc_nick"))) { // TODO better from own + Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); + if (messages != null) { // TODO not here + foreach (Entities.Message m in messages) { + if (m.equals(message)) { + m.marked = Entities.Message.Marked.RECEIVED; + } + } + } + } + } + + private class BookmarksRetrieveResponseListener : Xep.Bookmarks.ConferencesRetrieveResponseListener, Object { + MucManager outer = null; + Account account = null; + + public BookmarksRetrieveResponseListener(MucManager outer, Account account) { + this.outer = outer; + this.account = account; + } + + public void on_result(Core.XmppStream stream, ArrayList conferences) { + foreach (Xep.Bookmarks.Conference bookmark in conferences) { + Jid jid = new Jid(bookmark.jid); + outer.conference_bookmarks[jid] = bookmark; + if (bookmark.autojoin) { + outer.join(account, jid, bookmark.nick); + } + } + } + } + + private class MucEnterListenerImpl : Xep.Muc.MucEnterListener, Object { // TODO + private MucManager outer; + private Jid jid; + private string nick; + private Account account; + public MucEnterListenerImpl(MucManager outer, Jid jid, string nick, Account account) { + this.outer = outer; + this.jid = jid; + this.nick = nick; + this.account = account; + } + public void on_success() { + outer.groupchat_joined(account, jid, nick); + } + public void on_error(Xep.Muc.MucEnterError error) { } + } +} +} \ No newline at end of file diff --git a/libdino/src/service/pgp_manager.vala b/libdino/src/service/pgp_manager.vala new file mode 100644 index 00000000..6f3b63d7 --- /dev/null +++ b/libdino/src/service/pgp_manager.vala @@ -0,0 +1,54 @@ +using Gee; + +using Dino.Entities; + +namespace Dino { + public class PgpManager : StreamInteractionModule, Object { + public const string id = "pgp_manager"; + + public const string MESSAGE_ENCRYPTED = "pgp"; + + private StreamInteractor stream_interactor; + private Database db; + private HashMap pgp_key_ids = new HashMap(Jid.hash_bare_func, Jid.equals_bare_func); + + public static void start(StreamInteractor stream_interactor, Database db) { + PgpManager m = new PgpManager(stream_interactor, db); + stream_interactor.add_module(m); + } + + private PgpManager(StreamInteractor stream_interactor, Database db) { + this.stream_interactor = stream_interactor; + this.db = db; + + stream_interactor.account_added.connect(on_account_added); + } + + public string? get_key_id(Account account, Jid jid) { + return db.get_pgp_key(jid); + } + + public static PgpManager? get_instance(StreamInteractor stream_interactor) { + return (PgpManager) stream_interactor.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.pgp_modules[account].received_jid_key_id.connect((stream, jid, key_id) => { + on_jid_key_received(account, new Jid(jid), key_id); + }); + } + + private void on_jid_key_received(Account account, Jid jid, string key_id) { + if (!pgp_key_ids.has_key(jid) || pgp_key_ids[jid] != key_id) { + if (!MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { + db.set_pgp_key(jid.bare_jid, key_id); + } + } + pgp_key_ids[jid] = key_id; + } + } +} \ No newline at end of file diff --git a/libdino/src/service/presence_manager.vala b/libdino/src/service/presence_manager.vala new file mode 100644 index 00000000..b89c6570 --- /dev/null +++ b/libdino/src/service/presence_manager.vala @@ -0,0 +1,150 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { +public class PresenceManager : StreamInteractionModule, Object { + public const string id = "presence_manager"; + + public signal void show_received(Show show, Jid jid, Account account); + public signal void received_subscription_request(Jid jid, Account account); + + private StreamInteractor stream_interactor; + private HashMap>> shows = new HashMap>>(Jid.hash_bare_func, Jid.equals_bare_func); + private HashMap> resources = new HashMap>(Jid.hash_bare_func, Jid.equals_bare_func); + + public static void start(StreamInteractor stream_interactor) { + PresenceManager m = new PresenceManager(stream_interactor); + stream_interactor.add_module(m); + } + + private PresenceManager(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + stream_interactor.account_added.connect(on_account_added); + } + + public Show get_last_show(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Xmpp.Presence.Stanza? presence = Xmpp.Presence.Flag.get_flag(stream).get_presence(jid.to_string()); + if (presence != null) { + return new Show(jid, presence.show, new DateTime.now_local()); + } + } + return new Show(jid, Show.OFFLINE, new DateTime.now_local()); + } + + public HashMap>? get_shows(Jid jid, Account account) { + return shows[jid]; + } + + public ArrayList? get_full_jids(Jid jid, Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + Gee.List resources = Xmpp.Presence.Flag.get_flag(stream).get_resources(jid.bare_jid.to_string()); + if (resources == null) { + return null; + } + ArrayList ret = new ArrayList(Jid.equals_func); + foreach (string resource in resources) { + ret.add(new Jid(resource)); + } + return ret; + } + return null; + } + + public void request_subscription(Account account, Jid jid) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xmpp.Presence.Module.get_module(stream).request_subscription(stream, jid.bare_jid.to_string()); + } + + public void approve_subscription(Account account, Jid jid) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xmpp.Presence.Module.get_module(stream).approve_subscription(stream, jid.bare_jid.to_string()); + } + + public void deny_subscription(Account account, Jid jid) { + Core.XmppStream stream = stream_interactor.get_stream(account); + if (stream != null) Xmpp.Presence.Module.get_module(stream).deny_subscription(stream, jid.bare_jid.to_string()); + } + + public static PresenceManager? get_instance(StreamInteractor stream_interactor) { + return (PresenceManager) stream_interactor.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.presence_modules[account].received_available_show.connect((stream, jid, show) => + on_received_available_show(account, new Jid(jid), show) + ); + stream_interactor.module_manager.presence_modules[account].received_unavailable.connect((stream, jid) => + on_received_unavailable(account, new Jid(jid)) + ); + stream_interactor.module_manager.presence_modules[account].received_subscription_request.connect((stream, jid) => + received_subscription_request(new Jid(jid), account) + ); + } + + private void on_received_available_show(Account account, Jid jid, string show) { + lock (resources) { + if (!resources.has_key(jid)){ + resources[jid] = new ArrayList(Jid.equals_func); + } + if (!resources[jid].contains(jid)) { + resources[jid].add(jid); + } + } + add_show(account, jid, show); + } + + private void on_received_unavailable(Account account, Jid jid) { + lock (resources) { + if (resources.has_key(jid)) { + resources[jid].remove(jid); + if (resources[jid].size == 0 || jid.is_bare()) { + resources.unset(jid); + } + } + } + add_show(account, jid, Show.OFFLINE); + } + + private void add_show(Account account, Jid jid, string s) { + Show show = new Show(jid, s, new DateTime.now_local()); + lock (shows) { + if (!shows.has_key(jid)) { + shows[jid] = new HashMap>(); + } + if (!shows[jid].has_key(jid)) { + shows[jid][jid] = new ArrayList(); + } + shows[jid][jid].add(show); + } + show_received(show, jid, account); + } +} + +public class Show : Object { + public const string ONLINE = Xmpp.Presence.Stanza.SHOW_ONLINE; + public const string AWAY = Xmpp.Presence.Stanza.SHOW_AWAY; + public const string CHAT = Xmpp.Presence.Stanza.SHOW_CHAT; + public const string DND = Xmpp.Presence.Stanza.SHOW_DND; + public const string XA = Xmpp.Presence.Stanza.SHOW_XA; + public const string OFFLINE = "offline"; + + public Jid jid; + public string as; + public DateTime datetime; + + public Show(Jid jid, string show, DateTime datetime) { + this.jid = jid; + this.as = show; + this.datetime = datetime; + } +} +} \ No newline at end of file diff --git a/libdino/src/service/roster_manager.vala b/libdino/src/service/roster_manager.vala new file mode 100644 index 00000000..106405e2 --- /dev/null +++ b/libdino/src/service/roster_manager.vala @@ -0,0 +1,82 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + public class RosterManager : StreamInteractionModule, Object { + public const string id = "roster_manager"; + + public signal void removed_roster_item(Account account, Jid jid, Roster.Item roster_item); + public signal void updated_roster_item(Account account, Jid jid, Roster.Item roster_item); + + private StreamInteractor stream_interactor; + + public static void start(StreamInteractor stream_interactor) { + RosterManager m = new RosterManager(stream_interactor); + stream_interactor.add_module(m); + } + + public RosterManager(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + stream_interactor.account_added.connect(on_account_added); + } + + public ArrayList get_roster(Account account) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + ArrayList ret = new ArrayList(); + if (stream != null) { + ret.add_all(Xmpp.Roster.Flag.get_flag(stream).get_roster()); + } + return ret; + } + + public Roster.Item? get_roster_item(Account account, Jid jid) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) { + return Xmpp.Roster.Flag.get_flag(stream).get_item(jid.bare_jid.to_string()); + } + return null; + } + + public void remove_jid(Account account, Jid jid) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) Xmpp.Roster.Module.get_module(stream).remove_jid(stream, jid.bare_jid.to_string()); + } + + public void add_jid(Account account, Jid jid, string? handle) { + Core.XmppStream? stream = stream_interactor.get_stream(account); + if (stream != null) Xmpp.Roster.Module.get_module(stream).add_jid(stream, jid.bare_jid.to_string(), handle); + } + + public static RosterManager? get_instance(StreamInteractor stream_interactor) { + return (RosterManager) stream_interactor.get_module(id); + } + + internal string get_id() { + return id; + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.roster_modules[account].received_roster.connect( (stream, roster) => { + on_roster_received(account, roster); + }); + stream_interactor.module_manager.roster_modules[account].item_removed.connect( (stream, roster_item) => { + removed_roster_item(account, new Jid(roster_item.jid), roster_item); + }); + stream_interactor.module_manager.roster_modules[account].item_updated.connect( (stream, roster_item) => { + on_roster_item_updated(account, roster_item); + }); + } + + private void on_roster_received(Account account, Collection roster_items) { + foreach (Roster.Item roster_item in roster_items) { + on_roster_item_updated(account, roster_item); + } + } + + private void on_roster_item_updated(Account account, Roster.Item roster_item) { + updated_roster_item(account, new Jid(roster_item.jid), roster_item); + } + } +} \ No newline at end of file diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala new file mode 100644 index 00000000..f3859e3b --- /dev/null +++ b/libdino/src/service/stream_interactor.vala @@ -0,0 +1,70 @@ +using Gee; + +using Xmpp; +using Dino.Entities; + +namespace Dino { + +public class StreamInteractor { + + public signal void account_added(Account account); + public signal void stream_negotiated(Account account); + + public ModuleManager module_manager; + public ConnectionManager connection_manager; + private ArrayList interaction_modules = new ArrayList(); + + public StreamInteractor(Database db) { + module_manager = new ModuleManager(db); + connection_manager = new ConnectionManager(module_manager); + + connection_manager.stream_opened.connect(on_stream_opened); + } + + public void connect(Account account) { + module_manager.add_account(account); + account_added(account); + connection_manager.connect(account); + } + + public void disconnect(Account account) { + connection_manager.disconnect(account); + } + + public ArrayList get_accounts() { + ArrayList ret = new ArrayList(Account.equals_func); + foreach (Account account in connection_manager.get_managed_accounts()) { + ret.add(account); + } + return ret; + } + + public Core.XmppStream? get_stream(Account account) { + return connection_manager.get_stream(account); + } + + public void add_module(StreamInteractionModule module) { + interaction_modules.add(module); + } + + public StreamInteractionModule? get_module(string id) { + foreach (StreamInteractionModule module in interaction_modules) { + if (module.get_id() == id) { + return module; + } + } + return null; + } + + private void on_stream_opened(Account account, Core.XmppStream stream) { + stream.stream_negotiated.connect( (stream) => { + stream_negotiated(account); + }); + } +} + +public interface StreamInteractionModule : Object { + internal abstract string get_id(); +} + +} \ No newline at end of file diff --git a/libdino/src/settings.vala b/libdino/src/settings.vala new file mode 100644 index 00000000..17177232 --- /dev/null +++ b/libdino/src/settings.vala @@ -0,0 +1,28 @@ +namespace Dino { + +public class Settings { + + private GLib.Settings gsettings; + + public bool send_read { + get { return gsettings.get_boolean("send-read"); } + set { gsettings.set_boolean("send-read", value); } + } + + public bool convert_utf8_smileys { + get { return gsettings.get_boolean("convert-utf8-smileys"); } + set { gsettings.set_boolean("convert-utf8-smileys", value); } + } + + public Settings(GLib.Settings gsettings) { + this.gsettings = gsettings; + } + + public static Settings instance() { + SettingsSchemaSource sss = SettingsSchemaSource.get_default(); + SettingsSchema schema = sss.lookup("org.dino-im", false); + return new Settings(new GLib.Settings.full(schema, null, null)); + } +} + +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/chat/add_contact_dialog.vala b/libdino/src/ui/add_conversation/chat/add_contact_dialog.vala new file mode 100644 index 00000000..1be0225b --- /dev/null +++ b/libdino/src/ui/add_conversation/chat/add_contact_dialog.vala @@ -0,0 +1,67 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Chat { + +[GtkTemplate (ui = "/org/dino-im/add_conversation/add_contact_dialog.ui")] +protected class AddContactDialog : Gtk.Dialog { + + [GtkChild] + private ComboBoxText accounts_comboboxtext; + + [GtkChild] + private Button ok_button; + + [GtkChild] + private Button cancel_button; + + [GtkChild] + private Entry jid_entry; + + [GtkChild] + private Entry alias_entry; + + [GtkChild] + private CheckButton subscribe_checkbutton; + + private StreamInteractor stream_interactor; + + public AddContactDialog(StreamInteractor stream_interactor) { + Object(use_header_bar : 1); + this.stream_interactor = stream_interactor; + + foreach (Account account in stream_interactor.get_accounts()) { + accounts_comboboxtext.append_text(account.bare_jid.to_string()); + } + accounts_comboboxtext.set_active(0); + + cancel_button.clicked.connect(() => { close(); }); + ok_button.clicked.connect(on_ok_button_clicked); + jid_entry.changed.connect(on_jid_entry_changed); + } + + private void on_ok_button_clicked() { + string? alias = alias_entry.text == "" ? null : alias_entry.text; + Account? account = null; + Jid jid = new Jid(jid_entry.text); + foreach (Account account2 in stream_interactor.get_accounts()) { + print(account2.bare_jid.to_string() + "\n"); + if (accounts_comboboxtext.get_active_text() == account2.bare_jid.to_string()) { + account = account2; + } + } + RosterManager.get_instance(stream_interactor).add_jid(account, jid, alias); + if (subscribe_checkbutton.active) { + PresenceManager.get_instance(stream_interactor).request_subscription(account, jid); + } + close(); + } + + private void on_jid_entry_changed() { + Jid parsed_jid = Jid.parse(jid_entry.text); + ok_button.set_sensitive(parsed_jid != null && parsed_jid.resourcepart == null); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/chat/dialog.vala b/libdino/src/ui/add_conversation/chat/dialog.vala new file mode 100644 index 00000000..80dac68e --- /dev/null +++ b/libdino/src/ui/add_conversation/chat/dialog.vala @@ -0,0 +1,82 @@ +using Gee; +using Gdk; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Chat { + +public class Dialog : Gtk.Dialog { + + public signal void conversation_opened(Conversation conversation); + + private Button ok_button; + + private RosterList roster_list; + private SelectJidFragment select_jid_fragment; + private StreamInteractor stream_interactor; + + public Dialog(StreamInteractor stream_interactor) { + Object(use_header_bar : 1); + this.title = "Start Chat"; + this.modal = true; + this.stream_interactor = stream_interactor; + + setup_headerbar(); + setup_view(); + } + + private void setup_headerbar() { + HeaderBar header_bar = get_header_bar() as HeaderBar; + header_bar.show_close_button = false; + + Button cancel_button = new Button(); + cancel_button.set_label("Cancel"); + cancel_button.visible = true; + header_bar.pack_start(cancel_button); + + ok_button = new Button(); + ok_button.get_style_context().add_class("suggested-action"); + ok_button.label = "Start"; + ok_button.sensitive = false; + ok_button.visible = true; + header_bar.pack_end(ok_button); + + cancel_button.clicked.connect(() => { close(); }); + ok_button.clicked.connect(on_ok_button_clicked); + } + + private void setup_view() { + roster_list = new RosterList(stream_interactor); + roster_list.row_activated.connect(() => { ok_button.clicked(); }); + select_jid_fragment = new SelectJidFragment(stream_interactor, roster_list); + select_jid_fragment.add_jid.connect((row) => { + AddContactDialog add_contact_dialog = new AddContactDialog(stream_interactor); + add_contact_dialog.set_transient_for(this); + add_contact_dialog.show(); + }); + select_jid_fragment.edit_jid.connect(() => { + + }); + select_jid_fragment.remove_jid.connect((row) => { + ListRow list_row = roster_list.get_selected_row() as ListRow; + RosterManager.get_instance(stream_interactor).remove_jid(list_row.account, list_row.jid); + }); + select_jid_fragment.notify["done"].connect(() => { + ok_button.sensitive = select_jid_fragment.done; + }); + get_content_area().add(select_jid_fragment); + } + + protected void on_ok_button_clicked() { + ListRow? selected_row = roster_list.get_selected_row() as ListRow; + if (selected_row != null) { + // TODO move in list to front immediately + ConversationManager.get_instance(stream_interactor).ensure_start_conversation(selected_row.jid, selected_row.account); + Conversation conversation = ConversationManager.get_instance(stream_interactor).get_conversation(selected_row.jid, selected_row.account); + conversation_opened(conversation); + } + close(); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/chat/roster_list.vala b/libdino/src/ui/add_conversation/chat/roster_list.vala new file mode 100644 index 00000000..9e970d8c --- /dev/null +++ b/libdino/src/ui/add_conversation/chat/roster_list.vala @@ -0,0 +1,77 @@ +using Gee; +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui.AddConversation.Chat { +protected class RosterList : FilterableList { + + public signal void conversation_selected(Conversation? conversation); + private StreamInteractor stream_interactor; + + private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); + + public RosterList(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + set_filter_func(filter); + set_header_func(header); + set_sort_func(sort); + + RosterManager.get_instance(stream_interactor).removed_roster_item.connect( (account, jid, roster_item) => { + Idle.add(() => { on_removed_roster_item(account, jid, roster_item); return false;});}); + RosterManager.get_instance(stream_interactor).updated_roster_item.connect( (account, jid, roster_item) => { + Idle.add(() => { on_updated_roster_item(account, jid, roster_item); return false;});}); + + foreach (Account account in stream_interactor.get_accounts()) { + foreach (Roster.Item roster_item in RosterManager.get_instance(stream_interactor).get_roster(account)) { + on_updated_roster_item(account, new Jid(roster_item.jid), roster_item); + } + } + } + + private void on_removed_roster_item(Account account, Jid jid, Roster.Item roster_item) { + if (rows.has_key(jid)) { + remove(rows[jid]); + rows.unset(jid); + } + } + + private void on_updated_roster_item(Account account, Jid jid, Roster.Item roster_item) { + on_removed_roster_item(account, jid, roster_item); + ListRow row = new ListRow.from_jid(stream_interactor, new Jid(roster_item.jid), account); + rows[jid] = row; + add(row); + invalidate_sort(); + invalidate_filter(); + } + + private void header(ListBoxRow row, ListBoxRow? before_row) { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + } + + private bool filter(ListBoxRow r) { + if (r.get_type().is_a(typeof(ListRow))) { + ListRow row = r as ListRow; + if (filter_values != null) { + foreach (string filter in filter_values) { + if (!(row.name_label.label.down().contains(filter.down()) || + row.jid.to_string().down().contains(filter.down()))) { + return false; + } + } + } + } + return true; + } + + public override int sort(ListBoxRow row1, ListBoxRow row2) { + ListRow c1 = (row1 as ListRow); + ListRow c2 = (row2 as ListRow); + return c1.name_label.label.collate(c2.name_label.label); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/conference/add_groupchat_dialog.vala b/libdino/src/ui/add_conversation/conference/add_groupchat_dialog.vala new file mode 100644 index 00000000..05589fe1 --- /dev/null +++ b/libdino/src/ui/add_conversation/conference/add_groupchat_dialog.vala @@ -0,0 +1,107 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Conference { + +[GtkTemplate (ui = "/org/dino-im/add_conversation/add_groupchat_dialog.ui")] +protected class AddGroupchatDialog : Gtk.Dialog { + + [GtkChild] + private Stack accounts_stack; + + [GtkChild] + private ComboBoxText accounts_comboboxtext; + + [GtkChild] + private Label account_label; + + [GtkChild] + private Button ok_button; + + [GtkChild] + private Button cancel_button; + + [GtkChild] + private Entry jid_entry; + + [GtkChild] + private Entry alias_entry; + + [GtkChild] + private Entry nick_entry; + + [GtkChild] + private CheckButton autojoin_checkbutton; + + private StreamInteractor stream_interactor; + private Xmpp.Xep.Bookmarks.Conference? edit_confrence = null; + private bool alias_entry_changed = false; + + public AddGroupchatDialog(StreamInteractor stream_interactor) { + Object(use_header_bar : 1); + this.stream_interactor = stream_interactor; + ok_button.label = "Add"; + ok_button.get_style_context().add_class("suggested-action"); // TODO why doesn't it work in XML + accounts_stack.set_visible_child_name("combobox"); + foreach (Account account in stream_interactor.get_accounts()) { + accounts_comboboxtext.append_text(account.bare_jid.to_string()); + } + accounts_comboboxtext.set_active(0); + + cancel_button.clicked.connect(() => { close(); }); + ok_button.clicked.connect(on_ok_button_clicked); + jid_entry.key_release_event.connect(on_jid_key_release); + nick_entry.key_release_event.connect(check_ok); + } + + public AddGroupchatDialog.for_conference(StreamInteractor stream_interactor, Account account, Xmpp.Xep.Bookmarks.Conference conference) { + this(stream_interactor); + edit_confrence = conference; + ok_button.label = "Save"; + ok_button.sensitive = true; + accounts_stack.set_visible_child_name("label"); + account_label.label = account.bare_jid.to_string(); + jid_entry.text = conference.jid; + nick_entry.text = conference.nick; + autojoin_checkbutton.active = conference.autojoin; + alias_entry.text = conference.name; + } + + private bool on_jid_key_release() { + check_ok(); + if (!alias_entry_changed) { + Jid? parsed_jid = Jid.parse(jid_entry.text); + alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text; + } + return false; + } + + private bool check_ok() { + Jid? parsed_jid = Jid.parse(jid_entry.text); + ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null && + nick_entry.text != "" && alias_entry.text != null; + return false; + } + + private void on_ok_button_clicked() { + Account? account = null; + foreach (Account account2 in stream_interactor.get_accounts()) { + if (accounts_comboboxtext.get_active_text() == account2.bare_jid.to_string()) { + account = account2; + } + } + Xmpp.Xep.Bookmarks.Conference conference = new Xmpp.Xep.Bookmarks.Conference(jid_entry.text); + conference.nick = nick_entry.text; + conference.name = alias_entry.text; + conference.autojoin = autojoin_checkbutton.active; + if (edit_confrence == null) { + MucManager.get_instance(stream_interactor).add_bookmark(account, conference); + } else { + MucManager.get_instance(stream_interactor).replace_bookmark(account, edit_confrence, conference); + } + close(); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/conference/conference_details_fragment.vala b/libdino/src/ui/add_conversation/conference/conference_details_fragment.vala new file mode 100644 index 00000000..324c133d --- /dev/null +++ b/libdino/src/ui/add_conversation/conference/conference_details_fragment.vala @@ -0,0 +1,175 @@ +using Gdk; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Conference { + +[GtkTemplate (ui = "/org/dino-im/add_conversation/conference_details_fragment.ui")] +protected class ConferenceDetailsFragment : Box { + + public bool done { + get { + Jid? parsed_jid = Jid.parse(jid); + return parsed_jid != null && parsed_jid.localpart != null && + parsed_jid.resourcepart == null && nick != ""; + } + private set {} + } + + public Account account { + owned get { + foreach (Account account in stream_interactor.get_accounts()) { + if (accounts_comboboxtext.get_active_text() == account.bare_jid.to_string()) { + return account; + } + } + return null; + } + set { + accounts_label.label = value.bare_jid.to_string(); + accounts_comboboxtext.set_active_id(value.bare_jid.to_string()); + } + } + public string jid { + get { return jid_entry.text; } + set { + jid_label.label = value; + jid_entry.text = value; + } + } + public string nick { + get { return nick_entry.text; } + set { + nick_label.label = value; + nick_entry.text = value; + } + } + public string password { + get { return password_entry.text == "" ? null : password_entry.text; } + set { + password_label.label = value; + password_entry.text = value; + } + } + + [GtkChild] + private Stack accounts_stack; + + [GtkChild] + private Stack jid_stack; + + [GtkChild] + private Stack nick_stack; + + [GtkChild] + private Stack password_stack; + + [GtkChild] + private Button accounts_button; + + [GtkChild] + private Button jid_button; + + [GtkChild] + private Button nick_button; + + [GtkChild] + private Button password_button; + + [GtkChild] + private Label accounts_label; + + [GtkChild] + private Label jid_label; + + [GtkChild] + private Label nick_label; + + [GtkChild] + private Label password_label; + + [GtkChild] + private ComboBoxText accounts_comboboxtext; + + [GtkChild] + private Entry jid_entry; + + [GtkChild] + private Entry nick_entry; + + [GtkChild] + private Entry password_entry; + + private StreamInteractor stream_interactor; + + public ConferenceDetailsFragment(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + accounts_stack.set_visible_child_name("label"); + jid_stack.set_visible_child_name("label"); + nick_stack.set_visible_child_name("label"); + password_stack.set_visible_child_name("label"); + + accounts_button.clicked.connect(() => { set_active_stack(accounts_stack); }); + jid_button.clicked.connect(() => { set_active_stack(jid_stack); }); + nick_button.clicked.connect(() => { set_active_stack(nick_stack); }); + password_button.clicked.connect(() => { set_active_stack(password_stack); }); + + accounts_comboboxtext.changed.connect(() => {accounts_label.label = accounts_comboboxtext.get_active_text(); }); + jid_entry.key_release_event.connect(on_jid_key_release_event); + nick_entry.key_release_event.connect(on_nick_key_release_event); + password_entry.key_release_event.connect(on_password_key_release_event); + + jid_entry.key_release_event.connect(() => { done = true; return false; }); // just for notifying + nick_entry.key_release_event.connect(() => { done = true; return false; }); + + foreach (Account account in stream_interactor.get_accounts()) { + accounts_comboboxtext.append_text(account.bare_jid.to_string()); + } + accounts_comboboxtext.set_active(0); + } + + public void set_editable() { + accounts_stack.set_visible_child_name("entry"); + nick_stack.set_visible_child_name("entry"); + password_stack.set_visible_child_name("entry"); + } + + public void clear() { + jid = ""; + nick = ""; + password = ""; + } + + private bool on_jid_key_release_event(EventKey event) { + jid_label.label = jid_entry.text; + if (event.keyval == Key.Return) jid_stack.set_visible_child_name("label"); + return false; + } + + private bool on_nick_key_release_event(EventKey event) { + nick_label.label = nick_entry.text; + if (event.keyval == Key.Return) nick_stack.set_visible_child_name("label"); + return false; + } + + private bool on_password_key_release_event(EventKey event) { + string filler = ""; + for (int i = 0; i < password_entry.text.length; i++) filler += password_entry.get_invisible_char().to_string(); + password_label.label = filler; + if (event.keyval == Key.Return) password_stack.set_visible_child_name("label"); + return false; + } + + private void set_active_stack(Stack stack) { + stack.set_visible_child_name("entry"); + if (stack != accounts_stack) accounts_stack.set_visible_child_name("label"); + if (stack != jid_stack) jid_stack.set_visible_child_name("label"); + if (stack != nick_stack) nick_stack.set_visible_child_name("label"); + if (stack != password_stack) password_stack.set_visible_child_name("label"); + } + +} + +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/conference/conference_list.vala b/libdino/src/ui/add_conversation/conference/conference_list.vala new file mode 100644 index 00000000..2e461472 --- /dev/null +++ b/libdino/src/ui/add_conversation/conference/conference_list.vala @@ -0,0 +1,105 @@ +using Gee; +using Gtk; + +using Xmpp; +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Conference { +protected class ConferenceList : FilterableList { + + public signal void conversation_selected(Conversation? conversation); + + private StreamInteractor stream_interactor; + private HashMap> lists = new HashMap>(); + + public ConferenceList(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + set_filter_func(filter); + set_header_func(header); + set_sort_func(sort); + + MucManager.get_instance(stream_interactor).bookmarks_updated.connect((account, conferences) => { + Idle.add(() => { + lists[account] = conferences; + refresh_conferences(); + return false; + }); + }); + + foreach (Account account in stream_interactor.get_accounts()) { + MucManager.get_instance(stream_interactor).get_bookmarks(account, new BookmarksListener(this, stream_interactor, account)); + } + } + + public void refresh_conferences() { + @foreach((widget) => { remove(widget); }); + foreach (Account account in lists.keys) { + foreach (Xep.Bookmarks.Conference conference in lists[account]) { + add(new ConferenceListRow(stream_interactor, conference, account)); + } + } + } + + private class BookmarksListener : Xep.Bookmarks.ConferencesRetrieveResponseListener, Object { + ConferenceList outer; + Account account; + public BookmarksListener(ConferenceList outer, StreamInteractor stream_interactor, Account account) { + this.outer = outer; + this.account = account; + } + + public void on_result(Core.XmppStream stream, ArrayList conferences) { + outer.lists[account] = conferences; + Idle.add(() => { outer.refresh_conferences(); return false; }); + } + } + + private void header(ListBoxRow row, ListBoxRow? before_row) { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + } + + private bool filter(ListBoxRow r) { + if (r.get_type().is_a(typeof(ListRow))) { + ListRow row = r as ListRow; + if (filter_values != null) { + foreach (string filter in filter_values) { + if (!(row.name_label.label.down().contains(filter.down()) || + row.jid.to_string().down().contains(filter.down()))) { + return false; + } + } + } + } + return true; + } + + public override int sort(ListBoxRow row1, ListBoxRow row2) { + ListRow c1 = (row1 as ListRow); + ListRow c2 = (row2 as ListRow); + return c1.name_label.label.collate(c2.name_label.label); + } +} + +internal class ConferenceListRow : ListRow { + + public Xep.Bookmarks.Conference bookmark; + + public ConferenceListRow(StreamInteractor stream_interactor, Xep.Bookmarks.Conference bookmark, Account account) { + this.jid = new Jid(bookmark.jid); + this.account = account; + this.bookmark = bookmark; + + if (bookmark.name != "" && bookmark.name != bookmark.jid) { + name_label.label = bookmark.name; + via_label.label = bookmark.jid; + } else { + name_label.label = bookmark.jid; + via_label.visible = false; + } + image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_stateless(true).draw_jid(stream_interactor, jid, account)); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/conference/dialog.vala b/libdino/src/ui/add_conversation/conference/dialog.vala new file mode 100644 index 00000000..ff548699 --- /dev/null +++ b/libdino/src/ui/add_conversation/conference/dialog.vala @@ -0,0 +1,166 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation.Conference { + +public class Dialog : Gtk.Dialog { + + public signal void conversation_opened(Conversation conversation); + + private Stack stack = new Stack(); + private Button cancel_button; + private Button ok_button; + private Label cancel_label = new Label("Cancel") {visible=true}; + private Image cancel_image = new Image.from_icon_name("go-previous-symbolic", IconSize.MENU) {visible=true}; + + private SelectJidFragment select_fragment; + private ConferenceDetailsFragment details_fragment; + private ConferenceList conference_list; + + private StreamInteractor stream_interactor; + + public Dialog(StreamInteractor stream_interactor) { + Object(use_header_bar : 1); + this.title = "Join Conference"; + this.modal = true; + this.stream_interactor = stream_interactor; + + stack.visible = true; + stack.vhomogeneous = false; + get_content_area().add(stack); + + setup_headerbar(); + setup_jid_add_view(); + setup_conference_details_view(); + show_jid_add_view(); + } + + private void show_jid_add_view() { + cancel_button.remove(cancel_image); + cancel_button.add(cancel_label); + cancel_button.clicked.disconnect(show_jid_add_view); + cancel_button.clicked.connect(close); + ok_button.label = "Next"; + ok_button.sensitive = select_fragment.done; + ok_button.clicked.disconnect(on_ok_button_clicked); + ok_button.clicked.connect(on_next_button_clicked); + details_fragment.notify["done"].disconnect(set_ok_sensitive_from_details); + select_fragment.notify["done"].connect(set_ok_sensitive_from_select); + stack.transition_type = StackTransitionType.SLIDE_RIGHT; + stack.set_visible_child_name("select"); + } + + private void show_conference_details_view() { + cancel_button.remove(cancel_label); + cancel_button.add(cancel_image); + cancel_button.clicked.disconnect(close); + cancel_button.clicked.connect(show_jid_add_view); + ok_button.label = "Join"; + ok_button.sensitive = details_fragment.done; + ok_button.clicked.disconnect(on_next_button_clicked); + ok_button.clicked.connect(on_ok_button_clicked); + select_fragment.notify["done"].disconnect(set_ok_sensitive_from_select); + details_fragment.notify["done"].connect(set_ok_sensitive_from_details); + stack.transition_type = StackTransitionType.SLIDE_LEFT; + stack.set_visible_child_name("details"); + animate_window_resize(); + } + + private void setup_headerbar() { + HeaderBar header_bar = get_header_bar() as HeaderBar; + header_bar.show_close_button = false; + + cancel_button = new Button(); + header_bar.pack_start(cancel_button); + cancel_button.visible = true; + + ok_button = new Button(); + header_bar.pack_end(ok_button); + ok_button.get_style_context().add_class("suggested-action"); + ok_button.visible = true; + ok_button.can_focus = true; + ok_button.can_default = true; + ok_button.has_default = true; + } + + private void setup_jid_add_view() { + conference_list = new ConferenceList(stream_interactor); + conference_list.row_activated.connect(() => { ok_button.clicked(); }); + select_fragment = new SelectJidFragment(stream_interactor, conference_list); + select_fragment.add_jid.connect((row) => { + AddGroupchatDialog dialog = new AddGroupchatDialog(stream_interactor); + dialog.set_transient_for(this); + dialog.show(); + }); + select_fragment.edit_jid.connect((row) => { + ConferenceListRow conference_row = row as ConferenceListRow; + AddGroupchatDialog dialog = new AddGroupchatDialog.for_conference(stream_interactor, conference_row.account, conference_row.bookmark); + dialog.set_transient_for(this); + dialog.show(); + }); + select_fragment.remove_jid.connect((row) => { + ConferenceListRow conference_row = row as ConferenceListRow; + MucManager.get_instance(stream_interactor).remove_bookmark(conference_row.account, conference_row.bookmark); + }); + stack.add_named(select_fragment, "select"); + } + + private void setup_conference_details_view() { + details_fragment = new ConferenceDetailsFragment(stream_interactor); + stack.add_named(details_fragment, "details"); + } + + private void set_ok_sensitive_from_select() { + ok_button.sensitive = select_fragment.done; + } + + private void set_ok_sensitive_from_details() { + ok_button.sensitive = select_fragment.done; + } + + private void on_next_button_clicked() { + details_fragment.clear(); + ListRow? row = conference_list.get_selected_row() as ListRow; + ConferenceListRow? conference_row = conference_list.get_selected_row() as ConferenceListRow; + if (conference_row != null) { + details_fragment.jid = conference_row.bookmark.jid; + details_fragment.nick = conference_row.bookmark.nick; + if (conference_row.bookmark.password != null) details_fragment.password = conference_row.bookmark.password; + ok_button.grab_focus(); + } else if (row != null) { + details_fragment.jid = row.jid.to_string(); + details_fragment.set_editable(); + } + show_conference_details_view(); + } + + private void on_ok_button_clicked() { + MucManager.get_instance(stream_interactor).join(details_fragment.account, new Jid(details_fragment.jid), details_fragment.nick, details_fragment.password); + close(); + } + + private void close() { + base.close(); + } + + private void animate_window_resize() { + int def_height, curr_width, curr_height; + get_size(out curr_width, out curr_height); + stack.get_preferred_height(null, out def_height); + int difference = def_height - curr_height; + Timer timer = new Timer(); + Timeout.add((int) (stack.transition_duration / 30), + () => { + ulong microsec; + timer.elapsed(out microsec); + ulong millisec = microsec / 1000; + double partial = double.min(1, (double) millisec / stack.transition_duration); + resize(curr_width, (int) (curr_height + difference * partial)); + return millisec < stack.transition_duration; + }); + } +} + +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/list_row.vala b/libdino/src/ui/add_conversation/list_row.vala new file mode 100644 index 00000000..5c2eff97 --- /dev/null +++ b/libdino/src/ui/add_conversation/list_row.vala @@ -0,0 +1,43 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation { + +[GtkTemplate (ui = "/org/dino-im/add_conversation/list_row.ui")] +public class ListRow : ListBoxRow { + + [GtkChild] + public Image image; + + [GtkChild] + public Label name_label; + + [GtkChild] + public Label via_label; + + public Jid? jid; + public Account? account; + + public ListRow() {} + + public ListRow.from_jid(StreamInteractor stream_interactor, Jid jid, Account account) { + this.jid = jid; + this.account = account; + + string display_name = Util.get_display_name(stream_interactor, jid, account); + if (stream_interactor.get_accounts().size > 1) { + via_label.label = @"via $(account.bare_jid)"; + this.has_tooltip = true; + set_tooltip_text(jid.to_string()); + } else if (display_name != jid.bare_jid.to_string()){ + via_label.label = jid.bare_jid.to_string(); + } else { + via_label.visible = false; + } + name_label.label = display_name; + image.set_from_pixbuf((new AvatarGenerator(35, 35)).draw_jid(stream_interactor, jid, account)); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/add_conversation/select_jid_fragment.vala b/libdino/src/ui/add_conversation/select_jid_fragment.vala new file mode 100644 index 00000000..847a9ecb --- /dev/null +++ b/libdino/src/ui/add_conversation/select_jid_fragment.vala @@ -0,0 +1,124 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.AddConversation { + +[GtkTemplate (ui = "/org/dino-im/add_conversation/select_jid_fragment.ui")] +public class SelectJidFragment : Gtk.Box { + + public signal void add_jid(); + public signal void edit_jid(ListRow row); + public signal void remove_jid(ListRow row); + public bool done { + get { + return filterable_list.get_selected_row() != null; + } + private set {} } + + [GtkChild] + private Entry entry; + + [GtkChild] + private Box box; + + [GtkChild] + private Button add_button; + + [GtkChild] + private Button edit_button; + + [GtkChild] + private Button remove_button; + + private FilterableList filterable_list; + private ArrayList added_rows = new ArrayList(); + private StreamInteractor stream_interactor; + + public SelectJidFragment(StreamInteractor stream_interactor, FilterableList filterable_list) { + this.stream_interactor = stream_interactor; + this.filterable_list = filterable_list; + + filterable_list.visible = true; + filterable_list.activate_on_single_click = false; + filterable_list.vexpand = true; + box.add(filterable_list); + + filterable_list.set_sort_func(sort); + filterable_list.row_selected.connect(check_buttons_active); + filterable_list.row_selected.connect(() => { done = true; }); // just for notifying + entry.changed.connect(on_entry_changed); + add_button.clicked.connect(() => { add_jid(); }); + remove_button.clicked.connect(() => { remove_jid(filterable_list.get_selected_row() as ListRow); }); + edit_button.clicked.connect(() => { edit_jid(filterable_list.get_selected_row() as ListRow); }); + } + + private void on_entry_changed() { + foreach (AddListRow row in added_rows) { + filterable_list.remove(row); + } + added_rows.clear(); + + string[] ? values; + string str = entry.get_text(); + values = str == "" ? null : str.split(" "); + filterable_list.set_filter_values(values); + Jid? parsed_jid = Jid.parse(str); + if (parsed_jid != null && parsed_jid.localpart != null) { + foreach (Account account in stream_interactor.get_accounts()) { + AddListRow row = new AddListRow(stream_interactor, str, account); + filterable_list.add(row); + added_rows.add(row); + } + } + } + + private void check_buttons_active() { + ListBoxRow? row = filterable_list.get_selected_row(); + bool active = row != null && !row.get_type().is_a(typeof(AddListRow)); + edit_button.sensitive = active; + remove_button.sensitive = active; + } + + private int sort(ListBoxRow row1, ListBoxRow row2) { + AddListRow al1 = (row1 as AddListRow); + AddListRow al2 = (row2 as AddListRow); + if (al1 != null && al2 == null) { + return -1; + } else if (al2 != null && al1 == null) { + return 1; + } + return filterable_list.sort(row1, row2); + } + + private class AddListRow : ListRow { + + public AddListRow(StreamInteractor stream_interactor, string jid, Account account) { + this.account = account; + this.jid = new Jid(jid); + + name_label.label = jid; + if (stream_interactor.get_accounts().size > 1) { + via_label.label = account.bare_jid.to_string(); + } else { + via_label.visible = false; + } + image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_greyscale(true).draw_text("?")); + } + } +} + +public abstract class FilterableList : Gtk.ListBox { + public string[]? filter_values; + + public void set_filter_values(string[] values) { + if (filter_values == values) return; + filter_values = values; + invalidate_filter(); + } + + public abstract int sort(ListBoxRow row1, ListBoxRow row2); +} + +} \ No newline at end of file diff --git a/libdino/src/ui/application.vala b/libdino/src/ui/application.vala new file mode 100644 index 00000000..0878e50d --- /dev/null +++ b/libdino/src/ui/application.vala @@ -0,0 +1,113 @@ +using Gtk; + +using Dino.Entities; + +public class Dino.Ui.Application : Gtk.Application { + + private Database db; + private StreamInteractor stream_interaction; + + private Notifications notifications; + private UnifiedWindow? window; + private ConversationSelector.View? filterable_conversation_list; + private ConversationSelector.List? conversation_list; + private ConversationSummary.View? conversation_frame; + private ChatInput? chat_input; + + public Application() { + this.db = new Database("store.sqlite3"); + this.stream_interaction = new StreamInteractor(db); + + AvatarManager.start(stream_interaction, db); + MessageManager.start(stream_interaction, db); + CounterpartInteractionManager.start(stream_interaction); + PresenceManager.start(stream_interaction); + MucManager.start(stream_interaction); + PgpManager.start(stream_interaction, db); + RosterManager.start(stream_interaction); + ConversationManager.start(stream_interaction, db); + ChatInteraction.start(stream_interaction); + + Notify.init("dino"); + notifications = new Notifications(stream_interaction); + notifications.start(); + + load_css(); + } + + public override void activate() { + create_set_app_menu(); + create_window(); + window.show_all(); + restore(); + } + + private void create_window() { + window = new UnifiedWindow(this, stream_interaction); + + filterable_conversation_list = window.filterable_conversation_list; + conversation_list = window.filterable_conversation_list.conversation_list; + conversation_frame = window.conversation_frame; + chat_input = window.chat_input; + } + + private void show_accounts_window() { + ManageAccounts.Dialog dialog = new ManageAccounts.Dialog(stream_interaction, db); + dialog.set_transient_for(window); + dialog.account_enabled.connect(add_connection); + dialog.account_disabled.connect(remove_connection); + dialog.show(); + } + + private void show_settings_window() { + SettingsDialog dialog = new SettingsDialog(); + dialog.set_transient_for(window); + dialog.show(); + } + + private void create_set_app_menu() { + SimpleAction accounts_action = new SimpleAction("accounts", null); + accounts_action.activate.connect(show_accounts_window); + add_action(accounts_action); + + SimpleAction settings_action = new SimpleAction("settings", null); + settings_action.activate.connect(show_settings_window); + add_action(settings_action); + + SimpleAction quit_action = new SimpleAction("quit", null); + quit_action.activate.connect(quit); + add_action(quit_action); + add_accelerator("Q", "app.quit", null); + + Builder builder = new Builder.from_resource("/org/dino-im/menu_app.ui"); + MenuModel menu = builder.get_object("menu_app") as MenuModel; + + set_app_menu(menu); + } + + private void restore() { + foreach (Account account in db.get_accounts()) { + if (account.enabled) add_connection(account); + } + } + + private void add_connection(Account account) { + stream_interaction.connect(account); + } + + private void remove_connection(Account account) { + stream_interaction.disconnect(account); + } + + private void load_css() { + var css_provider = new Gtk.CssProvider (); + try { + var file = File.new_for_uri("resource:///org/dino-im/style.css"); + css_provider.load_from_file (file); + } catch (GLib.Error e) { + warning ("loading css: %s", e.message); + } + Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } +} + diff --git a/libdino/src/ui/avatar_generator.vala b/libdino/src/ui/avatar_generator.vala new file mode 100644 index 00000000..e168c4a4 --- /dev/null +++ b/libdino/src/ui/avatar_generator.vala @@ -0,0 +1,233 @@ +using Cairo; +using Gee; +using Gdk; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui { +public class AvatarGenerator { + + private const string COLOR_GREY = "E0E0E0"; + private const string GROUPCHAT_ICON = "system-users-symbolic"; + + StreamInteractor? stream_interactor; + bool greyscale = false; + bool stateless = false; + int width; + int height; + int scale_factor; + + public AvatarGenerator(int width, int height, int scale_factor = 1) { + this.width = width; + this.height = height; + this.scale_factor = scale_factor; + } + + public Pixbuf draw_jid(StreamInteractor stream_interactor, Jid jid, Account account) { + this.stream_interactor = stream_interactor; + return crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor)); + } + + public Pixbuf draw_message(StreamInteractor stream_interactor, Message message) { + Jid? real_jid = MucManager.get_instance(stream_interactor).get_message_real_jid(message); + return draw_jid(stream_interactor, real_jid != null ? real_jid : message.from, message.account); + } + + public Pixbuf draw_conversation(StreamInteractor stream_interactor, Conversation conversation) { + return draw_jid(stream_interactor, conversation.counterpart, conversation.account); + } + + public Pixbuf draw_account(StreamInteractor stream_interactor, Account account) { + return draw_jid(stream_interactor, account.bare_jid, account); + } + + public Pixbuf draw_text(string text) { + string color = greyscale ? COLOR_GREY : Util.get_avatar_hex_color(text); + Pixbuf pixbuf = draw_colored_rectangle_text(color, text, width, height); + return crop_corners(pixbuf); + } + + public AvatarGenerator set_greyscale(bool greyscale) { + this.greyscale = greyscale; + return this; + } + + public AvatarGenerator set_stateless(bool stateless) { + this.stateless = stateless; + return this; + } + + private int get_left_border() { + return (int)Math.floor(scale_factor/2.0); + } + + private int get_right_border() { + return (int)Math.ceil(scale_factor/2.0); + } + + private void add_tile_to_pixbuf(Pixbuf pixbuf, Jid jid, Account account, int width, int height, int x, int y) { + Pixbuf tile = draw_chat_tile(jid, account, width, height); + tile.copy_area(0, 0, width, height, pixbuf, x, y); + } + + private Pixbuf draw_tile(Jid jid, Account account, int width, int height) { + if (MucManager.get_instance(stream_interactor).is_groupchat(jid, account)) { + return draw_groupchat_tile(jid, account, width, height); + } else { + return draw_chat_tile(jid, account, width, height); + } + } + + private Pixbuf draw_chat_tile(Jid jid, Account account, int width, int height) { + if (MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { + Jid? real_jid = MucManager.get_instance(stream_interactor).get_real_jid(jid, account); + if (real_jid != null) { + return draw_tile(real_jid, account, width, height); + } + } + Pixbuf? avatar = AvatarManager.get_instance(stream_interactor).get_avatar(account, jid); + if (avatar != null) { + double desired_ratio = (double) width / height; + double avatar_ratio = (double) avatar.width / avatar.height; + if (avatar_ratio > desired_ratio) { + int comp_width = width * avatar.height / height; + avatar = new Pixbuf.subpixbuf(avatar, avatar.width / 2 - comp_width / 2, 0, comp_width, avatar.height); + } else if (avatar_ratio < desired_ratio) { + int comp_height = height * avatar.width / width; + avatar = new Pixbuf.subpixbuf(avatar, 0, avatar.height / 2 - comp_height / 2, avatar.width, comp_height); + } + avatar = avatar.scale_simple(width, height, InterpType.BILINEAR); + if (greyscale) avatar = convert_to_greyscale(avatar); + return avatar; + } else { + string display_name = Util.get_display_name(stream_interactor, jid, account); + string color = greyscale ? COLOR_GREY : Util.get_avatar_hex_color(display_name); + return draw_colored_rectangle_text(color, display_name.get_char(0).toupper().to_string(), width, height); + } + } + + private Pixbuf draw_groupchat_tile(Jid jid, Account account, int width, int height) { + ArrayList? occupants = MucManager.get_instance(stream_interactor).get_other_occupants(jid, account); + if (stateless || occupants == null || occupants.size == 0) { + return draw_chat_tile(jid, account, width, height); + } + Pixbuf pixbuf = initialize_pixbuf(width, height); + if (occupants.size == 1 || occupants.size == 2 || occupants.size == 3) { + add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height, 0, 0); + if (occupants.size == 1) { + add_tile_to_pixbuf(pixbuf, account.bare_jid, account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); + } else if (occupants.size == 2) { + add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); + } else if (occupants.size == 3) { + add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); + add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); + } + } else if (occupants.size >= 4) { + add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height / 2 - get_right_border(), 0, 0); + add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); + add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_right_border(), height / 2 - get_left_border(), 0, height / 2 + get_left_border()); + if (occupants.size == 4) { + add_tile_to_pixbuf(pixbuf, occupants[3], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); + } else if (occupants.size > 4) { + Pixbuf plus_pixbuf = draw_colored_rectangle_text("555753", "+", width / 2 - get_left_border(), height / 2 - get_left_border()); + if (greyscale) plus_pixbuf = convert_to_greyscale(plus_pixbuf); + plus_pixbuf.copy_area(0, 0, width / 2 - get_left_border(), height / 2 - get_left_border(), pixbuf, width / 2 + get_left_border(), height / 2 + get_left_border()); + } + } + return pixbuf; + } + + public Pixbuf draw_colored_icon(string hex_color, string icon, int width, int height) { + int ICON_SIZE = width > 20 * scale_factor ? 17 * scale_factor : 14 * scale_factor; + + Context rectancle_context = new Context(new ImageSurface(Format.ARGB32, width, height)); + draw_colored_rectangle(rectancle_context, hex_color, width, height); + + Pixbuf icon_pixbuf = IconTheme.get_default().load_icon(icon, ICON_SIZE, IconLookupFlags.FORCE_SIZE); + Surface icon_surface = cairo_surface_create_from_pixbuf(icon_pixbuf, 1, null); + Context context = new Context(icon_surface); + context.set_operator(Operator.IN); + context.set_source_rgba(1, 1, 1, 1); + context.rectangle(0, 0, width, height); + context.fill(); + + rectancle_context.set_source_surface(icon_surface, width / 2 - ICON_SIZE / 2, height / 2 - ICON_SIZE / 2); + rectancle_context.paint(); + + return pixbuf_get_from_surface(rectancle_context.get_target(), 0, 0, width, height); + } + + public Pixbuf draw_colored_rectangle_text(string hex_color, string text, int width, int height) { + Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); + draw_colored_rectangle(ctx, hex_color, width, height); + draw_center_text(ctx, text, width < 40 * scale_factor ? 17 * scale_factor : 25 * scale_factor, width, height); + return pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); + } + + private static void draw_center_text(Context ctx, string text, int fontsize, int width, int height) { + ctx.select_font_face("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); + ctx.set_font_size(fontsize); + Cairo.TextExtents extents; + ctx.text_extents(text, out extents); + double x_pos = width/2 - (extents.width/2 + extents.x_bearing); + double y_pos = height/2 - (extents.height/2 + extents.y_bearing); + ctx.move_to(x_pos, y_pos); + ctx.set_source_rgba(1, 1, 1, 1); + ctx.show_text(text); + } + + private static void draw_colored_rectangle(Context ctx, string hex_color, int width, int height) { + set_source_hex_color(ctx, hex_color); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + } + + private static Pixbuf convert_to_greyscale(Pixbuf pixbuf) { + Surface surface = cairo_surface_create_from_pixbuf(pixbuf, 1, null); + Context context = new Context(surface); + // convert to greyscale + context.set_operator(Operator.HSL_COLOR); + context.set_source_rgb(1, 1, 1); + context.rectangle(0, 0, pixbuf.width, pixbuf.height); + context.fill(); + // make the visible part more light + context.set_operator(Operator.ATOP); + context.set_source_rgba(1, 1, 1, 0.7); + context.rectangle(0, 0, pixbuf.width, pixbuf.height); + context.fill(); + return pixbuf_get_from_surface(context.get_target(), 0, 0, pixbuf.width, pixbuf.height); + } + + private Pixbuf crop_corners(Pixbuf pixbuf, double radius = 3) { + radius *= scale_factor; + Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); + cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); + double degrees = Math.PI / 180.0; + ctx.new_sub_path(); + ctx.arc(pixbuf.width - radius, radius, radius, -90 * degrees, 0 * degrees); + ctx.arc(pixbuf.width - radius, pixbuf.height - radius, radius, 0 * degrees, 90 * degrees); + ctx.arc(radius, pixbuf.height - radius, radius, 90 * degrees, 180 * degrees); + ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); + ctx.close_path(); + ctx.clip(); + ctx.paint(); + return pixbuf_get_from_surface(ctx.get_target(), 0, 0, pixbuf.width, pixbuf.height); + } + + private static Pixbuf initialize_pixbuf(int width, int height) { + Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); + ctx.set_source_rgba(1, 1, 1, 0); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + return pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); + } + + private static void set_source_hex_color(Context ctx, string hex_color) { + ctx.set_source_rgba((double) hex_color.substring(0, 2).to_long(null, 16) / 255, + (double) hex_color.substring(2, 2).to_long(null, 16) / 255, + (double) hex_color.substring(4, 2).to_long(null, 16) / 255, + hex_color.length > 6 ? (double) hex_color.substring(6, 2).to_long(null, 16) / 255 : 1); + } +} +} diff --git a/libdino/src/ui/chat_input.vala b/libdino/src/ui/chat_input.vala new file mode 100644 index 00000000..d2f9c562 --- /dev/null +++ b/libdino/src/ui/chat_input.vala @@ -0,0 +1,123 @@ +using Gdk; +using Gee; +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui { +[GtkTemplate (ui = "/org/dino-im/chat_input.ui")] +public class ChatInput : Grid { + + [GtkChild] + private TextView text_input; + + private Conversation? conversation; + private StreamInteractor stream_interactor; + private HashMap entry_cache = new HashMap(Conversation.hash_func, Conversation.equals_func); + private static HashMap smiley_translations = new HashMap(); + + static construct { + smiley_translations[":)"] = "🙂"; + smiley_translations[":D"] = "😀"; + smiley_translations[";)"] = "😉"; + smiley_translations["O:)"] = "😇"; + smiley_translations["]:>"] = "😈"; + smiley_translations[":o"] = "😮"; + smiley_translations[":P"] = "😛"; + smiley_translations[";P"] = "😜"; + smiley_translations[":("] = "🙁"; + smiley_translations[":'("] = "😢"; + smiley_translations[":/"] = "😕"; + smiley_translations["-.-"] = "😑"; + } + + public ChatInput(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public void initialize_for_conversation(Conversation conversation) { + if (this.conversation != null) { + if (text_input.buffer.text != "") { + entry_cache[this.conversation] = text_input.buffer.text; + } else { + entry_cache.unset(this.conversation); + } + } + this.conversation = conversation; + text_input.buffer.text = ""; + if (entry_cache.has_key(conversation)) { + text_input.buffer.text = entry_cache[conversation]; + } + text_input.key_press_event.connect(on_text_input_key_press); + text_input.key_release_event.connect(on_text_input_key_release); + text_input.grab_focus(); + } + + private void send_text() { + string text = text_input.buffer.text; + if (text.has_prefix("/")) { + string[] token = text.split(" ", 2); + switch(token[0]) { + case "/kick": + MucManager.get_instance(stream_interactor).kick(conversation.account, conversation.counterpart, token[1]); + break; + case "/me": + MessageManager.get_instance(stream_interactor).send_message(text, conversation); + break; + case "/nick": + MucManager.get_instance(stream_interactor).change_nick(conversation.account, conversation.counterpart, token[1]); + break; + case "/ping": // TODO remove this + Xep.Ping.Module.get_module(stream_interactor.get_stream(conversation.account)) + .send_ping(stream_interactor.get_stream(conversation.account), @"$(conversation.counterpart.bare_jid)/$(token[1])"); + Xep.Ping.Module.get_module(stream_interactor.get_stream(conversation.account)).get_id(); + break; + case "/topic": + MucManager.get_instance(stream_interactor).change_subject(conversation.account, conversation.counterpart, token[1]); + break; + } + } else { + MessageManager.get_instance(stream_interactor).send_message(text, conversation); + } + text_input.buffer.text = ""; + } + + private bool on_text_input_key_press(EventKey event) { + if (event.keyval == Key.space || event.keyval == Key.Return) { + check_convert_smiley(); + } + if (event.keyval == Key.Return) { + if (event.state == ModifierType.SHIFT_MASK) { + text_input.buffer.insert_at_cursor("\n", 1); + } else if (text_input.buffer.text != ""){ + send_text(); + } + return true; + } + return false; + } + + private void check_convert_smiley() { + if (Dino.Settings.instance().convert_utf8_smileys) { + foreach (string smiley in smiley_translations.keys) { + if (text_input.buffer.text.has_suffix(smiley)) { + if (text_input.buffer.text.length == smiley.length || + text_input.buffer.text[text_input.buffer.text.length - smiley.length - 1] == ' ') { + text_input.buffer.text = text_input.buffer.text.substring(0, text_input.buffer.text.length - smiley.length) + smiley_translations[smiley]; + } + } + } + } + } + + private bool on_text_input_key_release(EventKey event) { + if (text_input.buffer.text != "") { + ChatInteraction.get_instance(stream_interactor).on_message_entered(conversation); + } else { + ChatInteraction.get_instance(stream_interactor).on_message_cleared(conversation); + } + return false; + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_list_titlebar.vala b/libdino/src/ui/conversation_list_titlebar.vala new file mode 100644 index 00000000..4bcc9c71 --- /dev/null +++ b/libdino/src/ui/conversation_list_titlebar.vala @@ -0,0 +1,47 @@ +using Gtk; + +using Dino.Entities; + +[GtkTemplate (ui = "/org/dino-im/conversation_list_titlebar.ui")] +public class Dino.Ui.ConversationListTitlebar : Gtk.HeaderBar { + + public signal void conversation_opened(Conversation conversation); + + [GtkChild] + private MenuButton add_button; + + [GtkChild] + public ToggleButton search_button; + + private StreamInteractor stream_interactor; + + public ConversationListTitlebar(Window application, StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + create_add_menu(application); + } + + private void create_add_menu(Window window) { + SimpleAction contacts_action = new SimpleAction("add_chat", null); + contacts_action.activate.connect(() => { + AddConversation.Chat.Dialog add_chat_dialog = new AddConversation.Chat.Dialog(stream_interactor); + add_chat_dialog.set_transient_for((Window) get_toplevel()); + add_chat_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); + add_chat_dialog.show(); + }); + window.get_application().add_action(contacts_action); + + SimpleAction conference_action = new SimpleAction("add_conference", null); + conference_action.activate.connect(() => { + AddConversation.Conference.Dialog add_conference_dialog = new AddConversation.Conference.Dialog(stream_interactor); + add_conference_dialog.set_transient_for((Window) get_toplevel()); + add_conference_dialog.conversation_opened.connect((conversation) => conversation_opened(conversation)); + add_conference_dialog.show(); + }); + window.get_application().add_action(conference_action); + + Builder builder = new Builder.from_resource("/org/dino-im/menu_add.ui"); + MenuModel menu = builder.get_object("menu_add") as MenuModel; + add_button.set_menu_model(menu); + } +} + diff --git a/libdino/src/ui/conversation_selector/chat_row.vala b/libdino/src/ui/conversation_selector/chat_row.vala new file mode 100644 index 00000000..1613b404 --- /dev/null +++ b/libdino/src/ui/conversation_selector/chat_row.vala @@ -0,0 +1,88 @@ +using Gdk; +using Gee; +using Gtk; + +using Xmpp; +using Dino.Entities; + +namespace Dino.Ui.ConversationSelector { +public class ChatRow : ConversationRow { + + public ChatRow(StreamInteractor stream_interactor, Conversation conversation) { + base(stream_interactor, conversation); + has_tooltip = true; + query_tooltip.connect ((x, y, keyboard_tooltip, tooltip) => { + tooltip.set_custom(generate_tooltip()); + return true; + }); + update_avatar(); + } + + public override void on_show_received(Show show) { + update_avatar(); + } + + public override void network_connection(bool connected) { + if (!connected) { + set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)).set_greyscale(true).draw_conversation(stream_interactor, conversation), image.scale_factor); + } else { + update_avatar(); + } + } + + public void on_updated_roster_item(Roster.Item roster_item) { + if (roster_item.name != null) { + display_name = roster_item.name; + update_name(); + } + update_avatar(); + } + + public void update_avatar() { + ArrayList full_jids = PresenceManager.get_instance(stream_interactor).get_full_jids(conversation.counterpart, conversation.account); + set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) + .set_greyscale(full_jids == null) + .draw_conversation(stream_interactor, conversation), image.scale_factor); + } + + private Widget generate_tooltip() { + Builder builder = new Builder.from_resource("/org/dino-im/conversation_selector/chat_row_tooltip.ui"); + Box main_box = builder.get_object("main_box") as Box; + Box inner_box = builder.get_object("inner_box") as Box; + Label jid_label = builder.get_object("jid_label") as Label; + + jid_label.label = conversation.counterpart.to_string(); + + ArrayList? full_jids = PresenceManager.get_instance(stream_interactor).get_full_jids(conversation.counterpart, conversation.account); + if (full_jids != null) { + for (int i = 0; i < full_jids.size; i++) { + Box box = new Box(Orientation.HORIZONTAL, 5); + + Show show = PresenceManager.get_instance(stream_interactor).get_last_show(full_jids[i], conversation.account); + Image image = new Image(); + Pixbuf pixbuf; + int icon_size = 13 * image.scale_factor; + if (show.as == Show.AWAY) { + pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_away.svg", icon_size, icon_size, true); + } else if (show.as == Show.XA || show.as == Show.DND) { + pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_dnd.svg", icon_size, icon_size, true); + } else if (show.as == Show.CHAT) { + pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_chat.svg", icon_size, icon_size, true); + } else { + pixbuf = new Pixbuf.from_resource_at_scale("/org/dino-im/img/status_online.svg", icon_size, icon_size, true); + } + Util.image_set_from_scaled_pixbuf(image, pixbuf); + box.add(image); + + Label resource = new Label(full_jids[i].resourcepart); + resource.xalign = 0; + box.add(resource); + box.show_all(); + + inner_box.add(box); + } + } + return main_box; + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_selector/conversation_row.vala b/libdino/src/ui/conversation_selector/conversation_row.vala new file mode 100644 index 00000000..e641cab2 --- /dev/null +++ b/libdino/src/ui/conversation_selector/conversation_row.vala @@ -0,0 +1,175 @@ +using Gee; +using Gdk; +using Gtk; +using Pango; + +using Xmpp; +using Dino.Entities; + +namespace Dino.Ui.ConversationSelector { + +[GtkTemplate (ui = "/org/dino-im/conversation_selector/conversation_row.ui")] +public abstract class ConversationRow : ListBoxRow { + + [GtkChild] + protected Image image; + + [GtkChild] + private Label name_label; + + [GtkChild] + private Label time_label; + + [GtkChild] + private Label message_label; + + [GtkChild] + protected Button x_button; + + [GtkChild] + private Revealer time_revealer; + + [GtkChild] + private Revealer xbutton_revealer; + + [GtkChild] + public Revealer main_revealer; + + public Conversation conversation { get; private set; } + + protected const int AVATAR_SIZE = 40; + + protected string display_name; + protected string message; + protected DateTime time; + protected bool read = true; + + + protected StreamInteractor stream_interactor; + + construct { + name_label.attributes = new AttrList(); + } + + public ConversationRow(StreamInteractor stream_interactor, Conversation conversation) { + this.conversation = conversation; + this.stream_interactor = stream_interactor; + + x_button.clicked.connect(on_x_button_clicked); + + update_name(Util.get_conversation_display_name(stream_interactor, conversation)); + Entities.Message message = MessageManager.get_instance(stream_interactor).get_last_message(conversation); + if (message != null) { + message_received(message); + } + } + + public void update() { + update_time(); + } + + public void message_received(Entities.Message message) { + update_message(message.body.replace("\n", " ")); + update_time(message.time.to_local()); + } + + public void set_avatar(Pixbuf pixbuf, int scale_factor = 1) { + Util.image_set_from_scaled_pixbuf(image, pixbuf, scale_factor); + image.queue_draw(); + } + + public void mark_read() { + update_read(true); + } + + public void mark_unread() { + update_read(false); + } + + public abstract void on_show_received(Show presence); + public abstract void network_connection(bool connected); + + protected void update_name(string? new_name = null) { + if (new_name != null) { + display_name = new_name; + } + name_label.label = display_name; + } + + protected void update_time(DateTime? new_time = null) { + time_label.visible = true; + if (new_time != null) { + time = new_time; + } + if (time != null) { + time_label.label = get_relative_time(time); + } + } + + protected void update_message(string? new_message = null) { + if (new_message != null) { + message = new_message; + } + if (message != null) { + message_label.visible = true; + message_label.label = message; + } + } + + protected void update_read(bool read) { + this.read = read; + if (read) { + name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); + time_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); + message_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD))); + } else { + name_label.attributes.insert(attr_weight_new(Weight.BOLD)); + time_label.attributes.insert(attr_weight_new(Weight.BOLD)); + message_label.attributes.insert(attr_weight_new(Weight.BOLD)); + } + name_label.label = name_label.label; // TODO initializes redrawing, which would otherwise not happen. nicer? + time_label.label = time_label.label; + message_label.label = message_label.label; + } + + private void on_x_button_clicked() { + main_revealer.set_transition_type(RevealerTransitionType.SLIDE_UP); + main_revealer.set_reveal_child(false); + main_revealer.notify["child-revealed"].connect(() => { + conversation.active = false; + }); + } + + public override void state_flags_changed(StateFlags flags) { + StateFlags curr_flags = get_state_flags(); + if ((curr_flags & StateFlags.PRELIGHT) != 0) { + time_revealer.set_reveal_child(false); + xbutton_revealer.set_reveal_child(true); + } else { + time_revealer.set_reveal_child(true); + xbutton_revealer.set_reveal_child(false); + } + } + + private static string get_relative_time(DateTime datetime) { + DateTime now = new DateTime.now_local(); + TimeSpan timespan = now.difference(datetime); + if (timespan > 365 * TimeSpan.DAY) { + return datetime.get_year().to_string(); + } else if (timespan > 7 * TimeSpan.DAY) { + return datetime.format("%d.%m"); + } else if (timespan > 2 * TimeSpan.DAY) { + return datetime.format("%a"); + } else if (timespan > 1 * TimeSpan.DAY) { + return "Yesterday"; + } else if (timespan > 9 * TimeSpan.MINUTE) { + return datetime.format("%H:%M"); + } else if (timespan > 1 * TimeSpan.MINUTE) { + return (timespan / TimeSpan.MINUTE).to_string() + " min ago"; + } else { + return "Just now"; + } + } + +} +} diff --git a/libdino/src/ui/conversation_selector/groupchat_row.vala b/libdino/src/ui/conversation_selector/groupchat_row.vala new file mode 100644 index 00000000..bec2181e --- /dev/null +++ b/libdino/src/ui/conversation_selector/groupchat_row.vala @@ -0,0 +1,33 @@ +using Dino.Entities; + +namespace Dino.Ui.ConversationSelector { +public class GroupchatRow : ConversationRow { + + public GroupchatRow(StreamInteractor stream_interactor, Conversation conversation) { + base(stream_interactor, conversation); + has_tooltip = true; + set_tooltip_text(conversation.counterpart.bare_jid.to_string()); + set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) + .set_greyscale(true) + .draw_conversation(stream_interactor, conversation), image.scale_factor); + x_button.clicked.connect(on_x_button_clicked); + } + + + public override void on_show_received(Show show) { + set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) + .draw_conversation(stream_interactor, conversation), image.scale_factor); + } + + public override void network_connection(bool connected) { + set_avatar((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) + .set_greyscale(!connected || + MucManager.get_instance(stream_interactor).get_nick(conversation.counterpart, conversation.account) == null) // TODO better currently joined + .draw_conversation(stream_interactor, conversation), image.scale_factor); + } + + private void on_x_button_clicked() { + MucManager.get_instance(stream_interactor).part(conversation.account, conversation.counterpart); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_selector/list.vala b/libdino/src/ui/conversation_selector/list.vala new file mode 100644 index 00000000..e6a5231c --- /dev/null +++ b/libdino/src/ui/conversation_selector/list.vala @@ -0,0 +1,173 @@ +using Gee; +using Gtk; + +using Xmpp; +using Dino.Entities; + +namespace Dino.Ui.ConversationSelector { +public class List : ListBox { + + public signal void conversation_selected(Conversation conversation); + + private StreamInteractor stream_interactor; + private string[]? filter_values; + private HashMap rows = new HashMap(Conversation.hash_func, Conversation.equals_func); + + public List(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + + get_style_context().add_class("sidebar"); + set_filter_func(filter); + set_header_func(header); + set_sort_func(sort); + + ChatInteraction.get_instance(stream_interactor).conversation_read.connect((conversation) => { + Idle.add(() => {rows[conversation].mark_read(); return false;}); + }); + ChatInteraction.get_instance(stream_interactor).conversation_unread.connect((conversation) => { + Idle.add(() => {rows[conversation].mark_unread(); return false;}); + }); + ConversationManager.get_instance(stream_interactor).conversation_activated.connect((conversation) => { + Idle.add(() => {add_conversation(conversation); return false;}); + }); + MessageManager.get_instance(stream_interactor).message_received.connect((message, conversation) => { + Idle.add(() => {message_received(message, conversation); return false;}); + }); + MessageManager.get_instance(stream_interactor).message_sent.connect((message, conversation) => { + Idle.add(() => {message_received(message, conversation); return false;}); + }); + PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { + Idle.add(() => { + Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); + if (conversation != null && rows.has_key(conversation)) rows[conversation].on_show_received(show); + return false; + }); + }); + RosterManager.get_instance(stream_interactor).updated_roster_item.connect((account, jid, roster_item) => { + Idle.add(() => { + Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); + if (conversation != null && rows.has_key(conversation)) { + ChatRow row = rows[conversation] as ChatRow; + if (row != null) row.on_updated_roster_item(roster_item); + } + return false; + }); + }); + AvatarManager.get_instance(stream_interactor).received_avatar.connect((avatar, jid, account) => { + Idle.add(() => { + Conversation? conversation = ConversationManager.get_instance(stream_interactor).get_conversation(jid, account); + if (conversation != null && rows.has_key(conversation)) { + ChatRow row = rows[conversation] as ChatRow; + if (row != null) row.update_avatar(); + } + return false; + }); + }); + stream_interactor.connection_manager.connection_state_changed.connect((account, state) => { + Idle.add(() => { + foreach (ConversationRow row in rows.values) { + if (row.conversation.account.equals(account)) row.network_connection(state == ConnectionManager.ConnectionState.CONNECTED); + } + return false; + }); + }); + Timeout.add_seconds(60, () => { + foreach (ConversationRow row in rows.values) row.update(); + return true; + }); + } + + public override void row_activated(ListBoxRow r) { + if (r.get_type().is_a(typeof(ConversationRow))) { + ConversationRow row = r as ConversationRow; + conversation_selected(row.conversation); + } + } + + public void set_filter_values(string[]? values) { + if (filter_values == values) { + return; + } + filter_values = values; + invalidate_filter(); + } + + public void add_conversation(Conversation conversation) { + ConversationRow row; + if (!rows.has_key(conversation)) { + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + row = new GroupchatRow(stream_interactor, conversation); + } else { + row = new ChatRow(stream_interactor, conversation); + } + rows[conversation] = row; + add(row); + row.main_revealer.set_reveal_child(true); + conversation.notify["active"].connect((s, p) => { + if (rows.has_key(conversation) && !conversation.active) { + remove_conversation(conversation); + } + }); + } + invalidate_sort(); + queue_draw(); + } + + public void remove_conversation(Conversation conversation) { + remove(rows[conversation]); + rows.unset(conversation); + } + + public void on_conversation_selected(Conversation conversation) { + if (!rows.has_key(conversation)) { + add_conversation(conversation); + } + this.select_row(rows[conversation]); + } + + private void message_received(Entities.Message message, Conversation conversation) { + if (rows.has_key(conversation)) { + rows[conversation].message_received(message); + invalidate_sort(); + } + } + + private void header(ListBoxRow row, ListBoxRow? before_row) { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + } + + private bool filter(ListBoxRow r) { + if (r.get_type().is_a(typeof(ConversationRow))) { + ConversationRow row = r as ConversationRow; + if (filter_values != null && filter_values.length != 0) { + foreach (string filter in filter_values) { + if (!(Util.get_conversation_display_name(stream_interactor, row.conversation).down().contains(filter.down()) || + row.conversation.counterpart.to_string().down().contains(filter.down()))) { + return false; + } + } + } + } + return true; + } + + private int sort(ListBoxRow row1, ListBoxRow row2) { + ConversationRow cr1 = row1 as ConversationRow; + ConversationRow cr2 = row2 as ConversationRow; + if (cr1 != null && cr2 != null) { + Conversation c1 = cr1.conversation; + Conversation c2 = cr2.conversation; + int comp = c2.last_active.compare(c1.last_active); + if (comp == 0) { + return Util.get_conversation_display_name(stream_interactor, c1) + .collate(Util.get_conversation_display_name(stream_interactor, c2)); + } else { + return comp; + } + } + return 0; + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_selector/view.vala b/libdino/src/ui/conversation_selector/view.vala new file mode 100644 index 00000000..c551d258 --- /dev/null +++ b/libdino/src/ui/conversation_selector/view.vala @@ -0,0 +1,56 @@ +using Gee; +using Gtk; +using Gdk; + +using Dino.Entities; + +namespace Dino.Ui.ConversationSelector { + +[GtkTemplate (ui = "/org/dino-im/conversation_selector/view.ui")] +public class View : Grid { + public List conversation_list; + + [GtkChild] + public SearchEntry search_entry; + + [GtkChild] + public SearchBar search_bar; + + [GtkChild] + private ScrolledWindow scrolled; + + public View(StreamInteractor stream_interactor) { + conversation_list = new List(stream_interactor); + scrolled.add(conversation_list); + search_entry.key_release_event.connect(search_key_release_event); + search_entry.search_changed.connect(search_changed); + } + + public void conversation_selected(Conversation? conversation) { + search_entry.set_text(""); + } + + private void refilter() { + string[]? values = null; + string str = search_entry.get_text (); + if (str != "") values = str.split(" "); + conversation_list.set_filter_values(values); + } + + private void search_changed(Editable editable) { + refilter(); + } + + private bool search_key_release_event(EventKey event) { + conversation_list.select_row(conversation_list.get_row_at_y(0)); + if (event.keyval == Key.Down) { + ConversationRow? row = (ConversationRow) conversation_list.get_row_at_index(0); + if (row != null) { + conversation_list.select_row(row); + row.grab_focus(); + } + } + return false; + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_summary/merged_message_item.vala b/libdino/src/ui/conversation_summary/merged_message_item.vala new file mode 100644 index 00000000..3647d082 --- /dev/null +++ b/libdino/src/ui/conversation_summary/merged_message_item.vala @@ -0,0 +1,170 @@ +using Gee; +using Gdk; +using Gtk; +using Markup; + +using Dino.Entities; + +namespace Dino.Ui.ConversationSummary { + +[GtkTemplate (ui = "/org/dino-im/conversation_summary/message_item.ui")] +public class MergedMessageItem : Grid { + + public Conversation conversation { get; set; } + public Jid from { get; private set; } + public DateTime initial_time { get; private set; } + public ArrayList messages = new ArrayList(Message.equals_func); + + [GtkChild] + private Image image; + + [GtkChild] + private Label time_label; + + [GtkChild] + private Label name_label; + + [GtkChild] + private Image encryption_image; + + [GtkChild] + private Image received_image; + + [GtkChild] + private TextView message_text_view; + + public MergedMessageItem(StreamInteractor stream_interactor, Conversation conversation, Message message) { + this.conversation = conversation; + this.from = message.from; + this.initial_time = message.time; + setup_tags(); + add_message(message); + + time_label.label = get_relative_time(initial_time.to_local()); + string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account); + name_label.set_markup(@"$display_name"); + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_message(stream_interactor, message)); + if (message.encryption == Entities.Message.Encryption.PGP) { + encryption_image.visible = true; + encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.SMALL_TOOLBAR); + } + } + + public void update() { + time_label.label = get_relative_time(initial_time.to_local()); + } + + public void add_message(Message message) { + TextIter end; + message_text_view.buffer.get_end_iter(out end); + if (messages.size > 0) { + message_text_view.buffer.insert(ref end, "\n", -1); + } + message_text_view.buffer.insert(ref end, message.body, -1); + format_suffix_urls(message.body); + messages.add(message); + message.notify["marked"].connect_after(update_received); // TODO other thread? not main? css error? gtk main? + update_received(); + } + + private void update_received() { + bool all_received = true; + bool all_read = true; + foreach (Message message in messages) { + if (message.marked == Message.Marked.WONTSEND) { + received_image.visible = true; + Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default(); + Gtk.IconInfo? icon_info = icon_theme.lookup_icon("dialog-warning-symbolic", IconSize.SMALL_TOOLBAR, 0); + received_image.set_from_pixbuf(icon_info.load_symbolic({1,0,0,1})); + return; + } else if (message.marked != Message.Marked.READ) { + all_read = false; + if (message.marked != Message.Marked.RECEIVED) { + all_received = false; + } + } + } + if (all_read) { + received_image.visible = true; + received_image.set_from_resource("/org/dino-im/img/double_tick.svg"); + } else if (all_received) { + received_image.visible = true; + received_image.set_from_resource("/org/dino-im/img/tick.svg"); + } else if (received_image.visible) { + received_image.set_from_icon_name("image-loading-symbolic", IconSize.SMALL_TOOLBAR); + } + } + + private void format_suffix_urls(string text) { + int absolute_start = message_text_view.buffer.text.length - text.length; + + Regex url_regex = new Regex("""(?i)\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))"""); + MatchInfo match_info; + url_regex.match(text, 0, out match_info); + for (; match_info.matches(); match_info.next()) { + string? url = match_info.fetch(0); + int start; + int end; + match_info.fetch_pos(0, out start, out end); + TextIter start_iter; + TextIter end_iter; + message_text_view.buffer.get_iter_at_offset(out start_iter, absolute_start + start); + message_text_view.buffer.get_iter_at_offset(out end_iter, absolute_start + end); + message_text_view.buffer.apply_tag_by_name("url", start_iter, end_iter); + } + } + + private void setup_tags() { + message_text_view.buffer.create_tag("url", underline: Pango.Underline.SINGLE, foreground: "blue"); + message_text_view.button_release_event.connect(open_url); + message_text_view.motion_notify_event.connect(change_cursor_over_url); + } + + private bool open_url(EventButton event_button) { + int buffer_x, buffer_y; + message_text_view.window_to_buffer_coords(TextWindowType.TEXT, (int) event_button.x, (int) event_button.y, out buffer_x, out buffer_y); + TextIter iter; + message_text_view.get_iter_at_location(out iter, buffer_x, buffer_y); + TextIter start_iter = iter, end_iter = iter; + if (start_iter.backward_to_tag_toggle(null) && end_iter.forward_to_tag_toggle(null)) { + string url = start_iter.get_text(end_iter); + try{ + AppInfo.launch_default_for_uri(url, null); + } catch (Error err) { + print("Tryed to open " + url); + } + } + return false; + } + + private bool change_cursor_over_url(EventMotion event_motion) { + TextIter iter; + message_text_view.get_iter_at_location(out iter, (int) event_motion.x, (int) event_motion.y); + if (iter.has_tag(message_text_view.buffer.tag_table.lookup("url"))) { + event_motion.window.set_cursor(new Cursor.for_display(get_display(), CursorType.HAND2)); + } else { + event_motion.window.set_cursor(new Cursor.for_display(get_display(), CursorType.XTERM)); + } + return false; + } + + private static string get_relative_time(DateTime datetime) { + DateTime now = new DateTime.now_local(); + TimeSpan timespan = now.difference(datetime); + if (timespan > 365 * TimeSpan.DAY) { + return datetime.format("%d.%m.%Y %H:%M"); + } else if (timespan > 7 * TimeSpan.DAY) { + return datetime.format("%d.%m %H:%M"); + } else if (timespan > 1 * TimeSpan.DAY) { + return datetime.format("%a, %H:%M"); + } else if (timespan > 9 * TimeSpan.MINUTE) { + return datetime.format("%H:%M"); + } else if (timespan > TimeSpan.MINUTE) { + return (timespan / TimeSpan.MINUTE).to_string() + " min ago"; + } else { + return "Just now"; + } + } +} + +} diff --git a/libdino/src/ui/conversation_summary/merged_status_item.vala b/libdino/src/ui/conversation_summary/merged_status_item.vala new file mode 100644 index 00000000..78b156e9 --- /dev/null +++ b/libdino/src/ui/conversation_summary/merged_status_item.vala @@ -0,0 +1,30 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ConversationSummary { + +private class MergedStatusItem : Expander { + + private StreamInteractor stream_interactor; + private Conversation conversation; + private ArrayList statuses = new ArrayList(); + + public MergedStatusItem(StreamInteractor stream_interactor, Conversation conversation, Show show) { + set_hexpand(true); + add_status(show); + } + + public void add_status(Show show) { + statuses.add(show); + StatusItem status_item = new StatusItem(stream_interactor, conversation, @"is $(show.as)"); + if (statuses.size == 1) { + label = show.as; + } else { + label = @"changed their status $(statuses.size) times"; + add(new Label(show.as)); + } + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_summary/status_item.vala b/libdino/src/ui/conversation_summary/status_item.vala new file mode 100644 index 00000000..5918d008 --- /dev/null +++ b/libdino/src/ui/conversation_summary/status_item.vala @@ -0,0 +1,29 @@ +using Gtk; +using Markup; + +using Dino.Entities; + +namespace Dino.Ui.ConversationSummary { + +private class StatusItem : Grid { + + private Image image = new Image(); + private Label label = new Label(""); + + private StreamInteractor stream_interactor; + private Conversation conversation; + + public StatusItem(StreamInteractor stream_interactor, Conversation conversation, string? text) { + Object(column_spacing : 7); + set_hexpand(true); + this.stream_interactor = stream_interactor; + this.conversation = conversation; + image.set_from_pixbuf((new AvatarGenerator(30, 30)).set_greyscale(true).draw_conversation(stream_interactor, conversation)); + attach(image, 0, 0, 1, 1); + attach(label, 1, 0, 1, 1); + string display_name = Util.get_display_name(stream_interactor, conversation.counterpart, conversation.account); + label.set_markup(@" $(escape_text(display_name)) $text "); + show_all(); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/conversation_summary/view.vala b/libdino/src/ui/conversation_summary/view.vala new file mode 100644 index 00000000..59cf88aa --- /dev/null +++ b/libdino/src/ui/conversation_summary/view.vala @@ -0,0 +1,222 @@ +using Gee; +using Gtk; +using Pango; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui.ConversationSummary { + +[GtkTemplate (ui = "/org/dino-im/conversation_summary/view.ui")] +public class View : Box { + + public Conversation? conversation { get; private set; } + public HashMap message_items = new HashMap(Entities.Message.hash_func, Entities.Message.equals_func); + + [GtkChild] + private ScrolledWindow scrolled; + + [GtkChild] + private Box main; + + private StreamInteractor stream_interactor; + private MergedMessageItem? last_message_item; + private StatusItem typing_status; + private Entities.Message? earliest_message; + double? was_value; + double? was_upper; + double? was_page_size; + Object reloading_lock = new Object(); + bool reloading = false; + + public View(StreamInteractor stream_interactor) { + Object(homogeneous : false, spacing : 0); + this.stream_interactor = stream_interactor; + scrolled.vadjustment.notify["upper"].connect_after(on_upper_notify); + scrolled.vadjustment.notify["value"].connect(on_value_notify); + + CounterpartInteractionManager.get_instance(stream_interactor).received_state.connect((account, jid, state) => { + Idle.add(() => { on_received_state(account, jid, state); return false; }); + }); + MessageManager.get_instance(stream_interactor).message_received.connect((message, conversation) => { + Idle.add(() => { show_message(message, conversation, true); return false; }); + }); + MessageManager.get_instance(stream_interactor).message_sent.connect((message, conversation) => { + Idle.add(() => { show_message(message, conversation, true); return false; }); + }); + PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { + Idle.add(() => { on_show_received(show, jid, account); return false; }); + }); + Timeout.add_seconds(60, () => { + foreach (MergedMessageItem message_item in message_items.values) { + message_item.update(); + } + return true; + }); + } + + public void initialize_for_conversation(Conversation? conversation) { + this.conversation = conversation; + clear(); + message_items.clear(); + was_upper = null; + was_page_size = null; + last_message_item = null; + + ArrayList objects = new ArrayList(); + Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages(conversation); + if (messages != null && messages.size > 0) { + earliest_message = messages[0]; + objects.add_all(messages); + } + HashMap>? shows = PresenceManager.get_instance(stream_interactor).get_shows(conversation.counterpart, conversation.account); + if (shows != null) { + foreach (Jid jid in shows.keys) objects.add_all(shows[jid]); + } + objects.sort((a, b) => { + DateTime? dt1 = null; + DateTime? dt2 = null; + Entities.Message m1 = a as Entities.Message; + if (m1 != null) dt1 = m1.time; + Show s1 = a as Show; + if (s1 != null) dt1 = s1.datetime; + Entities.Message m2 = b as Entities.Message; + if (m2 != null) dt2 = m2.time; + Show s2 = b as Show; + if (s2 != null) dt2 = s2.datetime; + return dt1.compare(dt2); + }); + foreach (Object o in objects) { + Entities.Message message = o as Entities.Message; + Show show = o as Show; + if (message != null) { + show_message(message, conversation); + } else if (show != null) { + on_show_received(show, conversation.counterpart, conversation.account); + } + } + update_chat_state(); + } + + private void on_received_state(Account account, Jid jid, string state) { + if (conversation != null && conversation.account.equals(account) && conversation.counterpart.equals_bare(jid)) { + update_chat_state(state); + } + } + + private void update_chat_state(string? state = null) { + string? state_ = state; + if (state_ == null) { + state_ = CounterpartInteractionManager.get_instance(stream_interactor).get_chat_state(conversation.account, conversation.counterpart); + } + if (typing_status != null) { + main.remove(typing_status); + } + if (state_ != null) { + if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING || state_ == Xep.ChatStateNotifications.STATE_PAUSED) { + if (state_ == Xep.ChatStateNotifications.STATE_COMPOSING) { + typing_status = new StatusItem(stream_interactor, conversation, "is typing..."); + } else if (state_ == Xep.ChatStateNotifications.STATE_PAUSED) { + typing_status = new StatusItem(stream_interactor, conversation, "has stoped typing"); + } + main.add(typing_status); + } + } + } + + private void on_show_received(Show show, Jid jid, Account account) { + + } + + private void on_upper_notify() { + if (was_upper == null || scrolled.vadjustment.value > was_upper - was_page_size - 1 || + scrolled.vadjustment.value > was_upper - was_page_size - 1) { // scrolled down or content smaller than page size + scrolled.vadjustment.value = scrolled.vadjustment.upper - scrolled.vadjustment.page_size; // scroll down + } else if (scrolled.vadjustment.value < scrolled.vadjustment.upper - scrolled.vadjustment.page_size - 1){ + scrolled.vadjustment.value = scrolled.vadjustment.upper - was_upper + scrolled.vadjustment.value; // stay at same content + } + was_upper = scrolled.vadjustment.upper; + was_page_size = scrolled.vadjustment.page_size; + lock(reloading_lock) { + reloading = false; + } + } + + private void on_value_notify() { + if (scrolled.vadjustment.value < 200) { + load_earlier_messages(); + } + } + + private void load_earlier_messages() { + was_value = scrolled.vadjustment.value; + lock(reloading_lock) { + if(reloading) return; + reloading = true; + } + Gee.List? messages = MessageManager.get_instance(stream_interactor).get_messages_before(conversation, earliest_message); + if (messages != null && messages.size > 0) { + earliest_message = messages[0]; + MergedMessageItem? current_item = null; + int items_added = 0; + for (int i = 0; i < messages.size; i++) { + if (current_item != null && should_merge_message(current_item, messages[i])) { + current_item.add_message(messages[i]); + } else { + current_item = new MergedMessageItem(stream_interactor, conversation, messages[i]); + force_alloc_width(current_item, main.get_allocated_width()); + main.add(current_item); + message_items[messages[i]] = current_item; + main.reorder_child(current_item, items_added); + items_added++; + } + } + return; + } + reloading = false; + } + + private void show_message(Entities.Message message, Conversation conversation, bool animate = false) { + if (this.conversation != null && this.conversation.equals(conversation)) { + if (should_merge_message(last_message_item, message)) { + last_message_item.add_message(message); + } else { + MergedMessageItem message_item = new MergedMessageItem(stream_interactor, conversation, message); + if (animate) { + Revealer revealer = new Revealer() {transition_duration = 200, transition_type = RevealerTransitionType.SLIDE_UP, visible = true}; + revealer.add(message_item); + force_alloc_width(revealer, main.get_allocated_width()); + main.add(revealer); + revealer.set_reveal_child(true); + } else { + force_alloc_width(message_item, main.get_allocated_width()); + main.add(message_item); + } + last_message_item = message_item; + } + message_items[message] = last_message_item; + update_chat_state(); + } + } + + private bool should_merge_message(MergedMessageItem? message_item, Entities.Message message) { + return message_item != null && + message_item.from.equals(message.from) && + message_item.messages.get(0).encryption == message.encryption && + message.time.difference(message_item.initial_time) < TimeSpan.MINUTE && + (message_item.messages.get(0).marked == Entities.Message.Marked.WONTSEND) == (message.marked == Entities.Message.Marked.WONTSEND); + } + + private void force_alloc_width(Widget widget, int width) { + Allocation alloc = Allocation(); + widget.get_preferred_width(out alloc.width, null); + widget.get_preferred_height(out alloc.height, null); + alloc.width = width; + widget.size_allocate(alloc); + } + + private void clear() { + main.@foreach((widget) => { main.remove(widget); }); + } +} +} diff --git a/libdino/src/ui/conversation_titlebar.vala b/libdino/src/ui/conversation_titlebar.vala new file mode 100644 index 00000000..25304e1a --- /dev/null +++ b/libdino/src/ui/conversation_titlebar.vala @@ -0,0 +1,124 @@ +using Gtk; + +using Dino.Entities; + +[GtkTemplate (ui = "/org/dino-im/conversation_titlebar.ui")] +public class Dino.Ui.ConversationTitlebar : Gtk.HeaderBar { + + [GtkChild] + private MenuButton menu_button; + + [GtkChild] + private MenuButton encryption_button; + private RadioButton? button_unencrypted; + private RadioButton? button_pgp; + + [GtkChild] + private MenuButton groupchat_button; + + private StreamInteractor stream_interactor; + private Conversation? conversation; + + public ConversationTitlebar(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + MucManager.get_instance(stream_interactor).groupchat_subject_set.connect((account, jid, subject) => { + Idle.add(() => { on_groupchat_subject_set(account, jid, subject); return false; }); + }); + create_conversation_menu(); + create_encryption_menu(); + } + + public void initialize_for_conversation(Conversation conversation) { + this.conversation = conversation; + update_encryption_menu_state(); + update_encryption_menu_icon(); + update_groupchat_menu(); + update_title(); + update_subtitle(); + } + + private void update_encryption_menu_state() { + string? pgp_id = PgpManager.get_instance(stream_interactor).get_key_id(conversation.account, conversation.counterpart); + button_pgp.set_sensitive(pgp_id != null); + switch (conversation.encryption) { + case Conversation.Encryption.UNENCRYPTED: + button_unencrypted.set_active(true); + break; + case Conversation.Encryption.PGP: + button_pgp.set_active(true); + break; + } + } + + private void update_encryption_menu_icon() { + encryption_button.visible = (conversation.type_ == Conversation.Type.CHAT); + if (conversation.type_ == Conversation.Type.CHAT) { + if (conversation.encryption == Conversation.Encryption.UNENCRYPTED) { + encryption_button.set_image(new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON)); + } else { + encryption_button.set_image(new Image.from_icon_name("changes-prevent-symbolic", IconSize.BUTTON)); + } + } + } + + private void update_groupchat_menu() { + groupchat_button.visible = conversation.type_ == Conversation.Type.GROUPCHAT; + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + groupchat_button.set_use_popover(true); + Popover popover = new Popover(null); + OccupantList occupant_list = new OccupantList(stream_interactor, conversation); + popover.add(occupant_list); + occupant_list.show_all(); + groupchat_button.set_popover(popover); + } + } + + private void update_title() { + set_title(Util.get_conversation_display_name(stream_interactor, conversation)); + } + + private void update_subtitle(string? subtitle = null) { + if (subtitle != null) { + set_subtitle(subtitle); + } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { + string subject = MucManager.get_instance(stream_interactor).get_groupchat_subject(conversation.counterpart, conversation.account); + set_subtitle(subject != "" ? subject : null); + } else { + set_subtitle(null); + } + } + + private void create_conversation_menu() { + Builder builder = new Builder.from_resource("/org/dino-im/menu_conversation.ui"); + MenuModel menu = builder.get_object("menu_conversation") as MenuModel; + menu_button.set_menu_model(menu); + } + + private void create_encryption_menu() { + Builder builder = new Builder.from_resource("/org/dino-im/menu_encryption.ui"); + PopoverMenu menu = builder.get_object("menu_encryption") as PopoverMenu; + button_unencrypted = builder.get_object("button_unencrypted") as RadioButton; + button_pgp = builder.get_object("button_pgp") as RadioButton; + encryption_button.set_use_popover(true); + encryption_button.set_popover(menu); + encryption_button.set_image(new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON)); + + button_unencrypted.toggled.connect(() => { + if (conversation != null) { + if (button_unencrypted.get_active()) { + conversation.encryption = Conversation.Encryption.UNENCRYPTED; + } else if (button_pgp.get_active()) { + conversation.encryption = Conversation.Encryption.PGP; + } + update_encryption_menu_icon(); + } + }); + } + + private void on_groupchat_subject_set(Account account, Jid jid, string subject) { + if (conversation != null && conversation.counterpart.equals_bare(jid) && conversation.account.equals(account)) { + update_subtitle(subject); + } + } +} + diff --git a/libdino/src/ui/manage_accounts/account_row.vala b/libdino/src/ui/manage_accounts/account_row.vala new file mode 100644 index 00000000..6ca4daf6 --- /dev/null +++ b/libdino/src/ui/manage_accounts/account_row.vala @@ -0,0 +1,24 @@ +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ManageAccounts { + +[GtkTemplate (ui = "/org/dino-im/manage_accounts/account_row.ui")] +public class AccountRow : Gtk.ListBoxRow { + + [GtkChild] + public Image image; + + [GtkChild] + public Label jid_label; + + public Account account; + + public AccountRow(StreamInteractor stream_interactor, Account account) { + this.account = account; + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(40, 40, image.scale_factor)).draw_account(stream_interactor, account)); + jid_label.set_label(account.bare_jid.to_string()); + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/manage_accounts/add_account_dialog.vala b/libdino/src/ui/manage_accounts/add_account_dialog.vala new file mode 100644 index 00000000..b22fca3a --- /dev/null +++ b/libdino/src/ui/manage_accounts/add_account_dialog.vala @@ -0,0 +1,70 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ManageAccounts { + +[GtkTemplate (ui = "/org/dino-im/manage_accounts/add_account_dialog.ui")] +public class AddAccountDialog : Gtk.Dialog { + + public signal void added(Account account); + + [GtkChild] + private Button cancel_button; + + [GtkChild] + private Button ok_button; + + [GtkChild] + private Entry alias_entry; + + [GtkChild] + private Entry jid_entry; + + [GtkChild] + private Entry password_entry; + + public AddAccountDialog(StreamInteractor stream_interactor) { + Object(use_header_bar : 1); + this.title = "Add Account"; + + cancel_button.clicked.connect(() => { close(); }); + ok_button.clicked.connect(on_ok_button_clicked); + jid_entry.changed.connect(on_jid_entry_changed); + jid_entry.focus_out_event.connect(on_jid_entry_focus_out_event); + } + + private void on_jid_entry_changed() { + Jid? jid = Jid.parse(jid_entry.text); + if (jid != null && jid.localpart != null && jid.resourcepart == null) { + ok_button.set_sensitive(true); + jid_entry.secondary_icon_name = null; + } else { + ok_button.set_sensitive(false); + } + } + + private bool on_jid_entry_focus_out_event() { + Jid? jid = Jid.parse(jid_entry.text); + if (jid == null || jid.localpart == null || jid.resourcepart != null) { + jid_entry.secondary_icon_name = "dialog-warning-symbolic"; + // TODO why doesn't the tooltip work + jid_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY, "JID should be of the form \"user@example.com\""); + } else { + jid_entry.secondary_icon_name = null; + } + return false; + } + + private void on_ok_button_clicked() { + Account account = new Account.from_bare_jid(jid_entry.get_text()); + account.resourcepart = "dino"; + account.alias = alias_entry.get_text(); + account.enabled = false; + account.password = password_entry.get_text(); + added(account); + close(); + } +} +} diff --git a/libdino/src/ui/manage_accounts/dialog.vala b/libdino/src/ui/manage_accounts/dialog.vala new file mode 100644 index 00000000..5d18cb30 --- /dev/null +++ b/libdino/src/ui/manage_accounts/dialog.vala @@ -0,0 +1,193 @@ +using Gdk; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui.ManageAccounts { + +[GtkTemplate (ui = "/org/dino-im/manage_accounts/dialog.ui")] +public class Dialog : Gtk.Window { + + public signal void account_enabled(Account account); + public signal void account_disabled(Account account); + + [GtkChild] public Stack main_stack; + [GtkChild] public ListBox account_list; + [GtkChild] public Button no_accounts_add; + [GtkChild] public ToolButton add_button; + [GtkChild] public ToolButton remove_button; + [GtkChild] public Image image; + [GtkChild] public Button image_button; + [GtkChild] public Label jid_label; + [GtkChild] public Switch active_switch; + [GtkChild] public Stack password_stack; + [GtkChild] public Label password_label; + [GtkChild] public Button password_button; + [GtkChild] public Entry password_entry; + [GtkChild] public Stack alias_stack; + [GtkChild] public Label alias_label; + [GtkChild] public Button alias_button; + [GtkChild] public Entry alias_entry; + [GtkChild] public Stack pgp_stack; + [GtkChild] public Label pgp_label; + [GtkChild] public Button pgp_button; + [GtkChild] public ComboBoxText pgp_combobox; + + + private Database db; + private StreamInteractor stream_interactor; + + construct { + account_list.row_selected.connect(account_list_row_selected); + add_button.clicked.connect(add_button_clicked); + no_accounts_add.clicked.connect(add_button_clicked); + remove_button.clicked.connect(remove_button_clicked); + password_entry.key_release_event.connect(on_password_key_release_event); + alias_entry.key_release_event.connect(on_alias_key_release_event); + image_button.clicked.connect(on_image_button_clicked); + + main_stack.set_visible_child_name("no_accounts"); + } + + public Dialog(StreamInteractor stream_interactor, Database db) { + this.db = db; + this.stream_interactor = stream_interactor; + foreach (Account account in db.get_accounts()) { + add_account(account); + } + + AvatarManager.get_instance(stream_interactor).received_avatar.connect((pixbuf, jid, account) => { + Idle.add(() => { + on_received_avatar(pixbuf, jid, account); + return false; + });}); + + if (account_list.get_row_at_index(0) != null) account_list.select_row(account_list.get_row_at_index(0)); + } + + public AccountRow add_account(Account account) { + AccountRow account_item = new AccountRow (stream_interactor, account); + account_list.add(account_item); + main_stack.set_visible_child_name("accounts_exist"); + return account_item; + } + + private void add_button_clicked() { + AddAccountDialog add_account_dialog = new AddAccountDialog(stream_interactor); + add_account_dialog.set_transient_for(this); + add_account_dialog.added.connect((account) => { + db.add_account(account); + AccountRow account_item = add_account(account); + account_list.select_row(account_item); + account_list.queue_draw(); + }); + add_account_dialog.show(); + } + + private void remove_button_clicked() { + AccountRow account_item = account_list.get_selected_row() as AccountRow; + if (account_item != null) { + account_list.remove(account_item); + account_list.queue_draw(); + if (account_item.account.enabled) account_disabled(account_item.account); + db.remove_account(account_item.account); + if (account_list.get_row_at_index(0) != null) { + account_list.select_row(account_list.get_row_at_index(0)); + } else { + main_stack.set_visible_child_name("no_accounts"); + } + } + } + + private void account_list_row_selected(ListBoxRow? row) { + AccountRow? account_item = row as AccountRow; + if (account_item != null) populate_grid_data(account_item.account); + } + + private void populate_grid_data(Account account) { + active_switch.state_set.disconnect(on_active_switch_state_changed); + + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); + active_switch.set_active(account.enabled); + jid_label.label = account.bare_jid.to_string(); + + string filler = ""; + for (int i = 0; i < account.password.length; i++) filler += password_entry.get_invisible_char().to_string(); + password_label.label = filler; + password_stack.set_visible_child_name("label"); + password_entry.text = account.password; + + alias_stack.set_visible_child_name("label"); + alias_label.label = account.alias; + alias_entry.text = account.alias; + + password_button.clicked.connect(() => { set_active_stack(password_stack); }); + alias_button.clicked.connect(() => { set_active_stack(alias_stack); }); + active_switch.state_set.connect(on_active_switch_state_changed); + } + + private void on_image_button_clicked() { + FileChooserDialog chooser = new FileChooserDialog ( + "Select avatar", this, FileChooserAction.OPEN, + "Cancel", ResponseType.CANCEL, + "Select", ResponseType.ACCEPT); + FileFilter filter = new FileFilter(); + filter.add_mime_type("image/*"); + chooser.set_filter(filter); + if (chooser.run() == Gtk.ResponseType.ACCEPT) { + string uri = chooser.get_filename(); + Account account = (account_list.get_selected_row() as AccountRow).account; + AvatarManager.get_instance(stream_interactor).publish(account, uri); + } + chooser.close(); + } + + private bool on_active_switch_state_changed(bool state) { + Account account = (account_list.get_selected_row() as AccountRow).account; + account.enabled = state; + if (state) { + account_enabled(account); + } else { + account_disabled(account); + } + return false; + } + + private bool on_password_key_release_event(EventKey event) { + Account account = (account_list.get_selected_row() as AccountRow).account; + account.password = password_entry.text; + string filler = ""; + for (int i = 0; i < account.password.length; i++) filler += password_entry.get_invisible_char().to_string(); + password_label.label = filler; + if (event.keyval == Key.Return) { + password_stack.set_visible_child_name("label"); + } + return false; + } + + private bool on_alias_key_release_event(EventKey event) { + Account account = (account_list.get_selected_row() as AccountRow).account; + account.alias = alias_entry.text; + alias_label.label = alias_entry.text; + if (event.keyval == Key.Return) { + alias_stack.set_visible_child_name("label"); + } + return false; + } + + private void on_received_avatar(Pixbuf pixbuf, Jid jid, Account account) { + Account curr_account = (account_list.get_selected_row() as AccountRow).account; + if (curr_account.equals(account) && jid.equals(account.bare_jid)) { + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); + } + } + + private void set_active_stack(Stack stack) { + stack.set_visible_child_name("entry"); + if (stack != password_stack) password_stack.set_visible_child_name("label"); + if (stack != alias_stack) alias_stack.set_visible_child_name("label"); + if (stack != pgp_stack) pgp_stack.set_visible_child_name("label"); + } +} +} + diff --git a/libdino/src/ui/notifications.vala b/libdino/src/ui/notifications.vala new file mode 100644 index 00000000..46bc6bf5 --- /dev/null +++ b/libdino/src/ui/notifications.vala @@ -0,0 +1,55 @@ +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui { +public class Notifications : GLib.Object { + + private StreamInteractor stream_interactor; + private Notify.Notification notification = new Notify.Notification("", null, null); + + public Notifications(StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + } + + public void start() { + MessageManager.get_instance(stream_interactor).message_received.connect(on_message_received); + PresenceManager.get_instance(stream_interactor).received_subscription_request.connect(on_received_subscription_request); + } + + private void on_message_received(Entities.Message message, Conversation conversation) { + if (!ChatInteraction.get_instance(stream_interactor).is_active_focus()) { + string display_name = Util.get_conversation_display_name(stream_interactor, conversation); + if (MucManager.get_instance(stream_interactor).is_groupchat(conversation.counterpart, conversation.account)) { + string muc_occupant = Util.get_display_name(stream_interactor, message.from, conversation.account); + display_name = muc_occupant + " in " + display_name; + } + notification.update(display_name, message.body, null); + notification.set_image_from_pixbuf((new AvatarGenerator(40, 40)).draw_conversation(stream_interactor, conversation)); + notification.set_timeout(3); + try { + notification.show(); + } catch (Error error) { } + } + } + + private void on_received_subscription_request(Jid jid, Account account) { + Notify.Notification notification = new Notify.Notification("Subscription request", jid.bare_jid.to_string(), null); + notification.set_image_from_pixbuf((new AvatarGenerator(40, 40)).draw_jid(stream_interactor, jid, account)); + notification.add_action("accept", "Accept", () => { + PresenceManager.get_instance(stream_interactor).approve_subscription(account, jid); + try { + notification.close(); + } catch (Error error) { } + }); + notification.add_action("deny", "Deny", () => { + PresenceManager.get_instance(stream_interactor).deny_subscription(account, jid); + try { + notification.close(); + } catch (Error error) { } + }); + try { + notification.show(); + } catch (Error error) { } + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/occupant_list.vala b/libdino/src/ui/occupant_list.vala new file mode 100644 index 00000000..921f7e70 --- /dev/null +++ b/libdino/src/ui/occupant_list.vala @@ -0,0 +1,112 @@ +using Gee; +using Gtk; + +using Dino.Entities; + +namespace Dino.Ui{ +[GtkTemplate (ui = "/org/dino-im/occupant_list.ui")] +public class OccupantList : Box { + + public signal void conversation_selected(Conversation? conversation); + private StreamInteractor stream_interactor; + + [GtkChild] + private ListBox list_box; + + [GtkChild] + private SearchEntry search_entry; + + private Conversation? conversation; + private string[]? filter_values; + private HashMap rows = new HashMap(Jid.hash_func, Jid.equals_func); + + public OccupantList(StreamInteractor stream_interactor, Conversation conversation) { + this.stream_interactor = stream_interactor; + list_box.set_header_func(header); + list_box.set_sort_func(sort); + list_box.set_filter_func(filter); + search_entry.search_changed.connect(search_changed); + + PresenceManager.get_instance(stream_interactor).show_received.connect((show, jid, account) => { + Idle.add(() => { on_show_received(show, jid, account); return false; }); + }); + RosterManager.get_instance(stream_interactor).updated_roster_item.connect(on_updated_roster_item); + + initialize_for_conversation(conversation); + } + + public void initialize_for_conversation(Conversation conversation) { + this.conversation = conversation; + ArrayList? occupants = MucManager.get_instance(stream_interactor).get_occupants(conversation.counterpart, conversation.account); + if (occupants != null) { + foreach (Jid occupant in occupants) { + add_occupant(occupant); + } + } + } + + private void refilter() { + string[]? values = null; + string str = search_entry.get_text (); + if (str != "") values = str.split(" "); + if (filter_values == values) return; + filter_values = values; + list_box.invalidate_filter(); + } + + private void search_changed(Editable editable) { + refilter(); + } + + public void add_occupant(Jid jid) { + rows[jid] = new OccupantListRow(stream_interactor, conversation.account, jid); + list_box.add(rows[jid]); + list_box.invalidate_filter(); + list_box.invalidate_sort(); + } + + public void remove_occupant(Jid jid) { + list_box.remove(rows[jid]); + rows.unset(jid); + } + + private void on_updated_roster_item(Account account, Jid jid, Xmpp.Roster.Item roster_item) { + + } + + private void on_show_received(Show show, Jid jid, Account account) { + if (conversation != null && conversation.counterpart.equals_bare(jid)) { + if (show.as == Show.OFFLINE && rows.has_key(jid)) { + remove_occupant(jid); + } else if (show.as != Show.OFFLINE && !rows.has_key(jid)) { + add_occupant(jid); + } + } + } + + private void header(ListBoxRow row, ListBoxRow? before_row) { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + } + + private bool filter(ListBoxRow r) { + if (r.get_type().is_a(typeof(OccupantListRow))) { + OccupantListRow row = r as OccupantListRow; + foreach (string filter in filter_values) { + return row.name_label.label.down().contains(filter.down()); + } + } + return true; + } + + private int sort(ListBoxRow row1, ListBoxRow row2) { + if (row1.get_type().is_a(typeof(OccupantListRow)) && row2.get_type().is_a(typeof(OccupantListRow))) { + OccupantListRow c1 = row1 as OccupantListRow; + OccupantListRow c2 = row2 as OccupantListRow; + return c1.name_label.label.collate(c2.name_label.label); + } + return 0; + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/occupant_list_row.vala b/libdino/src/ui/occupant_list_row.vala new file mode 100644 index 00000000..067455b5 --- /dev/null +++ b/libdino/src/ui/occupant_list_row.vala @@ -0,0 +1,27 @@ +using Gtk; + +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui { + +[GtkTemplate (ui = "/org/dino-im/occupant_list_item.ui")] +public class OccupantListRow : ListBoxRow { + + [GtkChild] + private Image image; + + [GtkChild] + public Label name_label; + + public OccupantListRow(StreamInteractor stream_interactor, Account account, Jid jid) { + name_label.label = Util.get_display_name(stream_interactor, jid, account); + Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_jid(stream_interactor, jid, account)); + //has_tooltip = true; + } + + public void on_presence_received(Presence.Stanza presence) { + + } +} +} \ No newline at end of file diff --git a/libdino/src/ui/settings_dialog.vala b/libdino/src/ui/settings_dialog.vala new file mode 100644 index 00000000..600ec873 --- /dev/null +++ b/libdino/src/ui/settings_dialog.vala @@ -0,0 +1,27 @@ +using Gtk; + +namespace Dino.Ui { + +[GtkTemplate (ui = "/org/dino-im/settings_dialog.ui")] +class SettingsDialog : Dialog { + + [GtkChild] + private CheckButton marker_checkbutton; + + [GtkChild] + private CheckButton emoji_checkbutton; + + Dino.Settings settings = Dino.Settings.instance(); + + public SettingsDialog() { + Object(use_header_bar : 1); + + marker_checkbutton.active = settings.send_read; + emoji_checkbutton.active = settings.convert_utf8_smileys; + + marker_checkbutton.toggled.connect(() => { settings.send_read = marker_checkbutton.active; }); + emoji_checkbutton.toggled.connect(() => { settings.convert_utf8_smileys = emoji_checkbutton.active; }); + } +} + +} \ No newline at end of file diff --git a/libdino/src/ui/unified_window.vala b/libdino/src/ui/unified_window.vala new file mode 100644 index 00000000..8a9500e9 --- /dev/null +++ b/libdino/src/ui/unified_window.vala @@ -0,0 +1,78 @@ +using Gtk; + +using Dino.Entities; + +public class Dino.Ui.UnifiedWindow : Window { + public ChatInput chat_input; + public ConversationListTitlebar conversation_list_titlebar; + public ConversationSelector.View filterable_conversation_list; + public ConversationSummary.View conversation_frame; + public ConversationTitlebar conversation_titlebar; + public Paned paned; + + private StreamInteractor stream_interactor; + private Conversation? conversation; + + public UnifiedWindow(Application application, StreamInteractor stream_interactor) { + Object(application : application); + this.stream_interactor = stream_interactor; + focus_in_event.connect(on_focus_in_event); + focus_out_event.connect(on_focus_out_event); + + default_width = 1200; + default_height = 700; + + chat_input = new ChatInput(stream_interactor); + conversation_frame = new ConversationSummary.View(stream_interactor); + conversation_titlebar = new ConversationTitlebar(stream_interactor); + paned = new Paned(Orientation.HORIZONTAL); + paned.set_position(300); + filterable_conversation_list = new ConversationSelector.View(stream_interactor); + conversation_list_titlebar = new ConversationListTitlebar(this, stream_interactor); + conversation_list_titlebar.search_button.bind_property("active", filterable_conversation_list.search_bar, "search-mode-enabled", + BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + Grid grid = new Grid(); + grid.orientation = Orientation.VERTICAL; + Paned toolbar_paned = new Paned(Orientation.HORIZONTAL); + + add(paned); + paned.add1(filterable_conversation_list); + paned.add2(grid); + + grid.add(conversation_frame); + grid.add(new Separator(Orientation.HORIZONTAL)); + grid.add(chat_input); + + conversation_frame.show_all(); + + toolbar_paned.add1(conversation_list_titlebar); + toolbar_paned.add2(conversation_titlebar); + paned.bind_property("position", toolbar_paned, "position", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + set_titlebar(toolbar_paned); + + filterable_conversation_list.conversation_list.conversation_selected.connect(on_conversation_selected); + conversation_list_titlebar.conversation_opened.connect(on_conversation_selected); + } + + private void on_conversation_selected(Conversation conversation) { + this.conversation = conversation; + ChatInteraction.get_instance(stream_interactor).on_conversation_selected(conversation); + conversation.active = true; // only for conversation_selected + filterable_conversation_list.conversation_list.on_conversation_selected(conversation); // only for conversation_opened + + chat_input.initialize_for_conversation(conversation); + conversation_frame.initialize_for_conversation(conversation); + conversation_titlebar.initialize_for_conversation(conversation); + } + + private bool on_focus_in_event() { + ChatInteraction.get_instance(stream_interactor).window_focus_in(conversation); + return false; + } + + private bool on_focus_out_event() { + ChatInteraction.get_instance(stream_interactor).window_focus_out(conversation); + return false; + } +} + diff --git a/libdino/src/ui/util.vala b/libdino/src/ui/util.vala new file mode 100644 index 00000000..d06afe67 --- /dev/null +++ b/libdino/src/ui/util.vala @@ -0,0 +1,71 @@ +using Gtk; + +using Dino.Entities; +using Xmpp; + +public class Dino.Ui.Util : GLib.Object { + + private const string[] tango_colors_light = {"FCE94F", "FCAF3E", "E9B96E", "8AE234", "729FCF", "AD7FA8", "EF2929"}; + private const string[] tango_colors_medium = {"EDD400", "F57900", "C17D11", "73D216", "3465A4", "75507B", "CC0000"}; + private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673AB7", "3f51B5", "2196F3", "03A9f4", "00BCD4", "009688", "4CAF50", "8BC34a", "CDDC39", "FFEB3B", "FFC107", "FF9800", "FF5722", "795548"}; + private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFF176", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; + private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFF59D", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; + + public static string get_avatar_hex_color(string name) { + return material_colors_300[name.hash() % material_colors_300.length]; +// return tango_colors_light[name.hash() % tango_colors_light.length]; + } + + public static string get_name_hex_color(string name) { + return material_colors_500[name.hash() % material_colors_500.length]; +// return tango_colors_medium[name.hash() % tango_colors_medium.length]; + } + + public static string color_for_show(string show) { + switch(show) { + case "online": return "#9CCC65"; + case "away": return "#FFCA28"; + case "chat": return "#66BB6A"; + case "xa": return "#EF5350"; + case "dnd": return "#EF5350"; + default: return "#BDBDBD"; + } + } + + public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { + return get_display_name(stream_interactor, conversation.counterpart, conversation.account); + } + + public static string get_display_name(StreamInteractor stream_interactor, Jid jid, Account account) { + if (MucManager.get_instance(stream_interactor).is_groupchat_occupant(jid, account)) { + return jid.resourcepart; + } else { + if (jid.bare_jid.equals(account.bare_jid.bare_jid)) { + if (account.alias == null || account.alias == "") { + return account.bare_jid.to_string(); + } else { + return account.alias; + } + } + Roster.Item roster_item = RosterManager.get_instance(stream_interactor).get_roster_item(account, jid); + if (roster_item != null && roster_item.name != null) { + return roster_item.name; + } + return jid.bare_jid.to_string(); + } + } + + public static string get_message_display_name(StreamInteractor stream_interactor, Entities.Message message, Account account) { + Jid? real_jid = MucManager.get_instance(stream_interactor).get_message_real_jid(message); + if (real_jid != null) { + return get_display_name(stream_interactor, real_jid, account); + } else { + return get_display_name(stream_interactor, message.from, account); + } + } + + public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0) { + if (scale == 0) scale = image.get_scale_factor(); + image.set_from_surface(Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale, image.get_window())); + } +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 00000000..cfb14eed --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,35 @@ +find_package(Vala REQUIRED) +find_package(PkgConfig REQUIRED) +include(${VALA_USE_FILE}) + +set(MAIN_PACKAGES + gee-0.8 + gio-2.0 + glib-2.0 + gtk+-3.0 + gmodule-2.0 + sqlite3 +) + +pkg_check_modules(MAIN REQUIRED ${MAIN_PACKAGES}) + +vala_precompile(MAIN_VALA_C +SOURCES + src/main.vala +CUSTOM_VAPIS + ${CMAKE_BINARY_DIR}/xmpp-vala/xmpp-vala.vapi + ${CMAKE_BINARY_DIR}/qlite/qlite.vapi + ${CMAKE_BINARY_DIR}/libdino/dino.vapi +PACKAGES + ${MAIN_PACKAGES} +OPTIONS + --target-glib=2.38 + -g + --thread +) + +set(CFLAGS ${VALA_CFLAGS} ${MAIN_CFLAGS} -I${CMAKE_BINARY_DIR}/xmpp-vala -I${CMAKE_BINARY_DIR}/qlite -I${CMAKE_BINARY_DIR}/libdino) +add_definitions(${CFLAGS}) +add_executable(dino ${MAIN_VALA_C}) +add_dependencies(dino dino-vapi) +target_link_libraries(dino libdino) \ No newline at end of file diff --git a/main/src/main.vala b/main/src/main.vala new file mode 100644 index 00000000..80f9bea5 --- /dev/null +++ b/main/src/main.vala @@ -0,0 +1,20 @@ +using Dino.Entities; +using Dino.Ui; + +namespace Dino { + +void main(string[] args) { + Gtk.init(ref args); + Dino.Ui.Application app = new Dino.Ui.Application(); + PluginLoader loader = new PluginLoader(); + foreach(string plugin in new string[]{}) { + try { + loader.load(plugin, app); + } catch (Dino.PluginError e) { + print(@"Error loading plugin $plugin: $(e.message)\n"); + } + } + app.run(args); +} + +} \ No newline at end of file diff --git a/qlite/CMakeLists.txt b/qlite/CMakeLists.txt index d19ed2d2..70ac249f 100644 --- a/qlite/CMakeLists.txt +++ b/qlite/CMakeLists.txt @@ -34,7 +34,7 @@ OPTIONS --vapidir=${CMAKE_SOURCE_DIR}/vapi ) -set(CFLAGS ${QLITE_CFLAGS} -g ${VALA_CFLAGS}) +set(CFLAGS ${VALA_CFLAGS} ${QLITE_CFLAGS}) add_definitions(${CFLAGS}) add_library(qlite SHARED ${QLITE_VALA_C}) target_link_libraries(qlite ${QLITE_LIBRARIES}) diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt index 95c9c44b..a0e91841 100644 --- a/xmpp-vala/CMakeLists.txt +++ b/xmpp-vala/CMakeLists.txt @@ -9,7 +9,7 @@ set(ENGINE_PACKAGES gee-0.8 gio-2.0 glib-2.0 - gtk+-3.0 + gdk-3.0 ) pkg_check_modules(ENGINE REQUIRED ${ENGINE_PACKAGES}) @@ -80,10 +80,10 @@ OPTIONS --vapidir=${CMAKE_SOURCE_DIR}/vapi ) -set(CFLAGS ${ENGINE_CFLAGS} ${GPGME_CFLAGS} ${LIBUUID_CFLAGS} -g ${VALA_CFLAGS} -I${CMAKE_BINARY_DIR}/gpgme-vala) +set(CFLAGS ${VALA_CFLAGS} ${ENGINE_CFLAGS} ${GPGME_CFLAGS} ${LIBUUID_CFLAGS} -I${CMAKE_BINARY_DIR}/gpgme-vala) add_definitions(${CFLAGS}) add_library(xmpp-vala SHARED ${ENGINE_VALA_C}) -add_dependencies(xmpp-vala gpgme-vala gpgme-vapi) +add_dependencies(xmpp-vala gpgme-vapi) target_link_libraries(xmpp-vala gpgme-vala ${ENGINE_LIBRARIES} ${GPGME_LIBRARIES} ${LIBUUID_LIBRARIES}) add_custom_target(xmpp-vala-vapi diff --git a/xmpp-vala/src/core/xmpp_stream.vala b/xmpp-vala/src/core/xmpp_stream.vala index 5f92113d..2cba2b54 100644 --- a/xmpp-vala/src/core/xmpp_stream.vala +++ b/xmpp-vala/src/core/xmpp_stream.vala @@ -93,7 +93,7 @@ public class XmppStream { } } - public IOStream get_stream() { + public IOStream? get_stream() { return stream; } -- cgit v1.2.3-54-g00ecf