(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
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 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
Standard: Cpp11
AccessModifierOffset: -8
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
@ -10,3 +11,4 @@ IndentCaseLabels: false
IndentWidth: 8
KeepEmptyLinesAtTheStartOfBlocks: false
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 == linux ]; then ./.ci/linux/install-deps.sh; fi
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
script:
- 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 ./.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)
project(nheko C CXX)
option(BUILD_TESTS "Build all tests" 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
#
@ -156,6 +171,7 @@ set(SRC_FILES
src/RoomMessages.cc
src/RoomState.cc
src/SideBarActions.cc
src/UserSettingsPage.cc
src/Splitter.cc
src/Sync.cc
src/TextInputWidget.cc
@ -184,6 +200,7 @@ set(SRC_FILES
src/ui/RippleOverlay.cc
src/ui/OverlayWidget.cc
src/ui/TextField.cc
src/ui/ToggleButton.cc
src/ui/Theme.cc
src/ui/ThemeManager.cc
)
@ -245,6 +262,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/RoomInfoListItem.h
include/RoomList.h
include/SideBarActions.h
include/UserSettingsPage.h
include/Splitter.h
include/TextInputWidget.h
include/TimelineItem.h
@ -268,6 +286,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/ui/Ripple.h
include/ui/RippleOverlay.h
include/ui/TextField.h
include/ui/ToggleButton.h
include/ui/Theme.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)
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)
enable_testing()
@ -332,15 +343,15 @@ else()
set (NHEKO_LIBS matrix_events Qt5::Widgets Qt5::Network Qt5::Concurrent ${LMDB_LIBRARY})
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)
add_executable (nheko ${NHEKO_DEPS})
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
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)
else()
add_executable (nheko ${NHEKO_DEPS})
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS})
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:
@cmake -DBUILD_TESTS=OFF -H. -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Debug
@cmake --build build
@ -17,19 +12,14 @@ test:
@cmake --build build
@cd build && GTEST_COLOR=1 ctest --verbose
app: release-debug $(APP_TEMPLATE)
@cp -fp ./build/$(APP_NAME) $(APP_TEMPLATE)/Contents/MacOS
@echo "Created '$(APP_NAME).app' in '$(APP_TEMPLATE)'"
linux-appimage:
@./.ci/linux/deploy.sh
app-install: app
cp -Rf $(APP_TEMPLATE) /Applications/
macos-app: release-debug
@./.ci/macos/deploy.sh
dmg: app
hdiutil create $(MAC_DIST_DIR)/Nheko.dmg \
-volname "$(APP_NAME)" \
-fs HFS+ \
-srcfolder $(MAC_DIST_DIR) \
-ov -format UDZO
macos-app-install:
cp -Rf build/nheko.app /Applications
run:
@./build/nheko

View File

@ -16,10 +16,12 @@ but we are getting close to a more feature complete client.
Specifically there is support for:
- Joining & leaving rooms
- Sending & receiving images and emoji.
- Receiving typing notifications.
- Typing notifications.
### 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
```bash
pacaur -S nheko-git
@ -37,10 +39,6 @@ sudo layman -a matrix
sudo emerge -a nheko
```
#### Windows
You can find an installer [here](https://ci.appveyor.com/project/mujx/nheko/branch/master/artifacts).
### Build Requirements
- 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.
##### MacOS
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
#### Nix
Download the repo as mentioned above and run

View File

@ -15,14 +15,32 @@ build:
install:
- set QT_DIR=C:\Qt\5.8\msvc2015_64
- 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:
- 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
after_build:
# Variables
- set BUILD=%APPVEYOR_BUILD_FOLDER%
- echo %BUILD%
- mkdir NhekoRelease
- copy build\Release\nheko.exe 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\bin\ssleay32.dll .\NhekoRelease\ssleay32.dll
- 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:
- path: nheko_win_64.zip
- path: NhekoRelease\nheko.exe
- path: nheko_setup.exe
- path: nheko-installer.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
#include <QFrame>
#include <QHBoxLayout>
#include <QMap>
#include <QPixmap>
@ -49,6 +50,7 @@ class LeftRoom;
constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
class ChatPage : public QWidget
{
@ -69,6 +71,7 @@ signals:
void unreadMessages(int count);
void showNotification(const QString &msg);
void showLoginPage(const QString &msg);
void showUserSettingsPage();
private slots:
void showUnreadMessageNotification(int count);
@ -119,10 +122,8 @@ private:
QWidget *sideBarTopWidget_;
QVBoxLayout *sideBarTopWidgetLayout_;
QWidget *content_;
QFrame *content_;
QVBoxLayout *contentLayout_;
QHBoxLayout *topBarLayout_;
QVBoxLayout *mainContentLayout_;
CommunitiesList *communitiesList_;
RoomList *room_list_;
@ -153,6 +154,7 @@ private:
// Keeps track of the users currently typing on each room.
QMap<QString, QList<QString>> typingUsers_;
QTimer *typingRefresher_;
QSharedPointer<QuickSwitcher> quickSwitcher_;
QSharedPointer<OverlayModal> quickSwitcherModal_;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,8 @@
#include <QSharedPointer>
#include <QStackedWidget>
#include "MessageEvent.h"
class JoinedRoom;
class MatrixClient;
class RoomInfoListItem;
@ -61,12 +63,13 @@ signals:
public slots:
void setHistoryView(const QString &room_id);
void sendTextMessage(const QString &msg);
void sendEmoteMessage(const QString &msg);
void sendImageMessage(const QString &roomid, const QString &filename, const QString &url);
void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid, const QString &filename, const QString &url);
private slots:
void messageSent(const QString &eventid, const QString &roomid, int txnid);
void messageSendFailed(const QString &roomid, int txnid);
private:
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
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>
class Event : public Deserializable, public Serializable
{
public:
Content content() const;
EventType eventType() const;
UnsignedData unsignedData() const { return unsignedData_; }
void deserialize(const QJsonValue &data) override;
QJsonObject serialize() const override;
@ -73,6 +92,7 @@ public:
private:
Content content_;
EventType type_;
UnsignedData unsignedData_;
};
template<class Content>
@ -100,6 +120,9 @@ Event<Content>::deserialize(const QJsonValue &data)
content_.deserialize(object.value("content"));
type_ = extractEventType(object);
if (object.contains("unsigned"))
unsignedData_.deserialize(object.value("unsigned"));
}
template<class Content>
@ -149,6 +172,9 @@ Event<Content>::serialize() const
object["content"] = content_.serialize();
if (!unsignedData_.isEmpty())
object["unsigned"] = unsignedData_.serialize();
return object;
}
} // namespace events

View File

@ -12,8 +12,7 @@ enum class AvatarType
Letter
};
namespace sidebar
{
namespace sidebar {
static const int SmallSize = 60;
static const int NormalSize = 300;
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

@ -72,8 +72,8 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
topLayout_->addWidget(splitter);
// SideBar
sideBar_ = new QWidget(this);
sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, 0));
sideBar_ = new QFrame(this);
sideBar_->setMinimumSize(QSize(ui::sidebar::NormalSize, parent->height()));
sideBarLayout_ = new QVBoxLayout(sideBar_);
sideBarLayout_->setSpacing(0);
sideBarLayout_->setMargin(0);
@ -93,40 +93,33 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
sideBarTopWidget_ = new QWidget(sideBar_);
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_);
sideBarTopWidgetLayout_->setSpacing(0);
sideBarTopWidgetLayout_->setMargin(0);
sideBarLayout_->addWidget(user_info_widget_);
sideBarLayout_->addWidget(room_list_);
sideBarLayout_->addWidget(sidebarActions_);
// Content
content_ = new QWidget(this);
content_ = new QFrame(this);
contentLayout_ = new QVBoxLayout(content_);
contentLayout_->setSpacing(0);
contentLayout_->setMargin(0);
topBarLayout_ = new QHBoxLayout();
topBarLayout_->setSpacing(0);
mainContentLayout_ = new QVBoxLayout();
mainContentLayout_->setSpacing(0);
mainContentLayout_->setMargin(0);
top_bar_ = new TopRoomBar(this);
view_manager_ = new TimelineViewManager(client, this);
contentLayout_->addLayout(topBarLayout_);
contentLayout_->addLayout(mainContentLayout_);
contentLayout_->addWidget(top_bar_);
contentLayout_->addWidget(view_manager_);
// Splitter
splitter->addWidget(sideBar_);
splitter->addWidget(content_);
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_);
splitter->setSizes({ui::sidebar::NormalSize, parent->width() - ui::sidebar::NormalSize});
text_input_ = new TextInputWidget(this);
typingDisplay_ = new TypingDisplay(this);
@ -135,6 +128,8 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
user_info_widget_ = new UserInfoWidget(sideBarTopWidget_);
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(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout()));
@ -150,6 +145,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
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, text_input_, &TextInputWidget::focusLineEdit);
@ -170,6 +166,20 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
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_,
&TimelineViewManager::updateRoomsLastMessage,
room_list_,
@ -183,12 +193,12 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect(text_input_,
SIGNAL(sendTextMessage(const QString &)),
view_manager_,
SLOT(sendTextMessage(const QString &)));
SLOT(queueTextMessage(const QString &)));
connect(text_input_,
SIGNAL(sendEmoteMessage(const QString &)),
view_manager_,
SLOT(sendEmoteMessage(const QString &)));
SLOT(queueEmoteMessage(const QString &)));
connect(text_input_,
&TextInputWidget::sendJoinRoomRequest,
@ -205,7 +215,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
this,
[=](QString roomid, QString filename, QString url) {
text_input_->hideUploadSpinner();
view_manager_->sendImageMessage(roomid, filename, url);
view_manager_->queueImageMessage(roomid, filename, url);
});
connect(client_.data(),
@ -604,6 +614,7 @@ ChatPage::showQuickSwitcher()
connect(quickSwitcher_.data(), &QuickSwitcher::closing, this, [=]() {
if (!this->quickSwitcherModal_.isNull())
this->quickSwitcherModal_->fadeOut();
this->text_input_->setFocus(Qt::FocusReason::PopupFocusReason);
});
}
@ -617,8 +628,12 @@ ChatPage::showQuickSwitcher()
QMap<QString, QString> rooms;
for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it)
rooms.insert(it.value().getName(), it.key());
for (auto it = state_manager_.constBegin(); it != state_manager_.constEnd(); ++it) {
QString deambiguator = it.value().canonical_alias.content().alias();
if (deambiguator == "")
deambiguator = it.key();
rooms.insert(it.value().getName() + " (" + deambiguator + ")", it.key());
}
quickSwitcher_->setRoomList(rooms);
quickSwitcherModal_->fadeIn();
@ -662,13 +677,20 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_id
{
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.sort();
if (current_room_ == roomid)
if (current_room_ == roomid) {
typingDisplay_->setUsers(users);
}
typingUsers_.insert(roomid, users);
}

View File

@ -480,8 +480,7 @@ const QList<Emoji> EmojiProvider::food = {
Emoji{QString::fromUtf8("\xf0\x9f\xa5\x84"), ":spoon:"},
};
const QList<Emoji> EmojiProvider::activity =
{
const QList<Emoji> EmojiProvider::activity = {
Emoji{QString::fromUtf8("\xf0\x9f\x91\xbe"), ":space_invader:"},
Emoji{QString::fromUtf8("\xf0\x9f\x95\xb4"), ":levitate:"},
Emoji{QString::fromUtf8("\xf0\x9f\xa4\xba"), ":fencer:"},

View File

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

View File

@ -119,7 +119,6 @@ MatrixClient::login(const QString &username, const QString &password) noexcept
}
});
}
void
MatrixClient::logout() noexcept
{
@ -260,9 +259,11 @@ MatrixClient::sync() noexcept
}
void
MatrixClient::sendRoomMessage(matrix::events::MessageEventType ty,
MatrixClient::sendRoomMessage(mtx::events::MessageType ty,
int txnId,
const QString &roomid,
const QString &msg,
const QFileInfo &fileinfo,
const QString &url) noexcept
{
QUrlQuery query;
@ -940,3 +941,53 @@ MatrixClient::leaveRoom(const QString &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(); });
connect(roomSearch_, &QLineEdit::returnPressed, this, [=]() {
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();
});

View File

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

View File

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

View File

@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QAbstractTextDocumentLayout>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
@ -25,20 +26,144 @@
#include "Config.h"
#include "TextInputWidget.h"
static constexpr size_t INPUT_HISTORY_SIZE = 127;
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);
typingTimer_ = new QTimer(this);
typingTimer_->setInterval(1000);
typingTimer_->setSingleShot(true);
connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping);
}
void
FilteredTextEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
emit enterPressed();
else
const bool isModifier = (event->modifiers() != Qt::NoModifier);
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);
}
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)
: QFrame(parent)
@ -97,13 +222,18 @@ TextInputWidget::TextInputWidget(QWidget *parent)
setLayout(topLayout_);
connect(sendMessageBtn_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked()));
connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit);
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_,
SIGNAL(emojiSelected(const QString &)),
this,
SLOT(addSelectedEmoji(const QString &)));
connect(input_, &FilteredTextEdit::startedTyping, this, &TextInputWidget::startedTyping);
connect(input_, &FilteredTextEdit::stoppedTyping, this, &TextInputWidget::stoppedTyping);
}
void
@ -131,50 +261,13 @@ TextInputWidget::addSelectedEmoji(const QString &emoji)
}
void
TextInputWidget::onSendButtonClicked()
TextInputWidget::command(QString command, QString args)
{
auto msgText = input_->document()->toPlainText().trimmed();
if (msgText.isEmpty())
return;
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);
if (command == "me") {
sendEmoteMessage(args);
} else if (command == "join") {
sendJoinRoomRequest(args);
}
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
@ -226,6 +319,16 @@ TextInputWidget::hideUploadSpinner()
spinner_->stop();
}
TextInputWidget::~TextInputWidget()
TextInputWidget::~TextInputWidget() {}
void
TextInputWidget::stopTyping()
{
input_->stopTyping();
}
void
TextInputWidget::focusInEvent(QFocusEvent *event)
{
input_->setFocus(event->reason());
}

View File

@ -88,12 +88,12 @@ TimelineItem::TimelineItem(events::MessageEventType ty,
descriptionMsg_ = {"", userid, body, descriptiveTime(timestamp)};
} else {
descriptionMsg_ = {
"You: ", userid, body, descriptiveTime(QDateTime::currentDateTime())
};
"You: ", userid, body, descriptiveTime(QDateTime::currentDateTime())};
}
body = body.toHtmlEscaped();
body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
generateTimestamp(timestamp);
if (withSender) {
@ -204,6 +204,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
generateTimestamp(timestamp);
body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
body = "<i style=\"color: #565E5E\">" + body + "</i>";
if (with_sender) {
@ -246,6 +247,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
generateTimestamp(timestamp);
emoteMsg = emoteMsg.toHtmlEscaped();
emoteMsg.replace(URL_REGEX, URL_HTML);
emoteMsg.replace("\n", "<br/>");
if (with_sender) {
generateBody(displayName, emoteMsg);
@ -285,6 +287,7 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
body = body.toHtmlEscaped();
body.replace(URL_REGEX, URL_HTML);
body.replace("\n", "<br/>");
if (with_sender) {
generateBody(displayName, body);

View File

@ -17,6 +17,7 @@
#include <QApplication>
#include <QDebug>
#include <QFileInfo>
#include <QSettings>
#include <QTimer>
@ -179,6 +180,10 @@ TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msg
isTimelineFinished = false;
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
// name.
auto ii = msgs.chunk().size();
@ -241,9 +246,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[text.eventId()] = true;
if (isPendingMessage(
text.eventId(), text.content().body(), text.sender(), local_user_)) {
removePendingMessage(text.eventId(), text.content().body());
QString txnid = text.unsignedData().transactionId();
if (!txnid.isEmpty() &&
isPendingMessage(txnid, text.sender(), local_user_)) {
removePendingMessage(txnid);
return nullptr;
}
@ -287,9 +293,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[img.eventId()] = true;
if (isPendingMessage(
img.eventId(), img.msgContent().url(), img.sender(), local_user_)) {
removePendingMessage(img.eventId(), img.msgContent().url());
QString txnid = img.unsignedData().transactionId();
if (!txnid.isEmpty() &&
isPendingMessage(txnid, img.sender(), local_user_)) {
removePendingMessage(txnid);
return nullptr;
}
@ -313,11 +320,10 @@ TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection dire
eventIds_[emote.eventId()] = true;
if (isPendingMessage(emote.eventId(),
emote.content().body(),
emote.sender(),
local_user_)) {
removePendingMessage(emote.eventId(), emote.content().body());
QString txnid = emote.unsignedData().transactionId();
if (!txnid.isEmpty() &&
isPendingMessage(txnid, emote.sender(), local_user_)) {
removePendingMessage(txnid);
return nullptr;
}
@ -495,16 +501,16 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
void
TimelineView::updatePendingMessage(int txn_id, QString event_id)
{
for (auto &msg : pending_msgs_) {
if (msg.txn_id == txn_id) {
if (pending_msgs_.head().txn_id == txn_id) { // We haven't received it yet
auto msg = pending_msgs_.dequeue();
msg.event_id = event_id;
break;
}
pending_sent_msgs_.append(msg);
}
sendNextPendingMessage();
}
void
TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body, int txn_id)
TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString &body)
{
QSettings settings;
auto user_id = settings.value("auth/user_id").toString();
@ -519,12 +525,13 @@ TimelineView::addUserMessage(matrix::events::MessageEventType ty, const QString
lastSender_ = user_id;
PendingMessage message(txn_id, body, "", view_item);
pending_msgs_.push_back(message);
int txn_id = client_->incrementTransactionId();
PendingMessage message(ty, txn_id, body, "", "", view_item);
handleNewUserMessage(message);
}
void
TimelineView::addUserMessage(const QString &url, const QString &filename, int txn_id)
TimelineView::addUserMessage(const QString &url, const QString &filename)
{
QSettings settings;
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;
PendingMessage message(txn_id, url, "", view_item);
pending_msgs_.push_back(message);
int txn_id = client_->incrementTransactionId();
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
@ -558,8 +593,7 @@ TimelineView::notifyForLastEvent()
}
bool
TimelineView::isPendingMessage(const QString &eventid,
const QString &body,
TimelineView::isPendingMessage(const QString &txnid,
const QString &sender,
const QString &local_userid)
{
@ -567,7 +601,12 @@ TimelineView::isPendingMessage(const QString &eventid,
return false;
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;
}
@ -575,14 +614,28 @@ TimelineView::isPendingMessage(const QString &eventid,
}
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) {
if (QString::number(it->txn_id) == txnid) {
int index = std::distance(pending_msgs_.begin(), it);
if (it->event_id == eventid || it->body == body) {
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)
, client_(client)
{
setStyleSheet("QWidget { background: #fff; color: #e8e8e8; border: none;}");
setStyleSheet("border: none;");
connect(
client_.data(), &MatrixClient::messageSent, this, &TimelineViewManager::messageSent);
connect(client_.data(),
&MatrixClient::messageSendFailed,
this,
&TimelineViewManager::messageSendFailed);
}
TimelineViewManager::~TimelineViewManager()
@ -53,28 +58,32 @@ TimelineViewManager::messageSent(const QString &event_id, const QString &roomid,
}
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 view = views_[room_id];
view->addUserMessage(matrix::events::MessageEventType::Text, msg, client_->transactionId());
client_->sendRoomMessage(matrix::events::MessageEventType::Text, room_id, msg);
view->addUserMessage(matrix::events::MessageEventType::Text, msg);
}
void
TimelineViewManager::sendEmoteMessage(const QString &msg)
TimelineViewManager::queueEmoteMessage(const QString &msg)
{
auto room_id = active_room_;
auto view = views_[room_id];
view->addUserMessage(
matrix::events::MessageEventType::Emote, msg, client_->transactionId());
client_->sendRoomMessage(matrix::events::MessageEventType::Emote, room_id, msg);
view->addUserMessage(matrix::events::MessageEventType::Emote, msg);
}
void
TimelineViewManager::sendImageMessage(const QString &roomid,
TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &filename,
const QString &url)
{
@ -85,9 +94,7 @@ TimelineViewManager::sendImageMessage(const QString &roomid,
auto view = views_[roomid];
view->addUserMessage(url, filename, client_->transactionId());
client_->sendRoomMessage(
matrix::events::MessageEventType::Image, roomid, QFileInfo(filename).fileName(), url);
view->addUserMessage(url, filename);
}
void

View File

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

View File

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

View File

@ -33,9 +33,7 @@ UserInfoWidget::UserInfoWidget(QWidget *parent)
, logoutDialog_{nullptr}
, logoutButtonSize_{20}
{
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
setSizePolicy(sizePolicy);
setMinimumSize(QSize(0, 65));
setFixedHeight(65);
topLayout_ = new QHBoxLayout(this);
topLayout_->setSpacing(0);
@ -142,6 +140,8 @@ UserInfoWidget::resizeEvent(QResizeEvent *event)
displayNameLabel_->show();
userIdLabel_->show();
}
QWidget::resizeEvent(event);
}
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;
}
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 <QFontDatabase>
#include <QLibraryInfo>
#include <QNetworkProxy>
#include <QSettings>
#include <QTranslator>
#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
main(int argc, char *argv[])
{
@ -62,6 +89,8 @@ main(int argc, char *argv[])
appTranslator.load("nheko_" + lang, ":/translations");
app.installTranslator(&appTranslator);
setupProxy();
MainWindow w;
// Move the MainWindow to the center

View File

@ -58,11 +58,17 @@ Theme::setColor(const QString &key, ui::Color color)
static const QColor palette[] = {
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),
};

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)
{
// NameEventContent
auto data = QJsonObject{ { "content", QJsonObject{ { "name", "Room Name" } } },
{ "type", "m.room.name" } };
auto data =
QJsonObject{{"content", QJsonObject{{"name", "Room Name"}}}, {"type", "m.room.name"}};
Event<NameEventContent> name_event;
name_event.deserialize(data);
@ -32,41 +32,51 @@ TEST(BaseEvent, Deserialization)
// TopicEventContent
data = QJsonObject{{"content", QJsonObject{{"topic", "Room Topic"}}},
{"unsigned", QJsonObject{{"age", 22}, {"transaction_id", "randomid"}}},
{"type", "m.room.topic"}};
Event<TopicEventContent> topic_event;
topic_event.deserialize(data);
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);
// AvatarEventContent
data = QJsonObject{ { "content", QJsonObject{ { "url", "https://matrix.org" } } },
data = QJsonObject{
{"content", QJsonObject{{"url", "https://matrix.org"}}},
{"unsigned", QJsonObject{{"age", 1343434343}, {"transaction_id", "m33434.33"}}},
{"type", "m.room.avatar"}};
Event<AvatarEventContent> avatar_event;
avatar_event.deserialize(data);
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);
// AliasesEventContent
data =
QJsonObject{ { "content",
QJsonObject{
{ "aliases", QJsonArray{ "#test:matrix.org", "#test2:matrix.org" } } } },
data = QJsonObject{
{"content",
QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}}},
{"unsigned", QJsonObject{{"transaction_id", "m33434.33"}}},
{"type", "m.room.aliases"}};
Event<AliasesEventContent> aliases_event;
aliases_event.deserialize(data);
EXPECT_EQ(aliases_event.content().aliases().size(), 2);
EXPECT_EQ(aliases_event.unsignedData().transactionId(), "m33434.33");
EXPECT_EQ(aliases_event.serialize(), data);
// CreateEventContent
data = QJsonObject{{"content", QJsonObject{{"creator", "@alice:matrix.org"}}},
{"unsigned", QJsonObject{{"age", 2233}}},
{"type", "m.room.create"}};
Event<CreateEventContent> create_event;
create_event.deserialize(data);
EXPECT_EQ(create_event.content().creator(), "@alice:matrix.org");
EXPECT_EQ(create_event.unsignedData().age(), 2233);
EXPECT_EQ(create_event.serialize(), data);
// JoinRulesEventContent
@ -81,8 +91,8 @@ TEST(BaseEvent, Deserialization)
TEST(BaseEvent, DeserializationException)
{
auto data = QJsonObject{ { "content", QJsonObject{ { "rule", "private" } } },
{ "type", "m.room.join_rules" } };
auto data =
QJsonObject{{"content", QJsonObject{{"rule", "private"}}}, {"type", "m.room.join_rules"}};
Event<JoinRulesEventContent> event1;
ASSERT_THROW(event1.deserialize(data), DeserializationException);
@ -92,6 +102,13 @@ TEST(BaseEvent, DeserializationException)
Event<JoinRulesEventContent> event2;
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)
@ -178,33 +195,28 @@ TEST(EventType, Mapping)
{
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.aliases"}}),
EventType::RoomAliases);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.avatar" } }),
EventType::RoomAvatar);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.avatar"}}), EventType::RoomAvatar);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.canonical_alias"}}),
EventType::RoomCanonicalAlias);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.create" } }),
EventType::RoomCreate);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.create"}}), EventType::RoomCreate);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.history_visibility"}}),
EventType::RoomHistoryVisibility);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.join_rules"}}),
EventType::RoomJoinRules);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.member" } }),
EventType::RoomMember);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.member"}}), EventType::RoomMember);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.message"}}),
EventType::RoomMessage);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.name"}}), EventType::RoomName);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.power_levels"}}),
EventType::RoomPowerLevels);
EXPECT_EQ(extractEventType(QJsonObject{ { "type", "m.room.topic" } }),
EventType::RoomTopic);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.topic"}}), EventType::RoomTopic);
EXPECT_EQ(extractEventType(QJsonObject{{"type", "m.room.unknown"}}),
EventType::Unsupported);
}
TEST(AliasesEventContent, Deserialization)
{
auto data =
QJsonObject{ { "aliases", QJsonArray{ "#test:matrix.org", "#test2:matrix.org" } } };
auto data = QJsonObject{{"aliases", QJsonArray{"#test:matrix.org", "#test2:matrix.org"}}};
AliasesEventContent content;
content.deserialize(data);