(broken) Merge remote-tracking branch 'upstream/master' into communities

This commit is contained in:
Max Sandholm 2018-01-01 18:28:47 +02:00
commit 2d592b1408
70 changed files with 4086 additions and 2109 deletions

20
.ci/linux/deploy.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
mkdir -p appdir
cp build/nheko appdir/
cp resources/nheko.desktop appdir/
cp resources/nheko*.png appdir/
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
chmod a+x linuxdeployqt*.AppImage
unset QTDIR
unset QT_PLUGIN_PATH
unset LD_LIBRARY_PATH
./linuxdeployqt*.AppImage ./appdir/*.desktop -bundle-non-qt-libs
./linuxdeployqt*.AppImage ./appdir/*.desktop -appimage
chmod +x nheko-x86_64.AppImage

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
sudo add-apt-repository -y ppa:beineri/opt-qt58-trusty sudo add-apt-repository -y ppa:beineri/opt-qt592-trusty
sudo add-apt-repository -y ppa:george-edison55/cmake-3.x sudo add-apt-repository -y ppa:george-edison55/cmake-3.x
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -qq -y qt58base qt58tools cmake liblmdb-dev sudo apt-get install -qq -y qt59base qt59tools cmake liblmdb-dev

11
.ci/macos/deploy.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
# Add Qt binaries to path
PATH=/usr/local/opt/qt/bin/:${PATH}
sudo macdeployqt build/nheko.app -dmg
user=$(id -nu)
sudo chown ${user} build/nheko.dmg
mv build/nheko.dmg .

View File

@ -1,225 +0,0 @@
############################################################################################
# NSIS Installation Script created by NSIS Quick Setup Script Generator v1.09.18
# Entirely Edited with NullSoft Scriptable Installation System
# by Vlasis K. Barkas aka Red Wine red_wine@freemail.gr Sep 2006
############################################################################################
!define APP_NAME "nheko"
!define COMP_NAME "mujx"
!define WEB_SITE "https://github.com/mujx/nheko"
!define VERSION "0.1.0.0"
!define COPYRIGHT "mujx <20> 2017"
!define DESCRIPTION "Desktop client for the Matrix protocol"
!define LICENSE_TXT "LICENSE.txt"
!define INSTALLER_NAME "..\..\nheko_setup.exe"
!define INPUT_DIR "..\..\NhekoRelease"
!define MAIN_APP_EXE "nheko.exe"
!define INSTALL_TYPE "SetShellVarContext all"
!define REG_ROOT "HKLM"
!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}"
!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
!define REG_START_MENU "Start Menu Folder"
var SM_Folder
######################################################################
VIProductVersion "${VERSION}"
VIAddVersionKey "ProductName" "${APP_NAME}"
VIAddVersionKey "CompanyName" "${COMP_NAME}"
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${VERSION}"
######################################################################
SetCompressor ZLIB
Name "${APP_NAME}"
Caption "${APP_NAME}"
OutFile "${INSTALLER_NAME}"
BrandingText "${APP_NAME}"
XPStyle on
InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" ""
InstallDir "$PROGRAMFILES\nheko"
######################################################################
!include "MUI.nsh"
!define MUI_ICON "..\..\resources\nheko.ico"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "..\..\resources\nheko.bmp"
!define MUI_HEADERIMAGE_RIGHT
!define MUI_ABORTWARNING
!define MUI_UNABORTWARNING
!insertmacro MUI_PAGE_WELCOME
!ifdef LICENSE_TXT
!insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}"
!endif
!insertmacro MUI_PAGE_DIRECTORY
!ifdef REG_START_MENU
!define MUI_STARTMENUPAGE_DEFAULTFOLDER "nheko"
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}"
!define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}"
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}"
!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder
!endif
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE "English"
######################################################################
Section -MainProgram
${INSTALL_TYPE}
SetOverwrite ifnewer
SetOutPath "$INSTDIR"
File /r "${INPUT_DIR}\*"
SectionEnd
######################################################################
Section -Icons_Reg
SetOutPath "$INSTDIR"
WriteUninstaller "$INSTDIR\uninstall.exe"
!ifdef REG_START_MENU
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
CreateDirectory "$SMPROGRAMS\$SM_Folder"
CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
!ifdef WEB_SITE
WriteIniStr "$INSTDIR\${APP_NAME} website.url" "InternetShortcut" "URL" "${WEB_SITE}"
CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME} Website.lnk" "$INSTDIR\${APP_NAME} website.url"
!endif
!insertmacro MUI_STARTMENU_WRITE_END
!endif
!ifndef REG_START_MENU
CreateDirectory "$SMPROGRAMS\nheko"
CreateShortCut "$SMPROGRAMS\nheko\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}"
CreateShortCut "$SMPROGRAMS\nheko\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe"
!ifdef WEB_SITE
WriteIniStr "$INSTDIR\${APP_NAME} website.url" "InternetShortcut" "URL" "${WEB_SITE}"
CreateShortCut "$SMPROGRAMS\nheko\${APP_NAME} Website.lnk" "$INSTDIR\${APP_NAME} website.url"
!endif
!endif
WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
!ifdef WEB_SITE
WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}"
!endif
SectionEnd
######################################################################
Section Uninstall
${INSTALL_TYPE}
########
!macro BadPathsCheck
StrCpy $R0 $INSTDIR "" -2
StrCmp $R0 ":\" bad
StrCpy $R0 $INSTDIR "" -14
StrCmp $R0 "\Program Files" bad
StrCpy $R0 $INSTDIR "" -8
StrCmp $R0 "\Windows" bad
StrCpy $R0 $INSTDIR "" -6
StrCmp $R0 "\WinNT" bad
StrCpy $R0 $INSTDIR "" -9
StrCmp $R0 "\system32" bad
StrCpy $R0 $INSTDIR "" -8
StrCmp $R0 "\Desktop" bad
StrCpy $R0 $INSTDIR "" -23
StrCmp $R0 "\Documents and Settings" bad
StrCpy $R0 $INSTDIR "" -13
StrCmp $R0 "\My Documents" bad done
bad:
MessageBox MB_OK|MB_ICONSTOP "Install path invalid!"
Abort
done:
!macroend
StrCmp $INSTDIR "" 0 +2
StrCpy $INSTDIR $EXEDIR
# Check that the uninstall isn't dangerous.
!insertmacro BadPathsCheck
# Does path end with "\nheko"?
!define CHECK_PATH "\nheko"
StrLen $R1 "${CHECK_PATH}"
StrCpy $R0 $INSTDIR "" -$R1
StrCmp $R0 "${CHECK_PATH}" +3
MessageBox MB_YESNO|MB_ICONQUESTION "Unrecognised uninstall path. Continue anyway?" IDYES +2
Abort
IfFileExists "$INSTDIR\*.*" 0 +2
IfFileExists "$INSTDIR\nheko.exe" +3
MessageBox MB_OK|MB_ICONSTOP "Install path invalid!"
Abort
########
RMDir /r /REBOOTOK $INSTDIR
Delete "$INSTDIR\uninstall.exe"
!ifdef WEB_SITE
Delete "$INSTDIR\${APP_NAME} website.url"
!endif
!ifdef REG_START_MENU
!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder
Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk"
Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk"
!ifdef WEB_SITE
Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME} Website.lnk"
!endif
Delete "$DESKTOP\${APP_NAME}.lnk"
RmDir "$SMPROGRAMS\$SM_Folder"
!endif
!ifndef REG_START_MENU
Delete "$SMPROGRAMS\nheko\${APP_NAME}.lnk"
Delete "$SMPROGRAMS\nheko\Uninstall ${APP_NAME}.lnk"
!ifdef WEB_SITE
Delete "$SMPROGRAMS\nheko\${APP_NAME} Website.lnk"
!endif
Delete "$DESKTOP\${APP_NAME}.lnk"
RmDir "$SMPROGRAMS\nheko"
!endif
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
SectionEnd
######################################################################

View File

@ -1,5 +1,6 @@
--- ---
Language: Cpp Language: Cpp
Standard: Cpp11
AccessModifierOffset: -8 AccessModifierOffset: -8
AlignAfterOpenBracket: Align AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true AlignConsecutiveAssignments: true
@ -10,3 +11,4 @@ IndentCaseLabels: false
IndentWidth: 8 IndentWidth: 8
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Right PointerAlignment: Right
Cpp11BracedListStyle: true

20
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,20 @@
<!--
If you want to request a feature or ask a question, feel free to remove all the irrelevant text.
-->
### System:
- Nheko commit/version:
- Operating System:
- Qt version:
- C++ compiler:
- Desktop Environment: <!-- for Linux -->
### Actual behavior
### Expected behavior
### Steps to reproduce
<!-- If the program crashed. -->
### Debugger traceback

View File

@ -21,9 +21,33 @@ install:
- if [ $TRAVIS_OS_NAME == osx ]; then export CMAKE_PREFIX_PATH=/usr/local/opt/qt5; fi - if [ $TRAVIS_OS_NAME == osx ]; then export CMAKE_PREFIX_PATH=/usr/local/opt/qt5; fi
- if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/install-deps.sh; fi - if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/install-deps.sh; fi
before_script: before_script:
- if [ $TRAVIS_OS_NAME == linux ]; then source /opt/qt58/bin/qt58-env.sh; fi - if [ $TRAVIS_OS_NAME == linux ]; then source /opt/qt59/bin/qt59-env.sh; fi
- cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release - cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
script: script:
- make -C build -j2 - make -C build -j2
- if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/run-tests.sh; fi
- if [ $TRAVIS_OS_NAME == osx ]; then make lint; fi - if [ $TRAVIS_OS_NAME == osx ]; then make lint; fi
- if [ $TRAVIS_OS_NAME == osx ]; then ./.ci/macos/deploy.sh; fi
- if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/run-tests.sh; fi
- if [ $TRAVIS_OS_NAME == linux ]; then ./.ci/linux/deploy.sh; fi
deploy:
- skip_cleanup: true
overwrite: true
provider: releases
api_key:
secure: oprXzESukFiXBeF2BXkXUlegsAQc95Ub4kc/OkoNFaYBvqpA+IGpWHmHCx5JPny/OT3Kc2Urpe2JUeGSWDHZ7UCKDjH+NzGP3uN82fHh/HiszG/Srw7+hWEHm1ve+gMK9GS8pr+yUsUrPP0UfVXlPoI4pBWa4zGi2Ndb/SqvjCgIHFLtGyoBo6CydKQ/AyWiXSMKDfJL+Dx4JLIPP4RTCBJy8ZrZ8m/a5Tgy4Ij6+djjrgYCZCEhGxSYw7xDIT/9SV8g9NkrbisqBDrILzAH8Yhe4XMRMXo88OAxV5+Vb9Rw1hrqczez6lpFDbJetv8MjofND+pSoAFwtjaL1wTFK9Ud6w4O9AuHlEQH9cGVdvsxFcosRwJVh58x51JM9ptoktqhx/HHJBTuCHCYYlHwtRwbwqnMYdLzKZG5FnujT8DG+9mcva1fL6tzW/XD505VPMWwXFC/2/pvolgAkTFFXYSALAwZlK3IgoXU8Gok/3B4iHofzQsFf6Yq3BI/88x7tVASUqiYhoKrO50+gb6pNIRCyWgGUiBEVXBp6Ziq3ORQPyQJg7i9HHUGTUu74yvGLHWLwjNQzZP/hxJZK3VlJxzyXntdOxiJc8iOzNrU+rPKBAlAaE6bQDOoniIysEgdD5BXHTLFzPvts4P1n2Ckor5/rNJ+qXR8GU+/y7e1GKU=
file: nheko-x86_64.AppImage
on:
condition: $TRAVIS_OS_NAME == linux
repo: mujx/nheko
tags: true
- skip_cleanup: true
overwrite: true
provider: releases
api_key:
secure: oprXzESukFiXBeF2BXkXUlegsAQc95Ub4kc/OkoNFaYBvqpA+IGpWHmHCx5JPny/OT3Kc2Urpe2JUeGSWDHZ7UCKDjH+NzGP3uN82fHh/HiszG/Srw7+hWEHm1ve+gMK9GS8pr+yUsUrPP0UfVXlPoI4pBWa4zGi2Ndb/SqvjCgIHFLtGyoBo6CydKQ/AyWiXSMKDfJL+Dx4JLIPP4RTCBJy8ZrZ8m/a5Tgy4Ij6+djjrgYCZCEhGxSYw7xDIT/9SV8g9NkrbisqBDrILzAH8Yhe4XMRMXo88OAxV5+Vb9Rw1hrqczez6lpFDbJetv8MjofND+pSoAFwtjaL1wTFK9Ud6w4O9AuHlEQH9cGVdvsxFcosRwJVh58x51JM9ptoktqhx/HHJBTuCHCYYlHwtRwbwqnMYdLzKZG5FnujT8DG+9mcva1fL6tzW/XD505VPMWwXFC/2/pvolgAkTFFXYSALAwZlK3IgoXU8Gok/3B4iHofzQsFf6Yq3BI/88x7tVASUqiYhoKrO50+gb6pNIRCyWgGUiBEVXBp6Ziq3ORQPyQJg7i9HHUGTUu74yvGLHWLwjNQzZP/hxJZK3VlJxzyXntdOxiJc8iOzNrU+rPKBAlAaE6bQDOoniIysEgdD5BXHTLFzPvts4P1n2Ckor5/rNJ+qXR8GU+/y7e1GKU=
file: nheko.dmg
on:
condition: $TRAVIS_OS_NAME == osx
repo: mujx/nheko
tags: true

810
.vscode/.cmaketools.json vendored Normal file
View File

@ -0,0 +1,810 @@
{
"variant": {
"label": "Debug",
"keywordSettings": {
"buildType": "debug"
},
"description": "Emit debug information without performing optimizations"
},
"activeEnvironments": [],
"codeModel": {
"configurations": [
{
"name": "Debug",
"projects": [
{
"buildDirectory": "/home/max/Program/nheko/build",
"name": "nheko",
"sourceDirectory": "/home/max/Program/nheko",
"targets": [
{
"artifacts": [
"/home/max/Program/nheko/build/nheko"
],
"buildDirectory": "/home/max/Program/nheko/build",
"crossReferences": {
"backtrace": [
{
"line": 355,
"name": "add_executable",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"relatedStatements": [
{
"backtrace": [
{
"line": 233,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 234,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 235,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 236,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 238,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 239,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 356,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
},
{
"backtrace": [
{
"line": 356,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
},
{
"backtrace": [
{
"line": 356,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
},
{
"backtrace": [
{
"line": 356,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
},
{
"backtrace": [
{
"line": 356,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
}
]
},
"fileGroups": [
{
"isGenerated": false,
"sources": [
"resources/res.qrc",
"build/translations.qrc"
]
},
{
"compileFlags": " -Wall -Wextra -march=native -Werror -pipe -pedantic -Wunreachable-code -fdiagnostics-color=always -g -fPIC -std=gnu++11",
"defines": [
"QT_CONCURRENT_LIB",
"QT_CORE_LIB",
"QT_GUI_LIB",
"QT_NETWORK_LIB",
"QT_WIDGETS_LIB"
],
"includePath": [
{
"path": "/home/max/Program/nheko/build"
},
{
"path": "/home/max/Program/nheko"
},
{
"path": "/home/max/Program/nheko/include"
},
{
"path": "/home/max/Program/nheko/include/ui"
},
{
"path": "/home/max/Program/nheko/include/events"
},
{
"path": "/home/max/Program/nheko/include/events/messages"
},
{
"path": "/home/max/Program/nheko/libs/lmdbxx"
},
{
"isSystem": true,
"path": "/usr/include/qt5"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtCore"
},
{
"isSystem": true,
"path": "/usr/lib64/qt5/./mkspecs/linux-g++"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtWidgets"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtGui"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtNetwork"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtConcurrent"
}
],
"isGenerated": false,
"language": "CXX",
"sources": [
"src/AvatarProvider.cc",
"src/Cache.cc",
"src/ChatPage.cc",
"src/CommunitiesListItem.cc",
"src/CommunitiesList.cc",
"src/Community.cc",
"src/Deserializable.cc",
"src/EmojiCategory.cc",
"src/EmojiItemDelegate.cc",
"src/EmojiPanel.cc",
"src/EmojiPickButton.cc",
"src/EmojiProvider.cc",
"src/ImageItem.cc",
"src/ImageOverlayDialog.cc",
"src/InputValidator.cc",
"src/JoinRoomDialog.cc",
"src/LeaveRoomDialog.cc",
"src/Login.cc",
"src/LoginPage.cc",
"src/LogoutDialog.cc",
"src/MainWindow.cc",
"src/MatrixClient.cc",
"src/Profile.cc",
"src/QuickSwitcher.cc",
"src/Register.cc",
"src/RegisterPage.cc",
"src/RoomInfoListItem.cc",
"src/RoomList.cc",
"src/RoomMessages.cc",
"src/RoomState.cc",
"src/SideBarActions.cc",
"src/UserSettingsPage.cc",
"src/Splitter.cc",
"src/Sync.cc",
"src/TextInputWidget.cc",
"src/TimelineItem.cc",
"src/TimelineView.cc",
"src/TimelineViewManager.cc",
"src/TopRoomBar.cc",
"src/TrayIcon.cc",
"src/TypingDisplay.cc",
"src/UserInfoWidget.cc",
"src/Versions.cc",
"src/WelcomePage.cc",
"src/main.cc",
"src/ui/Avatar.cc",
"src/ui/Badge.cc",
"src/ui/LoadingIndicator.cc",
"src/ui/FlatButton.cc",
"src/ui/FloatingButton.cc",
"src/ui/Label.cc",
"src/ui/OverlayModal.cc",
"src/ui/ScrollBar.cc",
"src/ui/SnackBar.cc",
"src/ui/RaisedButton.cc",
"src/ui/Ripple.cc",
"src/ui/RippleOverlay.cc",
"src/ui/OverlayWidget.cc",
"src/ui/TextField.cc",
"src/ui/ToggleButton.cc",
"src/ui/Theme.cc",
"src/ui/ThemeManager.cc"
]
},
{
"isGenerated": true,
"sources": [
"build/nheko_de.qm",
"build/nheko_el.qm",
"build/nheko_en.qm",
"build/nheko_nl.qm",
"build/include/moc_AvatarProvider.cpp.rule",
"build/include/moc_ChatPage.cpp.rule",
"build/include/moc_CommunitiesListItem.cpp.rule",
"build/include/moc_CommunitiesList.cpp.rule",
"build/include/moc_Community.cpp.rule",
"build/include/moc_EmojiCategory.cpp.rule",
"build/include/moc_EmojiItemDelegate.cpp.rule",
"build/include/moc_EmojiPanel.cpp.rule",
"build/include/moc_EmojiPickButton.cpp.rule",
"build/include/ui/moc_FloatingButton.cpp.rule",
"build/include/moc_ImageItem.cpp.rule",
"build/include/moc_ImageOverlayDialog.cpp.rule",
"build/include/moc_JoinRoomDialog.cpp.rule",
"build/include/moc_LeaveRoomDialog.cpp.rule",
"build/include/moc_LoginPage.cpp.rule",
"build/include/moc_LogoutDialog.cpp.rule",
"build/include/moc_MainWindow.cpp.rule",
"build/include/moc_MatrixClient.cpp.rule",
"build/include/moc_QuickSwitcher.cpp.rule",
"build/include/moc_RegisterPage.cpp.rule",
"build/include/moc_RoomInfoListItem.cpp.rule",
"build/include/moc_RoomList.cpp.rule",
"build/include/moc_SideBarActions.cpp.rule",
"build/include/moc_UserSettingsPage.cpp.rule",
"build/include/moc_Splitter.cpp.rule",
"build/include/moc_TextInputWidget.cpp.rule",
"build/include/moc_TimelineItem.cpp.rule",
"build/include/moc_TimelineView.cpp.rule",
"build/include/moc_TimelineViewManager.cpp.rule",
"build/include/moc_TopRoomBar.cpp.rule",
"build/include/moc_TrayIcon.cpp.rule",
"build/include/moc_TypingDisplay.cpp.rule",
"build/include/moc_UserInfoWidget.cpp.rule",
"build/include/moc_WelcomePage.cpp.rule",
"build/include/ui/moc_Avatar.cpp.rule",
"build/include/ui/moc_Badge.cpp.rule",
"build/include/ui/moc_LoadingIndicator.cpp.rule",
"build/include/ui/moc_FlatButton.cpp.rule",
"build/include/ui/moc_Label.cpp.rule",
"build/include/ui/moc_OverlayWidget.cpp.rule",
"build/include/ui/moc_ScrollBar.cpp.rule",
"build/include/ui/moc_SnackBar.cpp.rule",
"build/include/ui/moc_RaisedButton.cpp.rule",
"build/include/ui/moc_Ripple.cpp.rule",
"build/include/ui/moc_RippleOverlay.cpp.rule",
"build/include/ui/moc_TextField.cpp.rule",
"build/include/ui/moc_ToggleButton.cpp.rule",
"build/include/ui/moc_Theme.cpp.rule",
"build/include/ui/moc_ThemeManager.cpp.rule",
"build/nheko_de.qm.rule",
"build/nheko_el.qm.rule",
"build/nheko_en.qm.rule",
"build/nheko_nl.qm.rule"
]
},
{
"compileFlags": " -Wall -Wextra -march=native -Werror -pipe -pedantic -Wunreachable-code -fdiagnostics-color=always -g -fPIC -std=gnu++11",
"defines": [
"QT_CONCURRENT_LIB",
"QT_CORE_LIB",
"QT_GUI_LIB",
"QT_NETWORK_LIB",
"QT_WIDGETS_LIB"
],
"includePath": [
{
"path": "/home/max/Program/nheko/build"
},
{
"path": "/home/max/Program/nheko"
},
{
"path": "/home/max/Program/nheko/include"
},
{
"path": "/home/max/Program/nheko/include/ui"
},
{
"path": "/home/max/Program/nheko/include/events"
},
{
"path": "/home/max/Program/nheko/include/events/messages"
},
{
"path": "/home/max/Program/nheko/libs/lmdbxx"
},
{
"isSystem": true,
"path": "/usr/include/qt5"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtCore"
},
{
"isSystem": true,
"path": "/usr/lib64/qt5/./mkspecs/linux-g++"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtWidgets"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtGui"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtNetwork"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtConcurrent"
}
],
"isGenerated": true,
"language": "CXX",
"sources": [
"build/include/moc_AvatarProvider.cpp",
"build/include/moc_ChatPage.cpp",
"build/include/moc_CommunitiesListItem.cpp",
"build/include/moc_CommunitiesList.cpp",
"build/include/moc_Community.cpp",
"build/include/moc_EmojiCategory.cpp",
"build/include/moc_EmojiItemDelegate.cpp",
"build/include/moc_EmojiPanel.cpp",
"build/include/moc_EmojiPickButton.cpp",
"build/include/ui/moc_FloatingButton.cpp",
"build/include/moc_ImageItem.cpp",
"build/include/moc_ImageOverlayDialog.cpp",
"build/include/moc_JoinRoomDialog.cpp",
"build/include/moc_LeaveRoomDialog.cpp",
"build/include/moc_LoginPage.cpp",
"build/include/moc_LogoutDialog.cpp",
"build/include/moc_MainWindow.cpp",
"build/include/moc_MatrixClient.cpp",
"build/include/moc_QuickSwitcher.cpp",
"build/include/moc_RegisterPage.cpp",
"build/include/moc_RoomInfoListItem.cpp",
"build/include/moc_RoomList.cpp",
"build/include/moc_SideBarActions.cpp",
"build/include/moc_UserSettingsPage.cpp",
"build/include/moc_Splitter.cpp",
"build/include/moc_TextInputWidget.cpp",
"build/include/moc_TimelineItem.cpp",
"build/include/moc_TimelineView.cpp",
"build/include/moc_TimelineViewManager.cpp",
"build/include/moc_TopRoomBar.cpp",
"build/include/moc_TrayIcon.cpp",
"build/include/moc_TypingDisplay.cpp",
"build/include/moc_UserInfoWidget.cpp",
"build/include/moc_WelcomePage.cpp",
"build/include/ui/moc_Avatar.cpp",
"build/include/ui/moc_Badge.cpp",
"build/include/ui/moc_LoadingIndicator.cpp",
"build/include/ui/moc_FlatButton.cpp",
"build/include/ui/moc_Label.cpp",
"build/include/ui/moc_OverlayWidget.cpp",
"build/include/ui/moc_ScrollBar.cpp",
"build/include/ui/moc_SnackBar.cpp",
"build/include/ui/moc_RaisedButton.cpp",
"build/include/ui/moc_Ripple.cpp",
"build/include/ui/moc_RippleOverlay.cpp",
"build/include/ui/moc_TextField.cpp",
"build/include/ui/moc_ToggleButton.cpp",
"build/include/ui/moc_Theme.cpp",
"build/include/ui/moc_ThemeManager.cpp",
"build/qrc_res.cpp",
"build/qrc_translations.cpp"
]
}
],
"fullName": "nheko",
"linkFlags": "-rdynamic",
"linkLanguageFlags": "-Wall -Wextra -march=native -Werror -pipe -pedantic -Wunreachable-code -fdiagnostics-color=always -g",
"linkLibraries": "libmatrix_events.a /usr/lib64/libQt5Widgets.so.5.9.2 /usr/lib64/libQt5Network.so.5.9.2 /usr/lib64/libQt5Concurrent.so.5.9.2 -llmdb /usr/lib64/libQt5Gui.so.5.9.2 /usr/lib64/libQt5Core.so.5.9.2",
"linkerLanguage": "CXX",
"name": "nheko",
"sourceDirectory": "/home/max/Program/nheko",
"type": "EXECUTABLE"
},
{
"buildDirectory": "/home/max/Program/nheko/build",
"crossReferences": {
"backtrace": [
{
"line": 300,
"name": "add_custom_target",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"relatedStatements": [
{
"backtrace": [
{
"line": 233,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 234,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 235,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 236,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 238,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 239,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
}
]
},
"fileGroups": [
{
"isGenerated": true,
"sources": [
"build/CMakeFiles/LANG_QRC",
"build/CMakeFiles/LANG_QRC.rule",
"build/nheko_de.qm.rule",
"build/nheko_el.qm.rule",
"build/nheko_en.qm.rule",
"build/nheko_nl.qm.rule"
]
}
],
"fullName": "LANG_QRC",
"name": "LANG_QRC",
"sourceDirectory": "/home/max/Program/nheko",
"type": "UTILITY"
},
{
"artifacts": [
"/home/max/Program/nheko/build/libmatrix_events.a"
],
"buildDirectory": "/home/max/Program/nheko/build",
"crossReferences": {
"backtrace": [
{
"line": 317,
"name": "add_library",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"relatedStatements": [
{
"backtrace": [
{
"line": 233,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 234,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 235,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 236,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 238,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 239,
"name": "include_directories",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_include_directories"
},
{
"backtrace": [
{
"line": 318,
"name": "target_link_libraries",
"path": "/home/max/Program/nheko/CMakeLists.txt"
},
{
"path": "/home/max/Program/nheko/CMakeLists.txt"
}
],
"type": "target_link_libraries"
}
]
},
"fileGroups": [
{
"compileFlags": " -Wall -Wextra -march=native -Werror -pipe -pedantic -Wunreachable-code -fdiagnostics-color=always -g -fPIC -std=gnu++11",
"defines": [
"QT_CORE_LIB"
],
"includePath": [
{
"path": "/home/max/Program/nheko/build"
},
{
"path": "/home/max/Program/nheko"
},
{
"path": "/home/max/Program/nheko/include"
},
{
"path": "/home/max/Program/nheko/include/ui"
},
{
"path": "/home/max/Program/nheko/include/events"
},
{
"path": "/home/max/Program/nheko/include/events/messages"
},
{
"path": "/home/max/Program/nheko/libs/lmdbxx"
},
{
"isSystem": true,
"path": "/usr/include/qt5"
},
{
"isSystem": true,
"path": "/usr/include/qt5/QtCore"
},
{
"isSystem": true,
"path": "/usr/lib64/qt5/./mkspecs/linux-g++"
}
],
"isGenerated": false,
"language": "CXX",
"sources": [
"src/events/Event.cc",
"src/events/AliasesEventContent.cc",
"src/events/AvatarEventContent.cc",
"src/events/CanonicalAliasEventContent.cc",
"src/events/CreateEventContent.cc",
"src/events/HistoryVisibilityEventContent.cc",
"src/events/JoinRulesEventContent.cc",
"src/events/MemberEventContent.cc",
"src/events/MessageEventContent.cc",
"src/events/NameEventContent.cc",
"src/events/PowerLevelsEventContent.cc",
"src/events/TopicEventContent.cc",
"src/events/messages/Audio.cc",
"src/events/messages/Emote.cc",
"src/events/messages/File.cc",
"src/events/messages/Image.cc",
"src/events/messages/Location.cc",
"src/events/messages/Notice.cc",
"src/events/messages/Text.cc",
"src/events/messages/Video.cc",
"src/Deserializable.cc"
]
}
],
"fullName": "libmatrix_events.a",
"linkerLanguage": "CXX",
"name": "matrix_events",
"sourceDirectory": "/home/max/Program/nheko",
"type": "STATIC_LIBRARY"
}
]
}
]
}
],
"cookie": "0.46582309898312313",
"inReplyTo": "codemodel",
"type": "reply"
}
}

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "null",
"includePath": [],
"defines": [],
"browse": {
"path": [],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"intelliSenseMode": "clang-x64"
}
],
"version": 3
}

12
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,12 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "echo",
"type": "shell",
"command": "echo Hello"
}
]
}

View File

@ -1,10 +1,25 @@
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.1)
project(nheko C CXX)
option(BUILD_TESTS "Build all tests" OFF) option(BUILD_TESTS "Build all tests" OFF)
option(APPVEYOR_BUILD "Build on appveyor" OFF) option(APPVEYOR_BUILD "Build on appveyor" OFF)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# Include Qt basic functions
include(QtCommon)
project(nheko LANGUAGES C CXX VERSION 0.1.0)
# Set PROJECT_VERSION_PATCH and PROJECT_VERSION_TWEAK to 0 if not present, needed by add_project_meta
fix_project_version()
# Set additional project information
set(COMPANY "Nheko")
set(COPYRIGHT "Copyright (c) 2017 Mujx")
set(IDENTIFIER "com.mujx.nheko")
add_project_meta(META_FILES_TO_INCLUDE)
# #
# LMDB # LMDB
# #
@ -156,6 +171,7 @@ set(SRC_FILES
src/RoomMessages.cc src/RoomMessages.cc
src/RoomState.cc src/RoomState.cc
src/SideBarActions.cc src/SideBarActions.cc
src/UserSettingsPage.cc
src/Splitter.cc src/Splitter.cc
src/Sync.cc src/Sync.cc
src/TextInputWidget.cc src/TextInputWidget.cc
@ -184,6 +200,7 @@ set(SRC_FILES
src/ui/RippleOverlay.cc src/ui/RippleOverlay.cc
src/ui/OverlayWidget.cc src/ui/OverlayWidget.cc
src/ui/TextField.cc src/ui/TextField.cc
src/ui/ToggleButton.cc
src/ui/Theme.cc src/ui/Theme.cc
src/ui/ThemeManager.cc src/ui/ThemeManager.cc
) )
@ -245,6 +262,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/RoomInfoListItem.h include/RoomInfoListItem.h
include/RoomList.h include/RoomList.h
include/SideBarActions.h include/SideBarActions.h
include/UserSettingsPage.h
include/Splitter.h include/Splitter.h
include/TextInputWidget.h include/TextInputWidget.h
include/TimelineItem.h include/TimelineItem.h
@ -268,6 +286,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/ui/Ripple.h include/ui/Ripple.h
include/ui/RippleOverlay.h include/ui/RippleOverlay.h
include/ui/TextField.h include/ui/TextField.h
include/ui/ToggleButton.h
include/ui/Theme.h include/ui/Theme.h
include/ui/ThemeManager.h include/ui/ThemeManager.h
) )
@ -298,14 +317,6 @@ qt5_add_resources(QRC resources/res.qrc)
add_library(matrix_events ${MATRIX_EVENTS} src/Deserializable.cc) add_library(matrix_events ${MATRIX_EVENTS} src/Deserializable.cc)
target_link_libraries(matrix_events Qt5::Core) target_link_libraries(matrix_events Qt5::Core)
#
# Bundle icons.
#
if (APPLE)
set(ICON_FILE resources/nheko.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
endif()
if (BUILD_TESTS) if (BUILD_TESTS)
enable_testing() enable_testing()
@ -332,15 +343,15 @@ else()
set (NHEKO_LIBS matrix_events Qt5::Widgets Qt5::Network Qt5::Concurrent ${LMDB_LIBRARY}) set (NHEKO_LIBS matrix_events Qt5::Widgets Qt5::Network Qt5::Concurrent ${LMDB_LIBRARY})
endif() endif()
set (NHEKO_DEPS ${OS_BUNDLE} ${SRC_FILES} ${UI_HEADERS} ${MOC_HEADERS} ${QRC} ${LANG_QRC} ${QM_SRC}) set (NHEKO_DEPS ${SRC_FILES} ${UI_HEADERS} ${MOC_HEADERS} ${QRC} ${LANG_QRC} ${QM_SRC} ${META_FILES_TO_INCLUDE})
if(APPLE) if(APPLE)
add_executable (nheko ${NHEKO_DEPS}) add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras) target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
elseif(WIN32) elseif(WIN32)
add_executable (nheko ${ICON_FILE} ${NHEKO_DEPS}) add_executable (nheko ${OS_BUNDLE} ${ICON_FILE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain) target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
else() else()
add_executable (nheko ${NHEKO_DEPS}) add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS}) target_link_libraries (nheko ${NHEKO_LIBS})
endif() endif()

View File

@ -1,9 +1,4 @@
APP_NAME = nheko
MAC_DIST_DIR = dist/MacOS
APP_TEMPLATE = $(MAC_DIST_DIR)/Nheko.app
# Linux specific helpers
debug: debug:
@cmake -DBUILD_TESTS=OFF -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Debug @cmake -DBUILD_TESTS=OFF -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Debug
@cmake --build build @cmake --build build
@ -17,19 +12,14 @@ test:
@cmake --build build @cmake --build build
@cd build && GTEST_COLOR=1 ctest --verbose @cd build && GTEST_COLOR=1 ctest --verbose
app: release-debug $(APP_TEMPLATE) linux-appimage:
@cp -fp ./build/$(APP_NAME) $(APP_TEMPLATE)/Contents/MacOS @./.ci/linux/deploy.sh
@echo "Created '$(APP_NAME).app' in '$(APP_TEMPLATE)'"
app-install: app macos-app: release-debug
cp -Rf $(APP_TEMPLATE) /Applications/ @./.ci/macos/deploy.sh
dmg: app macos-app-install:
hdiutil create $(MAC_DIST_DIR)/Nheko.dmg \ cp -Rf build/nheko.app /Applications
-volname "$(APP_NAME)" \
-fs HFS+ \
-srcfolder $(MAC_DIST_DIR) \
-ov -format UDZO
run: run:
@./build/nheko @./build/nheko

View File

@ -16,10 +16,12 @@ but we are getting close to a more feature complete client.
Specifically there is support for: Specifically there is support for:
- Joining & leaving rooms - Joining & leaving rooms
- Sending & receiving images and emoji. - Sending & receiving images and emoji.
- Receiving typing notifications. - Typing notifications.
### Installation ### Installation
There are pre-built nigtly releases [here](https://github.com/mujx/nheko/releases/tag/nightly) for Linux ([AppImage](https://appimage.org/)), Mac and Windows.
#### Arch Linux #### Arch Linux
```bash ```bash
pacaur -S nheko-git pacaur -S nheko-git
@ -37,10 +39,6 @@ sudo layman -a matrix
sudo emerge -a nheko sudo emerge -a nheko
``` ```
#### Windows
You can find an installer [here](https://ci.appveyor.com/project/mujx/nheko/branch/master/artifacts).
### Build Requirements ### Build Requirements
- Qt5 (5.7 or greater). Qt 5.7 adds support for color font rendering with - Qt5 (5.7 or greater). Qt 5.7 adds support for color font rendering with
@ -105,15 +103,7 @@ make -C build
The `nheko` binary will be located in the `build` directory. The `nheko` binary will be located in the `build` directory.
##### MacOS #### Nix
You can create an app bundle with `make app`. The output will be located at
`dist/MacOS/Nheko.app` which can be copied to `/Applications/Nheko.app`.
You can also create a disk image with `make dmg`. The output will be located at
`dist/MacOS/Nheko.dmg`
##### Nix
Download the repo as mentioned above and run Download the repo as mentioned above and run

View File

@ -15,14 +15,32 @@ build:
install: install:
- set QT_DIR=C:\Qt\5.8\msvc2015_64 - set QT_DIR=C:\Qt\5.8\msvc2015_64
- set PATH=%PATH%;%QT_DIR%\bin;C:\MinGW\bin - set PATH=%PATH%;%QT_DIR%\bin;C:\MinGW\bin
- copy LICENSE .ci\windows\LICENSE.txt
- cinst nsis --version 3.0 -y -installArgs /D=C:\nsis
build_script: build_script:
- cmake -G "Visual Studio 14 2015 Win64" -H. -Bbuild -DAPPVEYOR_BUILD=ON # VERSION format: branch-master/branch-1.2
# INSTVERSION format: x.y.z
# WINVERSION format: 9999.0.0.123/1.2.0.234
- if "%APPVEYOR_REPO_TAG%"=="false" set INSTVERSION=0.1.0
- if "%APPVEYOR_REPO_TAG%"=="false" set VERSION=0.1.0
- if "%APPVEYOR_REPO_TAG%"=="false" if "%APPVEYOR_REPO_BRANCH%"=="master" set INSTVERSION=9999.0
- if "%APPVEYOR_REPO_TAG%"=="false" set WINVERSION=%INSTVERSION%.0.%APPVEYOR_BUILD_NUMBER%
# VERSION format: v1.2.3/v1.3.4
# INSTVERSION format: 1.2.3/1.3.4
# WINVERSION format: 1.2.3.123/1.3.4.234
- if "%APPVEYOR_REPO_TAG%"=="true" set VERSION=%APPVEYOR_REPO_TAG_NAME%
- if "%APPVEYOR_REPO_TAG%"=="true" set INSTVERSION=%VERSION:~1%
- if "%APPVEYOR_REPO_TAG%"=="true" set WINVERSION=%VERSION:~1%.%APPVEYOR_BUILD_NUMBER%
- set DATE=%date:~10,4%-%date:~4,2%-%date:~7,2%
- echo %VERSION%
- echo %INSTVERSION%
- echo %DATE%
- cmake -G "Visual Studio 14 2015 Win64" -H. -Bbuild -DAPPVEYOR_BUILD=ON -DCMAKE_BUILD_TYPE=Release
- cmake --build build --config Release - cmake --build build --config Release
after_build: after_build:
# Variables
- set BUILD=%APPVEYOR_BUILD_FOLDER%
- echo %BUILD%
- mkdir NhekoRelease - mkdir NhekoRelease
- copy build\Release\nheko.exe NhekoRelease\nheko.exe - copy build\Release\nheko.exe NhekoRelease\nheko.exe
- windeployqt --qmldir C:\Qt\5.8\msvc2015_64\qml\ --release NhekoRelease\nheko.exe - windeployqt --qmldir C:\Qt\5.8\msvc2015_64\qml\ --release NhekoRelease\nheko.exe
@ -31,9 +49,58 @@ after_build:
- copy C:\OpenSSL-Win64\lib\libeay32.lib .\NhekoRelease\libeay32.lib - copy C:\OpenSSL-Win64\lib\libeay32.lib .\NhekoRelease\libeay32.lib
- copy C:\OpenSSL-Win64\bin\ssleay32.dll .\NhekoRelease\ssleay32.dll - copy C:\OpenSSL-Win64\bin\ssleay32.dll .\NhekoRelease\ssleay32.dll
- 7z a nheko_win_64.zip .\NhekoRelease\* - 7z a nheko_win_64.zip .\NhekoRelease\*
- C:\nsis\makensis .ci\windows\nheko.nsi - ls -lh build\Release\
- ls -lh NhekoRelease\
- mkdir NhekoData
- xcopy .\NhekoRelease\*.* NhekoData\*.* /s /e /c /y
#
# Create the Qt Installer Framework version
#
- mkdir installer
- mkdir installer\config
- mkdir installer\packages
- mkdir installer\packages\com.mujx.nheko
- mkdir installer\packages\com.mujx.nheko\data
- mkdir installer\packages\com.mujx.nheko\meta
- mkdir installer\packages\com.mujx.nheko.cleanup\meta
# Copy installer data
- copy %BUILD%\resources\nheko.ico installer\config
- copy %BUILD%\resources\nheko.png installer\config
- copy %BUILD%\LICENSE installer\packages\com.mujx.nheko\meta\license.txt
- copy %BUILD%\LICENSE installer\packages\com.mujx.nheko.cleanup\meta\license.txt
- copy %BUILD%\deploy\installer\config.xml installer\config
- copy %BUILD%\deploy\installer\controlscript.qs installer\config
- copy %BUILD%\deploy\installer\uninstall.qs installer\packages\com.mujx.nheko\data
- copy %BUILD%\deploy\installer\gui\package.xml installer\packages\com.mujx.nheko\meta
- copy %BUILD%\deploy\installer\gui\installscript.qs installer\packages\com.mujx.nheko\meta
- copy %BUILD%\deploy\installer\cleanup\package.xml installer\packages\com.mujx.nheko.cleanup\meta
- copy %BUILD%\deploy\installer\cleanup\installscript.qs installer\packages\com.mujx.nheko.cleanup\meta
# Amend version and date
- sed -i "s/__VERSION__/0.1.0/" installer\config\config.xml
- sed -i "s/__VERSION__/0.1.0/" installer\packages\com.mujx.nheko\meta\package.xml
- sed -i "s/__VERSION__/0.1.0/" installer\packages\com.mujx.nheko.cleanup\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\com.mujx.nheko\meta\package.xml
- sed -i "s/__DATE__/%DATE%/" installer\packages\com.mujx.nheko.cleanup\meta\package.xml
# Copy nheko data
- xcopy NhekoData\*.* installer\packages\com.mujx.nheko\data\*.* /s /e /c /y
- move NhekoRelease\nheko.exe installer\packages\com.mujx.nheko\data
- mkdir tools
- curl -L -O https://download.qt.io/official_releases/qt-installer-framework/3.0.1/QtInstallerFramework-win-x86.exe
- 7z x QtInstallerFramework-win-x86.exe -otools -aoa
- set PATH=%BUILD%\tools\bin;%PATH%
- binarycreator.exe -f -c installer\config\config.xml -p installer\packages nheko-installer.exe
deploy:
description: "Development builds"
provider: GitHub
auth_token:
secure: YqB7hcM+4482eSHhtVR7ZA7N7lE78y8BC897/7UDTBQd+NWdWFW/6S+oKDie9TT7
artifact: nheko-installer.exe
force_update: true
prerelease: true
on:
appveyor_repo_tag: true
artifacts: artifacts:
- path: nheko_win_64.zip - path: nheko_win_64.zip
- path: NhekoRelease\nheko.exe - path: nheko-installer.exe
- path: nheko_setup.exe

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

76
cmake/QtCommon.cmake Normal file
View File

@ -0,0 +1,76 @@
macro(fix_project_version)
if (NOT PROJECT_VERSION_PATCH)
set(PROJECT_VERSION_PATCH 0)
endif()
if (NOT PROJECT_VERSION_TWEAK)
set(PROJECT_VERSION_TWEAK 0)
endif()
endmacro()
macro(add_project_meta FILES_TO_INCLUDE)
if (NOT RESOURCE_FOLDER)
set(RESOURCE_FOLDER resources)
endif()
if (NOT ICON_NAME)
set(ICON_NAME nheko)
endif()
if (APPLE)
set(ICON_FILE ${RESOURCE_FOLDER}/${ICON_NAME}.icns)
elseif (WIN32)
set(ICON_FILE ${RESOURCE_FOLDER}/${ICON_NAME}.ico)
endif()
if (WIN32)
configure_file("${PROJECT_SOURCE_DIR}/cmake/windows_metafile.rc.in"
"windows_metafile.rc"
)
set(RES_FILES "windows_metafile.rc")
set(CMAKE_RC_COMPILER_INIT windres)
ENABLE_LANGUAGE(RC)
SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif()
if (APPLE)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
# Identify MacOS bundle
set(MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME})
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION})
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
set(MACOSX_BUNDLE_COPYRIGHT ${COPYRIGHT})
set(MACOSX_BUNDLE_GUI_IDENTIFIER ${IDENTIFIER})
set(MACOSX_BUNDLE_ICON_FILE ${ICON_NAME})
endif()
if (APPLE)
set(${FILES_TO_INCLUDE} ${ICON_FILE})
elseif (WIN32)
set(${FILES_TO_INCLUDE} ${RES_FILES})
endif()
endmacro()
macro(init_os_bundle)
if (APPLE)
set(OS_BUNDLE MACOSX_BUNDLE)
elseif (WIN32)
IF(CMAKE_BUILD_TYPE MATCHES Release)
set(OS_BUNDLE WIN32)
endif()
endif()
endmacro()
macro(fix_win_compiler)
if (MSVC)
set_target_properties(${PROJECT_NAME} PROPERTIES
WIN32_EXECUTABLE YES
LINK_FLAGS "/ENTRY:mainCRTStartup"
)
endif()
endmacro()
init_os_bundle()
fix_win_compiler()

View File

@ -0,0 +1,28 @@
#include "winver.h"
IDI_ICON1 ICON DISCARDABLE "@ICON_FILE@"
VS_VERSION_INFO VERSIONINFO
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,@PROJECT_VERSION_TWEAK@
FILEFLAGS 0x0L
FILEFLAGSMASK 0x3fL
FILEOS 0x00040004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "CompanyName", "@COMPANY@"
VALUE "FileDescription", "@PROJECT_NAME@"
VALUE "FileVersion", "@PROJECT_VERSION@"
VALUE "LegalCopyright", "@COPYRIGHT@"
VALUE "InternalName", "@PROJECT_NAME@"
VALUE "OriginalFilename", "@PROJECT_NAME@.exe"
VALUE "ProductName", "@PROJECT_NAME@"
VALUE "ProductVersion", "@PROJECT_VERSION@"
END
END
END

View File

@ -0,0 +1,28 @@
function Component()
{
}
Component.prototype.createOperations = function()
{
component.createOperations();
try
{
if( installer.value("os") === "win" )
{
/**
* Cleanup AppData and registry
*/
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C reg delete HKEY_CURRENT_USER\Software\nheko\nheko /f");
var localappdata = installer.environmentVariable("LOCALAPPDATA");
if( localappdata != "" )
{
component.addElevatedOperation("Execute","UNDOEXECUTE","cmd /C rmdir "+localappdata+"\nheko /f");
}
}
}
catch( e )
{
print( e );
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Package>
<DisplayName>Cleanup AppData and Registry</DisplayName>
<Description>Cleans up AppData and Registry when selected (logs you out) - Broken</Description>
<Version>__VERSION__</Version>
<ReleaseDate>__DATE__</ReleaseDate>
<SortingPriority>80</SortingPriority>
<Script>installscript.qs</Script>
<Checkable>false</Checkable>
</Package>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Installer>
<Name>Nheko</Name>
<Version>__VERSION__</Version>
<Title>Nheko Installer</Title>
<Publisher>Mujx</Publisher>
<ProductUrl>https://github.com/mujx/nheko</ProductUrl>
<InstallerWindowIcon>nheko</InstallerWindowIcon>
<InstallerApplicationIcon>nheko</InstallerApplicationIcon>
<Logo>nheko.png</Logo>
<StartMenuDir>Nheko</StartMenuDir>
<TargetDir>@ApplicationsDir@/nheko</TargetDir>
<RunProgram>@TargetDir@/nheko.exe</RunProgram>
<ControlScript>controlscript.qs</ControlScript>
</Installer>

View File

@ -0,0 +1,25 @@
/**
* Source: http://stackoverflow.com/questions/21389105/qt-installer-framework-offline-update-how
*/
function Controller()
{
}
Controller.prototype.TargetDirectoryPageCallback = function()
{
var widget = gui.currentPageWidget();
widget.TargetDirectoryLineEdit.textChanged.connect( this, Controller.prototype.targetChanged );
Controller.prototype.targetChanged( widget.TargetDirectoryLineEdit.text );
}
Controller.prototype.targetChanged = function( text )
{
if( text != "" && installer.fileExists(text + "/components.xml") )
{
if( QMessageBox.question("PreviousInstallation", "Previous installation detected", "Do you want to uninstall the previous installation?", QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes )
{
installer.execute( text+"/maintenancetool.exe", new Array("--script", text+"/uninstall.qs") )
}
}
}

View File

@ -0,0 +1,32 @@
function Component()
{
}
Component.prototype.createOperations = function()
{
component.createOperations();
try
{
if( installer.value("os") === "win" )
{
/**
* Start Menu Shortcut
*/
component.addOperation( "CreateShortcut", "@TargetDir@\\nheko.exe", "@StartMenuDir@\\nheko.lnk",
"workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\nheko.exe",
"iconId=0", "description=Desktop client for the Matrix protocol");
/**
* Desktop Shortcut
*/
component.addOperation( "CreateShortcut", "@TargetDir@\\nheko.exe", "@DesktopDir@\\nheko.lnk",
"workingDirectory=@TargetDir@", "iconPath=@TargetDir@\\nheko.exe",
"iconId=0", "description=Desktop client for the Matrix protocol");
}
}
catch( e )
{
print( e );
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<Package>
<DisplayName>Nheko</DisplayName>
<Description>Desktop client for the Matrix protocol</Description>
<Version>__VERSION__</Version>
<ReleaseDate>__DATE__</ReleaseDate>
<Licenses>
<License name="Nheko License - GPLv3" file="license.txt" />
</Licenses>
<Default>true</Default>
<ForcedInstallation>true</ForcedInstallation>
<SortingPriority>100</SortingPriority>
<Script>installscript.qs</Script>
</Package>

View File

@ -0,0 +1,18 @@
function Controller()
{
}
Controller.prototype.IntroductionPageCallback = function()
{
gui.clickButton( buttons.NextButton );
}
Controller.prototype.ReadyForInstallationPageCallback = function()
{
gui.clickButton( buttons.CommitButton );
}
Controller.prototype.FinishedPageCallback = function()
{
gui.clickButton( buttons.FinishButton );
}

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Nheko</string>
<key>CFBundleExecutable</key>
<string>nheko</string>
<key>CFBundleIdentifier</key>
<string>nheko</string>
<key>CFBundleName</key>
<string>Nheko</string>
<key>CFBundleIconFile</key>
<string>nheko.icns</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>NSHighResolutionCapable</key>
<true />
<key>NSMainNibFile</key>
<string></string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true />
</dict>
</plist>

View File

@ -17,6 +17,7 @@
#pragma once #pragma once
#include <QFrame>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMap> #include <QMap>
#include <QPixmap> #include <QPixmap>
@ -49,6 +50,7 @@ class LeftRoom;
constexpr int CONSENSUS_TIMEOUT = 1000; constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000; constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
class ChatPage : public QWidget class ChatPage : public QWidget
{ {
@ -69,6 +71,7 @@ signals:
void unreadMessages(int count); void unreadMessages(int count);
void showNotification(const QString &msg); void showNotification(const QString &msg);
void showLoginPage(const QString &msg); void showLoginPage(const QString &msg);
void showUserSettingsPage();
private slots: private slots:
void showUnreadMessageNotification(int count); void showUnreadMessageNotification(int count);
@ -119,10 +122,8 @@ private:
QWidget *sideBarTopWidget_; QWidget *sideBarTopWidget_;
QVBoxLayout *sideBarTopWidgetLayout_; QVBoxLayout *sideBarTopWidgetLayout_;
QWidget *content_; QFrame *content_;
QVBoxLayout *contentLayout_; QVBoxLayout *contentLayout_;
QHBoxLayout *topBarLayout_;
QVBoxLayout *mainContentLayout_;
CommunitiesList *communitiesList_; CommunitiesList *communitiesList_;
RoomList *room_list_; RoomList *room_list_;
@ -153,6 +154,7 @@ private:
// Keeps track of the users currently typing on each room. // Keeps track of the users currently typing on each room.
QMap<QString, QList<QString>> typingUsers_; QMap<QString, QList<QString>> typingUsers_;
QTimer *typingRefresher_;
QSharedPointer<QuickSwitcher> quickSwitcher_; QSharedPointer<QuickSwitcher> quickSwitcher_;
QSharedPointer<OverlayModal> quickSwitcherModal_; QSharedPointer<OverlayModal> quickSwitcherModal_;

View File

@ -30,6 +30,8 @@ class OverlayModal;
class RegisterPage; class RegisterPage;
class SnackBar; class SnackBar;
class TrayIcon; class TrayIcon;
class UserSettingsPage;
class UserSettings;
class WelcomePage; class WelcomePage;
class MainWindow : public QMainWindow class MainWindow : public QMainWindow
@ -59,6 +61,7 @@ private slots:
// Show the register page in the main window. // Show the register page in the main window.
void showRegisterPage(); void showRegisterPage();
void showUserSettingsPage();
// Show the chat page and start communicating with the given access token. // Show the chat page and start communicating with the given access token.
void showChatPage(QString user_id, QString home_server, QString token); void showChatPage(QString user_id, QString home_server, QString token);
@ -85,6 +88,8 @@ private:
// The main chat area. // The main chat area.
ChatPage *chat_page_; ChatPage *chat_page_;
UserSettingsPage *userSettingsPage_;
QSharedPointer<UserSettings> userSettings_;
// Used to hide undefined states between page transitions. // Used to hide undefined states between page transitions.
QSharedPointer<OverlayModal> progressModal_; QSharedPointer<OverlayModal> progressModal_;

View File

@ -19,6 +19,7 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QUrl> #include <QUrl>
#include <QFileInfo>
#include "MessageEvent.h" #include "MessageEvent.h"
@ -40,8 +41,10 @@ public:
void initialSync() noexcept; void initialSync() noexcept;
void sync() noexcept; void sync() noexcept;
void sendRoomMessage(matrix::events::MessageEventType ty, void sendRoomMessage(matrix::events::MessageEventType ty,
int txnId,
const QString &roomid, const QString &roomid,
const QString &msg, const QString &msg,
const QFileInfo &fileinfo,
const QString &url = "") noexcept; const QString &url = "") noexcept;
void login(const QString &username, const QString &password) noexcept; void login(const QString &username, const QString &password) noexcept;
void registerUser(const QString &username, void registerUser(const QString &username,
@ -59,10 +62,12 @@ public:
void uploadImage(const QString &roomid, const QString &filename); void uploadImage(const QString &roomid, const QString &filename);
void joinRoom(const QString &roomIdOrAlias); void joinRoom(const QString &roomIdOrAlias);
void leaveRoom(const QString &roomId); void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
void removeTypingNotification(const QString &roomid);
QUrl getHomeServer() { return server_; }; QUrl getHomeServer() { return server_; };
int transactionId() { return txn_id_; }; int transactionId() { return txn_id_; };
void incrementTransactionId() { txn_id_ += 1; }; int incrementTransactionId() { return ++txn_id_; };
void reset() noexcept; void reset() noexcept;
@ -109,6 +114,7 @@ signals:
void syncFailed(const QString &msg); void syncFailed(const QString &msg);
void joinFailed(const QString &msg); void joinFailed(const QString &msg);
void messageSent(const QString &event_id, const QString &roomid, const int txn_id); void messageSent(const QString &event_id, const QString &roomid, const int txn_id);
void messageSendFailed(const QString &roomid, const int txn_id);
void emoteSent(const QString &event_id, const QString &roomid, const int txn_id); void emoteSent(const QString &event_id, const QString &roomid, const int txn_id);
void messagesRetrieved(const QString &room_id, const RoomMessages &msgs); void messagesRetrieved(const QString &room_id, const RoomMessages &msgs);
void joinedRoom(const QString &room_id); void joinedRoom(const QString &room_id);

View File

@ -14,6 +14,9 @@ public:
SideBarActions(QWidget *parent = nullptr); SideBarActions(QWidget *parent = nullptr);
~SideBarActions(); ~SideBarActions();
signals:
void showSettings();
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;

View File

@ -17,6 +17,8 @@
#pragma once #pragma once
#include <deque>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QPaintEvent> #include <QPaintEvent>
#include <QTextEdit> #include <QTextEdit>
@ -29,18 +31,36 @@
namespace msgs = matrix::events::messages; namespace msgs = matrix::events::messages;
static const QString EMOTE_COMMAND("/me ");
static const QString JOIN_COMMAND("/join ");
class FilteredTextEdit : public QTextEdit class FilteredTextEdit : public QTextEdit
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit FilteredTextEdit(QWidget *parent = nullptr); explicit FilteredTextEdit(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *event);
void stopTyping();
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
void submit();
signals: signals:
void enterPressed(); void startedTyping();
void stoppedTyping();
void message(QString);
void command(QString name, QString args);
protected:
void keyPressEvent(QKeyEvent *event) override;
private:
std::deque<QString> true_history_, working_history_;
size_t history_index_;
QTimer *typingTimer_;
void textChanged();
void afterCompletion(int);
}; };
class TextInputWidget : public QFrame class TextInputWidget : public QFrame
@ -51,8 +71,9 @@ public:
TextInputWidget(QWidget *parent = 0); TextInputWidget(QWidget *parent = 0);
~TextInputWidget(); ~TextInputWidget();
void stopTyping();
public slots: public slots:
void onSendButtonClicked();
void openFileSelection(); void openFileSelection();
void hideUploadSpinner(); void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); }; void focusLineEdit() { input_->setFocus(); };
@ -66,10 +87,15 @@ signals:
void uploadImage(QString filename); void uploadImage(QString filename);
void sendJoinRoomRequest(const QString &room); void sendJoinRoomRequest(const QString &room);
void startedTyping();
void stoppedTyping();
protected:
void focusInEvent(QFocusEvent *event);
private: private:
void showUploadSpinner(); void showUploadSpinner();
QString parseEmoteCommand(const QString &cmd); void command(QString name, QString args);
QString parseJoinCommand(const QString &cmd);
QHBoxLayout *topLayout_; QHBoxLayout *topLayout_;
FilteredTextEdit *input_; FilteredTextEdit *input_;

View File

@ -19,6 +19,7 @@
#include <QLayout> #include <QLayout>
#include <QList> #include <QList>
#include <QQueue>
#include <QScrollArea> #include <QScrollArea>
#include "Emote.h" #include "Emote.h"
@ -42,18 +43,26 @@ namespace events = matrix::events;
// but not yet confirmed by the homeserver through sync. // but not yet confirmed by the homeserver through sync.
struct PendingMessage struct PendingMessage
{ {
matrix::events::MessageEventType ty;
int txn_id; int txn_id;
QString body; QString body;
QString filename;
QString event_id; QString event_id;
TimelineItem *widget; TimelineItem *widget;
PendingMessage(int txn_id, QString body, QString event_id, TimelineItem *widget) PendingMessage(matrix::events::MessageEventType ty,
: txn_id(txn_id) int txn_id,
QString body,
QString filename,
QString event_id,
TimelineItem *widget)
: ty(ty)
, txn_id(txn_id)
, body(body) , body(body)
, filename(filename)
, event_id(event_id) , event_id(event_id)
, widget(widget) , widget(widget)
{ {}
}
}; };
// In which place new TimelineItems should be inserted. // In which place new TimelineItems should be inserted.
@ -87,8 +96,8 @@ public:
// Add new events at the end of the timeline. // Add new events at the end of the timeline.
int addEvents(const Timeline &timeline); int addEvents(const Timeline &timeline);
void addUserMessage(matrix::events::MessageEventType ty, const QString &msg, int txn_id); void addUserMessage(matrix::events::MessageEventType ty, const QString &msg);
void addUserMessage(const QString &url, const QString &filename, int txn_id); void addUserMessage(const QString &url, const QString &filename);
void updatePendingMessage(int txn_id, QString event_id); void updatePendingMessage(int txn_id, QString event_id);
void scrollDown(); void scrollDown();
@ -103,6 +112,11 @@ public slots:
// Whether or not the initial batch has been loaded. // Whether or not the initial batch has been loaded.
bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }; bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; };
void handleFailedMessage(int txnid);
private slots:
void sendNextPendingMessage();
signals: signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info); void updateLastTimelineMessage(const QString &user, const DescInfo &info);
@ -116,13 +130,11 @@ private:
// sender's name. // sender's name.
bool isSenderRendered(const QString &user_id, TimelineDirection direction); bool isSenderRendered(const QString &user_id, TimelineDirection direction);
bool isPendingMessage(const QString &eventid, bool isPendingMessage(const QString &txnid, const QString &sender, const QString &userid);
const QString &body, void removePendingMessage(const QString &txnid);
const QString &sender,
const QString &userid);
void removePendingMessage(const QString &eventid, const QString &body);
bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); }; bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); };
void handleNewUserMessage(PendingMessage msg);
// Return nullptr if the event couldn't be parsed. // Return nullptr if the event couldn't be parsed.
TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction); TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction);
@ -163,6 +175,7 @@ private:
// The events currently rendered. Used for duplicate detection. // The events currently rendered. Used for duplicate detection.
QMap<QString, bool> eventIds_; QMap<QString, bool> eventIds_;
QList<PendingMessage> pending_msgs_; QQueue<PendingMessage> pending_msgs_;
QList<PendingMessage> pending_sent_msgs_;
QSharedPointer<MatrixClient> client_; QSharedPointer<MatrixClient> client_;
}; };

View File

@ -21,6 +21,8 @@
#include <QSharedPointer> #include <QSharedPointer>
#include <QStackedWidget> #include <QStackedWidget>
#include "MessageEvent.h"
class JoinedRoom; class JoinedRoom;
class MatrixClient; class MatrixClient;
class RoomInfoListItem; class RoomInfoListItem;
@ -61,12 +63,13 @@ signals:
public slots: public slots:
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void sendTextMessage(const QString &msg); void queueTextMessage(const QString &msg);
void sendEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
void sendImageMessage(const QString &roomid, const QString &filename, const QString &url); void queueImageMessage(const QString &roomid, const QString &filename, const QString &url);
private slots: private slots:
void messageSent(const QString &eventid, const QString &roomid, int txnid); void messageSent(const QString &eventid, const QString &roomid, int txnid);
void messageSendFailed(const QString &roomid, int txnid);
private: private:
QString active_room_; QString active_room_;

View File

@ -0,0 +1,86 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QComboBox>
#include <QFrame>
#include <QLayout>
#include <QSharedPointer>
#include <QWidget>
class Toggle;
constexpr int OptionMargin = 6;
constexpr int LayoutTopMargin = 50;
constexpr int LayoutBottomMargin = LayoutTopMargin;
class UserSettings
{
public:
UserSettings();
void save();
void load();
void setTheme(QString theme) { theme_ = theme; }
void setTray(bool state) { isTrayEnabled_ = state; }
QString theme() const { return !theme_.isEmpty() ? theme_ : "default"; }
bool isTrayEnabled() const { return isTrayEnabled_; }
private:
QString theme_;
bool isTrayEnabled_;
};
class HorizontalLine : public QFrame
{
Q_OBJECT
public:
HorizontalLine(QWidget *parent = nullptr);
};
class UserSettingsPage : public QWidget
{
Q_OBJECT
public:
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = 0);
protected:
void showEvent(QShowEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
signals:
void moveBack();
void trayOptionChanged(bool value);
private:
// Layouts
QVBoxLayout *topLayout_;
QVBoxLayout *mainLayout_;
QHBoxLayout *topBarLayout_;
// Shared settings object.
QSharedPointer<UserSettings> settings_;
Toggle *trayToggle_;
QComboBox *themeCombo_;
int sideMargin_ = 0;
};

View File

@ -60,12 +60,31 @@ isMessageEvent(EventType type);
bool bool
isStateEvent(EventType type); isStateEvent(EventType type);
class UnsignedData
: public Deserializable
, public Serializable
{
public:
double age() const { return age_; }
QString transactionId() const { return transaction_id_; }
bool isEmpty() const { return age_ <= 0 && transaction_id_.isEmpty(); }
void deserialize(const QJsonValue &data) override;
QJsonObject serialize() const override;
private:
double age_ = 0;
QString transaction_id_;
};
template<class Content> template<class Content>
class Event : public Deserializable, public Serializable class Event : public Deserializable, public Serializable
{ {
public: public:
Content content() const; Content content() const;
EventType eventType() const; EventType eventType() const;
UnsignedData unsignedData() const { return unsignedData_; }
void deserialize(const QJsonValue &data) override; void deserialize(const QJsonValue &data) override;
QJsonObject serialize() const override; QJsonObject serialize() const override;
@ -73,6 +92,7 @@ public:
private: private:
Content content_; Content content_;
EventType type_; EventType type_;
UnsignedData unsignedData_;
}; };
template<class Content> template<class Content>
@ -100,6 +120,9 @@ Event<Content>::deserialize(const QJsonValue &data)
content_.deserialize(object.value("content")); content_.deserialize(object.value("content"));
type_ = extractEventType(object); type_ = extractEventType(object);
if (object.contains("unsigned"))
unsignedData_.deserialize(object.value("unsigned"));
} }
template<class Content> template<class Content>
@ -149,6 +172,9 @@ Event<Content>::serialize() const
object["content"] = content_.serialize(); object["content"] = content_.serialize();
if (!unsignedData_.isEmpty())
object["unsigned"] = unsignedData_.serialize();
return object; return object;
} }
} // namespace events } // namespace events

View File

@ -12,8 +12,7 @@ enum class AvatarType
Letter Letter
}; };
namespace sidebar namespace sidebar {
{
static const int SmallSize = 60; static const int SmallSize = 60;
static const int NormalSize = 300; static const int NormalSize = 300;
static const int CommunitiesSidebarSize = 64; static const int CommunitiesSidebarSize = 64;

110
include/ui/ToggleButton.h Normal file
View File

@ -0,0 +1,110 @@
#pragma once
#include <QAbstractButton>
#include <QColor>
class ToggleTrack;
class ToggleThumb;
enum class Position
{
Left,
Right
};
class Toggle : public QAbstractButton
{
Q_OBJECT
Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor)
Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor)
Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor)
Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
public:
Toggle(QWidget *parent = nullptr);
void setState(bool isEnabled);
void setActiveColor(const QColor &color);
void setDisabledColor(const QColor &color);
void setInactiveColor(const QColor &color);
void setTrackColor(const QColor &color);
QColor activeColor() const { return activeColor_; };
QColor disabledColor() const { return disabledColor_; };
QColor inactiveColor() const { return inactiveColor_; };
QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); };
QSize sizeHint() const override { return QSize(64, 48); };
protected:
void paintEvent(QPaintEvent *event) override;
private:
void init();
void setupProperties();
ToggleTrack *track_;
ToggleThumb *thumb_;
QColor disabledColor_;
QColor activeColor_;
QColor inactiveColor_;
QColor trackColor_;
};
class ToggleThumb : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor)
public:
ToggleThumb(Toggle *parent);
Position shift() const { return position_; };
qreal offset() const { return offset_; };
QColor thumbColor() const { return thumbColor_; };
void setShift(Position position);
void setThumbColor(const QColor &color)
{
thumbColor_ = color;
update();
};
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
void updateOffset();
Toggle *const toggle_;
QColor thumbColor_;
Position position_;
qreal offset_;
};
class ToggleTrack : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor)
public:
ToggleTrack(Toggle *parent);
void setTrackColor(const QColor &color);
QColor trackColor() const { return trackColor_; };
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
Toggle *const toggle_;
QColor trackColor_;
};

View File

@ -31,11 +31,11 @@ static const lmdb::val NEXT_BATCH_KEY("next_batch");
static const lmdb::val transactionID("transaction_id"); static const lmdb::val transactionID("transaction_id");
Cache::Cache(const QString &userId) Cache::Cache(const QString &userId)
: env_{ nullptr } : env_{nullptr}
, stateDb_{ 0 } , stateDb_{0}
, roomDb_{ 0 } , roomDb_{0}
, isMounted_{ false } , isMounted_{false}
, userId_{ userId } , userId_{userId}
{} {}
void void

View File

@ -72,8 +72,8 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
topLayout_->addWidget(splitter); topLayout_->addWidget(splitter);
// SideBar // SideBar
sideBar_ = new QWidget(this); sideBar_ = new QFrame(this);
sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, 0)); sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, parent->height()));
sideBarLayout_ = new QVBoxLayout(sideBar_); sideBarLayout_ = new QVBoxLayout(sideBar_);
sideBarLayout_->setSpacing(0); sideBarLayout_->setSpacing(0);
sideBarLayout_->setMargin(0); sideBarLayout_->setMargin(0);
@ -93,40 +93,33 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
sideBarTopWidget_ = new QWidget(sideBar_); sideBarTopWidget_ = new QWidget(sideBar_);
sideBarTopWidget_->setStyleSheet("background-color: #d6dde3; color: #ebebeb;"); sideBarTopWidget_->setStyleSheet("background-color: #d6dde3; color: #ebebeb;");
sidebarActions_ = new SideBarActions(this);
connect(
sidebarActions_, &SideBarActions::showSettings, this, &ChatPage::showUserSettingsPage);
sideBarTopLayout_->addWidget(sideBarTopWidget_); user_info_widget_ = new UserInfoWidget(sideBar_);
room_list_ = new RoomList(client, sideBar_);
sideBarTopWidgetLayout_ = new QVBoxLayout(sideBarTopWidget_); sideBarLayout_->addWidget(user_info_widget_);
sideBarTopWidgetLayout_->setSpacing(0); sideBarLayout_->addWidget(room_list_);
sideBarTopWidgetLayout_->setMargin(0); sideBarLayout_->addWidget(sidebarActions_);
// Content // Content
content_ = new QWidget(this); content_ = new QFrame(this);
contentLayout_ = new QVBoxLayout(content_); contentLayout_ = new QVBoxLayout(content_);
contentLayout_->setSpacing(0); contentLayout_->setSpacing(0);
contentLayout_->setMargin(0); contentLayout_->setMargin(0);
topBarLayout_ = new QHBoxLayout(); top_bar_ = new TopRoomBar(this);
topBarLayout_->setSpacing(0); view_manager_ = new TimelineViewManager(client, this);
mainContentLayout_ = new QVBoxLayout();
mainContentLayout_->setSpacing(0);
mainContentLayout_->setMargin(0);
contentLayout_->addLayout(topBarLayout_); contentLayout_->addWidget(top_bar_);
contentLayout_->addLayout(mainContentLayout_); contentLayout_->addWidget(view_manager_);
// Splitter // Splitter
splitter->addWidget(sideBar_); splitter->addWidget(sideBar_);
splitter->addWidget(content_); splitter->addWidget(content_);
splitter->setSizes({ui::sidebar::NormalSize, parent->width() - ui::sidebar::NormalSize});
room_list_ = new RoomList(client, sideBar_);
sideBarMainLayout_->addWidget(room_list_);
top_bar_ = new TopRoomBar(this);
topBarLayout_->addWidget(top_bar_);
view_manager_ = new TimelineViewManager(client, this);
mainContentLayout_->addWidget(view_manager_);
text_input_ = new TextInputWidget(this); text_input_ = new TextInputWidget(this);
typingDisplay_ = new TypingDisplay(this); typingDisplay_ = new TypingDisplay(this);
@ -135,6 +128,8 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
user_info_widget_ = new UserInfoWidget(sideBarTopWidget_); user_info_widget_ = new UserInfoWidget(sideBarTopWidget_);
sideBarTopWidgetLayout_->addWidget(user_info_widget_); sideBarTopWidgetLayout_->addWidget(user_info_widget_);
typingRefresher_ = new QTimer(this);
typingRefresher_->setInterval(TYPING_REFRESH_TIMEOUT);
connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout())); connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout()));
connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout())); connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout()));
@ -150,6 +145,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
typingDisplay_->setUsers(users); typingDisplay_->setUsers(users);
}); });
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit); connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
@ -170,6 +166,20 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
room_list_->updateUnreadMessageCount(roomid, count); room_list_->updateUnreadMessageCount(roomid, count);
}); });
connect(text_input_, &TextInputWidget::startedTyping, this, [=]() {
typingRefresher_->start();
client_->sendTypingNotification(current_room_);
});
connect(text_input_, &TextInputWidget::stoppedTyping, this, [=]() {
typingRefresher_->stop();
client_->removeTypingNotification(current_room_);
});
connect(typingRefresher_, &QTimer::timeout, this, [=]() {
client_->sendTypingNotification(current_room_);
});
connect(view_manager_, connect(view_manager_,
&TimelineViewManager::updateRoomsLastMessage, &TimelineViewManager::updateRoomsLastMessage,
room_list_, room_list_,
@ -183,12 +193,12 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect(text_input_, connect(text_input_,
SIGNAL(sendTextMessage(const QString &)), SIGNAL(sendTextMessage(const QString &)),
view_manager_, view_manager_,
SLOT(sendTextMessage(const QString &))); SLOT(queueTextMessage(const QString &)));
connect(text_input_, connect(text_input_,
SIGNAL(sendEmoteMessage(const QString &)), SIGNAL(sendEmoteMessage(const QString &)),
view_manager_, view_manager_,
SLOT(sendEmoteMessage(const QString &))); SLOT(queueEmoteMessage(const QString &)));
connect(text_input_, connect(text_input_,
&TextInputWidget::sendJoinRoomRequest, &TextInputWidget::sendJoinRoomRequest,
@ -205,7 +215,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
this, this,
[=](QString roomid, QString filename, QString url) { [=](QString roomid, QString filename, QString url) {
text_input_->hideUploadSpinner(); text_input_->hideUploadSpinner();
view_manager_->sendImageMessage(roomid, filename, url); view_manager_->queueImageMessage(roomid, filename, url);
}); });
connect(client_.data(), connect(client_.data(),
@ -604,6 +614,7 @@ ChatPage::showQuickSwitcher()
connect(quickSwitcher_.data(), &QuickSwitcher::closing, this, [=]() { connect(quickSwitcher_.data(), &QuickSwitcher::closing, this, [=]() {
if (!this->quickSwitcherModal_.isNull()) if (!this->quickSwitcherModal_.isNull())
this->quickSwitcherModal_->fadeOut(); this->quickSwitcherModal_->fadeOut();
this->text_input_->setFocus(Qt::FocusReason::PopupFocusReason);
}); });
} }
@ -617,8 +628,12 @@ ChatPage::showQuickSwitcher()
QMap<QString, QString> rooms; QMap<QString, QString> rooms;
for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) {
rooms.insert(it.value().getName(), it.key()); QString deambiguator = it.value().canonical_alias.content().alias();
if (deambiguator == "")
deambiguator = it.key();
rooms.insert(it.value().getName() + " (" + deambiguator + ")", it.key());
}
quickSwitcher_->setRoomList(rooms); quickSwitcher_->setRoomList(rooms);
quickSwitcherModal_->fadeIn(); quickSwitcherModal_->fadeIn();
@ -662,13 +677,20 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_id
{ {
QStringList users; QStringList users;
for (const auto uid : user_ids) QSettings settings;
QString user_id = settings.value("auth/user_id").toString();
for (const auto uid : user_ids) {
if (uid == user_id)
continue;
users.append(TimelineViewManager::displayName(uid)); users.append(TimelineViewManager::displayName(uid));
}
users.sort(); users.sort();
if (current_room_ == roomid) if (current_room_ == roomid) {
typingDisplay_->setUsers(users); typingDisplay_->setUsers(users);
}
typingUsers_.insert(roomid, users); typingUsers_.insert(roomid, users);
} }

View File

@ -26,11 +26,11 @@
EmojiPanel::EmojiPanel(QWidget *parent) EmojiPanel::EmojiPanel(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, shadowMargin_{ 2 } , shadowMargin_{2}
, width_{ 370 } , width_{370}
, height_{ 350 } , height_{350}
, animationDuration_{ 100 } , animationDuration_{100}
, categoryIconSize_{ 20 } , categoryIconSize_{20}
{ {
setStyleSheet("QWidget {background: #fff; color: #e8e8e8; border: none;}" setStyleSheet("QWidget {background: #fff; color: #e8e8e8; border: none;}"
"QScrollBar:vertical { background-color: #fff; width: 8px; margin: 0px " "QScrollBar:vertical { background-color: #fff; width: 8px; margin: 0px "

View File

@ -20,7 +20,7 @@
EmojiPickButton::EmojiPickButton(QWidget *parent) EmojiPickButton::EmojiPickButton(QWidget *parent)
: FlatButton(parent) : FlatButton(parent)
, panel_{ nullptr } , panel_{nullptr}
{} {}
void void

File diff suppressed because it is too large Load Diff

View File

@ -32,8 +32,8 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
const events::MessageEvent<msgs::Image> &event, const events::MessageEvent<msgs::Image> &event,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, event_{ event } , event_{event}
, client_{ client } , client_{client}
{ {
setMouseTracking(true); setMouseTracking(true);
setCursor(Qt::PointingHandCursor); setCursor(Qt::PointingHandCursor);
@ -66,9 +66,9 @@ ImageItem::ImageItem(QSharedPointer<MatrixClient> client,
const QString &filename, const QString &filename,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, url_{ url } , url_{url}
, text_{ QFileInfo(filename).fileName() } , text_{QFileInfo(filename).fileName()}
, client_{ client } , client_{client}
{ {
setMouseTracking(true); setMouseTracking(true);
setCursor(Qt::PointingHandCursor); setCursor(Qt::PointingHandCursor);

View File

@ -22,8 +22,8 @@
#include "ImageOverlayDialog.h" #include "ImageOverlayDialog.h"
ImageOverlayDialog::ImageOverlayDialog(QPixmap image, QWidget *parent) ImageOverlayDialog::ImageOverlayDialog(QPixmap image, QWidget *parent)
: QWidget{ parent } : QWidget{parent}
, originalImage_{ image } , originalImage_{image}
{ {
setMouseTracking(true); setMouseTracking(true);
setParent(0); setParent(0);

View File

@ -41,10 +41,10 @@ LoginRequest::serialize() noexcept
#endif #endif
QJsonObject body{ QJsonObject body{
{ "type", "m.login.password" }, {"type", "m.login.password"},
{ "user", user_ }, {"user", user_},
{ "password", password_ }, {"password", password_},
{ "initial_device_display_name", initialDeviceName }, {"initial_device_display_name", initialDeviceName},
}; };
return QJsonDocument(body).toJson(QJsonDocument::Compact); return QJsonDocument(body).toJson(QJsonDocument::Compact);

View File

@ -28,7 +28,7 @@
LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent) LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, inferredServerAddress_() , inferredServerAddress_()
, client_{ client } , client_{client}
{ {
setStyleSheet("background-color: #fff"); setStyleSheet("background-color: #fff");

View File

@ -31,23 +31,21 @@
#include "RegisterPage.h" #include "RegisterPage.h"
#include "SnackBar.h" #include "SnackBar.h"
#include "TrayIcon.h" #include "TrayIcon.h"
#include "UserSettingsPage.h"
#include "WelcomePage.h" #include "WelcomePage.h"
MainWindow *MainWindow::instance_ = nullptr; MainWindow *MainWindow::instance_ = nullptr;
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, progressModal_{ nullptr } , progressModal_{nullptr}
, spinner_{ nullptr } , spinner_{nullptr}
{ {
QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
setSizePolicy(sizePolicy);
setWindowTitle("nheko"); setWindowTitle("nheko");
setObjectName("MainWindow"); setObjectName("MainWindow");
setStyleSheet("QWidget#MainWindow {background-color: #fff}"); setStyleSheet("QWidget#MainWindow {background-color: #fff}");
restoreWindowSize(); restoreWindowSize();
setMinimumSize(QSize(conf::window::minWidth, conf::window::minHeight));
QFont font("Open Sans"); QFont font("Open Sans");
font.setPixelSize(conf::fontSize); font.setPixelSize(conf::fontSize);
@ -55,12 +53,14 @@ MainWindow::MainWindow(QWidget *parent)
setFont(font); setFont(font);
client_ = QSharedPointer<MatrixClient>(new MatrixClient("matrix.org")); client_ = QSharedPointer<MatrixClient>(new MatrixClient("matrix.org"));
userSettings_ = QSharedPointer<UserSettings>(new UserSettings);
trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this);
welcome_page_ = new WelcomePage(this); welcome_page_ = new WelcomePage(this);
login_page_ = new LoginPage(client_, this); login_page_ = new LoginPage(client_, this);
register_page_ = new RegisterPage(client_, this); register_page_ = new RegisterPage(client_, this);
chat_page_ = new ChatPage(client_, this); chat_page_ = new ChatPage(client_, this);
userSettingsPage_ = new UserSettingsPage(userSettings_, this);
// Initialize sliding widget manager. // Initialize sliding widget manager.
pageStack_ = new QStackedWidget(this); pageStack_ = new QStackedWidget(this);
@ -68,6 +68,7 @@ MainWindow::MainWindow(QWidget *parent)
pageStack_->addWidget(login_page_); pageStack_->addWidget(login_page_);
pageStack_->addWidget(register_page_); pageStack_->addWidget(register_page_);
pageStack_->addWidget(chat_page_); pageStack_->addWidget(chat_page_);
pageStack_->addWidget(userSettingsPage_);
setCentralWidget(pageStack_); setCentralWidget(pageStack_);
@ -86,12 +87,21 @@ MainWindow::MainWindow(QWidget *parent)
showLoginPage(); showLoginPage();
}); });
connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [=]() {
pageStack_->setCurrentWidget(chat_page_);
});
connect(
userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
connect(trayIcon_, connect(trayIcon_,
SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, this,
SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
connect(
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
connect(client_.data(), connect(client_.data(),
SIGNAL(loginSuccess(QString, QString, QString)), SIGNAL(loginSuccess(QString, QString, QString)),
@ -101,8 +111,15 @@ MainWindow::MainWindow(QWidget *parent)
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this); QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit); connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
QShortcut *quickSwitchShortcut = new QShortcut(QKeySequence("Ctrl+K"), this);
connect(quickSwitchShortcut, &QShortcut::activated, this, [=]() {
chat_page_->showQuickSwitcher();
});
QSettings settings; QSettings settings;
trayIcon_->setVisible(userSettings_->isTrayEnabled());
if (hasActiveUser()) { if (hasActiveUser()) {
QString token = settings.value("auth/access_token").toString(); QString token = settings.value("auth/access_token").toString();
QString home_server = settings.value("auth/home_server").toString(); QString home_server = settings.value("auth/home_server").toString();
@ -234,10 +251,16 @@ MainWindow::showRegisterPage()
pageStack_->setCurrentWidget(register_page_); pageStack_->setCurrentWidget(register_page_);
} }
void
MainWindow::showUserSettingsPage()
{
pageStack_->setCurrentWidget(userSettingsPage_);
}
void void
MainWindow::closeEvent(QCloseEvent *event) MainWindow::closeEvent(QCloseEvent *event)
{ {
if (isVisible()) { if (isVisible() && userSettings_->isTrayEnabled()) {
event->ignore(); event->ignore();
hide(); hide();
} }

View File

@ -38,9 +38,9 @@
MatrixClient::MatrixClient(QString server, QObject *parent) MatrixClient::MatrixClient(QString server, QObject *parent)
: QNetworkAccessManager(parent) : QNetworkAccessManager(parent)
, clientApiUrl_{ "/_matrix/client/r0" } , clientApiUrl_{"/_matrix/client/r0"}
, mediaApiUrl_{ "/_matrix/media/r0" } , mediaApiUrl_{"/_matrix/media/r0"}
, server_{ "https://" + server } , server_{"https://" + server}
{ {
QSettings settings; QSettings settings;
txn_id_ = settings.value("client/transaction_id", 1).toInt(); txn_id_ = settings.value("client/transaction_id", 1).toInt();
@ -119,7 +119,6 @@ MatrixClient::login(const QString &username, const QString &password) noexcept
} }
}); });
} }
void void
MatrixClient::logout() noexcept MatrixClient::logout() noexcept
{ {
@ -260,9 +259,11 @@ MatrixClient::sync() noexcept
} }
void void
MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty, MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
int txnId,
const QString &roomid, const QString &roomid,
const QString &msg, const QString &msg,
const QFileInfo &fileinfo,
const QString &url) noexcept const QString &url) noexcept
{ {
QUrlQuery query; QUrlQuery query;
@ -940,3 +941,53 @@ MatrixClient::leaveRoom(const QString &roomId)
emit leftRoom(roomId); emit leftRoom(roomId);
}); });
} }
void
MatrixClient::sendTypingNotification(const QString &roomid, int timeoutInMillis)
{
QSettings settings;
QString user_id = settings.value("auth/user_id").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
endpoint.setQuery(query);
QString msgType("");
QJsonObject body;
body = {{"typing", true}, {"timeout", timeoutInMillis}};
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
}
void
MatrixClient::removeTypingNotification(const QString &roomid)
{
QSettings settings;
QString user_id = settings.value("auth/user_id").toString();
QUrlQuery query;
query.addQueryItem("access_token", token_);
QUrl endpoint(server_);
endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
endpoint.setQuery(query);
QString msgType("");
QJsonObject body;
body = {{"typing", false}};
QNetworkRequest request(QString(endpoint.toEncoded()));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
}

View File

@ -122,7 +122,16 @@ QuickSwitcher::QuickSwitcher(QWidget *parent)
roomSearch_, &RoomSearchInput::hiding, this, [=]() { completer_->popup()->hide(); }); roomSearch_, &RoomSearchInput::hiding, this, [=]() { completer_->popup()->hide(); });
connect(roomSearch_, &QLineEdit::returnPressed, this, [=]() { connect(roomSearch_, &QLineEdit::returnPressed, this, [=]() {
emit closing(); emit closing();
emit roomSelected(rooms_[this->roomSearch_->text().trimmed()]);
QString text("");
if (selection_ == -1) {
completer_->setCurrentRow(0);
text = completer_->currentCompletion();
} else {
text = this->roomSearch_->text().trimmed();
}
emit roomSelected(rooms_[text]);
roomSearch_->clear(); roomSearch_->clear();
}); });

View File

@ -27,7 +27,7 @@ RegisterRequest::RegisterRequest(const QString &username, const QString &passwor
QByteArray QByteArray
RegisterRequest::serialize() noexcept RegisterRequest::serialize() noexcept
{ {
QJsonObject body{ { "username", user_ }, { "password", password_ } }; QJsonObject body{{"username", user_}, {"password", password_}};
return QJsonDocument(body).toJson(QJsonDocument::Compact); return QJsonDocument(body).toJson(QJsonDocument::Compact);
} }

View File

@ -33,7 +33,7 @@ RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
: QWidget(parent) : QWidget(parent)
, state_(state) , state_(state)
, roomId_(room_id) , roomId_(room_id)
, roomSettings_{ settings } , roomSettings_{settings}
, isPressed_(false) , isPressed_(false)
, maxHeight_(IconSize + 2 * Padding) , maxHeight_(IconSize + 2 * Padding)
, unreadMsgCount_(0) , unreadMsgCount_(0)

View File

@ -33,12 +33,8 @@ RoomList::RoomList(QSharedPointer<MatrixClient> client, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, client_(client) , client_(client)
{ {
setStyleSheet("QWidget { border: none; }"); setStyleSheet(
"border: 1px solid #ccc; border-right: 0px solid #000; border-left: 0px solid #000;");
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
setSizePolicy(sizePolicy);
topLayout_ = new QVBoxLayout(this); topLayout_ = new QVBoxLayout(this);
topLayout_->setSpacing(0); topLayout_->setSpacing(0);
@ -51,7 +47,7 @@ RoomList::RoomList(QSharedPointer<MatrixClient> client, QWidget *parent)
scrollArea_->setWidgetResizable(true); scrollArea_->setWidgetResizable(true);
scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter);
scrollAreaContents_ = new QWidget(); scrollAreaContents_ = new QWidget(this);
contentsLayout_ = new QVBoxLayout(scrollAreaContents_); contentsLayout_ = new QVBoxLayout(scrollAreaContents_);
contentsLayout_->setSpacing(0); contentsLayout_->setSpacing(0);

View File

@ -5,13 +5,10 @@
#include "Theme.h" #include "Theme.h"
SideBarActions::SideBarActions(QWidget *parent) SideBarActions::SideBarActions(QWidget *parent)
: QWidget{ parent } : QWidget{parent}
{ {
setFixedHeight(conf::sidebarActions::height); setFixedHeight(conf::sidebarActions::height);
QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
setSizePolicy(sizePolicy);
layout_ = new QHBoxLayout(this); layout_ = new QHBoxLayout(this);
layout_->setMargin(0); layout_->setMargin(0);
@ -45,6 +42,8 @@ SideBarActions::SideBarActions(QWidget *parent)
layout_->addWidget(createRoomBtn_); layout_->addWidget(createRoomBtn_);
layout_->addWidget(joinRoomBtn_); layout_->addWidget(joinRoomBtn_);
layout_->addWidget(settingsBtn_); layout_->addWidget(settingsBtn_);
connect(settingsBtn_, &QPushButton::clicked, this, &SideBarActions::showSettings);
} }
SideBarActions::~SideBarActions() {} SideBarActions::~SideBarActions() {}

View File

@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QAbstractTextDocumentLayout>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QFileDialog> #include <QFileDialog>
@ -25,19 +26,143 @@
#include "Config.h" #include "Config.h"
#include "TextInputWidget.h" #include "TextInputWidget.h"
static constexpr size_t INPUT_HISTORY_SIZE = 127;
FilteredTextEdit::FilteredTextEdit(QWidget *parent) FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit(parent) : QTextEdit{parent}
, history_index_{0}
{ {
connect(document()->documentLayout(),
&QAbstractTextDocumentLayout::documentSizeChanged,
this,
&FilteredTextEdit::updateGeometry);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Fixed);
policy.setHeightForWidth(true);
setSizePolicy(policy);
working_history_.push_back("");
connect(this, &QTextEdit::textChanged, this, &FilteredTextEdit::textChanged);
setAcceptRichText(false); setAcceptRichText(false);
typingTimer_ = new QTimer(this);
typingTimer_->setInterval(1000);
typingTimer_->setSingleShot(true);
connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping);
} }
void void
FilteredTextEdit::keyPressEvent(QKeyEvent *event) FilteredTextEdit::keyPressEvent(QKeyEvent *event)
{ {
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) const bool isModifier = (event->modifiers() != Qt::NoModifier);
emit enterPressed();
else if (!isModifier) {
if (!typingTimer_->isActive())
emit startedTyping();
typingTimer_->start();
}
switch (event->key()) {
case Qt::Key_Return:
case Qt::Key_Enter:
if (!(event->modifiers() & Qt::ShiftModifier)) {
stopTyping();
submit();
} else {
QTextEdit::keyPressEvent(event); QTextEdit::keyPressEvent(event);
}
break;
case Qt::Key_Up: {
auto initial_cursor = textCursor();
QTextEdit::keyPressEvent(event);
if (textCursor() == initial_cursor &&
history_index_ + 1 < working_history_.size()) {
++history_index_;
setPlainText(working_history_[history_index_]);
moveCursor(QTextCursor::End);
}
break;
}
case Qt::Key_Down: {
auto initial_cursor = textCursor();
QTextEdit::keyPressEvent(event);
if (textCursor() == initial_cursor && history_index_ > 0) {
--history_index_;
setPlainText(working_history_[history_index_]);
moveCursor(QTextCursor::End);
}
break;
}
default:
QTextEdit::keyPressEvent(event);
break;
}
}
void
FilteredTextEdit::stopTyping()
{
typingTimer_->stop();
emit stoppedTyping();
}
QSize
FilteredTextEdit::sizeHint() const
{
ensurePolished();
auto margins = viewportMargins();
margins += document()->documentMargin();
QSize size = document()->size().toSize();
size.rwidth() += margins.left() + margins.right();
size.rheight() += margins.top() + margins.bottom();
return size;
}
QSize
FilteredTextEdit::minimumSizeHint() const
{
ensurePolished();
auto margins = viewportMargins();
margins += document()->documentMargin();
margins += contentsMargins();
QSize size(fontMetrics().averageCharWidth() * 10,
fontMetrics().lineSpacing() + margins.top() + margins.bottom());
return size;
}
void
FilteredTextEdit::submit()
{
if (true_history_.size() == INPUT_HISTORY_SIZE)
true_history_.pop_back();
true_history_.push_front(toPlainText());
working_history_ = true_history_;
working_history_.push_front("");
history_index_ = 0;
QString text = toPlainText();
if (text.startsWith('/')) {
int command_end = text.indexOf(' ');
if (command_end == -1)
command_end = text.size();
auto name = text.mid(1, command_end - 1);
auto args = text.mid(command_end + 1);
if (name.isEmpty() || name == "/") {
message(args);
} else {
command(name, args);
}
} else {
message(std::move(text));
}
clear();
}
void
FilteredTextEdit::textChanged()
{
working_history_[history_index_] = toPlainText();
} }
TextInputWidget::TextInputWidget(QWidget *parent) TextInputWidget::TextInputWidget(QWidget *parent)
@ -97,13 +222,18 @@ TextInputWidget::TextInputWidget(QWidget *parent)
setLayout(topLayout_); setLayout(topLayout_);
connect(sendMessageBtn_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked())); connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit);
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
connect(input_, SIGNAL(enterPressed()), sendMessageBtn_, SIGNAL(clicked())); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
connect(emojiBtn_, connect(emojiBtn_,
SIGNAL(emojiSelected(const QString &)), SIGNAL(emojiSelected(const QString &)),
this, this,
SLOT(addSelectedEmoji(const QString &))); SLOT(addSelectedEmoji(const QString &)));
connect(input_, &FilteredTextEdit::startedTyping, this, &TextInputWidget::startedTyping);
connect(input_, &FilteredTextEdit::stoppedTyping, this, &TextInputWidget::stoppedTyping);
} }
void void
@ -131,50 +261,13 @@ TextInputWidget::addSelectedEmoji(const QString &emoji)
} }
void void
TextInputWidget::onSendButtonClicked() TextInputWidget::command(QString command, QString args)
{ {
auto msgText = input_->document()->toPlainText().trimmed(); if (command == "me") {
sendEmoteMessage(args);
if (msgText.isEmpty()) } else if (command == "join") {
return; sendJoinRoomRequest(args);
if (msgText.startsWith(EMOTE_COMMAND)) {
auto text = parseEmoteCommand(msgText);
if (!text.isEmpty())
emit sendEmoteMessage(text);
} else if (msgText.startsWith(JOIN_COMMAND)) {
auto room = parseJoinCommand(msgText);
if (!room.isEmpty())
emit sendJoinRoomRequest(room);
} else {
emit sendTextMessage(msgText);
} }
input_->clear();
}
QString
TextInputWidget::parseJoinCommand(const QString &cmd)
{
auto room = cmd.right(cmd.size() - JOIN_COMMAND.size()).trimmed();
if (!room.isEmpty())
return room;
return QString("");
}
QString
TextInputWidget::parseEmoteCommand(const QString &cmd)
{
auto text = cmd.right(cmd.size() - EMOTE_COMMAND.size()).trimmed();
if (!text.isEmpty())
return text;
return QString("");
} }
void void
@ -226,6 +319,16 @@ TextInputWidget::hideUploadSpinner()
spinner_->stop(); spinner_->stop();
} }
TextInputWidget::~TextInputWidget() TextInputWidget::~TextInputWidget() {}
void
TextInputWidget::stopTyping()
{ {
input_->stopTyping();
}
void
TextInputWidget::focusInEvent(QFocusEvent *event)
{
input_->setFocus(event->reason());
} }

View File

@ -85,15 +85,15 @@ TimelineItem::TimelineItem(events::MessageEventType ty,
if (ty == events::MessageEventType::Emote) { if (ty == events::MessageEventType::Emote) {
body = QString("* %1 %2").arg(displayName).arg(body); body = QString("* %1 %2").arg(displayName).arg(body);
descriptionMsg_ = { "", userid, body, descriptiveTime(timestamp) }; descriptionMsg_ = {"", userid, body, descriptiveTime(timestamp)};
} else { } else {
descriptionMsg_ = { descriptionMsg_ = {
"You: ", userid, body, descriptiveTime(QDateTime::currentDateTime()) "You: ", userid, body, descriptiveTime(QDateTime::currentDateTime())};
};
} }
body = body.toHtmlEscaped(); body = body.toHtmlEscaped();
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
generateTimestamp(timestamp); generateTimestamp(timestamp);
if (withSender) { if (withSender) {
@ -114,14 +114,14 @@ TimelineItem::TimelineItem(ImageItem *image,
const QString &userid, const QString &userid,
bool withSender, bool withSender,
QWidget *parent) QWidget *parent)
: QWidget{ parent } : QWidget{parent}
{ {
init(); init();
auto displayName = TimelineViewManager::displayName(userid); auto displayName = TimelineViewManager::displayName(userid);
auto timestamp = QDateTime::currentDateTime(); auto timestamp = QDateTime::currentDateTime();
descriptionMsg_ = { "You", userid, " sent an image", descriptiveTime(timestamp) }; descriptionMsg_ = {"You", userid, " sent an image", descriptiveTime(timestamp)};
generateTimestamp(timestamp); generateTimestamp(timestamp);
@ -158,10 +158,10 @@ TimelineItem::TimelineItem(ImageItem *image,
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());
QSettings settings; QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName, descriptionMsg_ = {event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(), event.sender(),
" sent an image", " sent an image",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) }; descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
generateTimestamp(timestamp); generateTimestamp(timestamp);
@ -193,10 +193,10 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
: QWidget(parent) : QWidget(parent)
{ {
init(); init();
descriptionMsg_ = { TimelineViewManager::displayName(event.sender()), descriptionMsg_ = {TimelineViewManager::displayName(event.sender()),
event.sender(), event.sender(),
" sent a notification", " sent a notification",
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) }; descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
auto body = event.content().body().trimmed().toHtmlEscaped(); auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
@ -204,6 +204,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
generateTimestamp(timestamp); generateTimestamp(timestamp);
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
body = "<i style=\"color: #565E5E\">" + body + "</i>"; body = "<i style=\"color: #565E5E\">" + body + "</i>";
if (with_sender) { if (with_sender) {
@ -238,14 +239,15 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());
auto emoteMsg = QString("* %1 %2").arg(displayName).arg(body); auto emoteMsg = QString("* %1 %2").arg(displayName).arg(body);
descriptionMsg_ = { "", descriptionMsg_ = {"",
event.sender(), event.sender(),
emoteMsg, emoteMsg,
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) }; descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
generateTimestamp(timestamp); generateTimestamp(timestamp);
emoteMsg = emoteMsg.toHtmlEscaped(); emoteMsg = emoteMsg.toHtmlEscaped();
emoteMsg.replace(URL_REGEX, URL_HTML); emoteMsg.replace(URL_REGEX, URL_HTML);
emoteMsg.replace("\n", "<br/>");
if (with_sender) { if (with_sender) {
generateBody(displayName, emoteMsg); generateBody(displayName, emoteMsg);
@ -276,15 +278,16 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());
QSettings settings; QSettings settings;
descriptionMsg_ = { event.sender() == settings.value("auth/user_id") ? "You" : displayName, descriptionMsg_ = {event.sender() == settings.value("auth/user_id") ? "You" : displayName,
event.sender(), event.sender(),
QString(": %1").arg(body), QString(": %1").arg(body),
descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp())) }; descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
generateTimestamp(timestamp); generateTimestamp(timestamp);
body = body.toHtmlEscaped(); body = body.toHtmlEscaped();
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
if (with_sender) { if (with_sender) {
generateBody(displayName, body); generateBody(displayName, body);

View File

@ -17,6 +17,7 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <QFileInfo>
#include <QSettings> #include <QSettings>
#include <QTimer> #include <QTimer>
@ -49,8 +50,8 @@ TimelineView::TimelineView(const Timeline &timeline,
const QString &room_id, const QString &room_id,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, room_id_{ room_id } , room_id_{room_id}
, client_{ client } , client_{client}
{ {
init(); init();
addEvents(timeline); addEvents(timeline);
@ -60,8 +61,8 @@ TimelineView::TimelineView(QSharedPointer<MatrixClient> client,
const QString &room_id, const QString &room_id,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, room_id_{ room_id } , room_id_{room_id}
, client_{ client } , client_{client}
{ {
init(); init();
client_->messages(room_id_, ""); client_->messages(room_id_, "");
@ -179,6 +180,10 @@ TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msg
isTimelineFinished = false; isTimelineFinished = false;
QList<TimelineItem *> items; QList<TimelineItem *> items;
// Reset the sender of the first message in the timeline
// cause we're about to insert a new one.
firstSender_.clear();
// Parse in reverse order to determine where we should not show sender's // Parse in reverse order to determine where we should not show sender's
// name. // name.
auto ii = msgs.chunk().size(); auto ii = msgs.chunk().size();
@ -241,9 +246,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[text.eventId()] = true; eventIds_[text.eventId()] = true;
if (isPendingMessage( QString txnid = text.unsignedData().transactionId();
text.eventId(), text.content().body(), text.sender(), local_user_)) { if (!txnid.isEmpty() &&
removePendingMessage(text.eventId(), text.content().body()); isPendingMessage(txnid, text.sender(), local_user_)) {
removePendingMessage(txnid);
return nullptr; return nullptr;
} }
@ -287,9 +293,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[img.eventId()] = true; eventIds_[img.eventId()] = true;
if (isPendingMessage( QString txnid = img.unsignedData().transactionId();
img.eventId(), img.msgContent().url(), img.sender(), local_user_)) { if (!txnid.isEmpty() &&
removePendingMessage(img.eventId(), img.msgContent().url()); isPendingMessage(txnid, img.sender(), local_user_)) {
removePendingMessage(txnid);
return nullptr; return nullptr;
} }
@ -313,11 +320,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[emote.eventId()] = true; eventIds_[emote.eventId()] = true;
if (isPendingMessage(emote.eventId(), QString txnid = emote.unsignedData().transactionId();
emote.content().body(), if (!txnid.isEmpty() &&
emote.sender(), isPendingMessage(txnid, emote.sender(), local_user_)) {
local_user_)) { removePendingMessage(txnid);
removePendingMessage(emote.eventId(), emote.content().body());
return nullptr; return nullptr;
} }
@ -495,16 +501,16 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
void void
TimelineView::updatePendingMessage(int txn_id, QString event_id) TimelineView::updatePendingMessage(int txn_id, QString event_id)
{ {
for (auto &msg : pending_msgs_) { if (pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet
if (msg.txn_id == txn_id) { auto msg = pending_msgs_.dequeue();
msg.event_id = event_id; msg.event_id = event_id;
break; pending_sent_msgs_.append(msg);
}
} }
sendNextPendingMessage();
} }
void void
TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body, int txn_id) TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body)
{ {
QSettings settings; QSettings settings;
auto user_id = settings.value("auth/user_id").toString(); auto user_id = settings.value("auth/user_id").toString();
@ -519,12 +525,13 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString
lastSender_ = user_id; lastSender_ = user_id;
PendingMessage message(txn_id, body, "", view_item); int txn_id = client_->incrementTransactionId();
pending_msgs_.push_back(message); PendingMessage message(ty, txn_id, body, "", "", view_item);
handleNewUserMessage(message);
} }
void void
TimelineView::addUserMessage(const QString &url, const QString &filename, int txn_id) TimelineView::addUserMessage(const QString &url, const QString &filename)
{ {
QSettings settings; QSettings settings;
auto user_id = settings.value("auth/user_id").toString(); auto user_id = settings.value("auth/user_id").toString();
@ -541,8 +548,36 @@ TimelineView::addUserMessage(const QString &url, const QString &filename, int tx
lastSender_ = user_id; lastSender_ = user_id;
PendingMessage message(txn_id, url, "", view_item); int txn_id = client_->incrementTransactionId();
pending_msgs_.push_back(message); PendingMessage message(
matrix::events::MessageEventType::Image, txn_id, url, filename, "", view_item);
handleNewUserMessage(message);
}
void
TimelineView::handleNewUserMessage(PendingMessage msg)
{
pending_msgs_.enqueue(msg);
if (pending_msgs_.size() == 1 && pending_sent_msgs_.size() == 0)
sendNextPendingMessage();
}
void
TimelineView::sendNextPendingMessage()
{
if (pending_msgs_.size() == 0)
return;
PendingMessage &m = pending_msgs_.head();
switch (m.ty) {
case matrix::events::MessageEventType::Image:
client_->sendRoomMessage(
m.ty, m.txn_id, room_id_, QFileInfo(m.filename).fileName(), QFileInfo(m.filename), m.body);
break;
default:
client_->sendRoomMessage(m.ty, m.txn_id, room_id_, m.body, QFileInfo());
break;
}
} }
void void
@ -558,8 +593,7 @@ TimelineView::notifyForLastEvent()
} }
bool bool
TimelineView::isPendingMessage(const QString &eventid, TimelineView::isPendingMessage(const QString &txnid,
const QString &body,
const QString &sender, const QString &sender,
const QString &local_userid) const QString &local_userid)
{ {
@ -567,7 +601,12 @@ TimelineView::isPendingMessage(const QString &eventid,
return false; return false;
for (const auto &msg : pending_msgs_) { for (const auto &msg : pending_msgs_) {
if (msg.event_id == eventid || msg.body == body) if (QString::number(msg.txn_id) == txnid)
return true;
}
for (const auto &msg : pending_sent_msgs_) {
if (QString::number(msg.txn_id) == txnid)
return true; return true;
} }
@ -575,14 +614,28 @@ TimelineView::isPendingMessage(const QString &eventid,
} }
void void
TimelineView::removePendingMessage(const QString &eventid, const QString &body) TimelineView::removePendingMessage(const QString &txnid)
{ {
for (auto it = pending_sent_msgs_.begin(); it != pending_sent_msgs_.end(); ++it) {
if (QString::number(it->txn_id) == txnid) {
int index = std::distance(pending_sent_msgs_.begin(), it);
pending_sent_msgs_.removeAt(index);
return;
}
}
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) { for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
if (QString::number(it->txn_id) == txnid) {
int index = std::distance(pending_msgs_.begin(), it); int index = std::distance(pending_msgs_.begin(), it);
if (it->event_id == eventid || it->body == body) {
pending_msgs_.removeAt(index); pending_msgs_.removeAt(index);
break; return;
} }
} }
} }
void
TimelineView::handleFailedMessage(int txnid)
{
Q_UNUSED(txnid);
// Note: We do this even if the message has already been echoed.
QTimer::singleShot(500, this, SLOT(sendNextPendingMessage()));
}

View File

@ -31,10 +31,15 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<MatrixClient> client, QW
: QStackedWidget(parent) : QStackedWidget(parent)
, client_(client) , client_(client)
{ {
setStyleSheet("QWidget { background: #fff; color: #e8e8e8; border: none;}"); setStyleSheet("border: none;");
connect( connect(
client_.data(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent); client_.data(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent);
connect(client_.data(),
&MatrixClient::messageSendFailed,
this,
&TimelineViewManager::messageSendFailed);
} }
TimelineViewManager::~TimelineViewManager() TimelineViewManager::~TimelineViewManager()
@ -53,28 +58,32 @@ TimelineViewManager::messageSent(const QString &event_id, const QString &roomid,
} }
void void
TimelineViewManager::sendTextMessage(const QString &msg) TimelineViewManager::messageSendFailed(const QString &roomid, int txn_id)
{
auto view = views_[roomid];
view->handleFailedMessage(txn_id);
}
void
TimelineViewManager::queueTextMessage(const QString &msg)
{ {
auto room_id = active_room_; auto room_id = active_room_;
auto view = views_[room_id]; auto view = views_[room_id];
view->addUserMessage(matrix::events::MessageEventType::Text, msg, client_->transactionId()); view->addUserMessage(matrix::events::MessageEventType::Text, msg);
client_->sendRoomMessage(matrix::events::MessageEventType::Text, room_id, msg);
} }
void void
TimelineViewManager::sendEmoteMessage(const QString &msg) TimelineViewManager::queueEmoteMessage(const QString &msg)
{ {
auto room_id = active_room_; auto room_id = active_room_;
auto view = views_[room_id]; auto view = views_[room_id];
view->addUserMessage( view->addUserMessage(matrix::events::MessageEventType::Emote, msg);
matrix::events::MessageEventType::Emote, msg, client_->transactionId());
client_->sendRoomMessage(matrix::events::MessageEventType::Emote, room_id, msg);
} }
void void
TimelineViewManager::sendImageMessage(const QString &roomid, TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &filename, const QString &filename,
const QString &url) const QString &url)
{ {
@ -85,9 +94,7 @@ TimelineViewManager::sendImageMessage(const QString &roomid,
auto view = views_[roomid]; auto view = views_[roomid];
view->addUserMessage(url, filename, client_->transactionId()); view->addUserMessage(url, filename);
client_->sendRoomMessage(
matrix::events::MessageEventType::Image, roomid, QFileInfo(filename).fileName(), url);
} }
void void
@ -196,7 +203,7 @@ QString
TimelineViewManager::chooseRandomColor() TimelineViewManager::chooseRandomColor()
{ {
std::random_device random_device; std::random_device random_device;
std::mt19937 engine{ random_device() }; std::mt19937 engine{random_device()};
std::uniform_real_distribution<float> dist(0, 1); std::uniform_real_distribution<float> dist(0, 1);
float hue = dist(engine); float hue = dist(engine);

View File

@ -30,11 +30,10 @@
TopRoomBar::TopRoomBar(QWidget *parent) TopRoomBar::TopRoomBar(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, buttonSize_{ 32 } , buttonSize_{32}
{ {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setMinimumSize(QSize(0, 65)); setFixedHeight(65);
setStyleSheet("background-color: #fff; color: #171919;");
topLayout_ = new QHBoxLayout(); topLayout_ = new QHBoxLayout();
topLayout_->setSpacing(10); topLayout_->setSpacing(10);

View File

@ -123,9 +123,6 @@ TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
menu->addAction(quitAction_); menu->addAction(quitAction_);
setContextMenu(menu); setContextMenu(menu);
// We wait a little for the icon to load.
QTimer::singleShot(500, this, [=]() { show(); });
} }
void void

View File

@ -6,7 +6,7 @@
TypingDisplay::TypingDisplay(QWidget *parent) TypingDisplay::TypingDisplay(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, leftPadding_{ 24 } , leftPadding_{24}
{ {
QFont font; QFont font;
font.setPixelSize(conf::typingNotificationFontSize); font.setPixelSize(conf::typingNotificationFontSize);

View File

@ -29,13 +29,11 @@ UserInfoWidget::UserInfoWidget(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, display_name_("User") , display_name_("User")
, user_id_("@user:homeserver.org") , user_id_("@user:homeserver.org")
, logoutModal_{ nullptr } , logoutModal_{nullptr}
, logoutDialog_{ nullptr } , logoutDialog_{nullptr}
, logoutButtonSize_{ 20 } , logoutButtonSize_{20}
{ {
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); setFixedHeight(65);
setSizePolicy(sizePolicy);
setMinimumSize(QSize(0, 65));
topLayout_ = new QHBoxLayout(this); topLayout_ = new QHBoxLayout(this);
topLayout_->setSpacing(0); topLayout_->setSpacing(0);
@ -142,6 +140,8 @@ UserInfoWidget::resizeEvent(QResizeEvent *event)
displayNameLabel_->show(); displayNameLabel_->show();
userIdLabel_->show(); userIdLabel_->show();
} }
QWidget::resizeEvent(event);
} }
void void

155
src/UserSettingsPage.cc Normal file
View File

@ -0,0 +1,155 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QComboBox>
#include <QDebug>
#include <QLabel>
#include <QPushButton>
#include <QSettings>
#include "Config.h"
#include "FlatButton.h"
#include "UserSettingsPage.h"
#include <ToggleButton.h>
UserSettings::UserSettings() { load(); }
void
UserSettings::load()
{
QSettings settings;
isTrayEnabled_ = settings.value("user/window/tray", true).toBool();
theme_ = settings.value("user/theme", "default").toString();
}
void
UserSettings::save()
{
QSettings settings;
settings.beginGroup("user");
settings.beginGroup("window");
settings.setValue("tray", isTrayEnabled_);
settings.endGroup();
settings.setValue("theme", theme());
settings.endGroup();
}
HorizontalLine::HorizontalLine(QWidget *parent)
: QFrame{parent}
{
setFrameShape(QFrame::HLine);
setFrameShadow(QFrame::Sunken);
}
UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent)
: QWidget{parent}
, settings_{settings}
{
topLayout_ = new QVBoxLayout(this);
QIcon icon;
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
auto backBtn_ = new FlatButton(this);
backBtn_->setMinimumSize(QSize(24, 24));
backBtn_->setIcon(icon);
backBtn_->setIconSize(QSize(24, 24));
auto heading_ = new QLabel(tr("User Settings"));
heading_->setFont(QFont("Open Sans Bold", 22));
topBarLayout_ = new QHBoxLayout;
topBarLayout_->setSpacing(0);
topBarLayout_->setMargin(0);
topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter);
topBarLayout_->addWidget(heading_, 0, Qt::AlignBottom);
topBarLayout_->addStretch(1);
auto trayOptionLayout_ = new QHBoxLayout;
trayOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
auto trayLabel = new QLabel(tr("Minimize to tray"), this);
trayToggle_ = new Toggle(this);
trayToggle_->setActiveColor(QColor("#38A3D8"));
trayToggle_->setInactiveColor(QColor("gray"));
trayLabel->setFont(QFont("Open Sans", 15));
trayOptionLayout_->addWidget(trayLabel);
trayOptionLayout_->addWidget(trayToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
auto themeOptionLayout_ = new QHBoxLayout;
themeOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
auto themeLabel_ = new QLabel(tr("App theme"), this);
themeCombo_ = new QComboBox(this);
themeCombo_->addItem("Default");
themeCombo_->addItem("System");
themeLabel_->setFont(QFont("Open Sans", 15));
themeOptionLayout_->addWidget(themeLabel_);
themeOptionLayout_->addWidget(themeCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
auto general_ = new QLabel(tr("GENERAL"), this);
general_->setFont(QFont("Open Sans Bold", 17));
general_->setStyleSheet("color: #5d6565");
mainLayout_ = new QVBoxLayout;
mainLayout_->setSpacing(7);
mainLayout_->setContentsMargins(
sideMargin_, LayoutTopMargin, sideMargin_, LayoutBottomMargin);
mainLayout_->addWidget(general_, 1, Qt::AlignLeft | Qt::AlignVCenter);
mainLayout_->addWidget(new HorizontalLine(this));
mainLayout_->addLayout(trayOptionLayout_);
mainLayout_->addWidget(new HorizontalLine(this));
mainLayout_->addLayout(themeOptionLayout_);
mainLayout_->addWidget(new HorizontalLine(this));
topLayout_->addLayout(topBarLayout_);
topLayout_->addLayout(mainLayout_);
topLayout_->addStretch(1);
connect(themeCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
[=](const QString &text) { settings_->setTheme(text.toLower()); });
connect(trayToggle_, &Toggle::toggled, this, [=](bool isDisabled) {
settings_->setTray(!isDisabled);
emit trayOptionChanged(!isDisabled);
});
connect(backBtn_, &QPushButton::clicked, this, [=]() {
settings_->save();
emit moveBack();
});
}
void
UserSettingsPage::showEvent(QShowEvent *)
{
themeCombo_->setCurrentIndex((settings_->theme() == "default" ? 0 : 1));
trayToggle_->setState(!settings_->isTrayEnabled()); // Treats true as "off"
}
void
UserSettingsPage::resizeEvent(QResizeEvent *event)
{
sideMargin_ = width() * 0.2;
mainLayout_->setContentsMargins(
sideMargin_, LayoutTopMargin, sideMargin_, LayoutBottomMargin);
QWidget::resizeEvent(event);
}

View File

@ -78,3 +78,29 @@ matrix::events::isMessageEvent(EventType type)
{ {
return type == EventType::RoomMessage; return type == EventType::RoomMessage;
} }
void
matrix::events::UnsignedData::deserialize(const QJsonValue &data)
{
if (!data.isObject())
throw DeserializationException("UnsignedData is not a JSON object");
auto object = data.toObject();
transaction_id_ = object.value("transaction_id").toString();
age_ = object.value("age").toDouble();
}
QJsonObject
matrix::events::UnsignedData::serialize() const
{
QJsonObject object;
if (!transaction_id_.isEmpty())
object["transaction_id"] = transaction_id_;
if (age_ > 0)
object["age"] = age_;
return object;
}

View File

@ -19,11 +19,38 @@
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QFontDatabase> #include <QFontDatabase>
#include <QLibraryInfo> #include <QLibraryInfo>
#include <QNetworkProxy>
#include <QSettings> #include <QSettings>
#include <QTranslator> #include <QTranslator>
#include "MainWindow.h" #include "MainWindow.h"
void
setupProxy()
{
QSettings settings;
/**
To set up a SOCKS proxy:
[user]
proxy\socks\host=<>
proxy\socks\port=<>
proxy\socks\user=<>
proxy\socks\password=<>
**/
if (settings.contains("user/proxy/socks/host")) {
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(settings.value("user/proxy/socks/host").toString());
proxy.setPort(settings.value("user/proxy/socks/port").toInt());
if (settings.contains("user/proxy/socks/user"))
proxy.setUser(settings.value("user/proxy/socks/user").toString());
if (settings.contains("user/proxy/socks/password"))
proxy.setPassword(settings.value("user/proxy/socks/password").toString());
QNetworkProxy::setApplicationProxy(proxy);
}
}
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
@ -62,6 +89,8 @@ main(int argc, char *argv[])
appTranslator.load("nheko_" + lang, ":/translations"); appTranslator.load("nheko_" + lang, ":/translations");
app.installTranslator(&appTranslator); app.installTranslator(&appTranslator);
setupProxy();
MainWindow w; MainWindow w;
// Move the MainWindow to the center // Move the MainWindow to the center

View File

@ -22,8 +22,8 @@
OverlayModal::OverlayModal(QWidget *parent, QWidget *content) OverlayModal::OverlayModal(QWidget *parent, QWidget *content)
: OverlayWidget(parent) : OverlayWidget(parent)
, duration_{ 500 } , duration_{500}
, color_{ QColor(55, 55, 55) } , color_{QColor(55, 55, 55)}
{ {
setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_TranslucentBackground);

View File

@ -23,7 +23,7 @@
ScrollBar::ScrollBar(QScrollArea *area, QWidget *parent) ScrollBar::ScrollBar(QScrollArea *area, QWidget *parent)
: QScrollBar(parent) : QScrollBar(parent)
, area_{ area } , area_{area}
{ {
hideTimer_.setSingleShot(true); hideTimer_.setSingleShot(true);

View File

@ -58,11 +58,17 @@ Theme::setColor(const QString &key, ui::Color color)
static const QColor palette[] = { static const QColor palette[] = {
QColor("#171919"), QColor("#171919"),
QColor("#EBEBEB"), QColor("#C9C9C9"), QColor("#929292"), QColor("#EBEBEB"),
QColor("#C9C9C9"),
QColor("#929292"),
QColor("#1C3133"), QColor("#577275"), QColor("#46A451"), QColor("#1C3133"),
QColor("#577275"),
QColor("#46A451"),
QColor("#5D6565"), QColor("#E22826"), QColor("#81B3A9"), QColor("#5D6565"),
QColor("#E22826"),
QColor("#81B3A9"),
rgba(0, 0, 0, 0), rgba(0, 0, 0, 0),
}; };

212
src/ui/ToggleButton.cc Normal file
View File

@ -0,0 +1,212 @@
#include <QApplication>
#include <QColor>
#include <QEvent>
#include <QPainter>
#include "ToggleButton.h"
void
Toggle::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
}
Toggle::Toggle(QWidget *parent)
: QAbstractButton{parent}
{
init();
connect(this, &QAbstractButton::toggled, this, &Toggle::setState);
}
void
Toggle::setState(bool isEnabled)
{
thumb_->setShift(isEnabled ? Position::Right : Position::Left);
setupProperties();
}
void
Toggle::init()
{
track_ = new ToggleTrack(this);
thumb_ = new ToggleThumb(this);
setCursor(QCursor(Qt::PointingHandCursor));
setCheckable(true);
setChecked(false);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setState(false);
setupProperties();
QCoreApplication::processEvents();
}
void
Toggle::setupProperties()
{
if (isEnabled()) {
Position position = thumb_->shift();
thumb_->setThumbColor(trackColor());
if (position == Position::Left)
track_->setTrackColor(activeColor());
else if (position == Position::Right)
track_->setTrackColor(inactiveColor());
}
update();
}
void
Toggle::setDisabledColor(const QColor &color)
{
disabledColor_ = color;
setupProperties();
}
void
Toggle::setActiveColor(const QColor &color)
{
activeColor_ = color;
setupProperties();
}
void
Toggle::setInactiveColor(const QColor &color)
{
inactiveColor_ = color;
setupProperties();
}
void
Toggle::setTrackColor(const QColor &color)
{
trackColor_ = color;
setupProperties();
}
ToggleThumb::ToggleThumb(Toggle *parent)
: QWidget{parent}
, toggle_{parent}
, position_{Position::Right}
, offset_{0}
{
parent->installEventFilter(this);
}
void
ToggleThumb::setShift(Position position)
{
if (position_ != position) {
position_ = position;
updateOffset();
}
}
bool
ToggleThumb::eventFilter(QObject *obj, QEvent *event)
{
const QEvent::Type type = event->type();
if (QEvent::Resize == type || QEvent::Move == type) {
setGeometry(toggle_->rect().adjusted(8, 8, -8, -8));
updateOffset();
}
return QWidget::eventFilter(obj, event);
}
void
ToggleThumb::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white);
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
int s;
QRectF r;
s = height() - 10;
r = QRectF(5 + offset_, 5, s, s);
painter.drawEllipse(r);
if (!toggle_->isEnabled()) {
brush.setColor(toggle_->disabledColor());
painter.setBrush(brush);
painter.drawEllipse(r);
}
}
void
ToggleThumb::updateOffset()
{
const QSize s(size());
offset_ = position_ == Position::Left ? static_cast<qreal>(s.width() - s.height()) : 0;
update();
}
ToggleTrack::ToggleTrack(Toggle *parent)
: QWidget{parent}
, toggle_{parent}
{
Q_ASSERT(parent);
parent->installEventFilter(this);
}
void
ToggleTrack::setTrackColor(const QColor &color)
{
trackColor_ = color;
update();
}
bool
ToggleTrack::eventFilter(QObject *obj, QEvent *event)
{
const QEvent::Type type = event->type();
if (QEvent::Resize == type || QEvent::Move == type) {
setGeometry(toggle_->rect());
}
return QWidget::eventFilter(obj, event);
}
void
ToggleTrack::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QBrush brush;
if (toggle_->isEnabled()) {
brush.setColor(trackColor_);
painter.setOpacity(0.8);
} else {
brush.setColor(toggle_->disabledColor());
painter.setOpacity(0.6);
}
brush.setStyle(Qt::SolidPattern);
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
const int h = height() / 2;
const QRect r(0, h / 2, width(), h);
painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4);
}

View File

@ -22,8 +22,8 @@ using namespace matrix::events;
TEST(BaseEvent, Deserialization) TEST(BaseEvent, Deserialization)
{ {
// NameEventContent // NameEventContent
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Room Name" } } }, auto data =
{ "type", "m.room.name" } }; QJsonObject{{"content", QJsonObject{{"name", "Room Name"}}}, {"type", "m.room.name"}};
Event<NameEventContent> name_event; Event<NameEventContent> name_event;
name_event.deserialize(data); name_event.deserialize(data);
@ -31,47 +31,57 @@ TEST(BaseEvent, Deserialization)
EXPECT_EQ(name_event.serialize(), data); EXPECT_EQ(name_event.serialize(), data);
// TopicEventContent // TopicEventContent
data = QJsonObject{ { "content", QJsonObject{ { "topic", "Room Topic" } } }, data = QJsonObject{{"content", QJsonObject{{"topic", "Room Topic"}}},
{ "type", "m.room.topic" } }; {"unsigned", QJsonObject{{"age", 22}, {"transaction_id", "randomid"}}},
{"type", "m.room.topic"}};
Event<TopicEventContent> topic_event; Event<TopicEventContent> topic_event;
topic_event.deserialize(data); topic_event.deserialize(data);
EXPECT_EQ(topic_event.content().topic(), "Room Topic"); EXPECT_EQ(topic_event.content().topic(), "Room Topic");
EXPECT_EQ(topic_event.unsignedData().age(), 22);
EXPECT_EQ(topic_event.unsignedData().transactionId(), "randomid");
EXPECT_EQ(topic_event.serialize(), data); EXPECT_EQ(topic_event.serialize(), data);
// AvatarEventContent // AvatarEventContent
data = QJsonObject{ { "content", QJsonObject{ { "url", "https://matrix.org" } } }, data = QJsonObject{
{ "type", "m.room.avatar" } }; {"content", QJsonObject{{"url", "https://matrix.org"}}},
{"unsigned", QJsonObject{{"age", 1343434343}, {"transaction_id", "m33434.33"}}},
{"type", "m.room.avatar"}};
Event<AvatarEventContent> avatar_event; Event<AvatarEventContent> avatar_event;
avatar_event.deserialize(data); avatar_event.deserialize(data);
EXPECT_EQ(avatar_event.content().url().toString(), "https://matrix.org"); EXPECT_EQ(avatar_event.content().url().toString(), "https://matrix.org");
EXPECT_EQ(avatar_event.unsignedData().age(), 1343434343);
EXPECT_EQ(avatar_event.unsignedData().transactionId(), "m33434.33");
EXPECT_EQ(avatar_event.serialize(), data); EXPECT_EQ(avatar_event.serialize(), data);
// AliasesEventContent // AliasesEventContent
data = data = QJsonObject{
QJsonObject{ { "content", {"content",
QJsonObject{ QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}}},
{ "aliases", QJsonArray{ "#test:matrix.org", "#test2:matrix.org" } } } }, {"unsigned", QJsonObject{{"transaction_id", "m33434.33"}}},
{ "type", "m.room.aliases" } }; {"type", "m.room.aliases"}};
Event<AliasesEventContent> aliases_event; Event<AliasesEventContent> aliases_event;
aliases_event.deserialize(data); aliases_event.deserialize(data);
EXPECT_EQ(aliases_event.content().aliases().size(), 2); EXPECT_EQ(aliases_event.content().aliases().size(), 2);
EXPECT_EQ(aliases_event.unsignedData().transactionId(), "m33434.33");
EXPECT_EQ(aliases_event.serialize(), data); EXPECT_EQ(aliases_event.serialize(), data);
// CreateEventContent // CreateEventContent
data = QJsonObject{ { "content", QJsonObject{ { "creator", "@alice:matrix.org" } } }, data = QJsonObject{{"content", QJsonObject{{"creator", "@alice:matrix.org"}}},
{ "type", "m.room.create" } }; {"unsigned", QJsonObject{{"age", 2233}}},
{"type", "m.room.create"}};
Event<CreateEventContent> create_event; Event<CreateEventContent> create_event;
create_event.deserialize(data); create_event.deserialize(data);
EXPECT_EQ(create_event.content().creator(), "@alice:matrix.org"); EXPECT_EQ(create_event.content().creator(), "@alice:matrix.org");
EXPECT_EQ(create_event.unsignedData().age(), 2233);
EXPECT_EQ(create_event.serialize(), data); EXPECT_EQ(create_event.serialize(), data);
// JoinRulesEventContent // JoinRulesEventContent
data = QJsonObject{ { "content", QJsonObject{ { "join_rule", "private" } } }, data = QJsonObject{{"content", QJsonObject{{"join_rule", "private"}}},
{ "type", "m.room.join_rules" } }; {"type", "m.room.join_rules"}};
Event<JoinRulesEventContent> join_rules_event; Event<JoinRulesEventContent> join_rules_event;
join_rules_event.deserialize(data); join_rules_event.deserialize(data);
@ -81,27 +91,34 @@ TEST(BaseEvent, Deserialization)
TEST(BaseEvent, DeserializationException) TEST(BaseEvent, DeserializationException)
{ {
auto data = QJsonObject{ { "content", QJsonObject{ { "rule", "private" } } }, auto data =
{ "type", "m.room.join_rules" } }; QJsonObject{{"content", QJsonObject{{"rule", "private"}}}, {"type", "m.room.join_rules"}};
Event<JoinRulesEventContent> event1; Event<JoinRulesEventContent> event1;
ASSERT_THROW(event1.deserialize(data), DeserializationException); ASSERT_THROW(event1.deserialize(data), DeserializationException);
data = QJsonObject{ { "contents", QJsonObject{ { "join_rule", "private" } } }, data = QJsonObject{{"contents", QJsonObject{{"join_rule", "private"}}},
{ "type", "m.room.join_rules" } }; {"type", "m.room.join_rules"}};
Event<JoinRulesEventContent> event2; Event<JoinRulesEventContent> event2;
ASSERT_THROW(event2.deserialize(data), DeserializationException); ASSERT_THROW(event2.deserialize(data), DeserializationException);
data = QJsonObject{{"contents", QJsonObject{{"join_rule", "private"}}},
{"unsigned", QJsonObject{{"age", "222"}}},
{"type", "m.room.join_rules"}};
Event<JoinRulesEventContent> event3;
ASSERT_THROW(event3.deserialize(data), DeserializationException);
} }
TEST(RoomEvent, Deserialization) TEST(RoomEvent, Deserialization)
{ {
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } }, auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
{ "event_id", "$asdfafdf8af:matrix.org" }, {"event_id", "$asdfafdf8af:matrix.org"},
{ "room_id", "!aasdfaeae23r9:matrix.org" }, {"room_id", "!aasdfaeae23r9:matrix.org"},
{ "sender", "@alice:matrix.org" }, {"sender", "@alice:matrix.org"},
{ "origin_server_ts", 1323238293289323LL }, {"origin_server_ts", 1323238293289323LL},
{ "type", "m.room.name" } }; {"type", "m.room.name"}};
RoomEvent<NameEventContent> event; RoomEvent<NameEventContent> event;
event.deserialize(data); event.deserialize(data);
@ -116,11 +133,11 @@ TEST(RoomEvent, Deserialization)
TEST(RoomEvent, DeserializationException) TEST(RoomEvent, DeserializationException)
{ {
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } }, auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
{ "event_id", "$asdfafdf8af:matrix.org" }, {"event_id", "$asdfafdf8af:matrix.org"},
{ "room_id", "!aasdfaeae23r9:matrix.org" }, {"room_id", "!aasdfaeae23r9:matrix.org"},
{ "origin_server_ts", 1323238293289323LL }, {"origin_server_ts", 1323238293289323LL},
{ "type", "m.room.name" } }; {"type", "m.room.name"}};
RoomEvent<NameEventContent> event; RoomEvent<NameEventContent> event;
@ -133,14 +150,14 @@ TEST(RoomEvent, DeserializationException)
TEST(StateEvent, Deserialization) TEST(StateEvent, Deserialization)
{ {
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } }, auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
{ "event_id", "$asdfafdf8af:matrix.org" }, {"event_id", "$asdfafdf8af:matrix.org"},
{ "state_key", "some_state_key" }, {"state_key", "some_state_key"},
{ "prev_content", QJsonObject{ { "name", "Previous Name" } } }, {"prev_content", QJsonObject{{"name", "Previous Name"}}},
{ "room_id", "!aasdfaeae23r9:matrix.org" }, {"room_id", "!aasdfaeae23r9:matrix.org"},
{ "sender", "@alice:matrix.org" }, {"sender", "@alice:matrix.org"},
{ "origin_server_ts", 1323238293289323LL }, {"origin_server_ts", 1323238293289323LL},
{ "type", "m.room.name" } }; {"type", "m.room.name"}};
StateEvent<NameEventContent> event; StateEvent<NameEventContent> event;
event.deserialize(data); event.deserialize(data);
@ -157,13 +174,13 @@ TEST(StateEvent, Deserialization)
TEST(StateEvent, DeserializationException) TEST(StateEvent, DeserializationException)
{ {
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Name" } } }, auto data = QJsonObject{{"content", QJsonObject{{"name", "Name"}}},
{ "event_id", "$asdfafdf8af:matrix.org" }, {"event_id", "$asdfafdf8af:matrix.org"},
{ "prev_content", QJsonObject{ { "name", "Previous Name" } } }, {"prev_content", QJsonObject{{"name", "Previous Name"}}},
{ "room_id", "!aasdfaeae23r9:matrix.org" }, {"room_id", "!aasdfaeae23r9:matrix.org"},
{ "sender", "@alice:matrix.org" }, {"sender", "@alice:matrix.org"},
{ "origin_server_ts", 1323238293289323LL }, {"origin_server_ts", 1323238293289323LL},
{ "type", "m.room.name" } }; {"type", "m.room.name"}};
StateEvent<NameEventContent> event; StateEvent<NameEventContent> event;
@ -176,35 +193,30 @@ TEST(StateEvent, DeserializationException)
TEST(EventType, Mapping) TEST(EventType, Mapping)
{ {
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.aliases" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.aliases"}}),
EventType::RoomAliases); EventType::RoomAliases);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.avatar" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.avatar"}}), EventType::RoomAvatar);
EventType::RoomAvatar); EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.canonical_alias"}}),
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.canonical_alias" } }),
EventType::RoomCanonicalAlias); EventType::RoomCanonicalAlias);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.create" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.create"}}), EventType::RoomCreate);
EventType::RoomCreate); EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.history_visibility"}}),
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.history_visibility" } }),
EventType::RoomHistoryVisibility); EventType::RoomHistoryVisibility);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.join_rules" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.join_rules"}}),
EventType::RoomJoinRules); EventType::RoomJoinRules);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.member" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.member"}}), EventType::RoomMember);
EventType::RoomMember); EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.message"}}),
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.message" } }),
EventType::RoomMessage); EventType::RoomMessage);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.name" } }), EventType::RoomName); EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.name"}}), EventType::RoomName);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.power_levels" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.power_levels"}}),
EventType::RoomPowerLevels); EventType::RoomPowerLevels);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.topic" } }), EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.topic"}}), EventType::RoomTopic);
EventType::RoomTopic); EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.unknown"}}),
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.unknown" } }),
EventType::Unsupported); EventType::Unsupported);
} }
TEST(AliasesEventContent, Deserialization) TEST(AliasesEventContent, Deserialization)
{ {
auto data = auto data = QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}};
QJsonObject{ { "aliases", QJsonArray{ "#test:matrix.org", "#test2:matrix.org" } } };
AliasesEventContent content; AliasesEventContent content;
content.deserialize(data); content.deserialize(data);
@ -215,7 +227,7 @@ TEST(AliasesEventContent, Deserialization)
TEST(AliasesEventContent, NotAnObject) TEST(AliasesEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "#test:matrix.org", "#test2:matrix.org" }; auto data = QJsonArray{"#test:matrix.org", "#test2:matrix.org"};
AliasesEventContent content; AliasesEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -223,7 +235,7 @@ TEST(AliasesEventContent, NotAnObject)
TEST(AliasesEventContent, MissingKey) TEST(AliasesEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", QJsonArray{ "#test:matrix.org", "#test2:matrix.org" } } }; auto data = QJsonObject{{"key", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}};
AliasesEventContent content; AliasesEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -237,7 +249,7 @@ TEST(AliasesEventContent, MissingKey)
TEST(AvatarEventContent, Deserialization) TEST(AvatarEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "url", "https://matrix.org/avatar.png" } }; auto data = QJsonObject{{"url", "https://matrix.org/avatar.png"}};
AvatarEventContent content; AvatarEventContent content;
content.deserialize(data); content.deserialize(data);
@ -248,7 +260,7 @@ TEST(AvatarEventContent, Deserialization)
TEST(AvatarEventContent, NotAnObject) TEST(AvatarEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "key", "url" }; auto data = QJsonArray{"key", "url"};
AvatarEventContent content; AvatarEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -256,7 +268,7 @@ TEST(AvatarEventContent, NotAnObject)
TEST(AvatarEventContent, MissingKey) TEST(AvatarEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", "https://matrix.org" } }; auto data = QJsonObject{{"key", "https://matrix.org"}};
AvatarEventContent content; AvatarEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -270,7 +282,7 @@ TEST(AvatarEventContent, MissingKey)
TEST(CreateEventContent, Deserialization) TEST(CreateEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "creator", "@alice:matrix.org" } }; auto data = QJsonObject{{"creator", "@alice:matrix.org"}};
CreateEventContent content; CreateEventContent content;
content.deserialize(data); content.deserialize(data);
@ -281,7 +293,7 @@ TEST(CreateEventContent, Deserialization)
TEST(CreateEventContent, NotAnObject) TEST(CreateEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "creator", "alice" }; auto data = QJsonArray{"creator", "alice"};
CreateEventContent content; CreateEventContent content;
@ -290,7 +302,7 @@ TEST(CreateEventContent, NotAnObject)
TEST(CreateEventContent, MissingKey) TEST(CreateEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", "@alice:matrix.org" } }; auto data = QJsonObject{{"key", "@alice:matrix.org"}};
CreateEventContent content; CreateEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -304,23 +316,23 @@ TEST(CreateEventContent, MissingKey)
TEST(HistoryVisibilityEventContent, Deserialization) TEST(HistoryVisibilityEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "history_visibility", "invited" } }; auto data = QJsonObject{{"history_visibility", "invited"}};
HistoryVisibilityEventContent content; HistoryVisibilityEventContent content;
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Invited); EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Invited);
data = QJsonObject{ { "history_visibility", "joined" } }; data = QJsonObject{{"history_visibility", "joined"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Joined); EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Joined);
data = QJsonObject{ { "history_visibility", "shared" } }; data = QJsonObject{{"history_visibility", "shared"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Shared); EXPECT_EQ(content.historyVisibility(), HistoryVisibility::Shared);
data = QJsonObject{ { "history_visibility", "world_readable" } }; data = QJsonObject{{"history_visibility", "world_readable"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.historyVisibility(), HistoryVisibility::WorldReadable); EXPECT_EQ(content.historyVisibility(), HistoryVisibility::WorldReadable);
@ -328,7 +340,7 @@ TEST(HistoryVisibilityEventContent, Deserialization)
TEST(HistoryVisibilityEventContent, NotAnObject) TEST(HistoryVisibilityEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "history_visibility", "alice" }; auto data = QJsonArray{"history_visibility", "alice"};
HistoryVisibilityEventContent content; HistoryVisibilityEventContent content;
@ -337,7 +349,7 @@ TEST(HistoryVisibilityEventContent, NotAnObject)
TEST(HistoryVisibilityEventContent, InvalidHistoryVisibility) TEST(HistoryVisibilityEventContent, InvalidHistoryVisibility)
{ {
auto data = QJsonObject{ { "history_visibility", "wrong" } }; auto data = QJsonObject{{"history_visibility", "wrong"}};
HistoryVisibilityEventContent content; HistoryVisibilityEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -351,7 +363,7 @@ TEST(HistoryVisibilityEventContent, InvalidHistoryVisibility)
TEST(HistoryVisibilityEventContent, MissingKey) TEST(HistoryVisibilityEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", "joined" } }; auto data = QJsonObject{{"key", "joined"}};
HistoryVisibilityEventContent content; HistoryVisibilityEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -365,23 +377,23 @@ TEST(HistoryVisibilityEventContent, MissingKey)
TEST(JoinRulesEventContent, Deserialization) TEST(JoinRulesEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "join_rule", "invite" } }; auto data = QJsonObject{{"join_rule", "invite"}};
JoinRulesEventContent content; JoinRulesEventContent content;
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.joinRule(), JoinRule::Invite); EXPECT_EQ(content.joinRule(), JoinRule::Invite);
data = QJsonObject{ { "join_rule", "knock" } }; data = QJsonObject{{"join_rule", "knock"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.joinRule(), JoinRule::Knock); EXPECT_EQ(content.joinRule(), JoinRule::Knock);
data = QJsonObject{ { "join_rule", "private" } }; data = QJsonObject{{"join_rule", "private"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.joinRule(), JoinRule::Private); EXPECT_EQ(content.joinRule(), JoinRule::Private);
data = QJsonObject{ { "join_rule", "public" } }; data = QJsonObject{{"join_rule", "public"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.joinRule(), JoinRule::Public); EXPECT_EQ(content.joinRule(), JoinRule::Public);
@ -389,7 +401,7 @@ TEST(JoinRulesEventContent, Deserialization)
TEST(JoinRulesEventContent, NotAnObject) TEST(JoinRulesEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "rule", "alice" }; auto data = QJsonArray{"rule", "alice"};
JoinRulesEventContent content; JoinRulesEventContent content;
@ -398,7 +410,7 @@ TEST(JoinRulesEventContent, NotAnObject)
TEST(JoinRulesEventContent, InvalidHistoryVisibility) TEST(JoinRulesEventContent, InvalidHistoryVisibility)
{ {
auto data = QJsonObject{ { "join_rule", "wrong" } }; auto data = QJsonObject{{"join_rule", "wrong"}};
JoinRulesEventContent content; JoinRulesEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -412,7 +424,7 @@ TEST(JoinRulesEventContent, InvalidHistoryVisibility)
TEST(JoinRulesEventContent, MissingKey) TEST(JoinRulesEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", "invite" } }; auto data = QJsonObject{{"key", "invite"}};
JoinRulesEventContent content; JoinRulesEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -426,7 +438,7 @@ TEST(JoinRulesEventContent, MissingKey)
TEST(CanonicalAliasEventContent, Deserialization) TEST(CanonicalAliasEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "alias", "Room Alias" } }; auto data = QJsonObject{{"alias", "Room Alias"}};
CanonicalAliasEventContent content; CanonicalAliasEventContent content;
content.deserialize(data); content.deserialize(data);
@ -437,7 +449,7 @@ TEST(CanonicalAliasEventContent, Deserialization)
TEST(CanonicalAliasEventContent, NotAnObject) TEST(CanonicalAliasEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "alias", "Room Alias" }; auto data = QJsonArray{"alias", "Room Alias"};
CanonicalAliasEventContent content; CanonicalAliasEventContent content;
@ -446,7 +458,7 @@ TEST(CanonicalAliasEventContent, NotAnObject)
TEST(CanonicalAliasEventContent, MissingKey) TEST(CanonicalAliasEventContent, MissingKey)
{ {
auto data = QJsonObject{ { "key", "alias" } }; auto data = QJsonObject{{"key", "alias"}};
CanonicalAliasEventContent content; CanonicalAliasEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -462,29 +474,29 @@ TEST(MemberEventContent, Deserialization)
{ {
MemberEventContent content; MemberEventContent content;
auto data = QJsonObject{ { "membership", "join" } }; auto data = QJsonObject{{"membership", "join"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.membershipState(), Membership::Join); EXPECT_EQ(content.membershipState(), Membership::Join);
data = QJsonObject{ { "membership", "invite" }, { "displayname", "Username" } }; data = QJsonObject{{"membership", "invite"}, {"displayname", "Username"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.membershipState(), Membership::Invite); EXPECT_EQ(content.membershipState(), Membership::Invite);
EXPECT_EQ(content.displayName(), "Username"); EXPECT_EQ(content.displayName(), "Username");
data = QJsonObject{ { "membership", "leave" }, { "avatar_url", "https://matrix.org" } }; data = QJsonObject{{"membership", "leave"}, {"avatar_url", "https://matrix.org"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.membershipState(), Membership::Leave); EXPECT_EQ(content.membershipState(), Membership::Leave);
EXPECT_EQ(content.avatarUrl().toString(), "https://matrix.org"); EXPECT_EQ(content.avatarUrl().toString(), "https://matrix.org");
data = QJsonObject{ { "membership", "ban" } }; data = QJsonObject{{"membership", "ban"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.membershipState(), Membership::Ban); EXPECT_EQ(content.membershipState(), Membership::Ban);
data = QJsonObject{ { "membership", "knock" } }; data = QJsonObject{{"membership", "knock"}};
content.deserialize(data); content.deserialize(data);
EXPECT_EQ(content.membershipState(), Membership::Knock); EXPECT_EQ(content.membershipState(), Membership::Knock);
@ -492,7 +504,7 @@ TEST(MemberEventContent, Deserialization)
TEST(MemberEventContent, InvalidMembership) TEST(MemberEventContent, InvalidMembership)
{ {
auto data = QJsonObject{ { "membership", "wrong" } }; auto data = QJsonObject{{"membership", "wrong"}};
MemberEventContent content; MemberEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -506,7 +518,7 @@ TEST(MemberEventContent, InvalidMembership)
TEST(MemberEventContent, NotAnObject) TEST(MemberEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "name", "join" }; auto data = QJsonArray{"name", "join"};
MemberEventContent content; MemberEventContent content;
@ -515,7 +527,7 @@ TEST(MemberEventContent, NotAnObject)
TEST(MemberEventContent, MissingName) TEST(MemberEventContent, MissingName)
{ {
auto data = QJsonObject{ { "key", "random" } }; auto data = QJsonObject{{"key", "random"}};
MemberEventContent content; MemberEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -529,7 +541,7 @@ TEST(MemberEventContent, MissingName)
TEST(NameEventContent, Deserialization) TEST(NameEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "name", "Room Name" } }; auto data = QJsonObject{{"name", "Room Name"}};
NameEventContent content; NameEventContent content;
content.deserialize(data); content.deserialize(data);
@ -540,7 +552,7 @@ TEST(NameEventContent, Deserialization)
TEST(NameEventContent, NotAnObject) TEST(NameEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "name", "Room Name" }; auto data = QJsonArray{"name", "Room Name"};
NameEventContent content; NameEventContent content;
@ -549,7 +561,7 @@ TEST(NameEventContent, NotAnObject)
TEST(NameEventContent, MissingName) TEST(NameEventContent, MissingName)
{ {
auto data = QJsonObject{ { "key", "Room Name" } }; auto data = QJsonObject{{"key", "Room Name"}};
NameEventContent content; NameEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);
@ -582,17 +594,17 @@ TEST(PowerLevelsEventContent, DefaultValues)
TEST(PowerLevelsEventContent, FullDeserialization) TEST(PowerLevelsEventContent, FullDeserialization)
{ {
auto data = QJsonObject{ auto data = QJsonObject{
{ "ban", 1 }, {"ban", 1},
{ "invite", 2 }, {"invite", 2},
{ "kick", 3 }, {"kick", 3},
{ "redact", 4 }, {"redact", 4},
{ "events_default", 5 }, {"events_default", 5},
{ "state_default", 6 }, {"state_default", 6},
{ "users_default", 7 }, {"users_default", 7},
{ "events", QJsonObject{ { "m.message.text", 8 }, { "m.message.image", 9 } } }, {"events", QJsonObject{{"m.message.text", 8}, {"m.message.image", 9}}},
{ "users", QJsonObject{ { "@alice:matrix.org", 10 }, { "@bob:matrix.org", 11 } } }, {"users", QJsonObject{{"@alice:matrix.org", 10}, {"@bob:matrix.org", 11}}},
}; };
PowerLevelsEventContent power_levels; PowerLevelsEventContent power_levels;
@ -621,13 +633,13 @@ TEST(PowerLevelsEventContent, FullDeserialization)
TEST(PowerLevelsEventContent, PartialDeserialization) TEST(PowerLevelsEventContent, PartialDeserialization)
{ {
auto data = QJsonObject{ auto data = QJsonObject{
{ "ban", 1 }, {"ban", 1},
{ "invite", 2 }, {"invite", 2},
{ "events_default", 5 }, {"events_default", 5},
{ "users_default", 7 }, {"users_default", 7},
{ "users", QJsonObject{ { "@alice:matrix.org", 10 }, { "@bob:matrix.org", 11 } } }, {"users", QJsonObject{{"@alice:matrix.org", 10}, {"@bob:matrix.org", 11}}},
}; };
PowerLevelsEventContent power_levels; PowerLevelsEventContent power_levels;
@ -653,7 +665,7 @@ TEST(PowerLevelsEventContent, PartialDeserialization)
TEST(PowerLevelsEventContent, NotAnObject) TEST(PowerLevelsEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "test", "test2" }; auto data = QJsonArray{"test", "test2"};
PowerLevelsEventContent power_levels; PowerLevelsEventContent power_levels;
@ -662,7 +674,7 @@ TEST(PowerLevelsEventContent, NotAnObject)
TEST(TopicEventContent, Deserialization) TEST(TopicEventContent, Deserialization)
{ {
auto data = QJsonObject{ { "topic", "Room Topic" } }; auto data = QJsonObject{{"topic", "Room Topic"}};
TopicEventContent content; TopicEventContent content;
content.deserialize(data); content.deserialize(data);
@ -673,7 +685,7 @@ TEST(TopicEventContent, Deserialization)
TEST(TopicEventContent, NotAnObject) TEST(TopicEventContent, NotAnObject)
{ {
auto data = QJsonArray{ "topic", "Room Topic" }; auto data = QJsonArray{"topic", "Room Topic"};
TopicEventContent content; TopicEventContent content;
@ -682,7 +694,7 @@ TEST(TopicEventContent, NotAnObject)
TEST(TopicEventContent, MissingName) TEST(TopicEventContent, MissingName)
{ {
auto data = QJsonObject{ { "key", "Room Name" } }; auto data = QJsonObject{{"key", "Room Name"}};
TopicEventContent content; TopicEventContent content;
ASSERT_THROW(content.deserialize(data), DeserializationException); ASSERT_THROW(content.deserialize(data), DeserializationException);