├── .craft.ini ├── .flatpak-manifest.json ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab └── issue_templates │ └── bug.md ├── .kde-ci.yml ├── 128-logo.png ├── CMakeLists.txt ├── LICENSES ├── BSD-2-Clause.txt ├── BSD-3-Clause.txt ├── CC-BY-SA-4.0.txt ├── CC0-1.0.txt ├── GPL-2.0-only.txt ├── GPL-2.0-or-later.txt ├── GPL-3.0-only.txt ├── GPL-3.0-or-later.txt ├── LGPL-2.0-or-later.txt ├── LGPL-2.1-only.txt ├── LGPL-2.1-or-later.txt ├── LGPL-3.0-only.txt ├── LicenseRef-KDE-Accepted-GPL.txt ├── LicenseRef-KDE-Accepted-LGPL.txt └── MIT.txt ├── Messages.sh ├── README.md ├── REUSE.toml ├── android ├── AndroidManifest.xml ├── build.gradle ├── neochat-playstore.png └── res │ └── drawable │ ├── neochat.png │ └── splash.xml ├── appiumtests ├── CMakeLists.txt ├── createroomtest.py ├── data │ ├── sync_response_new_room.json │ ├── sync_response_no_rooms.json │ └── sync_response_rooms.json ├── login-server.py ├── logintest.py └── openuserdetailstest.py ├── autotests ├── CMakeLists.txt ├── actionstest.cpp ├── chatbarcachetest.cpp ├── chatdocumenthandlertest.cpp ├── data │ ├── localhost.crt │ ├── localhost.key │ ├── test-eventhandler-sync.json │ ├── test-linkpreviewerintial-sync.json │ ├── test-linkpreviewerreplace-sync.json │ ├── test-messageventmodel-sync.json │ ├── test-min-sync.json │ ├── test-multiplelink-event.json │ ├── test-pending-sync.json │ ├── test-pollhandlerstart-sync.json │ ├── test-reactionmodel-extra-sync.json │ ├── test-reactionmodel-sync.json │ └── test-texthandler-sync.json ├── delegatesizehelpertest.cpp ├── eventhandlertest.cpp ├── linkpreviewertest.cpp ├── mediasizehelpertest.cpp ├── messagecontentmodeltest.cpp ├── neochatroomtest.cpp ├── pollhandlertest.cpp ├── reactionmodeltest.cpp ├── server.cpp ├── server.h ├── testutils.h ├── texthandlertest.cpp ├── timelinemessagemodeltest.cpp └── windowcontrollertest.cpp ├── cmake ├── Findcmark.cmake ├── Flatpak.cmake └── Flatpak │ └── 99-noto-mono-color-emoji.conf ├── doc ├── CMakeLists.txt └── man-neochat.1.docbook ├── icons ├── 150-apps-neochat.png ├── 300-apps-neochat.png ├── 44-apps-neochat.png ├── hicolor │ ├── 128-apps-org.kde.neochat.png │ ├── 256-apps-org.kde.neochat.png │ ├── 32-apps-org.kde.neochat.png │ └── 512-apps-org.kde.neochat.png ├── icon.icns ├── icon.ico └── windows │ ├── promoimage-1920x1080.png │ ├── storelogo-1080x1080.png │ └── storelogo-720x1080.png ├── logo.png ├── memorytests ├── CMakeLists.txt ├── Main.qml ├── main.cpp ├── memtest-sync.json ├── memtesttimelinemodel.cpp └── memtesttimelinemodel.h ├── org.kde.neochat.appdata.xml ├── org.kde.neochat.desktop ├── org.kde.neochat.svg ├── org.kde.neochat.tray.svg ├── po ├── ar │ └── neochat.po ├── ast │ └── neochat.po ├── az │ └── neochat.po ├── ca │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── ca@valencia │ └── neochat.po ├── cs │ └── neochat.po ├── da │ └── neochat.po ├── de │ └── neochat.po ├── el │ └── neochat.po ├── en_GB │ └── neochat.po ├── eo │ └── neochat.po ├── es │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── eu │ └── neochat.po ├── fi │ └── neochat.po ├── fr │ └── neochat.po ├── gl │ └── neochat.po ├── he │ └── neochat.po ├── hi │ └── neochat.po ├── hu │ └── neochat.po ├── ia │ └── neochat.po ├── id │ └── neochat.po ├── ie │ └── neochat.po ├── it │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── ja │ └── neochat.po ├── ka │ └── neochat.po ├── ko │ └── neochat.po ├── lt │ └── neochat.po ├── lv │ └── neochat.po ├── nl │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── nn │ └── neochat.po ├── pa │ └── neochat.po ├── pl │ └── neochat.po ├── pt │ └── neochat.po ├── pt_BR │ └── neochat.po ├── ru │ └── neochat.po ├── sa │ └── neochat.po ├── sk │ └── neochat.po ├── sl │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── sv │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── ta │ └── neochat.po ├── tok │ └── neochat.po ├── tr │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── uk │ ├── docs │ │ └── neochat │ │ │ └── man-neochat.1.docbook │ └── neochat.po ├── zh_CN │ └── neochat.po └── zh_TW │ └── neochat.po ├── snapcraft.yaml ├── src ├── CMakeLists.txt ├── app │ ├── CMakeLists.txt │ ├── blurhash.cpp │ ├── blurhash.h │ ├── blurhashimageprovider.cpp │ ├── blurhashimageprovider.h │ ├── controller.cpp │ ├── controller.h │ ├── fakerunner.cpp │ ├── fakerunner.h │ ├── foreigntypes.h │ ├── identityserverhelper.cpp │ ├── identityserverhelper.h │ ├── logger.cpp │ ├── logger.h │ ├── main.cpp │ ├── mediamanager.cpp │ ├── mediamanager.h │ ├── models │ │ ├── commonroomsmodel.cpp │ │ ├── commonroomsmodel.h │ │ ├── notificationsmodel.cpp │ │ ├── notificationsmodel.h │ │ ├── serverlistmodel.cpp │ │ ├── serverlistmodel.h │ │ ├── userdirectorylistmodel.cpp │ │ └── userdirectorylistmodel.h │ ├── neochat.notifyrc │ ├── neochatconfig.kcfg │ ├── notificationsmanager.cpp │ ├── notificationsmanager.h │ ├── notifyrc.qrc │ ├── org.kde.neochat.service.in │ ├── plasma-runner-neochat.desktop │ ├── proxycontroller.cpp │ ├── proxycontroller.h │ ├── qml │ │ ├── AccountMenu.qml │ │ ├── AccountSwitchDialog.qml │ │ ├── AskDirectChatConfirmation.qml │ │ ├── AttachmentPane.qml │ │ ├── AvatarNotification.qml │ │ ├── AvatarTabButton.qml │ │ ├── ChooseRoomDialog.qml │ │ ├── CodeMaximizeComponent.qml │ │ ├── CollapsedRoomDelegate.qml │ │ ├── ConfirmLeaveDialog.qml │ │ ├── ConfirmLogoutDialog.qml │ │ ├── ConfirmUrlDialog.qml │ │ ├── ConsentDialog.qml │ │ ├── EditMenu.qml │ │ ├── EditStateDialog.qml │ │ ├── EmojiItem.qml │ │ ├── EmojiRow.qml │ │ ├── EmojiSas.qml │ │ ├── ExplorerDelegate.qml │ │ ├── FullScreenMap.qml │ │ ├── GlobalMenu.qml │ │ ├── GlobalMenuStub.qml │ │ ├── HoverLinkIndicator.qml │ │ ├── ImageEditorPage.qml │ │ ├── InvitationView.qml │ │ ├── JoinRoomDialog.qml │ │ ├── KeyVerificationDialog.qml │ │ ├── LocationChooser.qml │ │ ├── Main.qml │ │ ├── ManualRoomDialog.qml │ │ ├── ManualUserDialog.qml │ │ ├── MessageSourceSheet.qml │ │ ├── NeochatMaximizeComponent.qml │ │ ├── NewPollDialog.qml │ │ ├── NotificationsView.qml │ │ ├── OpenFileDialog.qml │ │ ├── OsmLocationPlugin.qml │ │ ├── QrCodeMaximizeComponent.qml │ │ ├── QrScannerPage.qml │ │ ├── QuickFormatBar.qml │ │ ├── QuickSwitcher.qml │ │ ├── ReasonDialog.qml │ │ ├── RecommendedSpaceDialog.qml │ │ ├── RoomPage.qml │ │ ├── ServerComboBox.qml │ │ ├── ShareAction.qml │ │ ├── ShareActionStub.qml │ │ ├── ShareDialog.qml │ │ ├── TypingPane.qml │ │ ├── UnlockSSSSDialog.qml │ │ ├── UserDetailDialog.qml │ │ ├── UserMenu.qml │ │ ├── UserSearchPage.qml │ │ ├── VerificationCanceled.qml │ │ └── VerificationMessage.qml │ ├── roommanager.cpp │ ├── roommanager.h │ ├── runner.cpp │ ├── runner.h │ ├── sharehandler.cpp │ ├── sharehandler.h │ ├── texttospeechhelper.cpp │ ├── texttospeechhelper.h │ ├── trayicon.cpp │ ├── trayicon.h │ ├── trayicon_sni.cpp │ ├── trayicon_sni.h │ ├── windowcontroller.cpp │ └── windowcontroller.h ├── chatbar │ ├── AttachDialog.qml │ ├── CMakeLists.txt │ ├── ChatBar.qml │ ├── CompletionMenu.qml │ ├── EmojiDelegate.qml │ ├── EmojiDialog.qml │ ├── EmojiGrid.qml │ ├── EmojiPicker.qml │ ├── EmojiTonesPicker.qml │ └── PieProgressBar.qml ├── devtools │ ├── AccountData.qml │ ├── CMakeLists.txt │ ├── DebugOptions.qml │ ├── DevtoolsPage.qml │ ├── FeatureFlagPage.qml │ ├── RoomData.qml │ ├── ServerData.qml │ ├── StateKeys.qml │ └── models │ │ ├── statefiltermodel.cpp │ │ ├── statefiltermodel.h │ │ ├── statekeysmodel.cpp │ │ ├── statekeysmodel.h │ │ ├── statemodel.cpp │ │ └── statemodel.h ├── libneochat │ ├── CMakeLists.txt │ ├── accountmanager.cpp │ ├── accountmanager.h │ ├── chatbarcache.cpp │ ├── chatbarcache.h │ ├── chatdocumenthandler.cpp │ ├── chatdocumenthandler.h │ ├── clipboard.cpp │ ├── clipboard.h │ ├── delegatesizehelper.cpp │ ├── delegatesizehelper.h │ ├── emojis.h │ ├── emojitones.cpp │ ├── emojitones.h │ ├── emojitones_data.h │ ├── enums │ │ ├── messagecomponenttype.h │ │ ├── messagetype.h │ │ ├── neochatroomtype.h │ │ ├── powerlevel.cpp │ │ ├── powerlevel.h │ │ ├── pushrule.h │ │ ├── roomsortorder.h │ │ ├── roomsortparameter.cpp │ │ └── roomsortparameter.h │ ├── eventhandler.cpp │ ├── eventhandler.h │ ├── events │ │ ├── imagepackevent.cpp │ │ ├── imagepackevent.h │ │ ├── locationbeaconevent.h │ │ ├── pollevent.cpp │ │ ├── pollevent.h │ │ └── widgetevent.h │ ├── filetransferpseudojob.cpp │ ├── filetransferpseudojob.h │ ├── filetype.cpp │ ├── filetype.h │ ├── jobs │ │ ├── neochatgetcommonroomsjob.cpp │ │ └── neochatgetcommonroomsjob.h │ ├── linkpreviewer.cpp │ ├── linkpreviewer.h │ ├── messagecomponent.h │ ├── models │ │ ├── actionsmodel.cpp │ │ ├── actionsmodel.h │ │ ├── completionmodel.cpp │ │ ├── completionmodel.h │ │ ├── completionproxymodel.cpp │ │ ├── completionproxymodel.h │ │ ├── customemojimodel.cpp │ │ ├── customemojimodel.h │ │ ├── emojimodel.cpp │ │ ├── emojimodel.h │ │ ├── imagepacksmodel.cpp │ │ ├── imagepacksmodel.h │ │ ├── livelocationsmodel.cpp │ │ ├── livelocationsmodel.h │ │ ├── locationsmodel.cpp │ │ ├── locationsmodel.h │ │ ├── roomlistmodel.cpp │ │ ├── roomlistmodel.h │ │ ├── stickermodel.cpp │ │ ├── stickermodel.h │ │ ├── userfiltermodel.cpp │ │ ├── userfiltermodel.h │ │ ├── userlistmodel.cpp │ │ └── userlistmodel.h │ ├── neochatconnection.cpp │ ├── neochatconnection.h │ ├── neochatroom.cpp │ ├── neochatroom.h │ ├── neochatroommember.cpp │ ├── neochatroommember.h │ ├── qml │ │ ├── CreateRoomDialog.qml │ │ ├── CreateSpaceDialog.qml │ │ ├── ExploreRoomsPage.qml │ │ ├── GroupChatDrawerHeader.qml │ │ ├── InviteUserPage.qml │ │ ├── LocationMapItem.qml │ │ └── SearchPage.qml │ ├── roomlastmessageprovider.cpp │ ├── roomlastmessageprovider.h │ ├── spacehierarchycache.cpp │ ├── spacehierarchycache.h │ ├── texthandler.cpp │ ├── texthandler.h │ ├── urlhelper.cpp │ ├── urlhelper.h │ ├── utils.cpp │ └── utils.h ├── login │ ├── CMakeLists.txt │ ├── Captcha.qml │ ├── Email.qml │ ├── Homeserver.qml │ ├── Loading.qml │ ├── Login.qml │ ├── LoginMethod.qml │ ├── LoginRegister.qml │ ├── LoginStep.qml │ ├── Password.qml │ ├── RegisterPassword.qml │ ├── Sso.qml │ ├── Terms.qml │ ├── Username.qml │ ├── WelcomePage.qml │ ├── login.cpp │ ├── login.h │ ├── registration.cpp │ └── registration.h ├── purpose │ ├── CMakeLists.txt │ ├── purposeplugin.cpp │ └── purposeplugin.json ├── roominfo │ ├── CMakeLists.txt │ ├── DirectChatDrawerHeader.qml │ ├── LocationsPage.qml │ ├── RoomDrawer.qml │ ├── RoomDrawerPage.qml │ ├── RoomInformation.qml │ ├── RoomMedia.qml │ ├── RoomPinnedMessagesPage.qml │ └── RoomSearchPage.qml ├── rooms │ ├── CMakeLists.txt │ ├── ExploreComponent.qml │ ├── ExploreComponentMobile.qml │ ├── RoomContextMenu.qml │ ├── RoomDelegate.qml │ ├── RoomListPage.qml │ ├── RoomTreeSection.qml │ ├── SpaceDrawer.qml │ ├── SpaceListContextMenu.qml │ ├── UserInfo.qml │ ├── UserInfoDesktop.qml │ └── models │ │ ├── publicroomlistmodel.cpp │ │ ├── publicroomlistmodel.h │ │ ├── roomtreeitem.cpp │ │ ├── roomtreeitem.h │ │ ├── roomtreemodel.cpp │ │ ├── roomtreemodel.h │ │ ├── sortfilterroomlistmodel.cpp │ │ ├── sortfilterroomlistmodel.h │ │ ├── sortfilterroomtreemodel.cpp │ │ ├── sortfilterroomtreemodel.h │ │ ├── sortfilterspacelistmodel.cpp │ │ └── sortfilterspacelistmodel.h ├── settings │ ├── AccountEditorPage.qml │ ├── AccountsPage.qml │ ├── AppearanceSettingsPage.qml │ ├── CMakeLists.txt │ ├── ColorScheme.qml │ ├── ConfirmDeactivateAccountDialog.qml │ ├── ConfirmEncryptionDialog.qml │ ├── DeviceDelegate.qml │ ├── DevicesCard.qml │ ├── DevicesPage.qml │ ├── EmoticonEditorPage.qml │ ├── EmoticonFormCard.qml │ ├── EmoticonsPage.qml │ ├── ExportKeysDialog.qml │ ├── GlobalNotificationsPage.qml │ ├── IdentityServerDelegate.qml │ ├── IgnoredUsersDialog.qml │ ├── ImportKeysDialog.qml │ ├── NeoChatGeneralPage.qml │ ├── NeoChatSecurityPage.qml │ ├── NeoChatSettingsView.qml │ ├── NetworkProxyPage.qml │ ├── NotificationRuleItem.qml │ ├── PasswordSheet.qml │ ├── Permissions.qml │ ├── PowerLevelDialog.qml │ ├── PushNotification.qml │ ├── RoomAdvancedPage.qml │ ├── RoomGeneralPage.qml │ ├── RoomProfile.qml │ ├── RoomSecurityPage.qml │ ├── RoomSettingsView.qml │ ├── RoomSortParameterDialog.qml │ ├── SelectParentDialog.qml │ ├── SelectSpacesDialog.qml │ ├── ThemeRadioButton.qml │ ├── ThreePIdCard.qml │ ├── colorschemer.cpp │ ├── colorschemer.h │ ├── models │ │ ├── accountemoticonmodel.cpp │ │ ├── accountemoticonmodel.h │ │ ├── devicesmodel.cpp │ │ ├── devicesmodel.h │ │ ├── devicesproxymodel.cpp │ │ ├── devicesproxymodel.h │ │ ├── emoticonfiltermodel.cpp │ │ ├── emoticonfiltermodel.h │ │ ├── permissionsmodel.cpp │ │ ├── permissionsmodel.h │ │ ├── pushrulemodel.cpp │ │ ├── pushrulemodel.h │ │ ├── roomsortparametermodel.cpp │ │ ├── roomsortparametermodel.h │ │ ├── threepidmodel.cpp │ │ └── threepidmodel.h │ ├── threepidaddhelper.cpp │ ├── threepidaddhelper.h │ ├── threepidbindhelper.cpp │ └── threepidbindhelper.h ├── spaces │ ├── CMakeLists.txt │ ├── RemoveChildDialog.qml │ ├── SelectExistingRoomDialog.qml │ ├── SpaceHierarchyDelegate.qml │ ├── SpaceHomePage.qml │ └── models │ │ ├── spacechildrenmodel.cpp │ │ ├── spacechildrenmodel.h │ │ ├── spacechildsortfiltermodel.cpp │ │ ├── spacechildsortfiltermodel.h │ │ ├── spacetreeitem.cpp │ │ └── spacetreeitem.h └── timeline │ ├── AudioComponent.qml │ ├── AuthorComponent.qml │ ├── AvatarFlow.qml │ ├── BaseMessageComponentChooser.qml │ ├── Bubble.qml │ ├── CMakeLists.txt │ ├── ChatBarComponent.qml │ ├── CodeComponent.qml │ ├── DelegateContextMenu.qml │ ├── EncryptedComponent.qml │ ├── EventDelegate.qml │ ├── FetchButtonComponent.qml │ ├── FileComponent.qml │ ├── FileDelegateContextMenu.qml │ ├── FlightReservationComponent.qml │ ├── FoodReservationComponent.qml │ ├── HiddenDelegate.qml │ ├── HotelReservationComponent.qml │ ├── ImageComponent.qml │ ├── ItineraryComponent.qml │ ├── ItineraryReservationComponent.qml │ ├── JourneySectionStopDelegateLineSegment.qml │ ├── LinkPreviewComponent.qml │ ├── LinkPreviewLoadComponent.qml │ ├── LiveLocationComponent.qml │ ├── LoadComponent.qml │ ├── LoadingDelegate.qml │ ├── LocationComponent.qml │ ├── MessageComponentChooser.qml │ ├── MessageDelegate.qml │ ├── MessageDelegateContextMenu.qml │ ├── MimeComponent.qml │ ├── PdfPreviewComponent.qml │ ├── PollComponent.qml │ ├── PredecessorDelegate.qml │ ├── QuickActions.qml │ ├── QuoteComponent.qml │ ├── ReactionComponent.qml │ ├── ReadMarkerDelegate.qml │ ├── ReplyAuthorComponent.qml │ ├── ReplyButtonComponent.qml │ ├── ReplyComponent.qml │ ├── ReplyMessageComponentChooser.qml │ ├── SectionDelegate.qml │ ├── SpacerDelegate.qml │ ├── StateComponent.qml │ ├── StateDelegate.qml │ ├── SuccessorDelegate.qml │ ├── TextComponent.qml │ ├── ThreadBodyComponent.qml │ ├── TimelineEndDelegate.qml │ ├── TimelineView.qml │ ├── TrainReservationComponent.qml │ ├── TransportIcon.qml │ ├── VideoComponent.qml │ ├── config-neochat.h.in │ ├── contentprovider.cpp │ ├── contentprovider.h │ ├── enums │ └── delegatetype.h │ ├── images │ ├── bike.svg │ ├── bike.svg.license │ ├── bus.svg │ ├── bus.svg.license │ ├── cablecar.svg │ ├── cablecar.svg.license │ ├── car.svg │ ├── car.svg.license │ ├── coach.svg │ ├── coach.svg.license │ ├── couchettecar.svg │ ├── couchettecar.svg.license │ ├── elevator.svg │ ├── elevator.svg.license │ ├── escalator.svg │ ├── escalator.svg.license │ ├── ferry.svg │ ├── ferry.svg.license │ ├── flight.svg │ ├── flight.svg.license │ ├── foodestablishment.svg │ ├── foodestablishment.svg.license │ ├── funicular.svg │ ├── funicular.svg.license │ ├── longdistancetrain.svg │ ├── longdistancetrain.svg.license │ ├── rapidtransit.svg │ ├── rapidtransit.svg.license │ ├── seat.svg │ ├── seat.svg.license │ ├── shuttle.svg │ ├── shuttle.svg.license │ ├── sleepingcar.svg │ ├── sleepingcar.svg.license │ ├── stairs.svg │ ├── stairs.svg.license │ ├── subway.svg │ ├── subway.svg.license │ ├── taxi.svg │ ├── taxi.svg.license │ ├── train.svg │ ├── train.svg.license │ ├── tramway.svg │ ├── tramway.svg.license │ ├── transfer.svg │ ├── transfer.svg.license │ ├── wait.svg │ ├── wait.svg.license │ ├── walk.svg │ └── walk.svg.license │ ├── locationhelper.cpp │ ├── locationhelper.h │ ├── mediasizehelper.cpp │ ├── mediasizehelper.h │ ├── messageattached.cpp │ ├── messageattached.h │ ├── messagedelegate.cpp │ ├── messagedelegate.h │ ├── models │ ├── itinerarymodel.cpp │ ├── itinerarymodel.h │ ├── linemodel.cpp │ ├── linemodel.h │ ├── mediamessagefiltermodel.cpp │ ├── mediamessagefiltermodel.h │ ├── messagecontentfiltermodel.cpp │ ├── messagecontentfiltermodel.h │ ├── messagecontentmodel.cpp │ ├── messagecontentmodel.h │ ├── messagefiltermodel.cpp │ ├── messagefiltermodel.h │ ├── messagemodel.cpp │ ├── messagemodel.h │ ├── pinnedmessagemodel.cpp │ ├── pinnedmessagemodel.h │ ├── pollanswermodel.cpp │ ├── pollanswermodel.h │ ├── reactionmodel.cpp │ ├── reactionmodel.h │ ├── readmarkermodel.cpp │ ├── readmarkermodel.h │ ├── searchmodel.cpp │ ├── searchmodel.h │ ├── threadmodel.cpp │ ├── threadmodel.h │ ├── timelinemessagemodel.cpp │ ├── timelinemessagemodel.h │ ├── timelinemodel.cpp │ ├── timelinemodel.h │ ├── webshortcutmodel.cpp │ └── webshortcutmodel.h │ ├── pollhandler.cpp │ ├── pollhandler.h │ ├── timelinedelegate.cpp │ └── timelinedelegate.h └── tools └── update-emojis.py /.craft.ini: -------------------------------------------------------------------------------- 1 | ; SPDX-FileCopyrightText: None 2 | ; SPDX-License-Identifier: CC0-1.0 3 | 4 | [BlueprintSettings] 5 | kde/frameworks/extra-cmake-modules.version=master 6 | kde/unreleased/kirigami-addons.version=master 7 | kde/applications/neochat.packageAppx=True 8 | libs/qt.qtMajorVersion=6 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | .clang-format 3 | .DS_Store 4 | .kdev4/ 5 | neochat.kdev4 6 | compile_commands.json 7 | .cache/ 8 | .vscode/ 9 | kate.project.ctags.* 10 | *.user 11 | .flatpak-builder/ 12 | .idea/ 13 | cmake-build-* 14 | src/res.generated.qrc 15 | .qmlls.ini 16 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: none 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | include: 5 | - project: sysadmin/ci-utilities 6 | file: 7 | - /gitlab-templates/reuse-lint.yml 8 | - /gitlab-templates/json-validation.yml 9 | - /gitlab-templates/xml-lint.yml 10 | - /gitlab-templates/yaml-lint.yml 11 | - /gitlab-templates/android-qt6.yml 12 | - /gitlab-templates/linux-qt6.yml 13 | - /gitlab-templates/linux-qt6-next.yml 14 | - /gitlab-templates/windows-qt6.yml 15 | - /gitlab-templates/freebsd-qt6.yml 16 | - /gitlab-templates/flatpak.yml 17 | - /gitlab-templates/snap-snapcraft-lxd.yml 18 | - /gitlab-templates/craft-android-qt6-apks.yml 19 | - /gitlab-templates/craft-appimage-qt6.yml 20 | - /gitlab-templates/craft-windows-x86-64-qt6.yml 21 | - /gitlab-templates/craft-windows-appx-qt6.yml 22 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/bug.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Description 8 | 9 | (General description of the bug) 10 | 11 | ## Steps to reproduce 12 | 13 | 1. Open app 14 | 2. ... 15 | 3. ... 16 | 17 | ## What is the current bug behavior? 18 | 19 | (What actually happens) 20 | 21 | ## What is the expected correct behavior? 22 | 23 | (What you should see instead) 24 | 25 | ## Relevant logs and/or screenshots 26 | 27 | (Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as 28 | it's very hard to read otherwise.) 29 | 30 | ## Possible fixes 31 | 32 | (If you can, link to the line of code that might be responsible for the problem) 33 | 34 | ## System/Matrix Information 35 | 36 | - **Distribution / Platform:** Ubuntu 20.04, openSUSE, Flatpak, Windows, MacOS, Android, ... 37 | - **Qt Version:** 5.15.2 38 | - **NeoChat version:** 1.2 39 | - **Quotient version:** 0.6.6 40 | - **Matrix server:** matrix.org, kde.org, .... 41 | 42 | /label ~Bug 43 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Tobias Fella 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | Dependencies: 5 | - 'on': ['Linux', 'Android', 'FreeBSD', 'Windows'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kcoreaddons': '@latest-kf6' 9 | 'frameworks/kirigami': '@latest-kf6' 10 | 'frameworks/ki18n': '@latest-kf6' 11 | 'frameworks/kconfig': '@latest-kf6' 12 | 'frameworks/syntax-highlighting': '@latest-kf6' 13 | 'frameworks/kitemmodels': '@latest-kf6' 14 | 'frameworks/kquickcharts': '@latest-kf6' 15 | 'frameworks/knotifications': '@latest-kf6' 16 | 'frameworks/kcolorscheme': '@latest-kf6' 17 | 'frameworks/kiconthemes': '@latest-kf6' 18 | 'libraries/kquickimageeditor': '@latest-kf6' 19 | 'frameworks/sonnet': '@latest-kf6' 20 | 'frameworks/prison': '@latest-kf6' 21 | 'libraries/kirigami-addons': '@latest-kf6' 22 | 'third-party/libquotient': '@latest' 23 | 'third-party/qtkeychain': '@latest' 24 | 'third-party/cmark': '@latest' 25 | 'third-party/qcoro': '@latest' 26 | - 'on': ['Windows', 'Linux', 'FreeBSD'] 27 | 'require': 28 | 'frameworks/qqc2-desktop-style': '@latest-kf6' 29 | 'frameworks/kio': '@latest-kf6' 30 | 'frameworks/kwindowsystem': '@latest-kf6' 31 | 'frameworks/kstatusnotifieritem': '@latest-kf6' 32 | 'frameworks/kcrash': '@latest-kf6' 33 | - 'on': ['Linux', 'FreeBSD'] 34 | 'require': 35 | 'frameworks/kdbusaddons': '@latest-kf6' 36 | 'frameworks/purpose': '@latest-kf6' 37 | 'libraries/kunifiedpush': '@latest-kf6' 38 | 39 | - 'on': ['Linux'] 40 | 'require': 41 | 'sdk/selenium-webdriver-at-spi': '@latest-kf6' 42 | 43 | Options: 44 | per-test-timeout: 90 45 | require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows'] 46 | -------------------------------------------------------------------------------- /128-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/128-logo.png -------------------------------------------------------------------------------- /LICENSES/BSD-2-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 22 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-KDE-Accepted-GPL.txt: -------------------------------------------------------------------------------- 1 | This library is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU General Public License as 3 | published by the Free Software Foundation; either version 3 of 4 | the license or (at your option) at any later version that is 5 | accepted by the membership of KDE e.V. (or its successor 6 | approved by the membership of KDE e.V.), which shall act as a 7 | proxy as defined in Section 14 of version 3 of the license. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-KDE-Accepted-LGPL.txt: -------------------------------------------------------------------------------- 1 | This library is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU Lesser General Public 3 | License as published by the Free Software Foundation; either 4 | version 3 of the license or (at your option) any later version 5 | that is accepted by the membership of KDE e.V. (or its successor 6 | approved by the membership of KDE e.V.), which shall act as a 7 | proxy as defined in Section 6 of version 3 of the license. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # SPDX-FileCopyrightText: None 3 | # SPDX-License-Identifier: CC0-1.0 4 | $XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot 5 | -------------------------------------------------------------------------------- /android/neochat-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/android/neochat-playstore.png -------------------------------------------------------------------------------- /android/res/drawable/neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/android/res/drawable/neochat.png -------------------------------------------------------------------------------- /android/res/drawable/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /appiumtests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | # SPDX-FileCopyrightText: 2022 Harald Sitter 3 | 4 | 5 | if(NOT BUILD_TESTING OR NOT CMAKE_SYSTEM_NAME MATCHES "Linux") 6 | return() 7 | endif() 8 | 9 | find_package(SeleniumWebDriverATSPI) 10 | set_package_properties(SeleniumWebDriverATSPI PROPERTIES 11 | DESCRIPTION "Server component for selenium tests using Linux accessibility infrastructure" 12 | PURPOSE "Needed for GUI tests" 13 | URL "https://invent.kde.org/sdk/selenium-webdriver-at-spi" 14 | TYPE OPTIONAL 15 | ) 16 | if(NOT SeleniumWebDriverATSPI_FOUND) 17 | return() 18 | endif() 19 | 20 | add_test( 21 | NAME logintest 22 | COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py 23 | ) 24 | 25 | add_test( 26 | NAME openuserdetailstest 27 | COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/openuserdetailstest.py 28 | ) 29 | -------------------------------------------------------------------------------- /appiumtests/data/sync_response_no_rooms.json: -------------------------------------------------------------------------------- 1 | { 2 | "next_batch": "batch1234" 3 | } 4 | -------------------------------------------------------------------------------- /autotests/chatdocumenthandlertest.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include 5 | #include 6 | 7 | #include "chatdocumenthandler.h" 8 | #include "neochatconfig.h" 9 | 10 | class ChatDocumentHandlerTest : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | private: 15 | ChatDocumentHandler emptyHandler; 16 | 17 | private Q_SLOTS: 18 | void initTestCase(); 19 | 20 | void nullComplete(); 21 | }; 22 | 23 | void ChatDocumentHandlerTest::initTestCase() 24 | { 25 | // HACK: this is to stop KStatusNotifierItem SEGFAULTING on cleanup. 26 | NeoChatConfig::self()->setSystemTray(false); 27 | } 28 | 29 | void ChatDocumentHandlerTest::nullComplete() 30 | { 31 | QTest::ignoreMessage(QtWarningMsg, "complete called with m_document set to nullptr."); 32 | emptyHandler.complete(0); 33 | } 34 | 35 | QTEST_MAIN(ChatDocumentHandlerTest) 36 | #include "chatdocumenthandlertest.moc" 37 | -------------------------------------------------------------------------------- /autotests/data/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDNTCCAh2gAwIBAgIUXbyWfTfcvVLrVB1qx36pW/7IkwMwDQYJKoZIhvcNAQEL 3 | BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE 4 | CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDEyMjQxNTAxMDNaGA8yNTcyMDcy 5 | NDE1MDEwM1owQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc 6 | MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD 7 | ggEPADCCAQoCggEBAKlxZ540TQ1uUDAR7ZJ9ue0PzcD2dPmblIIddyekvZS59V7X 8 | drhamclXpHE2EelR87Sexst0BaHH/jmrHwxCtwbeXHZ8ueJHkGHJ5DLZCCiwfG+Q 9 | gml7wlSXxXz37vie2tdlZh2yJSM8yvLAYceHb2zOskaGvul7ZITIS0JrPc3o6VZk 10 | +MYGkYtA2JfUsv3jH4oQbxOf7RXqhWNAXbB+3hlwRBwMIdyoBNK6YS9QSrTeS9jj 11 | UqgO5QmaQZOVvpaPf1Y/rHHLd2Qa6+a/cCJ1sr2biagb75AihpQFsK/oy6D1PP70 12 | zTe7hPWn/efEpmtCV7CQ8ti4cRu0Kjy0T8grtCsCAwEAAaMhMB8wHQYDVR0OBBYE 13 | FIFlylzwADNLfgTDNkhFeFelaEDxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ2rw4GLIU 14 | v+GY7Qru9LttkrQPd2bZXKxDMd/jT+wjmMVtqS4MAsCuDYwaYLjU1aWyqy0mN+lY 15 | A17kD0VjBNBy45sYqkZveY0ks8mCScBemtrIDmjz2tiueecBIEASwEPBOZgv5/MV 16 | cz864FiChF+2r8Zl8bhycGy9DEpRjzYKvIQWSDHQ3zpuh3iBnjfoieLHWX2kKCpk 17 | ouS3V6485rHNCWsZT5IcCwfBFQkOuWRJpIazpz4AfwZh1TK9+bgiKA5EyZjSNrKw 18 | xGQSpMSTRQMB0/FOCL/AixhN9unVFUViqUcdtSfoHE1VyBHv9kDT/cYms/Xl4B0t 19 | /ZSQJ/D/Km1+ 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /autotests/data/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCpcWeeNE0NblAw 3 | Ee2SfbntD83A9nT5m5SCHXcnpL2UufVe13a4WpnJV6RxNhHpUfO0nsbLdAWhx/45 4 | qx8MQrcG3lx2fLniR5BhyeQy2QgosHxvkIJpe8JUl8V89+74ntrXZWYdsiUjPMry 5 | wGHHh29szrJGhr7pe2SEyEtCaz3N6OlWZPjGBpGLQNiX1LL94x+KEG8Tn+0V6oVj 6 | QF2wft4ZcEQcDCHcqATSumEvUEq03kvY41KoDuUJmkGTlb6Wj39WP6xxy3dkGuvm 7 | v3AidbK9m4moG++QIoaUBbCv6Mug9Tz+9M03u4T1p/3nxKZrQlewkPLYuHEbtCo8 8 | tE/IK7QrAgMBAAECggEAH9qmeKrra2F4KLlOGNKS//qPGz4Z+ozhi95/NpA1Zb7Z 9 | 3pUSCBFcROo5i2D3WA4kiymoRLpQjrv60puVcCggoWVvK4VCKsR6Y6/hOx/q9T9M 10 | fWrE4ZC3FVEc+uPfZJT0nja9TkrdyXSV0LITD8Ap1eI7yJ9vR5R/bqj64QcpLMrU 11 | QeoQIy1oTMR+qdjj33duyRwBZU3Yf8FRB2iW6OILZ8hzFo1jngec7dph9a1RK4e0 12 | mEPdc9ywsKlDM7P0Y7zdmjar5XtQn87GiwNhz23f1fzCC2axLtOW0Xm4e4Qumehb 13 | WrIi6Vfq8IWMglU7QrBJ7iR0Ls+XoKA5GxomV2IJZQKBgQDoIkOl5YGPQ3iGR+WK 14 | e5/2Ml4G/uURzYiOlzSsyfoPXyO4EI2BJd5HkH+EvfgRx4xKkxUZRJdzR7llYPl8 15 | BFYcFitvhO8SbD0mNAB5YW7f+3v1pgEN2umzoKd389Zx5WqTZ7YB1VG5RN/Q1JJL 16 | 2JM0Xgamq2vNtx3roRPxDBeW7QKBgQC63R/bmACJbgIzfaVBX4Zie3NQG0/Hf+gF 17 | LnBwUmQDZOR7MY+kSiIUVMn3NuZRiCSCFBVwApruyK8r535JCibTVm5PWjvhFddY 18 | LgaPOCKGlm9TLScjoH1pErYgG3uJ4nXeRfXhg4mco6EkrC7RzQywrd0VDoqpuc1Y 19 | EKfEsYk8dwKBgE+mSh3nNOBKX1V73+f3aTiZqaeu2DyWkG+UtE9BclrJ40Cp9VPG 20 | AZH+o7KRWEgJdzqzYv7riSfWCWgesRv7hOxYMwktzLY+i3DLUQpVAy05ZhwwnJX7 21 | ckrfKfc/pGoqNLplUI8qecMfPciy14vMwR2r0Y5orTHFzi9mcqg35PQ1AoGAW2LX 22 | OLq+0HdHhk0Va8I+450CSRQCUUvhed87SANTPEG0Z/dWC3/h6NWKrGdh/k+5oxAV 23 | Z+EuSkdFPBCLt0bKtCKZ8h7sF+lplotz08kdQXsC2MfFU2wiySdIgK1QHp/tCxZl 24 | 6LM+sqdnoJrAjwRcB3AQJkMlV1ox7ba/hbdZqYMCgYBS6+JUXSSASpm5ZHd32a8m 25 | xwryEZ7H6Hek6lvMHdxmwoKat5dCavxw64nrtyeeGZpg1W3zLLyamF9x/8kMyr6y 26 | KKvtBfJ5sCvAbt80o9Pbs6R3yDB3AKiD3s3PQK7lol1nhE/8IbsF2r8JEQVcYd/k 27 | oBzkl7MrMyLhhaCqSxwqQQ== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /autotests/data/test-linkpreviewerintial-sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeline": { 3 | "events": [ 4 | { 5 | "content": { 6 | "body": "https://kde.org", 7 | "format": "org.matrix.custom.html", 8 | "formatted_body": "https://kde.org", 9 | "msgtype": "m.text" 10 | }, 11 | "origin_server_ts": 1704648567967, 12 | "sender": "@example:example.org", 13 | "type": "m.room.message", 14 | "unsigned": { 15 | "age": 112 16 | }, 17 | "event_id": "$validlink:example.org", 18 | "room_id": "!test:example.org" 19 | } 20 | ], 21 | "limited": true, 22 | "prev_batch": "t34-23535_0_0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /autotests/data/test-linkpreviewerreplace-sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeline": { 3 | "events": [ 4 | { 5 | "content": { 6 | "body": "* ", 7 | "format": "org.matrix.custom.html", 8 | "formatted_body": "no link", 9 | "m.new_content": { 10 | "body": "", 11 | "format": "org.matrix.custom.html", 12 | "formatted_body": "no link", 13 | "msgtype": "m.text" 14 | }, 15 | "m.relates_to": { 16 | "event_id": "$validlink:example.org", 17 | "rel_type": "m.replace" 18 | }, 19 | "msgtype": "m.text", 20 | "type": "m.room.message" 21 | }, 22 | "origin_server_ts": 1704648614969, 23 | "sender": "@example:example.org", 24 | "type": "m.room.message", 25 | "unsigned": { 26 | "age": 65 27 | }, 28 | "event_id": "$nolink:example.org", 29 | "room_id": "!test:example.org" 30 | } 31 | ], 32 | "limited": true, 33 | "prev_batch": "t34-23535_0_0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /autotests/data/test-multiplelink-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "content": { 3 | "body": "www.example.org https://kde.org", 4 | "msgtype": "m.text" 5 | }, 6 | "event_id": "$validlink1:example.org", 7 | "origin_server_ts": 1432735824654, 8 | "room_id": "!test:example.org", 9 | "sender": "@example:example.org", 10 | "type": "m.room.message", 11 | "unsigned": { 12 | "age": 1234 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /autotests/data/test-pending-sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeline": { 3 | "events": [ 4 | { 5 | "content": { 6 | "body": "New plain message", 7 | "msgtype": "m.text" 8 | }, 9 | "event_id": "$pendingmerge:example.org", 10 | "origin_server_ts": 123456, 11 | "room_id":"#myroom:kde.org", 12 | "sender":"@bob:kde.org", 13 | "type":"m.room.message", 14 | "unsigned": { 15 | "transaction_id": "17017181543521" 16 | } 17 | } 18 | ], 19 | "limited": true, 20 | "prev_batch": "t34-23535_0_0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /autotests/data/test-pollhandlerstart-sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeline": { 3 | "events": [ 4 | { 5 | "content": { 6 | "org.matrix.msc1767.text": "test\n1. option1\n2. option 2", 7 | "org.matrix.msc3381.poll.start": { 8 | "answers": [ 9 | { 10 | "id": "option1", 11 | "org.matrix.msc1767.text": "option1text" 12 | }, 13 | { 14 | "id": "option2", 15 | "org.matrix.msc1767.text": "option2text" 16 | } 17 | ], 18 | "kind": "org.matrix.msc3381.poll.undisclosed", 19 | "max_selections": 1, 20 | "question": { 21 | "body": "test", 22 | "msgtype": "m.text", 23 | "org.matrix.msc1767.text": "test" 24 | } 25 | } 26 | }, 27 | "event_id": "$153456789:example.org", 28 | "origin_server_ts": 1432735824654, 29 | "room_id": "!jEsUZKDJdhlrceRyVU:example.org", 30 | "sender": "@example:example.org", 31 | "type": "org.matrix.msc3381.poll.start", 32 | "unsigned": { 33 | "age": 1232 34 | } 35 | } 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /autotests/data/test-reactionmodel-extra-sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeline": { 3 | "events": [ 4 | { 5 | "content": { 6 | "m.relates_to": { 7 | "event_id": "$153456789:example.org", 8 | "key": "👍", 9 | "rel_type": "m.annotation" 10 | } 11 | }, 12 | "origin_server_ts": 1690322545183, 13 | "room_id": "!jEsUZKDJdhlrceRyVU:example.org", 14 | "sender": "@bob:example.org", 15 | "type": "m.reaction", 16 | "unsigned": { 17 | "age": 390159121 18 | }, 19 | "event_id": "$163456790:example.org", 20 | "age": 390159121 21 | }, 22 | { 23 | "content": { 24 | "m.relates_to": { 25 | "event_id": "$153456789:example.org", 26 | "key": "😆", 27 | "rel_type": "m.annotation" 28 | } 29 | }, 30 | "origin_server_ts": 1690322545184, 31 | "room_id": "!jEsUZKDJdhlrceRyVU:example.org", 32 | "sender": "@bob:example.org", 33 | "type": "m.reaction", 34 | "unsigned": { 35 | "age": 390159122 36 | }, 37 | "event_id": "$163456791:example.org", 38 | "age": 390159122 39 | } 40 | ], 41 | "limited": true, 42 | "prev_batch": "t34-23535_0_0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /autotests/neochatroomtest.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Carl Schwan 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "testutils.h" 13 | 14 | using namespace Quotient; 15 | 16 | class NeoChatRoomTest : public QObject { 17 | Q_OBJECT 18 | 19 | private: 20 | Connection *connection = nullptr; 21 | TestUtils::TestRoom *room = nullptr; 22 | 23 | private Q_SLOTS: 24 | void initTestCase(); 25 | void eventTest(); 26 | }; 27 | 28 | void NeoChatRoomTest::initTestCase() 29 | { 30 | connection = Connection::makeMockConnection(u"@bob:kde.org"_s); 31 | room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s); 32 | } 33 | 34 | void NeoChatRoomTest::eventTest() 35 | { 36 | QCOMPARE(room->timelineSize(), 1); 37 | } 38 | 39 | QTEST_GUILESS_MAIN(NeoChatRoomTest) 40 | #include "neochatroomtest.moc" 41 | -------------------------------------------------------------------------------- /autotests/server.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include 5 | #include 6 | 7 | class Server 8 | { 9 | public: 10 | Server(); 11 | 12 | void start(); 13 | 14 | /** 15 | * Create a room and place the user with id matrixId in it. 16 | * Returns the room's id 17 | */ 18 | QString createRoom(const QString &matrixId); 19 | 20 | void inviteUser(const QString &roomId, const QString &matrixId); 21 | void banUser(const QString &roomId, const QString &matrixId); 22 | void joinUser(const QString &roomId, const QString &matrixId); 23 | 24 | private: 25 | QHttpServer m_server; 26 | QSslServer m_sslServer; 27 | 28 | QHash> m_invitedUsers; 29 | QHash> m_bannedUsers; 30 | QHash> m_joinedUsers; 31 | 32 | QList> m_roomsToCreate; 33 | }; 34 | -------------------------------------------------------------------------------- /cmake/Findcmark.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Black Hat 2 | # SPDX-License-Identifier: GPL-3.0-only 3 | 4 | # 5 | # CMake module to search for the cmark library 6 | # 7 | 8 | # first try to find cmark-config.cmake 9 | # path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/' 10 | find_package(cmark CONFIG QUIET) 11 | if(cmark_FOUND AND TARGET cmark::cmark) 12 | # found it! 13 | return() 14 | endif() 15 | 16 | find_package(PkgConfig QUIET) 17 | if(PKG_CONFIG_FOUND) 18 | pkg_check_modules(PC_CMARK QUIET cmark) 19 | endif() 20 | 21 | if(NOT CMARK_INCLUDE_DIR) 22 | find_path(CMARK_INCLUDE_DIR 23 | NAMES cmark.h 24 | PATHS 25 | ${PC_CMARK_INCLUDEDIR} 26 | ${PC_CMARK_INCLUDE_DIRS} 27 | /usr/include 28 | /usr/local/include) 29 | endif() 30 | 31 | if(NOT CMARK_LIBRARY) 32 | find_library(CMARK_LIBRARY 33 | NAMES cmark 34 | HINTS 35 | ${PC_CMARK_LIBDIR} 36 | ${PC_CMARK_LIBRARY_DIRS} 37 | /usr/lib 38 | /usr/local/lib) 39 | endif() 40 | 41 | if(NOT TARGET cmark::cmark) 42 | add_library(cmark::cmark UNKNOWN IMPORTED) 43 | set_target_properties(cmark::cmark 44 | PROPERTIES INTERFACE_INCLUDE_DIRECTORIES 45 | ${CMARK_INCLUDE_DIR}) 46 | set_property(TARGET cmark::cmark APPEND 47 | PROPERTY IMPORTED_LOCATION ${CMARK_LIBRARY}) 48 | endif() 49 | 50 | include(FindPackageHandleStandardArgs) 51 | find_package_handle_standard_args(cmark 52 | DEFAULT_MSG 53 | CMARK_INCLUDE_DIR 54 | CMARK_LIBRARY) 55 | 56 | mark_as_advanced(CMARK_LIBRARY CMARK_INCLUDE_DIR) 57 | 58 | set(CMARK_LIBRARIES ${CMARK_LIBRARY}) 59 | set(CMARK_INCLUDE_DIRS ${CMARK_INCLUDE_DIR}) 60 | -------------------------------------------------------------------------------- /cmake/Flatpak.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Carl Schwan 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | include(GNUInstallDirs) 5 | 6 | # Include FontConfig config which uses the Emoji One font from the 7 | # KDE Flatpak SDK. 8 | install( 9 | FILES 10 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf 11 | DESTINATION 12 | ${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf 13 | ) 14 | 15 | -------------------------------------------------------------------------------- /cmake/Flatpak/99-noto-mono-color-emoji.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | serif 6 | 7 | Noto Color Emoji 8 | 9 | 10 | 11 | sans-serif 12 | 13 | Noto Color Emoji 14 | 15 | 16 | 17 | monospace 18 | 19 | Noto Color Emoji 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Carl Schwan 2 | # 3 | # SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR}) 6 | -------------------------------------------------------------------------------- /icons/150-apps-neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/150-apps-neochat.png -------------------------------------------------------------------------------- /icons/300-apps-neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/300-apps-neochat.png -------------------------------------------------------------------------------- /icons/44-apps-neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/44-apps-neochat.png -------------------------------------------------------------------------------- /icons/hicolor/128-apps-org.kde.neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/hicolor/128-apps-org.kde.neochat.png -------------------------------------------------------------------------------- /icons/hicolor/256-apps-org.kde.neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/hicolor/256-apps-org.kde.neochat.png -------------------------------------------------------------------------------- /icons/hicolor/32-apps-org.kde.neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/hicolor/32-apps-org.kde.neochat.png -------------------------------------------------------------------------------- /icons/hicolor/512-apps-org.kde.neochat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/hicolor/512-apps-org.kde.neochat.png -------------------------------------------------------------------------------- /icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/icon.icns -------------------------------------------------------------------------------- /icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/icon.ico -------------------------------------------------------------------------------- /icons/windows/promoimage-1920x1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/windows/promoimage-1920x1080.png -------------------------------------------------------------------------------- /icons/windows/storelogo-1080x1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/windows/storelogo-1080x1080.png -------------------------------------------------------------------------------- /icons/windows/storelogo-720x1080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/icons/windows/storelogo-720x1080.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/neochat/c958e8cba97c6b2fde3e1cfcfd85ab92d133ef85/logo.png -------------------------------------------------------------------------------- /memorytests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" ) 5 | 6 | qt_add_executable(timeline-memtest 7 | main.cpp 8 | ) 9 | 10 | target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin) 11 | target_link_libraries(timeline-memtest PUBLIC 12 | Qt::Core 13 | Qt::Quick 14 | Qt::Qml 15 | Qt::Gui 16 | Qt::QuickControls2 17 | Qt::Widgets 18 | KF6::I18nQml 19 | KF6::Kirigami 20 | QuotientQt6 21 | LibNeoChat 22 | Timeline 23 | ) 24 | 25 | ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE 26 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest 27 | QML_FILES 28 | Main.qml 29 | SOURCES 30 | memtesttimelinemodel.cpp 31 | memtesttimelinemodel.h 32 | DEPENDENCIES 33 | QtCore 34 | QtQuick 35 | IMPORTS 36 | org.kde.neochat.timeline 37 | ) 38 | -------------------------------------------------------------------------------- /memorytests/Main.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | QQC2.ApplicationWindow { 12 | id: root 13 | 14 | title: "Timeline Memory Test" 15 | 16 | minimumWidth: Kirigami.Units.gridUnit * 30 17 | minimumHeight: Kirigami.Units.gridUnit * 30 18 | 19 | visible: true 20 | 21 | QQC2.ScrollView { 22 | width: root.width 23 | height: root.height 24 | 25 | contentItem: ListView { 26 | cacheBuffer: 1000000 27 | model: messageFilterModel 28 | 29 | delegate: EventDelegate { 30 | room: memTestTimelineModel.room 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /memorytests/main.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "memtesttimelinemodel.h" 12 | #include "models/messagefiltermodel.h" 13 | 14 | using namespace Qt::StringLiterals; 15 | 16 | int main(int argc, char **argv) 17 | { 18 | QApplication app(argc, argv); 19 | 20 | KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat")); 21 | 22 | QQmlApplicationEngine engine; 23 | 24 | KLocalization::setupLocalizedContext(&engine); 25 | 26 | MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel; 27 | MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel); 28 | engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel); 29 | engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel); 30 | 31 | engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main"); 32 | 33 | return app.exec(); 34 | } 35 | -------------------------------------------------------------------------------- /memorytests/memtesttimelinemodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "memtesttimelinemodel.h" 5 | 6 | #include 7 | #include 8 | 9 | using namespace Quotient; 10 | 11 | MemTestTimelineModel::MemTestTimelineModel(QObject *parent) 12 | : MessageModel(parent) 13 | { 14 | beginResetModel(); 15 | m_connection = Connection::makeMockConnection(u"@bob:example.org"_s); 16 | m_room = new MemTestRoom(m_connection, u"#memtestroom:example.org"_s, u"memtest-sync.json"_s); 17 | 18 | for (const auto &eventIt : m_room->messageEvents()) { 19 | Q_EMIT newEventAdded(eventIt.event()); 20 | } 21 | 22 | endResetModel(); 23 | } 24 | 25 | std::optional> MemTestTimelineModel::getEventForIndex(QModelIndex index) const 26 | { 27 | return *m_room->messageEvents().at(index.row()).event(); 28 | } 29 | 30 | int MemTestTimelineModel::rowCount(const QModelIndex &parent) const 31 | { 32 | Q_UNUSED(parent); 33 | return m_room->messageEvents().size(); 34 | } 35 | 36 | #include "moc_memtesttimelinemodel.cpp" 37 | -------------------------------------------------------------------------------- /org.kde.neochat.tray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE) 5 | add_subdirectory(purpose) 6 | endif() 7 | 8 | add_subdirectory(libneochat) 9 | add_subdirectory(login) 10 | add_subdirectory(rooms) 11 | add_subdirectory(roominfo) 12 | add_subdirectory(timeline) 13 | add_subdirectory(spaces) 14 | add_subdirectory(chatbar) 15 | add_subdirectory(settings) 16 | add_subdirectory(devtools) 17 | add_subdirectory(app) 18 | -------------------------------------------------------------------------------- /src/app/blurhash.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018 Wolt Enterprises 2 | // SPDX-License-Identifier: MIT 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * @brief Returns the pixel array of the result image given the blurhash string. 10 | * 11 | * @param blurhash a string representing the blurhash to be decoded. 12 | * @param width the width of the resulting image. 13 | * @param height the height of the resulting image. 14 | * @param punch the factor to improve the contrast, default = 1. 15 | * @param nChannels the number of channels in the resulting image array, 3 = RGB, 4 = RGBA. 16 | * 17 | * @return A pointer to memory region where pixels are stored in (H, W, C) format. 18 | */ 19 | uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels); 20 | 21 | /** 22 | * @brief Checks if the Blurhash is valid or not. 23 | * 24 | * @param blurhash a string representing the blurhash. 25 | * 26 | * @return A bool (true if it is a valid blurhash, else false). 27 | */ 28 | bool isValidBlurhash(const char *blurhash); 29 | -------------------------------------------------------------------------------- /src/app/blurhashimageprovider.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "blurhashimageprovider.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "blurhash.h" 10 | 11 | BlurhashImageProvider::BlurhashImageProvider() 12 | : QQuickImageProvider(QQuickImageProvider::Image) 13 | { 14 | } 15 | 16 | QImage BlurhashImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) 17 | { 18 | if (id.isEmpty()) { 19 | return QImage(); 20 | } 21 | *size = requestedSize; 22 | if (size->width() == -1) { 23 | size->setWidth(256); 24 | } 25 | if (size->height() == -1) { 26 | size->setHeight(256); 27 | } 28 | auto data = decode(QUrl::fromPercentEncoding(id.toLatin1()).toLatin1().data(), size->width(), size->height(), 1, 3); 29 | QImage image(data, size->width(), size->height(), size->width() * 3, QImage::Format_RGB888, free, data); 30 | return image; 31 | } -------------------------------------------------------------------------------- /src/app/blurhashimageprovider.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * @class BlurhashImageProvider 10 | * 11 | * A QQuickImageProvider for blurhashes. 12 | * 13 | * @sa QQuickImageProvider 14 | */ 15 | class BlurhashImageProvider : public QQuickImageProvider 16 | { 17 | public: 18 | BlurhashImageProvider(); 19 | 20 | /** 21 | * @brief Return an image for a given ID. 22 | * 23 | * @sa QQuickImageProvider::requestImage 24 | */ 25 | QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; 26 | }; 27 | -------------------------------------------------------------------------------- /src/app/fakerunner.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "fakerunner.h" 5 | 6 | #include 7 | #include 8 | 9 | Q_SCRIPTABLE RemoteActions FakeRunner::Actions() 10 | { 11 | QCoreApplication::quit(); 12 | return {}; 13 | } 14 | 15 | Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm) 16 | { 17 | Q_UNUSED(searchTerm); 18 | QCoreApplication::quit(); 19 | return {}; 20 | } 21 | 22 | Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId) 23 | { 24 | Q_UNUSED(id); 25 | Q_UNUSED(actionId); 26 | QCoreApplication::quit(); 27 | } 28 | 29 | FakeRunner::FakeRunner() 30 | : QObject() 31 | { 32 | qDBusRegisterMetaType(); 33 | qDBusRegisterMetaType(); 34 | qDBusRegisterMetaType(); 35 | qDBusRegisterMetaType(); 36 | qDBusRegisterMetaType(); 37 | } 38 | 39 | #include "moc_fakerunner.cpp" 40 | -------------------------------------------------------------------------------- /src/app/fakerunner.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "runner.h" 9 | 10 | /** 11 | * This is a close-to-identical copy of the regular Runner interface, 12 | * only used when activated for push notifications. This stubs it out so 13 | * Plasma Search and Kickoff doesn't accidentally activate the push notification 14 | * service. 15 | * 16 | * @sa Runner 17 | */ 18 | class FakeRunner : public QObject, protected QDBusContext 19 | { 20 | Q_OBJECT 21 | Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1") 22 | 23 | public: 24 | Q_SCRIPTABLE RemoteActions Actions(); 25 | 26 | Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm); 27 | 28 | Q_SCRIPTABLE void Run(const QString &id, const QString &actionId); 29 | 30 | FakeRunner(); 31 | }; -------------------------------------------------------------------------------- /src/app/foreigntypes.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "controller.h" 15 | #include "neochatconfig.h" 16 | 17 | struct ForeignAccountRegistry { 18 | Q_GADGET 19 | QML_FOREIGN(Quotient::AccountRegistry) 20 | QML_NAMED_ELEMENT(AccountRegistry) 21 | QML_SINGLETON 22 | public: 23 | static Quotient::AccountRegistry *create(QQmlEngine *, QJSEngine *) 24 | { 25 | QQmlEngine::setObjectOwnership(Controller::instance().accounts(), QQmlEngine::CppOwnership); 26 | return Controller::instance().accounts(); 27 | } 28 | }; 29 | 30 | struct ForeignKeyVerificationSession { 31 | Q_GADGET 32 | QML_FOREIGN(Quotient::KeyVerificationSession) 33 | QML_NAMED_ELEMENT(KeyVerificationSession) 34 | QML_UNCREATABLE("") 35 | }; 36 | 37 | struct ForeignSSSSHandler { 38 | Q_GADGET 39 | QML_FOREIGN(Quotient::SSSSHandler) 40 | QML_NAMED_ELEMENT(SSSSHandler) 41 | }; 42 | 43 | struct ForeignKeyImport { 44 | Q_GADGET 45 | QML_SINGLETON 46 | QML_FOREIGN(Quotient::KeyImport) 47 | QML_NAMED_ELEMENT(KeyImport) 48 | }; 49 | -------------------------------------------------------------------------------- /src/app/logger.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | /** 7 | * Initlalize logging to file and enables some additional categories, which will only be logged to the file 8 | */ 9 | void initLogging(); 10 | -------------------------------------------------------------------------------- /src/app/mediamanager.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "mediamanager.h" 5 | 6 | void MediaManager::startPlayback() 7 | { 8 | Q_EMIT playbackStarted(); 9 | } 10 | 11 | #include "moc_mediamanager.cpp" 12 | -------------------------------------------------------------------------------- /src/app/mediamanager.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class MediaManager 11 | * 12 | * Manages media playback, like voice/audio messages, videos, etc. 13 | */ 14 | class MediaManager : public QObject 15 | { 16 | Q_OBJECT 17 | QML_ELEMENT 18 | QML_SINGLETON 19 | 20 | public: 21 | static MediaManager &instance() 22 | { 23 | static MediaManager _instance; 24 | return _instance; 25 | } 26 | static MediaManager *create(QQmlEngine *, QJSEngine *) 27 | { 28 | QQmlEngine::setObjectOwnership(&instance(), QQmlEngine::CppOwnership); 29 | return &instance(); 30 | } 31 | 32 | /** 33 | * @brief Notify other objects that media playback has started. 34 | */ 35 | Q_INVOKABLE void startPlayback(); 36 | 37 | Q_SIGNALS: 38 | /** 39 | * @brief Emitted when any media player starts playing. Other objects should stop / pause playback. 40 | */ 41 | void playbackStarted(); 42 | }; 43 | -------------------------------------------------------------------------------- /src/app/models/commonroomsmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "neochatconnection.h" 10 | #include "neochatroom.h" 11 | 12 | #include 13 | #include 14 | 15 | /** 16 | * @brief Model to show the common or mutual rooms between you and another user. 17 | */ 18 | class CommonRoomsModel : public QAbstractListModel 19 | { 20 | Q_OBJECT 21 | QML_ELEMENT 22 | Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED) 23 | Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED) 24 | Q_PROPERTY(int count READ rowCount NOTIFY countChanged) 25 | 26 | public: 27 | enum Roles { 28 | TextRole = Qt::DisplayRole, 29 | LongitudeRole, 30 | LatitudeRole, 31 | AssetRole, 32 | AuthorRole, 33 | }; 34 | Q_ENUM(Roles) 35 | 36 | explicit CommonRoomsModel(QObject *parent = nullptr); 37 | 38 | [[nodiscard]] NeoChatConnection *connection() const; 39 | void setConnection(NeoChatConnection *connection); 40 | 41 | [[nodiscard]] QString userId() const; 42 | void setUserId(const QString &userId); 43 | 44 | [[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override; 45 | [[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override; 46 | 47 | Q_SIGNALS: 48 | void connectionChanged(); 49 | void userIdChanged(); 50 | void countChanged(); 51 | 52 | private: 53 | void reload(); 54 | 55 | QPointer m_connection; 56 | QString m_userId; 57 | QList m_commonRooms; 58 | }; 59 | -------------------------------------------------------------------------------- /src/app/notifyrc.qrc: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | neochat.notifyrc 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/org.kde.neochat.service.in: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: none 2 | # SPDX-License-Identifier: CC0-1.0 3 | [D-BUS Service] 4 | Name=org.kde.neochat 5 | Exec=@CMAKE_INSTALL_PREFIX@/bin/neochat --dbus-activated -------------------------------------------------------------------------------- /src/app/proxycontroller.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "proxycontroller.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "neochatconfig.h" 10 | 11 | void ProxyController::setApplicationProxy() 12 | { 13 | auto cfg = NeoChatConfig::self(); 14 | QNetworkProxy proxy; 15 | 16 | switch (cfg->proxyType()) { 17 | case 1: 18 | proxy.setType(QNetworkProxy::HttpProxy); 19 | proxy.setHostName(cfg->proxyHost()); 20 | proxy.setPort(cfg->proxyPort()); 21 | proxy.setUser(cfg->proxyUser()); 22 | proxy.setPassword(cfg->proxyPassword()); 23 | QNetworkProxy::setApplicationProxy(proxy); 24 | break; 25 | case 2: 26 | proxy.setType(QNetworkProxy::Socks5Proxy); 27 | proxy.setHostName(cfg->proxyHost()); 28 | proxy.setPort(cfg->proxyPort()); 29 | proxy.setUser(cfg->proxyUser()); 30 | proxy.setPassword(cfg->proxyPassword()); 31 | QNetworkProxy::setApplicationProxy(proxy); 32 | break; 33 | case 3: 34 | proxy.setType(QNetworkProxy::NoProxy); 35 | QNetworkProxy::setApplicationProxy(proxy); 36 | break; 37 | default: 38 | QNetworkProxyFactory::setUseSystemConfiguration(true); 39 | break; 40 | } 41 | } 42 | 43 | ProxyController::ProxyController(QObject *parent) 44 | : QObject(parent) 45 | { 46 | } 47 | 48 | #include "moc_proxycontroller.cpp" 49 | -------------------------------------------------------------------------------- /src/app/proxycontroller.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class ProxyController : public QObject 10 | { 11 | Q_OBJECT 12 | QML_ELEMENT 13 | QML_SINGLETON 14 | 15 | public: 16 | static ProxyController &instance() 17 | { 18 | static ProxyController _instance; 19 | return _instance; 20 | } 21 | static ProxyController *create(QQmlEngine *engine, QJSEngine *) 22 | { 23 | engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership); 24 | return &instance(); 25 | } 26 | 27 | /** 28 | * @brief Sets the QNetworkProxy for the application. 29 | * 30 | * @sa QNetworkProxy::setApplicationProxy 31 | */ 32 | Q_INVOKABLE void setApplicationProxy(); 33 | 34 | private: 35 | explicit ProxyController(QObject *parent = nullptr); 36 | }; 37 | -------------------------------------------------------------------------------- /src/app/qml/AskDirectChatConfirmation.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | Kirigami.Dialog { 12 | id: root 13 | 14 | required property var user 15 | 16 | width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width) 17 | height: Kirigami.Units.gridUnit * 8 18 | 19 | standardButtons: QQC2.Dialog.Close 20 | title: i18nc("@title:dialog", "Start a chat") 21 | 22 | contentItem: QQC2.Label { 23 | text: i18n("Do you want to start a chat with %1?", root.user.displayName) 24 | textFormat: Text.PlainText 25 | wrapMode: Text.Wrap 26 | horizontalAlignment: Qt.AlignHCenter 27 | verticalAlignment: Qt.AlignVCenter 28 | } 29 | 30 | customFooterActions: [ 31 | Kirigami.Action { 32 | text: i18nc("@action:button", "Start Chat") 33 | icon.name: "im-user" 34 | onTriggered: { 35 | root.user.requestDirectChat(); 36 | root.close(); 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/app/qml/AvatarNotification.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.labs.components as KirigamiComponents 9 | 10 | KirigamiComponents.Avatar { 11 | id: root 12 | 13 | property int notificationCount 14 | 15 | property bool notificationHighlight 16 | 17 | property bool showNotificationLabel 18 | 19 | QQC2.Label { 20 | id: notificationCountLabel 21 | anchors.top: parent.top 22 | anchors.right: parent.right 23 | anchors.topMargin: -Kirigami.Units.smallSpacing 24 | anchors.rightMargin: -Kirigami.Units.smallSpacing 25 | z: 1 26 | width: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) 27 | height: Kirigami.Units.iconSizes.smallMedium 28 | 29 | text: root.notificationCount > 0 ? root.notificationCount : "" 30 | visible: root.showNotificationLabel 31 | color: Kirigami.Theme.textColor 32 | horizontalAlignment: Text.AlignHCenter 33 | verticalAlignment: Text.AlignVCenter 34 | background: Rectangle { 35 | visible: true 36 | Kirigami.Theme.colorSet: Kirigami.Theme.Button 37 | Kirigami.Theme.inherit: false 38 | color: root.notificationHighlight ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor 39 | radius: height / 2 40 | } 41 | 42 | TextMetrics { 43 | id: notificationCountTextMetrics 44 | text: notificationCountLabel.text 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/qml/ChooseRoomDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | pragma ComponentBehavior: Bound 5 | 6 | import QtQuick 7 | import QtQuick.Controls 8 | 9 | import org.kde.kirigami as Kirigami 10 | 11 | import org.kde.neochat 12 | 13 | SearchPage { 14 | id: root 15 | 16 | title: i18nc("@title", "Choose a Room") 17 | 18 | showSearchButton: false 19 | 20 | signal chosen(string roomId) 21 | 22 | required property NeoChatConnection connection 23 | 24 | model: RoomManager.sortFilterRoomListModel 25 | modelDelegate: RoomDelegate { 26 | onClicked: { 27 | root.chosen(currentRoom.id); 28 | root.closeDialog(); 29 | } 30 | connection: root.connection 31 | openOnClick: false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/qml/CollapsedRoomDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Carl Schwan 2 | // SPDX-License-Identifier: GPL-3.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | import QtQml.Models 8 | 9 | import org.kde.kirigami as Kirigami 10 | import org.kde.kirigamiaddons.labs.components as KirigamiComponents 11 | import org.kde.kitemmodels 12 | 13 | import org.kde.neochat 14 | 15 | QQC2.ItemDelegate { 16 | id: root 17 | 18 | required property NeoChatRoom currentRoom 19 | required property bool categoryVisible 20 | required property string filterText 21 | required property url avatar 22 | required property string displayName 23 | 24 | topPadding: Kirigami.Units.largeSpacing 25 | leftPadding: Kirigami.Units.largeSpacing 26 | rightPadding: Kirigami.Units.largeSpacing 27 | bottomPadding: Kirigami.Units.largeSpacing 28 | 29 | width: ListView.view.width 30 | height: visible ? ListView.view.width : 0 31 | 32 | visible: root.categoryVisible || filterText.length > 0 33 | 34 | contentItem: KirigamiComponents.Avatar { 35 | source: root.avatar 36 | name: root.displayName 37 | 38 | sourceSize { 39 | width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 40 | height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 41 | } 42 | } 43 | 44 | onClicked: RoomManager.resolveResource(currentRoom.id) 45 | 46 | Keys.onEnterPressed: RoomManager.resolveResource(currentRoom.id) 47 | Keys.onReturnPressed: RoomManager.resolveResource(currentRoom.id) 48 | 49 | QQC2.ToolTip.visible: text.length > 0 && hovered 50 | QQC2.ToolTip.text: root.displayName ?? "" 51 | QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 52 | } 53 | -------------------------------------------------------------------------------- /src/app/qml/ConfirmLeaveDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | Kirigami.PromptDialog { 12 | id: root 13 | 14 | required property NeoChatRoom room 15 | 16 | title: i18nc("@title:dialog", "Confirm Leaving Room") 17 | subtitle: root.room ? i18nc("Do you really want to leave ?", "Do you really want to leave %1?", root.room.displayNameForHtml) : "" 18 | dialogType: Kirigami.PromptDialog.Warning 19 | 20 | onRejected: { 21 | root.close(); 22 | } 23 | 24 | footer: QQC2.DialogButtonBox { 25 | standardButtons: QQC2.Dialog.Cancel 26 | 27 | QQC2.Button { 28 | text: i18nc("@action:button", "Leave Room") 29 | QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole 30 | icon.name: "arrow-left-symbolic" 31 | onClicked: root.room.forget(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/qml/ConfirmLogoutDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Carl Schwan 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import org.kde.kirigami as Kirigami 7 | 8 | import org.kde.neochat 9 | 10 | Kirigami.PromptDialog { 11 | id: root 12 | 13 | required property NeoChatConnection connection 14 | 15 | title: i18nc("@title:dialog", "Sign out") 16 | subtitle: i18n("Are you sure you want to sign out?") 17 | dialogType: Kirigami.PromptDialog.Warning 18 | 19 | onRejected: { 20 | root.close(); 21 | } 22 | 23 | footer: QQC2.DialogButtonBox { 24 | standardButtons: QQC2.Dialog.Cancel 25 | 26 | QQC2.Button { 27 | text: i18nc("@action:button", "Sign out") 28 | QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole 29 | onClicked: { 30 | root.connection.logout(true); 31 | root.close(); 32 | root.accepted(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/qml/ConfirmUrlDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | Kirigami.PromptDialog { 10 | id: root 11 | 12 | property url link 13 | 14 | title: i18nc("@title:dialog", "Open URL") 15 | subtitle: xi18nc("@info", "Do you want to open %1?", root.link) 16 | dialogType: Kirigami.PromptDialog.Warning 17 | 18 | standardButtons: QQC2.DialogButtonBox.Open | QQC2.DialogButtonBox.Cancel 19 | 20 | onAccepted: { 21 | Qt.openUrlExternally(root.link); 22 | root.close(); 23 | } 24 | 25 | onRejected: { 26 | root.close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/qml/ConsentDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | Kirigami.Dialog { 12 | id: root 13 | 14 | required property string url 15 | 16 | width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width) 17 | height: Kirigami.Units.gridUnit * 8 18 | leftPadding: Kirigami.Units.largeSpacing 19 | rightPadding: Kirigami.Units.largeSpacing 20 | 21 | title: i18nc("@title:dialog", "User Consent") 22 | 23 | contentItem: QQC2.Label { 24 | text: i18nc("@info", "Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.") 25 | wrapMode: Text.WordWrap 26 | horizontalAlignment: Qt.AlignHCenter 27 | verticalAlignment: Qt.AlignVCenter 28 | } 29 | customFooterActions: [ 30 | Kirigami.Action { 31 | text: i18nc("@action:button", "Open") 32 | icon.name: "internet-services" 33 | onTriggered: { 34 | UrlHelper.openUrl(root.url); 35 | root.close(); 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/app/qml/EmojiItem.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.neochat 10 | 11 | ColumnLayout { 12 | id: root 13 | 14 | property alias emoji: emojiLabel.text 15 | property alias description: descriptionLabel.text 16 | 17 | QQC2.Label { 18 | id: emojiLabel 19 | Layout.fillWidth: true 20 | Layout.preferredWidth: Kirigami.Units.iconSizes.huge 21 | Layout.preferredHeight: Kirigami.Units.iconSizes.huge 22 | verticalAlignment: Text.AlignVCenter 23 | horizontalAlignment: Text.AlignHCenter 24 | font.family: "emoji" 25 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4 26 | } 27 | QQC2.Label { 28 | id: descriptionLabel 29 | Layout.fillWidth: true 30 | verticalAlignment: Text.AlignVCenter 31 | horizontalAlignment: Text.AlignHCenter 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/qml/EmojiRow.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | RowLayout { 12 | id: root 13 | 14 | property alias model: repeater.model 15 | 16 | spacing: Kirigami.Units.largeSpacing 17 | 18 | Repeater { 19 | id: repeater 20 | delegate: EmojiItem { 21 | emoji: modelData.emoji 22 | description: modelData.description 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/qml/EmojiSas.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.neochat 10 | 11 | ColumnLayout { 12 | id: root 13 | 14 | required property var model 15 | 16 | signal accept 17 | signal reject 18 | 19 | spacing: Kirigami.Units.largeSpacing 20 | 21 | Item { 22 | Layout.fillHeight: true 23 | } 24 | QQC2.Label { 25 | Layout.fillWidth: true 26 | text: i18n("Confirm the emoji below are displayed on both devices, in the same order.") 27 | horizontalAlignment: Text.AlignHCenter 28 | wrapMode: Text.Wrap 29 | } 30 | EmojiRow { 31 | Layout.maximumWidth: implicitWidth 32 | Layout.alignment: Qt.AlignHCenter 33 | model: root.model.slice(0, 4) 34 | } 35 | EmojiRow { 36 | Layout.maximumWidth: implicitWidth 37 | Layout.alignment: Qt.AlignHCenter 38 | model: root.model.slice(4, 7) 39 | } 40 | RowLayout { 41 | Layout.fillWidth: true 42 | Layout.alignment: Qt.AlignHCenter 43 | QQC2.Button { 44 | anchors.bottom: parent.bottom 45 | text: i18n("They match") 46 | icon.name: "dialog-ok" 47 | onClicked: root.accept() 48 | } 49 | QQC2.Button { 50 | anchors.bottom: parent.bottom 51 | text: i18n("They don't match") 52 | icon.name: "dialog-cancel" 53 | onClicked: root.reject() 54 | } 55 | } 56 | Item { 57 | Layout.fillHeight: true 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/qml/GlobalMenuStub.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Joshua Goins 2 | // SPDX-FileCopyrightText: 2024 Carl Schwan 3 | // SPDX-License-Identifier: LGPL-2.0-or-later 4 | 5 | import QtQuick 6 | import QtQuick.Controls as QQC2 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | QQC2.Control { 11 | id: root 12 | 13 | property string text 14 | 15 | visible: !root.text.startsWith("https://matrix.to/") && root.text.length > 0 16 | Kirigami.Theme.colorSet: Kirigami.Theme.View 17 | 18 | z: 20 19 | 20 | Accessible.ignored: true 21 | 22 | contentItem: QQC2.Label { 23 | text: root.text.startsWith("https://matrix.to/") ? "" : root.text 24 | elide: Text.ElideRight 25 | Accessible.description: i18nc("@info screenreader", "The currently selected link") 26 | } 27 | 28 | background: Rectangle { 29 | color: Kirigami.Theme.backgroundColor 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/qml/OpenFileDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Dialogs 6 | 7 | FileDialog { 8 | id: root 9 | 10 | signal chosen(string path) 11 | 12 | title: i18n("Select a File") 13 | onAccepted: root.chosen(selectedFile) 14 | } 15 | -------------------------------------------------------------------------------- /src/app/qml/OsmLocationPlugin.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | pragma Singleton 5 | 6 | import QtQuick 7 | import QtLocation 8 | 9 | QtObject { 10 | id: root 11 | 12 | property string userAgent: Application.name + "/" + Application.version + " (kde-devel@kde.org)" 13 | property var plugin: Plugin { 14 | name: "osm" 15 | PluginParameter { 16 | name: "osm.useragent" 17 | value: root.userAgent 18 | } 19 | PluginParameter { 20 | name: "osm.mapping.providersrepository.address" 21 | value: "https://autoconfig.kde.org/qtlocation/" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/qml/QrCodeMaximizeComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigamiaddons.labs.components as Components 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.prison 9 | 10 | Components.AbstractMaximizeComponent { 11 | id: root 12 | 13 | required property string text 14 | property color avatarColor 15 | required property url avatarSource 16 | 17 | onOpened: forceActiveFocus() 18 | 19 | Shortcut { 20 | sequences: [StandardKey.Cancel] 21 | onActivated: root.close() 22 | } 23 | 24 | leading: Components.Avatar { 25 | id: userAvatar 26 | implicitWidth: Kirigami.Units.iconSizes.medium 27 | implicitHeight: Kirigami.Units.iconSizes.medium 28 | 29 | name: root.title 30 | source: root.avatarSource 31 | color: root.avatarColor 32 | } 33 | 34 | content: Item { 35 | Keys.onEscapePressed: root.close() 36 | Barcode { 37 | barcodeType: Barcode.QRCode 38 | content: root.text 39 | height: Math.min(parent.height, Kirigami.Units.gridUnit * 20) 40 | width: height 41 | anchors.centerIn: parent 42 | } 43 | MouseArea { 44 | id: closeArea 45 | anchors.fill: parent 46 | acceptedButtons: Qt.LeftButton 47 | onClicked: root.close() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app/qml/QrScannerPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtMultimedia 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.prison.scanner as Prison 10 | 11 | import org.kde.neochat 12 | 13 | Kirigami.Page { 14 | id: root 15 | 16 | title: i18nc("@title", "Scan a QR Code") 17 | 18 | required property NeoChatConnection connection 19 | padding: 0 20 | 21 | Component.onCompleted: camera.start() 22 | 23 | Connections { 24 | target: root.QQC2.ApplicationWindow.window 25 | function onClosing() { 26 | root.destroy(); 27 | } 28 | } 29 | 30 | VideoOutput { 31 | id: viewFinder 32 | anchors.centerIn: parent 33 | } 34 | 35 | Prison.VideoScanner { 36 | id: scanner 37 | property string previousText: "" 38 | formats: Prison.Format.QRCode | Prison.Format.Aztec 39 | onResultChanged: { 40 | if (result.text.length > 0 && result.text != scanner.previousText) { 41 | RoomManager.resolveResource(result.text, "qr"); 42 | scanner.previousText = result.text; 43 | } 44 | root.closeDialog(); 45 | } 46 | videoSink: viewFinder.videoSink 47 | } 48 | 49 | CaptureSession { 50 | camera: Camera { 51 | id: camera 52 | } 53 | imageCapture: ImageCapture { 54 | id: imageCapture 55 | } 56 | videoOutput: viewFinder 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/qml/QuickSwitcher.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kitemmodels 10 | 11 | import org.kde.neochat 12 | 13 | Kirigami.SearchDialog { 14 | id: root 15 | 16 | required property NeoChatConnection connection 17 | 18 | Shortcut { 19 | sequence: "Ctrl+K" 20 | onActivated: root.open() 21 | } 22 | 23 | onAccepted: if (currentItem) { 24 | currentItem.clicked(); 25 | } 26 | 27 | onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text 28 | model: RoomManager.sortFilterRoomListModel 29 | emptyText: i18nc("Placeholder message", "No room found") 30 | Kirigami.Action { 31 | id: exploreRoomAction 32 | text: i18nc("@action:button", "Explore rooms") 33 | icon.name: "compass" 34 | onTriggered: { 35 | root.close() 36 | let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), { 37 | connection: root.connection 38 | }, { 39 | title: i18nc("@title", "Explore Rooms") 40 | }); 41 | dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => { 42 | RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join"); 43 | }); 44 | } 45 | } 46 | 47 | Component.onCompleted: emptyHelpfulAction = exploreRoomAction 48 | 49 | parent: QQC2.Overlay.overlay 50 | 51 | delegate: RoomDelegate { 52 | connection: root.connection 53 | onClicked: root.close() 54 | showConfigure: false 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/qml/ShareActionStub.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | 4 | import org.kde.kirigami as Kirigami 5 | 6 | Kirigami.Action { 7 | property var inputData: ({}) 8 | property var room 9 | property string eventId 10 | visible: false 11 | } 12 | -------------------------------------------------------------------------------- /src/app/qml/VerificationMessage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.neochat 10 | 11 | ColumnLayout { 12 | id: root 13 | 14 | required property string icon 15 | required property string text 16 | 17 | anchors.fill: parent 18 | 19 | Item { 20 | Layout.fillHeight: true 21 | } 22 | Kirigami.Icon { 23 | Layout.fillWidth: true 24 | Layout.preferredWidth: Kirigami.Units.iconSizes.enormous 25 | Layout.preferredHeight: Kirigami.Units.iconSizes.enormous 26 | source: root.icon 27 | } 28 | QQC2.Label { 29 | Layout.fillWidth: true 30 | text: root.text 31 | textFormat: Text.MarkdownText 32 | horizontalAlignment: Text.AlignHCenter 33 | wrapMode: Text.Wrap 34 | } 35 | Item { 36 | Layout.fillHeight: true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/sharehandler.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "sharehandler.h" 5 | 6 | ShareHandler::ShareHandler(QObject *parent) 7 | : QObject(parent) 8 | { 9 | } 10 | 11 | QString ShareHandler::text() const 12 | { 13 | return m_text; 14 | } 15 | 16 | void ShareHandler::setText(const QString &text) 17 | { 18 | if (text == m_text) { 19 | return; 20 | } 21 | m_text = text; 22 | Q_EMIT textChanged(); 23 | } 24 | 25 | QString ShareHandler::room() const 26 | { 27 | return m_room; 28 | } 29 | 30 | void ShareHandler::setRoom(const QString &roomId) 31 | { 32 | if (roomId == m_room) { 33 | return; 34 | } 35 | m_room = roomId; 36 | Q_EMIT roomChanged(); 37 | } 38 | 39 | #include "moc_sharehandler.cpp" 40 | -------------------------------------------------------------------------------- /src/app/sharehandler.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class ShareHandler : public QObject 10 | { 11 | Q_OBJECT 12 | QML_ELEMENT 13 | QML_SINGLETON 14 | 15 | Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) 16 | Q_PROPERTY(QString room READ room WRITE setRoom NOTIFY roomChanged) 17 | 18 | public: 19 | static ShareHandler &instance() 20 | { 21 | static ShareHandler _instance; 22 | return _instance; 23 | } 24 | 25 | static ShareHandler *create(QQmlEngine *, QJSEngine *) 26 | { 27 | QQmlEngine::setObjectOwnership(&instance(), QQmlEngine::CppOwnership); 28 | return &instance(); 29 | } 30 | 31 | QString text() const; 32 | void setText(const QString &url); 33 | 34 | QString room() const; 35 | void setRoom(const QString &roomId); 36 | 37 | Q_SIGNALS: 38 | void textChanged(); 39 | void roomChanged(); 40 | 41 | private: 42 | explicit ShareHandler(QObject *parent = nullptr); 43 | 44 | QString m_text; 45 | QString m_room; 46 | }; 47 | -------------------------------------------------------------------------------- /src/app/texttospeechhelper.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Ritchie Frodomar 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "texttospeechhelper.h" 5 | 6 | void TextToSpeechHelper::speak(const QString &textToSpeak) 7 | { 8 | if (!m_speech) { 9 | m_speech = new QTextToSpeech(); 10 | } 11 | 12 | m_speech->say(textToSpeak); 13 | } 14 | 15 | #include "moc_texttospeechhelper.cpp" -------------------------------------------------------------------------------- /src/app/texttospeechhelper.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Ritchie Frodomar 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class TextToSpeechHelper : public QObject 11 | { 12 | Q_OBJECT 13 | QML_ELEMENT 14 | QML_SINGLETON 15 | 16 | public: 17 | Q_INVOKABLE void speak(const QString &textToSpeak); 18 | 19 | private: 20 | QTextToSpeech *m_speech = nullptr; 21 | }; -------------------------------------------------------------------------------- /src/app/trayicon.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Black Hat 2 | // SPDX-FileCopyrightText: 2021 Nicolas Fella 3 | // SPDX-License-Identifier: GPL-3.0-only 4 | 5 | #include "trayicon.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "windowcontroller.h" 13 | 14 | using namespace Qt::StringLiterals; 15 | 16 | TrayIcon::TrayIcon(QObject *parent) 17 | : QSystemTrayIcon(parent) 18 | { 19 | setIcon(QIcon(u":/icons/org.kde.neochat.tray.svg"_s)); 20 | QMenu *menu = new QMenu(); 21 | auto viewAction_ = new QAction(i18n("Show"), parent); 22 | 23 | connect(viewAction_, &QAction::triggered, this, [] { 24 | WindowController::instance().toggleWindow(); 25 | }); 26 | connect(this, &QSystemTrayIcon::activated, this, [](QSystemTrayIcon::ActivationReason reason) { 27 | if (reason == QSystemTrayIcon::Trigger) { 28 | WindowController::instance().toggleWindow(); 29 | } 30 | }); 31 | 32 | menu->addAction(viewAction_); 33 | 34 | menu->addSeparator(); 35 | 36 | auto quitAction = new QAction(i18n("Quit"), parent); 37 | quitAction->setIcon(QIcon::fromTheme(u"application-exit"_s)); 38 | connect(quitAction, &QAction::triggered, QCoreApplication::instance(), QCoreApplication::quit); 39 | 40 | menu->addAction(quitAction); 41 | 42 | setContextMenu(menu); 43 | } 44 | 45 | #include "moc_trayicon.cpp" 46 | -------------------------------------------------------------------------------- /src/app/trayicon.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Black Hat 2 | // SPDX-License-Identifier: GPL-3.0-only 3 | 4 | #pragma once 5 | 6 | // Modified from mujx/nheko's TrayIcon. 7 | 8 | #include 9 | 10 | /** 11 | * @class TrayIcon 12 | * 13 | * A class inheriting from QSystemTrayIcon to handle setting the system tray icon. 14 | * 15 | * Works for Windows, Linux and MacOS. 16 | * 17 | * @sa QSystemTrayIcon 18 | */ 19 | class TrayIcon : public QSystemTrayIcon 20 | { 21 | Q_OBJECT 22 | public: 23 | TrayIcon(QObject *parent = nullptr); 24 | }; 25 | -------------------------------------------------------------------------------- /src/app/trayicon_sni.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 2 | // SPDX-License-Identifier: GPL-3.0-only 3 | 4 | #include "trayicon_sni.h" 5 | #include 6 | 7 | #include "windowcontroller.h" 8 | 9 | using namespace Qt::StringLiterals; 10 | 11 | TrayIcon::TrayIcon(QObject *parent) 12 | : KStatusNotifierItem(parent) 13 | { 14 | setCategory(KStatusNotifierItem::ItemCategory::Communications); 15 | setIconByName(u"org.kde.neochat.tray"_s); 16 | 17 | connect(&WindowController::instance(), &WindowController::windowChanged, this, [this] { 18 | setAssociatedWindow(WindowController::instance().window()); 19 | }); 20 | } 21 | 22 | void TrayIcon::show() 23 | { 24 | setStatus(Active); 25 | } 26 | 27 | void TrayIcon::hide() 28 | { 29 | setStatus(Passive); 30 | } 31 | 32 | #include "moc_trayicon_sni.cpp" 33 | -------------------------------------------------------------------------------- /src/app/trayicon_sni.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 2 | // SPDX-License-Identifier: GPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * @class TrayIcon 10 | * 11 | * A class inheriting KStatusNotifierItem to provide a tray icon. 12 | * 13 | * @sa KStatusNotifierItem 14 | */ 15 | class TrayIcon : public KStatusNotifierItem 16 | { 17 | Q_OBJECT 18 | public: 19 | explicit TrayIcon(QObject *parent = nullptr); 20 | 21 | /** 22 | * @brief Show the tray icon. 23 | */ 24 | void show(); 25 | 26 | /** 27 | * @brief Hide the tray icon. 28 | */ 29 | void hide(); 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/windowcontroller.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Nicolas Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * @class WindowController 12 | * 13 | * A singleton class to help manage the NeoChat window. 14 | */ 15 | class WindowController : public QObject 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | QML_SINGLETON 20 | 21 | /** 22 | * @brief Whether KWindowSystem specific features are available. 23 | */ 24 | Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT) 25 | 26 | public: 27 | static WindowController &instance(); 28 | static WindowController *create(QQmlEngine *engine, QJSEngine *) 29 | { 30 | engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership); 31 | return &instance(); 32 | } 33 | 34 | /** 35 | * @brief Set the window that the will be managed. 36 | */ 37 | void setWindow(QWindow *window); 38 | 39 | /** 40 | * @brief Get the window that the will be managed. 41 | */ 42 | QWindow *window() const; 43 | 44 | /** 45 | * @brief Show the window and raise to the top. 46 | */ 47 | void showAndRaiseWindow(const QString &startupId); 48 | 49 | bool hasWindowSystem() const; 50 | 51 | /** 52 | * @brief Set the background blur status of the given item. 53 | */ 54 | Q_INVOKABLE void setBlur(QQuickItem *item, bool blur); 55 | 56 | /** 57 | * Toggles the window between hidden and visible. 58 | */ 59 | void toggleWindow(); 60 | 61 | Q_SIGNALS: 62 | /** 63 | * @brief Triggered if the managed window is changed. 64 | */ 65 | void windowChanged(); 66 | 67 | private: 68 | WindowController() = default; 69 | 70 | QWindow *m_window = nullptr; 71 | }; 72 | -------------------------------------------------------------------------------- /src/chatbar/AttachDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtCore 5 | import QtQuick 6 | import QtQuick.Controls as QQC2 7 | import QtQuick.Layouts 8 | 9 | import org.kde.kirigami as Kirigami 10 | 11 | import org.kde.neochat 12 | 13 | QQC2.Popup { 14 | id: root 15 | 16 | padding: Kirigami.Units.largeSpacing 17 | 18 | signal chosen(string path) 19 | 20 | contentItem: RowLayout { 21 | 22 | spacing: Kirigami.Units.smallSpacing 23 | 24 | QQC2.ToolButton { 25 | Layout.fillHeight: true 26 | 27 | icon.name: 'mail-attachment' 28 | 29 | text: i18n("Choose local file") 30 | 31 | onClicked: { 32 | root.close(); 33 | var fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay); 34 | fileDialog.chosen.connect(path => root.chosen(path)); 35 | fileDialog.open(); 36 | } 37 | } 38 | 39 | Kirigami.Separator {} 40 | 41 | QQC2.ToolButton { 42 | Layout.fillHeight: true 43 | 44 | icon.name: 'insert-image' 45 | text: i18n("Clipboard image") 46 | onClicked: { 47 | const path = StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + "/screenshots/" + (new Date()).getTime() + ".png"; 48 | if (!Clipboard.saveImage(path)) { 49 | return; 50 | } 51 | root.chosen(path); 52 | root.close(); 53 | } 54 | } 55 | } 56 | Component { 57 | id: openFileDialog 58 | 59 | OpenFileDialog { 60 | parentWindow: Window.window 61 | currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0] 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/chatbar/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Tobias Fella 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(Chatbar STATIC) 5 | ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.chatbar 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar 8 | QML_FILES 9 | AttachDialog.qml 10 | ChatBar.qml 11 | CompletionMenu.qml 12 | EmojiDelegate.qml 13 | EmojiGrid.qml 14 | PieProgressBar.qml 15 | EmojiPicker.qml 16 | EmojiDialog.qml 17 | EmojiTonesPicker.qml 18 | ) 19 | -------------------------------------------------------------------------------- /src/devtools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(Devtools STATIC) 5 | ecm_add_qml_module(Devtools GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.devtools 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools 8 | QML_FILES 9 | DevtoolsPage.qml 10 | AccountData.qml 11 | DebugOptions.qml 12 | FeatureFlagPage.qml 13 | RoomData.qml 14 | ServerData.qml 15 | StateKeys.qml 16 | SOURCES 17 | models/statefiltermodel.cpp 18 | models/statekeysmodel.cpp 19 | models/statemodel.cpp 20 | ) 21 | 22 | target_include_directories(Devtools PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models) 23 | target_link_libraries(Devtools PRIVATE 24 | Qt::Core 25 | LibNeoChat 26 | ) 27 | -------------------------------------------------------------------------------- /src/devtools/DebugOptions.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | FormCard.FormCard { 13 | id: root 14 | 15 | Layout.topMargin: Kirigami.Units.largeSpacing 16 | 17 | FormCard.FormCheckDelegate { 18 | text: i18nc("@option:check", "Show hidden events in the timeline") 19 | checked: NeoChatConfig.showAllEvents 20 | 21 | onToggled: NeoChatConfig.showAllEvents = checked 22 | } 23 | FormCard.FormCheckDelegate { 24 | id: roomAccountDataVisibleCheck 25 | text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification") 26 | description: i18n("Allow the user to start a verification session with devices that were already verified") 27 | checked: NeoChatConfig.alwaysVerifyDevice 28 | 29 | onToggled: NeoChatConfig.alwaysVerifyDevice = checked 30 | } 31 | FormCard.FormCheckDelegate { 32 | text: i18nc("@option:check", "Show focus in window header") 33 | checked: NeoChatConfig.windowTitleFocus 34 | 35 | onToggled: { 36 | NeoChatConfig.windowTitleFocus = checked; 37 | NeoChatConfig.save(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/devtools/FeatureFlagPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | FormCard.FormCard { 13 | id: root 14 | 15 | Layout.topMargin: Kirigami.Units.largeSpacing 16 | 17 | FormCard.FormCheckDelegate { 18 | id: roomAccountDataVisibleCheck 19 | text: i18nc("@option:check Enable the matrix 'threads' feature", "Threads") 20 | checked: NeoChatConfig.threads 21 | 22 | onToggled: NeoChatConfig.threads = checked 23 | } 24 | FormCard.FormCheckDelegate { 25 | text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup") 26 | checked: NeoChatConfig.secretBackup 27 | 28 | onToggled: NeoChatConfig.secretBackup = checked 29 | } 30 | FormCard.FormCheckDelegate { 31 | text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs") 32 | checked: NeoChatConfig.phone3PId 33 | 34 | onToggled: { 35 | NeoChatConfig.phone3PId = checked 36 | NeoChatConfig.save(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/devtools/ServerData.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | import org.kde.kitemmodels 11 | 12 | import org.kde.neochat 13 | 14 | ColumnLayout { 15 | id: root 16 | 17 | required property NeoChatConnection connection 18 | 19 | FormCard.FormHeader { 20 | title: i18n("Server Capabilities") 21 | } 22 | FormCard.FormCard { 23 | FormCard.FormTextDelegate { 24 | text: i18n("Can change password") 25 | description: root.connection.canChangePassword 26 | } 27 | } 28 | FormCard.FormHeader { 29 | title: i18n("Default Room Version") 30 | } 31 | FormCard.FormCard { 32 | FormCard.FormTextDelegate { 33 | text: root.connection.defaultRoomVersion 34 | } 35 | } 36 | FormCard.FormHeader { 37 | title: i18n("Available Room Versions") 38 | } 39 | FormCard.FormCard { 40 | Repeater { 41 | model: root.connection.getSupportedRoomVersions() 42 | 43 | delegate: FormCard.FormTextDelegate { 44 | text: modelData.id 45 | contentItem.children: QQC2.Label { 46 | text: modelData.status 47 | color: Kirigami.Theme.disabledTextColor 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/devtools/StateKeys.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import QtQuick.Window 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | import org.kde.kitemmodels 11 | 12 | import org.kde.neochat 13 | 14 | FormCard.FormCardPage { 15 | id: root 16 | 17 | required property NeoChatRoom room 18 | required property string eventType 19 | 20 | title: root.eventType 21 | 22 | FormCard.FormHeader { 23 | title: i18nc("The name of one instance of a state of configuration. Unless you really know what you're doing, best leave this untranslated.", "State Keys") 24 | } 25 | FormCard.FormCard { 26 | Repeater { 27 | model: StateKeysModel { 28 | id: stateKeysModel 29 | room: root.room 30 | eventType: root.eventType 31 | } 32 | 33 | delegate: FormCard.FormButtonDelegate { 34 | text: model.stateKey 35 | onClicked: openEventSource(model.stateKey) 36 | } 37 | } 38 | } 39 | 40 | function openEventSource(stateKey: string): void { 41 | root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), { 42 | model: stateKeysModel, 43 | allowEdit: true, 44 | room: root.room, 45 | type: root.eventType, 46 | stateKey: stateKey 47 | }, { 48 | title: i18nc("@title:window", "Event Source"), 49 | width: Kirigami.Units.gridUnit * 25 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/devtools/models/statefiltermodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "statefiltermodel.h" 5 | 6 | #include "statemodel.h" 7 | 8 | bool StateFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 9 | { 10 | Q_UNUSED(sourceParent); 11 | // No need to run the check if there are no items in m_stateEventTypesFiltered. 12 | if (m_stateEventTypesFiltered.empty()) { 13 | return true; 14 | } 15 | return !m_stateEventTypesFiltered.contains(sourceModel()->data(sourceModel()->index(sourceRow, 0), StateModel::TypeRole).toString()); 16 | } 17 | 18 | void StateFilterModel::addStateEventTypeFiltered(const QString &stateEventType) 19 | { 20 | if (!m_stateEventTypesFiltered.contains(stateEventType)) { 21 | m_stateEventTypesFiltered.append(stateEventType); 22 | invalidateFilter(); 23 | } 24 | } 25 | 26 | void StateFilterModel::removeStateEventTypeFiltered(const QString &stateEventType) 27 | { 28 | if (m_stateEventTypesFiltered.contains(stateEventType)) { 29 | m_stateEventTypesFiltered.removeAll(stateEventType); 30 | invalidateFilter(); 31 | } 32 | } 33 | 34 | #include "moc_statefiltermodel.cpp" 35 | -------------------------------------------------------------------------------- /src/devtools/models/statefiltermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class StateFilterModel 11 | * 12 | * This class creates a custom QSortFilterProxyModel for filtering a list of state events. 13 | * Event types can be filtered out by adding them to m_stateEventTypesFiltered. 14 | */ 15 | class StateFilterModel : public QSortFilterProxyModel 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | 20 | public: 21 | /** 22 | * @brief Custom filter function checking if an event type has been filtered out. 23 | * 24 | * The filter rejects a row if the state event type has been added to m_stateEventTypesFiltered. 25 | * 26 | * @sa m_stateEventTypesFiltered 27 | */ 28 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; 29 | 30 | /** 31 | * @brief Add an event type to m_stateEventTypesFiltered. 32 | * 33 | * @sa m_stateEventTypesFiltered 34 | */ 35 | Q_INVOKABLE void addStateEventTypeFiltered(const QString &stateEventType); 36 | 37 | /** 38 | * @brief Remove an event type from m_stateEventTypesFiltered. 39 | * 40 | * @sa m_stateEventTypesFiltered 41 | */ 42 | Q_INVOKABLE void removeStateEventTypeFiltered(const QString &stateEventType); 43 | 44 | private: 45 | QStringList m_stateEventTypesFiltered; 46 | }; 47 | -------------------------------------------------------------------------------- /src/libneochat/emojitones.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: None 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "emojitones.h" 5 | #include "models/emojimodel.h" 6 | 7 | using namespace Qt::StringLiterals; 8 | 9 | QMultiHash EmojiTones::_tones = { 10 | #include "emojitones_data.h" 11 | }; 12 | -------------------------------------------------------------------------------- /src/libneochat/emojitones.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: None 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | /** 9 | * @class EmojiTones 10 | * 11 | * This class provides a _tones variable with the available emoji tones to EmojiModel. 12 | * 13 | * @sa EmojiModel 14 | */ 15 | class EmojiTones 16 | { 17 | private: 18 | static QMultiHash _tones; 19 | 20 | friend class EmojiModel; 21 | }; 22 | -------------------------------------------------------------------------------- /src/libneochat/enums/messagetype.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class MessageType 11 | * 12 | * This class is designed to define the MessageType enumeration. 13 | */ 14 | class MessageType : public QObject 15 | { 16 | Q_OBJECT 17 | QML_ELEMENT 18 | QML_UNCREATABLE("") 19 | 20 | public: 21 | /** 22 | * @brief The types of messages that can be shown. 23 | */ 24 | enum Type { 25 | Information = 0, /**< Info message, typically highlight color. */ 26 | Positive, /**< Positive message, typically green. */ 27 | Warning, /**< Warning message, typically amber. */ 28 | Error, /**< Error message, typically red. */ 29 | }; 30 | Q_ENUM(Type); 31 | }; 32 | -------------------------------------------------------------------------------- /src/libneochat/enums/roomsortorder.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class RoomSortOrder 11 | * 12 | * This class is designed to define the RoomSortOrder enumeration. 13 | */ 14 | class RoomSortOrder : public QObject 15 | { 16 | Q_OBJECT 17 | QML_ELEMENT 18 | QML_UNCREATABLE("") 19 | 20 | public: 21 | /** 22 | * @brief The types of messages that can be shown. 23 | */ 24 | enum Order { 25 | Alphabetical = 0, /**< The room should be sorted alphabetically. */ 26 | Activity, /**< The room should be sorted by important activity. */ 27 | LastMessage, /**< The room should be sorted by last message in the room. */ 28 | Custom, /**< Use a custom sort order. */ 29 | }; 30 | Q_ENUM(Order); 31 | }; 32 | -------------------------------------------------------------------------------- /src/libneochat/events/locationbeaconevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace Quotient 9 | { 10 | 11 | // Defined so we can directly switch on type. 12 | DEFINE_SIMPLE_STATE_EVENT(LocationBeaconEvent, "org.matrix.msc3672.beacon_info", QString, body, "body") 13 | 14 | } // namespace Quotient 15 | -------------------------------------------------------------------------------- /src/libneochat/events/widgetevent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace Quotient 9 | { 10 | 11 | // Defined so we can directly switch on type. 12 | DEFINE_SIMPLE_STATE_EVENT(WidgetEvent, "im.vector.modular.widgets", QString, name, "name") 13 | 14 | } // namespace Quotient 15 | -------------------------------------------------------------------------------- /src/libneochat/filetransferpseudojob.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class FileTransferPseudoJob 11 | * 12 | * A class inherited from KJob to track a file download. 13 | * 14 | * @sa KJob 15 | */ 16 | class FileTransferPseudoJob : public KJob 17 | { 18 | Q_OBJECT 19 | public: 20 | enum Operation { 21 | Download, 22 | Upload, 23 | }; 24 | Q_ENUM(Operation) 25 | FileTransferPseudoJob(Operation operation, const QString &srcDest, const QString &path); 26 | 27 | /** 28 | * @brief Set the current number of bytes transferred. 29 | */ 30 | void fileTransferProgress(const QString &id, qint64 progress, qint64 total); 31 | 32 | /** 33 | * @brief Set the file transfer as complete. 34 | */ 35 | void fileTransferCompleted(const QString &id, const QUrl &localFile); 36 | 37 | /** 38 | * @brief Set the file transfer as failed. 39 | */ 40 | void fileTransferFailed(const QString &id, const QString &errorMessage = {}); 41 | 42 | /** 43 | * @brief Set the file transfer as canceled. 44 | */ 45 | void fileTransferCanceled(const QString &id); 46 | 47 | /** 48 | * @brief Start the file transfer. 49 | */ 50 | void start() override; 51 | 52 | protected: 53 | bool doKill() override; 54 | 55 | Q_SIGNALS: 56 | void cancelRequested(const QString &id); 57 | 58 | private: 59 | QString m_path; 60 | QString m_eventId; 61 | Operation m_operation; 62 | }; 63 | -------------------------------------------------------------------------------- /src/libneochat/jobs/neochatgetcommonroomsjob.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #include "neochatgetcommonroomsjob.h" 5 | 6 | using namespace Quotient; 7 | 8 | NeochatGetCommonRoomsJob::NeochatGetCommonRoomsJob(const QString &userId) 9 | : BaseJob(HttpVerb::Get, u"GetCommonRoomsJob"_s, "/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms", QUrlQuery({{u"user_id"_s, userId}})) 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/libneochat/jobs/neochatgetcommonroomsjob.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | // TODO: Upstream to libQuotient 9 | class NeochatGetCommonRoomsJob : public Quotient::BaseJob 10 | { 11 | public: 12 | explicit NeochatGetCommonRoomsJob(const QString &userId); 13 | }; 14 | -------------------------------------------------------------------------------- /src/libneochat/messagecomponent.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include "enums/messagecomponenttype.h" 7 | 8 | struct MessageComponent { 9 | MessageComponentType::Type type = MessageComponentType::Other; 10 | QString content; 11 | QVariantMap attributes; 12 | 13 | int operator==(const MessageComponent &right) const 14 | { 15 | return type == right.type && content == right.content && attributes == right.attributes; 16 | } 17 | 18 | bool isEmpty() const 19 | { 20 | return type == MessageComponentType::Other; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/libneochat/models/locationsmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "neochatroom.h" 12 | 13 | #include 14 | #include 15 | 16 | class LocationsModel : public QAbstractListModel 17 | { 18 | Q_OBJECT 19 | QML_ELEMENT 20 | 21 | public: 22 | enum Roles { 23 | TextRole = Qt::DisplayRole, 24 | LongitudeRole, 25 | LatitudeRole, 26 | AssetRole, 27 | AuthorRole, 28 | }; 29 | Q_ENUM(Roles) 30 | Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged) 31 | /** Bounding box of all locations covered by this model. */ 32 | Q_PROPERTY(QRectF boundingBox READ boundingBox NOTIFY boundingBoxChanged) 33 | 34 | explicit LocationsModel(QObject *parent = nullptr); 35 | 36 | [[nodiscard]] NeoChatRoom *room() const; 37 | void setRoom(NeoChatRoom *room); 38 | 39 | QRectF boundingBox() const; 40 | 41 | [[nodiscard]] QHash roleNames() const override; 42 | [[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override; 43 | [[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override; 44 | 45 | Q_SIGNALS: 46 | void roomChanged(); 47 | void boundingBoxChanged(); 48 | 49 | protected: 50 | bool event(QEvent *event) override; 51 | 52 | private: 53 | QPointer m_room; 54 | 55 | struct LocationData { 56 | QString eventId; 57 | float latitude; 58 | float longitude; 59 | QJsonObject content; 60 | Quotient::RoomMember member; 61 | }; 62 | QList m_locations; 63 | void addLocation(const Quotient::RoomMessageEvent *event); 64 | }; 65 | -------------------------------------------------------------------------------- /src/libneochat/models/userfiltermodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "userfiltermodel.h" 5 | 6 | #include "models/userlistmodel.h" 7 | 8 | bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 9 | { 10 | Q_UNUSED(sourceParent); 11 | if (!m_allowEmpty && m_filterText.length() < 1) { 12 | return false; 13 | } 14 | return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive) 15 | || sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive); 16 | } 17 | 18 | QString UserFilterModel::filterText() const 19 | { 20 | return m_filterText; 21 | } 22 | 23 | void UserFilterModel::setFilterText(const QString &filterText) 24 | { 25 | m_filterText = filterText; 26 | Q_EMIT filterTextChanged(); 27 | invalidateFilter(); 28 | } 29 | 30 | bool UserFilterModel::allowEmpty() const 31 | { 32 | return m_allowEmpty; 33 | } 34 | 35 | void UserFilterModel::setAllowEmpty(bool allowEmpty) 36 | { 37 | m_allowEmpty = allowEmpty; 38 | Q_EMIT allowEmptyChanged(); 39 | } 40 | 41 | #include "moc_userfiltermodel.cpp" 42 | -------------------------------------------------------------------------------- /src/libneochat/models/userfiltermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class UserFilterModel 11 | * 12 | * This class creates a custom QSortFilterProxyModel for filtering a users by either 13 | * display name or matrix ID. The filter can accept a full matrix id i.e. example:kde.org 14 | * to separate between accounts on different servers with similar names. 15 | */ 16 | class UserFilterModel : public QSortFilterProxyModel 17 | { 18 | Q_OBJECT 19 | QML_ELEMENT 20 | 21 | /** 22 | * @brief This property hold the text of the filter. 23 | * 24 | * The text is either a desired display name or matrix id. 25 | */ 26 | Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged) 27 | Q_PROPERTY(bool allowEmpty READ allowEmpty WRITE setAllowEmpty NOTIFY allowEmptyChanged) 28 | 29 | public: 30 | /** 31 | * @brief Custom filter function checking boith the display name and matrix ID. 32 | * 33 | * @note The filter cannot be modified and will always use the same filter properties. 34 | */ 35 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; 36 | 37 | QString filterText() const; 38 | void setFilterText(const QString &filterText); 39 | 40 | bool allowEmpty() const; 41 | void setAllowEmpty(bool allowEmpty); 42 | 43 | Q_SIGNALS: 44 | void filterTextChanged(); 45 | void allowEmptyChanged(); 46 | 47 | private: 48 | QString m_filterText; 49 | bool m_allowEmpty = false; 50 | }; 51 | -------------------------------------------------------------------------------- /src/libneochat/roomlastmessageprovider.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "roomlastmessageprovider.h" 5 | 6 | using namespace Qt::Literals::StringLiterals; 7 | 8 | RoomLastMessageProvider::RoomLastMessageProvider() 9 | : m_config(KSharedConfig::openStateConfig()) 10 | , m_configGroup(KConfigGroup(m_config, u"EventCache"_s)) 11 | { 12 | } 13 | 14 | RoomLastMessageProvider::~RoomLastMessageProvider() 15 | { 16 | m_config->sync(); 17 | } 18 | 19 | RoomLastMessageProvider &RoomLastMessageProvider::self() 20 | { 21 | static RoomLastMessageProvider instance; 22 | return instance; 23 | } 24 | 25 | bool RoomLastMessageProvider::hasKey(const QString &roomId) const 26 | { 27 | return m_configGroup.hasKey(roomId); 28 | } 29 | 30 | QByteArray RoomLastMessageProvider::read(const QString &roomId) const 31 | { 32 | return m_configGroup.readEntry(roomId, QByteArray{}); 33 | } 34 | 35 | void RoomLastMessageProvider::write(const QString &roomId, const QByteArray &json) 36 | { 37 | m_configGroup.writeEntry(roomId, json); 38 | } 39 | -------------------------------------------------------------------------------- /src/libneochat/roomlastmessageprovider.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * Store and retrieve the last message of a room. 11 | */ 12 | class RoomLastMessageProvider 13 | { 14 | public: 15 | /** 16 | * Get the global instance of RoomLastMessageProvider. 17 | */ 18 | static RoomLastMessageProvider &self(); 19 | ~RoomLastMessageProvider(); 20 | 21 | /** 22 | * Check if we have the last message content for the specified roomId. 23 | */ 24 | bool hasKey(const QString &roomId) const; 25 | 26 | /** 27 | * Read the last message content of the specified roomId. 28 | */ 29 | QByteArray read(const QString &roomId) const; 30 | 31 | /** 32 | * Write the last message content for the specified roomId. 33 | */ 34 | void write(const QString &roomId, const QByteArray &json); 35 | 36 | private: 37 | RoomLastMessageProvider(); 38 | 39 | KSharedConfig::Ptr m_config; 40 | KConfigGroup m_configGroup; 41 | }; 42 | -------------------------------------------------------------------------------- /src/libneochat/urlhelper.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Nicolas Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #include "urlhelper.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef Q_OS_ANDROID 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | // QDesktopServices::openUrl doesn't support XDG activation yet, OpenUrlJob does 16 | // On Android XDG activation is not relevant, so use QDesktopServices::openUrl to avoid the heavy KIO dependency 17 | void UrlHelper::openUrl(const QUrl &url) 18 | { 19 | #ifdef Q_OS_ANDROID 20 | QDesktopServices::openUrl(url); 21 | #else 22 | auto *job = new KIO::OpenUrlJob(url); 23 | job->start(); 24 | #endif 25 | } 26 | 27 | void UrlHelper::copyTo(const QUrl &origin, const QUrl &destination) 28 | { 29 | QFile originFile(origin.toLocalFile()); 30 | originFile.copy(destination.toLocalFile()); 31 | } 32 | 33 | #include "moc_urlhelper.cpp" 34 | -------------------------------------------------------------------------------- /src/libneochat/urlhelper.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Nicolas Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /** 11 | * @class UrlHelper 12 | * 13 | * A class to help manage URLs. 14 | */ 15 | class UrlHelper : public QObject 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | QML_SINGLETON 20 | 21 | public: 22 | /** 23 | * @brief Open the given URL in an appropriate app. 24 | */ 25 | Q_INVOKABLE void openUrl(const QUrl &url); 26 | 27 | /** 28 | * @brief Copy the given URL to the given location. 29 | */ 30 | Q_INVOKABLE void copyTo(const QUrl &origin, const QUrl &destination); 31 | }; 32 | -------------------------------------------------------------------------------- /src/login/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(Login STATIC) 5 | ecm_add_qml_module(Login GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.login 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login 8 | QML_FILES 9 | WelcomePage.qml 10 | LoginStep.qml 11 | Captcha.qml 12 | Email.qml 13 | Homeserver.qml 14 | Loading.qml 15 | Login.qml 16 | LoginMethod.qml 17 | LoginRegister.qml 18 | Password.qml 19 | RegisterPassword.qml 20 | Sso.qml 21 | Terms.qml 22 | Username.qml 23 | SOURCES 24 | login.cpp 25 | registration.cpp 26 | ) 27 | 28 | target_link_libraries(Login PRIVATE 29 | QuotientQt6 30 | LibNeoChat 31 | ) 32 | -------------------------------------------------------------------------------- /src/login/Captcha.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import QtWebView 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | import org.kde.neochat 12 | 13 | LoginStep { 14 | id: root 15 | 16 | FormCard.AbstractFormDelegate { 17 | background: null 18 | contentItem: WebView { 19 | id: webview 20 | url: "http://localhost:20847" 21 | implicitHeight: 500 22 | onLoadingChanged: { 23 | webview.runJavaScript("document.body.style.background = '" + Kirigami.Theme.backgroundColor + "'"); 24 | } 25 | 26 | Timer { 27 | id: timer 28 | repeat: true 29 | running: true 30 | interval: 300 31 | onTriggered: { 32 | if (!webview.visible) { 33 | return; 34 | } 35 | webview.runJavaScript("!!grecaptcha ? grecaptcha.getResponse() : \"\"", function (response) { 36 | if (!webview.visible || !response) 37 | return; 38 | timer.running = false; 39 | Registration.recaptchaResponse = response; 40 | }); 41 | } 42 | } 43 | } 44 | } 45 | previousAction: Kirigami.Action { 46 | onTriggered: root.processed("Username") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/login/Email.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | LoginStep { 13 | id: root 14 | 15 | onActiveFocusChanged: if (activeFocus) { 16 | emailField.forceActiveFocus(); 17 | } 18 | 19 | FormCard.FormTextFieldDelegate { 20 | id: emailField 21 | label: i18n("Add an e-mail address:") 22 | placeholderText: "user@example.com" 23 | onTextChanged: Registration.email = text 24 | Keys.onReturnPressed: { 25 | if (root.nextAction.enabled) { 26 | root.nextAction.trigger(); 27 | } 28 | } 29 | } 30 | 31 | FormCard.FormTextDelegate { 32 | id: confirmMessage 33 | text: i18n("Confirm e-mail address") 34 | visible: false 35 | description: i18n("A confirmation e-mail has been sent to your address. Please continue here after clicking on the confirmation link in the e-mail") 36 | } 37 | 38 | FormCard.FormButtonDelegate { 39 | id: resendButton 40 | text: i18nc("@button", "Re-send confirmation e-mail") 41 | onClicked: Registration.registerEmail() 42 | visible: false 43 | } 44 | 45 | nextAction: Kirigami.Action { 46 | enabled: emailField.text.length > 0 47 | onTriggered: { 48 | if (confirmMessage.visible) { 49 | Registration.registerAccount(); 50 | } else { 51 | Registration.registerEmail(); 52 | confirmMessage.visible = true; 53 | resendButton.visible = true; 54 | } 55 | } 56 | } 57 | previousAction: Kirigami.Action { 58 | onTriggered: root.processed("Username") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/login/Loading.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Tobias Fella 2 | // SPDX-License-Identifier: GPL-3.0-only 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | LoginStep { 13 | id: root 14 | 15 | FormCard.FormTextDelegate { 16 | textItem.wrapMode: Text.Wrap 17 | text: i18n("Please wait while your messages are loaded from the server. This might take a little while.") 18 | } 19 | FormCard.AbstractFormDelegate { 20 | contentItem: QQC2.BusyIndicator {} 21 | background: null 22 | } 23 | 24 | Connections { 25 | target: Controller 26 | function onConnectionAdded(connection) { 27 | connection.syncDone.connect(() => root.closeDialog()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/login/Login.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // SPDX-FileCopyrightText: 2020 Tobias Fella 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | import QtQuick 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | import org.kde.neochat 12 | 13 | LoginStep { 14 | id: root 15 | 16 | onActiveFocusChanged: if (activeFocus) 17 | matrixIdField.forceActiveFocus() 18 | 19 | Component.onCompleted: { 20 | LoginHelper.matrixId = ""; 21 | } 22 | 23 | FormCard.FormTextFieldDelegate { 24 | id: matrixIdField 25 | label: i18n("Matrix ID:") 26 | text: LoginHelper.matrixId 27 | placeholderText: "@user:example.org" 28 | Accessible.name: i18n("Matrix ID") 29 | onTextChanged: { 30 | LoginHelper.matrixId = text; 31 | } 32 | 33 | Keys.onReturnPressed: { 34 | root.nextAction.trigger(); 35 | } 36 | } 37 | 38 | nextAction: Kirigami.Action { 39 | text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue") 40 | onTriggered: { 41 | if (LoginHelper.supportsSso && LoginHelper.supportsPassword) { 42 | processed("LoginMethod"); 43 | } else if (LoginHelper.supportsSso) { 44 | processed("Sso"); 45 | } else { 46 | processed("Password"); 47 | } 48 | } 49 | enabled: LoginHelper.homeserverReachable 50 | } 51 | previousAction: Kirigami.Action { 52 | onTriggered: { 53 | root.processed("LoginRegister"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/login/LoginMethod.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | LoginStep { 12 | id: root 13 | 14 | onActiveFocusChanged: if (activeFocus) { 15 | loginPasswordButton.forceActiveFocus(); 16 | } 17 | 18 | FormCard.FormButtonDelegate { 19 | id: loginPasswordButton 20 | text: i18nc("@action:button", "Login with password") 21 | onClicked: processed("Password") 22 | } 23 | 24 | FormCard.FormButtonDelegate { 25 | id: loginSsoButton 26 | text: i18nc("@action:button", "Login with single sign-on") 27 | onClicked: processed("Sso") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/login/LoginRegister.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | LoginStep { 12 | id: root 13 | 14 | onActiveFocusChanged: if (activeFocus) { 15 | loginButton.forceActiveFocus(Qt.TabFocusReason); 16 | } 17 | 18 | Layout.fillWidth: true 19 | 20 | spacing: 0 21 | 22 | FormCard.FormButtonDelegate { 23 | id: loginButton 24 | text: i18nc("@action:button", "Login") 25 | onClicked: root.processed("Login") 26 | } 27 | 28 | FormCard.FormDelegateSeparator {} 29 | 30 | FormCard.FormButtonDelegate { 31 | text: i18nc("@action:button", "Register") 32 | onClicked: root.processed("Homeserver") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/login/LoginStep.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | /// Step for the login/registration flow 9 | ColumnLayout { 10 | id: root 11 | 12 | /// Set to true if the login step does not have any controls. This will ensure that the focus remains on the "continue" button 13 | property bool noControls: false 14 | 15 | /// Process this module, this is called by the continue button. 16 | /// Should call \sa processed when it finish successfully. 17 | property QQC2.Action nextAction: null 18 | 19 | /// Go to the previous module. This is called by the "go back" button. 20 | /// If no "go back" button should be shown, this should be null. 21 | property QQC2.Action previousAction: null 22 | 23 | /// Called when switching to the next step. 24 | signal processed(string nextComponent) 25 | 26 | /// Show a message in a banner at the top of the page. 27 | signal showMessage(string message) 28 | 29 | /// Clears any error messages currently being shown 30 | signal clearError 31 | 32 | /// Closes the login dialog 33 | signal closeDialog 34 | } 35 | -------------------------------------------------------------------------------- /src/login/Password.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | LoginStep { 13 | id: root 14 | 15 | Connections { 16 | target: LoginHelper 17 | function onConnected() { 18 | processed("Loading"); 19 | } 20 | } 21 | 22 | onActiveFocusChanged: if (activeFocus) 23 | passwordField.forceActiveFocus() 24 | 25 | FormCard.FormTextFieldDelegate { 26 | id: passwordField 27 | 28 | label: i18n("Password:") 29 | onTextChanged: LoginHelper.password = text 30 | enabled: !LoginHelper.isLoggingIn 31 | echoMode: TextInput.Password 32 | Accessible.name: i18n("Password") 33 | statusMessage: LoginHelper.isInvalidPassword ? i18n("Invalid username or password") : "" 34 | 35 | Keys.onReturnPressed: { 36 | root.nextAction.trigger(); 37 | } 38 | } 39 | 40 | nextAction: Kirigami.Action { 41 | text: i18nc("@action:button", "Login") 42 | enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn 43 | onTriggered: { 44 | root.clearError(); 45 | LoginHelper.login(); 46 | } 47 | } 48 | previousAction: Kirigami.Action { 49 | onTriggered: processed("Login") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/login/RegisterPassword.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | LoginStep { 12 | id: root 13 | 14 | onActiveFocusChanged: if (activeFocus) { 15 | passwordField.forceActiveFocus(); 16 | } 17 | 18 | FormCard.FormTextFieldDelegate { 19 | id: passwordField 20 | label: i18n("Password:") 21 | echoMode: TextInput.Password 22 | onTextChanged: Registration.password = text 23 | Keys.onReturnPressed: { 24 | confirmPasswordField.forceActiveFocus(); 25 | } 26 | } 27 | 28 | FormCard.FormTextFieldDelegate { 29 | id: confirmPasswordField 30 | label: i18n("Confirm Password:") 31 | enabled: passwordField.enabled 32 | echoMode: TextInput.Password 33 | statusMessage: passwordField.text.length === confirmPasswordField.text.length && passwordField.text !== confirmPasswordField.text ? i18n("The passwords do not match.") : "" 34 | Keys.onReturnPressed: { 35 | if (root.nextAction.enabled) { 36 | root.nextAction.trigger(); 37 | } 38 | } 39 | } 40 | 41 | nextAction: Kirigami.Action { 42 | onTriggered: { 43 | passwordField.enabled = false; 44 | Registration.registerAccount(); 45 | } 46 | enabled: passwordField.text === confirmPasswordField.text 47 | } 48 | 49 | previousAction: Kirigami.Action { 50 | onTriggered: root.processed("Username") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/login/Sso.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | LoginStep { 13 | id: root 14 | 15 | noControls: true 16 | 17 | Component.onCompleted: LoginHelper.loginWithSso() 18 | 19 | Connections { 20 | target: LoginHelper 21 | function onSsoUrlChanged() { 22 | UrlHelper.openUrl(LoginHelper.ssoUrl); 23 | } 24 | function onConnected() { 25 | processed("Loading"); 26 | } 27 | } 28 | 29 | FormCard.FormTextDelegate { 30 | text: i18n("Continue the login process in your browser.") 31 | } 32 | 33 | previousAction: Kirigami.Action { 34 | onTriggered: processed("Login") 35 | } 36 | 37 | nextAction: Kirigami.Action { 38 | text: i18nc("@action:button", "Re-open SSO URL") 39 | onTriggered: UrlHelper.openUrl(LoginHelper.ssoUrl) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/login/Terms.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat 11 | 12 | LoginStep { 13 | id: root 14 | 15 | noControls: true 16 | 17 | FormCard.FormTextDelegate { 18 | text: i18n("Terms & Conditions") 19 | description: i18n("By continuing with the registration, you agree to the following terms and conditions:") 20 | } 21 | 22 | Repeater { 23 | model: Registration.terms 24 | delegate: FormCard.FormTextDelegate { 25 | text: "" + modelData.title + "" 26 | onLinkActivated: Qt.openUrlExternally(modelData.url) 27 | } 28 | } 29 | 30 | nextAction: Kirigami.Action { 31 | onTriggered: { 32 | Registration.registerAccount(); 33 | } 34 | } 35 | previousAction: Kirigami.Action { 36 | onTriggered: root.processed("Username") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/login/Username.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigami as Kirigami 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | LoginStep { 12 | id: root 13 | 14 | onActiveFocusChanged: if (activeFocus) { 15 | usernameField.forceActiveFocus(); 16 | } 17 | 18 | FormCard.FormTextFieldDelegate { 19 | id: usernameField 20 | label: i18n("Username:") 21 | placeholderText: "user" 22 | onTextChanged: timer.restart() 23 | statusMessage: Registration.status === Registration.UsernameTaken ? i18n("Username unavailable") : "" 24 | Keys.onReturnPressed: { 25 | if (root.nextAction.enabled) { 26 | root.nextAction.trigger(); 27 | } 28 | } 29 | } 30 | 31 | Timer { 32 | id: timer 33 | interval: 500 34 | onTriggered: Registration.username = usernameField.text 35 | } 36 | 37 | nextAction: Kirigami.Action { 38 | text: Registration.status === Registration.TestingUsername ? i18n("Loading") : null 39 | 40 | onTriggered: root.processed("RegisterPassword") 41 | enabled: Registration.status === Registration.Ready 42 | } 43 | 44 | previousAction: Kirigami.Action { 45 | onTriggered: root.processed("Homeserver") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/purpose/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Tobias Fella 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | kcoreaddons_add_plugin(neochatshareplugin SOURCES purposeplugin.cpp INSTALL_NAMESPACE "kf6/purpose") 5 | target_link_libraries(neochatshareplugin 6 | Qt::DBus 7 | KF6::Purpose 8 | KF6::KIOGui 9 | ) 10 | -------------------------------------------------------------------------------- /src/purpose/purposeplugin.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace Qt::StringLiterals; 9 | 10 | class NeoChatJob : public Purpose::Job 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit NeoChatJob(QObject *parent) 15 | : Purpose::Job(parent) 16 | { 17 | } 18 | 19 | QStringList arrayToList(const QJsonArray &array) 20 | { 21 | QStringList ret; 22 | for (const auto &val : array) { 23 | ret += val.toString(); 24 | } 25 | return ret; 26 | } 27 | 28 | void start() override 29 | { 30 | const QJsonArray urlsJson = data().value("urls"_L1).toArray(); 31 | const QString title = data().value("title"_L1).toString(); 32 | const QString message = u"%1 - %2"_s.arg(title, arrayToList(urlsJson).join(QLatin1Char(' '))); 33 | 34 | auto *job = new KIO::CommandLauncherJob(u"neochat"_s, {u"--share"_s, message}); 35 | connect(job, &KJob::finished, this, &NeoChatJob::emitResult); 36 | job->start(); 37 | } 38 | }; 39 | 40 | class Q_DECL_EXPORT PurposePlugin : public Purpose::PluginBase 41 | { 42 | Q_OBJECT 43 | public: 44 | PurposePlugin(QObject *p, const QVariantList &) 45 | : Purpose::PluginBase(p) 46 | { 47 | } 48 | 49 | Purpose::Job *createJob() const override 50 | { 51 | return new NeoChatJob(nullptr); 52 | } 53 | }; 54 | 55 | K_PLUGIN_CLASS_WITH_JSON(PurposePlugin, "purposeplugin.json") 56 | 57 | #include "purposeplugin.moc" 58 | -------------------------------------------------------------------------------- /src/roominfo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(RoomInfo STATIC) 5 | ecm_add_qml_module(RoomInfo GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.roominfo 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/roominfo 8 | QML_FILES 9 | RoomDrawer.qml 10 | RoomDrawerPage.qml 11 | RoomInformation.qml 12 | RoomMedia.qml 13 | DirectChatDrawerHeader.qml 14 | LocationsPage.qml 15 | RoomPinnedMessagesPage.qml 16 | RoomSearchPage.qml 17 | ) 18 | -------------------------------------------------------------------------------- /src/roominfo/RoomPinnedMessagesPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Joshua Goins 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat.libneochat 10 | import org.kde.neochat.timeline 11 | 12 | /** 13 | * @brief Component for showing the pinned messages in a room. 14 | */ 15 | Kirigami.ScrollablePage { 16 | id: root 17 | 18 | /** 19 | * @brief The room to show the pinned messages for. 20 | */ 21 | required property NeoChatRoom room 22 | 23 | title: i18nc("@title", "Pinned Messages") 24 | 25 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 26 | Kirigami.Theme.inherit: false 27 | 28 | ListView { 29 | id: listView 30 | spacing: 0 31 | 32 | model: PinnedMessageModel { 33 | id: pinModel 34 | room: root.room 35 | } 36 | 37 | delegate: EventDelegate { 38 | room: root.room 39 | } 40 | 41 | section.property: "section" 42 | 43 | Kirigami.PlaceholderMessage { 44 | icon.name: "pin-symbolic" 45 | anchors.centerIn: parent 46 | text: i18nc("@info:placeholder", "No Pinned Messages") 47 | visible: listView.count === 0 48 | } 49 | 50 | Kirigami.LoadingPlaceholder { 51 | anchors.centerIn: parent 52 | visible: listView.count === 0 && pinModel.loading 53 | } 54 | 55 | Keys.onUpPressed: { 56 | if (listView.currentIndex > 0) { 57 | listView.decrementCurrentIndex(); 58 | } else { 59 | listView.currentIndex = -1; // This is so the list view doesn't appear to have two selected items 60 | listView.headerItem.forceActiveFocus(Qt.TabFocusReason); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/roominfo/RoomSearchPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | 6 | import org.kde.neochat.libneochat 7 | import org.kde.neochat.timeline 8 | 9 | /** 10 | * @brief Component for finding messages in a room. 11 | * 12 | * This component is based on a SearchPage and allows the user to enter a search 13 | * term into the input field and then search the room for messages with text that 14 | * matches the input. 15 | * 16 | * @sa SearchPage 17 | */ 18 | SearchPage { 19 | id: root 20 | 21 | /** 22 | * @brief The room the search is being performed in. 23 | */ 24 | required property NeoChatRoom room 25 | 26 | title: i18nc("@action:title", "Search Messages") 27 | 28 | model: SearchModel { 29 | id: searchModel 30 | room: root.room 31 | } 32 | 33 | modelDelegate: EventDelegate { 34 | room: root.room 35 | } 36 | 37 | searchFieldPlaceholder: i18n("Find messages…") 38 | noSearchPlaceholderMessage: i18n("Enter text to start searching") 39 | noResultPlaceholderMessage: i18n("No messages found") 40 | 41 | listVerticalLayoutDirection: ListView.BottomToTop 42 | } 43 | -------------------------------------------------------------------------------- /src/rooms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(Rooms STATIC) 5 | ecm_add_qml_module(Rooms GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.rooms 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/rooms 8 | QML_FILES 9 | RoomListPage.qml 10 | SpaceDrawer.qml 11 | RoomDelegate.qml 12 | RoomTreeSection.qml 13 | ExploreComponent.qml 14 | ExploreComponentMobile.qml 15 | UserInfo.qml 16 | UserInfoDesktop.qml 17 | RoomContextMenu.qml 18 | SpaceListContextMenu.qml 19 | SOURCES 20 | models/publicroomlistmodel.cpp 21 | models/roomtreeitem.cpp 22 | models/roomtreemodel.cpp 23 | models/sortfilterroomlistmodel.cpp 24 | models/sortfilterroomtreemodel.cpp 25 | models/sortfilterspacelistmodel.cpp 26 | ) 27 | 28 | ecm_qt_declare_logging_category(Rooms 29 | HEADER "publicroomlist_logging.h" 30 | IDENTIFIER "PublicRoomList" 31 | CATEGORY_NAME "org.kde.neochat.publicroomlistmodel" 32 | DESCRIPTION "Neochat: publicroomlistmodel" 33 | DEFAULT_SEVERITY Info 34 | EXPORT NEOCHAT 35 | ) 36 | 37 | target_include_directories(Rooms PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models) 38 | target_link_libraries(Rooms PRIVATE 39 | Qt::Core 40 | Qt::Quick 41 | KF6::Kirigami 42 | LibNeoChat 43 | ) 44 | -------------------------------------------------------------------------------- /src/rooms/UserInfoDesktop.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | import org.kde.neochat 11 | 12 | QQC2.ToolBar { 13 | id: root 14 | 15 | /** 16 | * @brief The connection for the current user. 17 | */ 18 | required property NeoChatConnection connection 19 | 20 | property bool collapsed: false 21 | 22 | padding: 0 23 | 24 | background: Rectangle { 25 | color: Kirigami.Theme.backgroundColor 26 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 27 | Kirigami.Theme.inherit: false 28 | } 29 | 30 | contentItem: ColumnLayout { 31 | spacing: 0 32 | Kirigami.Separator { 33 | Layout.fillWidth: true 34 | } 35 | UserInfo { 36 | collapsed: root.collapsed 37 | bottomEdge: true 38 | connection: root.connection 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/rooms/models/sortfilterspacelistmodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Snehit Sah 2 | // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | 4 | #include "sortfilterspacelistmodel.h" 5 | 6 | #include "models/roomlistmodel.h" 7 | 8 | using namespace Qt::StringLiterals; 9 | 10 | SortFilterSpaceListModel::SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent) 11 | : QSortFilterProxyModel{parent} 12 | { 13 | Q_ASSERT(sourceModel); 14 | setSourceModel(sourceModel); 15 | 16 | connect(this->sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList roles) { 17 | if (roles.contains(RoomListModel::IsChildSpaceRole)) { 18 | invalidate(); 19 | } 20 | Q_EMIT countChanged(); 21 | }); 22 | 23 | setSortRole(RoomListModel::RoomIdRole); 24 | sort(0); 25 | } 26 | 27 | bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 28 | { 29 | Q_UNUSED(source_parent); 30 | return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() 31 | && sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != u"upgraded"_s 32 | && !sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsChildSpaceRole).toBool(); 33 | } 34 | 35 | bool SortFilterSpaceListModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const 36 | { 37 | const auto idLeft = sourceModel()->data(source_left, RoomListModel::RoomIdRole).toString(); 38 | const auto idRight = sourceModel()->data(source_right, RoomListModel::RoomIdRole).toString(); 39 | return idLeft < idRight; 40 | } 41 | 42 | #include "moc_sortfilterspacelistmodel.cpp" 43 | -------------------------------------------------------------------------------- /src/rooms/models/sortfilterspacelistmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Snehit Sah 2 | // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "models/roomlistmodel.h" 10 | 11 | /** 12 | * @class SortFilterSpaceListModel 13 | * 14 | * This model sorts and filters the space list. 15 | * 16 | * The spaces are sorted by their matrix ID. The filter only shows space rooms, 17 | * but filters out upgraded spaces. 18 | */ 19 | class SortFilterSpaceListModel : public QSortFilterProxyModel 20 | { 21 | Q_OBJECT 22 | QML_ELEMENT 23 | QML_UNCREATABLE("") 24 | 25 | /** 26 | * @brief The number of spaces in the model. 27 | */ 28 | Q_PROPERTY(int count READ rowCount NOTIFY countChanged) 29 | 30 | public: 31 | explicit SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent = nullptr); 32 | 33 | Q_SIGNALS: 34 | void countChanged(); 35 | 36 | protected: 37 | /** 38 | * @brief Returns true if the value of source_left is less than source_right. 39 | * 40 | * @sa QSortFilterProxyModel::lessThan 41 | */ 42 | [[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; 43 | 44 | /** 45 | * @brief Whether a row should be shown out or not. 46 | * 47 | * @sa QSortFilterProxyModel::filterAcceptsRow 48 | */ 49 | [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; 50 | }; 51 | -------------------------------------------------------------------------------- /src/settings/ColorScheme.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | FormCard.FormComboBoxDelegate { 12 | id: root 13 | 14 | text: i18n("Color theme") 15 | textRole: "display" 16 | valueRole: "display" 17 | model: ColorSchemer.model 18 | Component.onCompleted: currentIndex = ColorSchemer.indexForCurrentScheme() 19 | onCurrentValueChanged: ColorSchemer.apply(currentIndex); 20 | } 21 | -------------------------------------------------------------------------------- /src/settings/ConfirmDeactivateAccountDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import QtQuick.Controls as QQC2 7 | 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | import org.kde.kirigami as Kirigami 10 | import org.kde.neochat 11 | 12 | Kirigami.PromptDialog { 13 | id: root 14 | 15 | required property NeoChatConnection connection 16 | 17 | title: i18nc("@title:dialog", "Confirm Account Deactivation") 18 | subtitle: i18n("Your account will be permanently disabled.\nThis cannot be undone.\nYour Matrix ID will not be available for new accounts.\nYour messages will stay available.") 19 | 20 | dialogType: Kirigami.PromptDialog.Warning 21 | 22 | mainItem: ColumnLayout { 23 | FormCard.FormTextFieldDelegate { 24 | id: passwordField 25 | label: i18nc("@label:textbox", "Password") 26 | echoMode: TextInput.Password 27 | horizontalPadding: 0 28 | bottomPadding: 0 29 | } 30 | 31 | FormCard.FormCheckDelegate { 32 | id: eraseDelegate 33 | text: i18nc("@label:checkbox", "Erase Data") 34 | description: i18nc("@info", "Request your server to delete as much user data as possible.") 35 | visible: connection.canEraseData 36 | } 37 | } 38 | 39 | footer: QQC2.DialogButtonBox { 40 | standardButtons: QQC2.Dialog.Cancel 41 | 42 | QQC2.Button { 43 | text: i18n("Deactivate account") 44 | icon.name: "emblem-warning" 45 | enabled: passwordField.text.length > 0 46 | onClicked: { 47 | root.connection.deactivateAccount(passwordField.text, eraseDelegate.checked); 48 | root.closeDialog(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/settings/ConfirmEncryptionDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Carl Schwan 2 | // SPDX-FileCopyrightText: 2022 Tobias Fella 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | import QtQuick 6 | import QtQuick.Controls as QQC2 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | Kirigami.PromptDialog { 12 | id: root 13 | 14 | title: i18nc("@title:dialog", "Activate Encryption") 15 | subtitle: i18n("It will not be possible to deactivate the encryption after it is enabled.") 16 | dialogType: Kirigami.PromptDialog.Warning 17 | 18 | property NeoChatRoom room 19 | 20 | onRejected: { 21 | root.close(); 22 | } 23 | 24 | footer: QQC2.DialogButtonBox { 25 | standardButtons: QQC2.Dialog.Cancel 26 | 27 | QQC2.Button { 28 | text: i18n("Activate Encryption") 29 | QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole 30 | onClicked: { 31 | root.room.activateEncryption(); 32 | root.close(); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/settings/DevicesCard.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 - 2023 Tobias Fella 2 | // SPDX-FileCopyrightText: 2022 James Graham 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | import QtQuick 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | import org.kde.neochat 12 | 13 | ColumnLayout { 14 | id: root 15 | 16 | required property string title 17 | required property var type 18 | required property bool showVerifyButton 19 | 20 | visible: deviceRepeater.count > 0 21 | 22 | FormCard.FormHeader { 23 | title: root.title 24 | } 25 | 26 | FormCard.FormCard { 27 | id: devicesCard 28 | 29 | Repeater { 30 | id: deviceRepeater 31 | model: DevicesProxyModel { 32 | sourceModel: devicesModel 33 | type: root.type 34 | } 35 | 36 | Kirigami.LoadingPlaceholder { 37 | visible: deviceModel.count === 0 // We can assume 0 means loading since there is at least one device 38 | anchors.centerIn: parent 39 | } 40 | 41 | delegate: DeviceDelegate { 42 | showVerifyButton: root.showVerifyButton 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/settings/EmoticonsPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | 9 | import org.kde.neochat 10 | 11 | FormCard.FormCardPage { 12 | id: root 13 | 14 | required property NeoChatConnection connection 15 | 16 | title: i18nc("@title:window", "Stickers & Emojis") 17 | 18 | FormCard.FormHeader { 19 | title: i18nc("@title:group", "Emojis") 20 | } 21 | EmoticonFormCard { 22 | emoticonType: EmoticonFormCard.Emojis 23 | connection: root.connection 24 | } 25 | 26 | FormCard.FormHeader { 27 | title: i18nc("@title:group", "Stickers") 28 | } 29 | EmoticonFormCard { 30 | emoticonType: EmoticonFormCard.Stickers 31 | connection: root.connection 32 | } 33 | 34 | property Component emoticonEditorPage: Component { 35 | id: emoticonEditorPage 36 | EmoticonEditorPage {} 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/settings/PasswordSheet.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | Kirigami.Dialog { 12 | id: root 13 | 14 | signal submitPassword(string password) 15 | 16 | title: i18nc("@title:dialog", "Enter password") 17 | 18 | preferredWidth: Kirigami.Units.gridUnit * 24 19 | 20 | standardButtons: QQC2.Dialog.Ok | QQC2.Dialog.Cancel 21 | onAccepted: { 22 | root.submitPassword(passwordField.text); 23 | passwordField.text = ""; 24 | root.close(); 25 | } 26 | 27 | ColumnLayout { 28 | FormCard.FormTextFieldDelegate { 29 | id: passwordField 30 | label: i18nc("@label:textbox", "Password:") 31 | echoMode: TextInput.Password 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/settings/PowerLevelDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | import org.kde.neochat 12 | 13 | Kirigami.Dialog { 14 | id: root 15 | title: i18nc("@title", "Edit User Power Level") 16 | 17 | property NeoChatRoom room 18 | property var userId 19 | property int powerLevel 20 | 21 | width: Kirigami.Units.gridUnit * 24 22 | 23 | standardButtons: QQC2.Dialog.NoButton 24 | 25 | onOpened: { 26 | if (root.opened) { 27 | powerLevelComboBox.currentIndex = powerLevelComboBox.indexOfValue(root.powerLevel); 28 | } 29 | } 30 | 31 | ColumnLayout { 32 | FormCard.FormComboBoxDelegate { 33 | id: powerLevelComboBox 34 | 35 | text: i18n("New power level") 36 | model: PowerLevelModel {} 37 | textRole: "name" 38 | valueRole: "value" 39 | } 40 | } 41 | customFooterActions: [ 42 | Kirigami.Action { 43 | text: i18n("Confirm") 44 | icon.name: "dialog-ok" 45 | onTriggered: { 46 | root.room.setUserPowerLevel(root.userId, powerLevelComboBox.currentValue); 47 | root.close(); 48 | root.destroy(); 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/settings/colorschemer.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #include 5 | #include 6 | 7 | #include "colorschemer.h" 8 | 9 | ColorSchemer::ColorSchemer(QObject *parent) 10 | : QObject(parent) 11 | { 12 | KColorSchemeManager::instance(); 13 | } 14 | 15 | ColorSchemer::~ColorSchemer() 16 | { 17 | } 18 | 19 | QAbstractItemModel *ColorSchemer::model() const 20 | { 21 | return KColorSchemeManager::instance()->model(); 22 | } 23 | 24 | void ColorSchemer::apply(int idx) 25 | { 26 | KColorSchemeManager::instance()->activateScheme(KColorSchemeManager::instance()->model()->index(idx, 0)); 27 | } 28 | 29 | int ColorSchemer::indexForCurrentScheme() 30 | { 31 | return KColorSchemeManager::instance()->indexForSchemeId(KColorSchemeManager::instance()->activeSchemeId()).row(); 32 | } 33 | 34 | #include "moc_colorschemer.cpp" 35 | -------------------------------------------------------------------------------- /src/settings/colorschemer.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.1-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class QAbstractItemModel; 10 | class KColorSchemeManager; 11 | 12 | /** 13 | * @class ColorSchemer 14 | * 15 | * A class to provide a wrapper around KColorSchemeManager to make it available in 16 | * QML. 17 | * 18 | * @sa KColorSchemeManager 19 | */ 20 | class ColorSchemer : public QObject 21 | { 22 | Q_OBJECT 23 | QML_ELEMENT 24 | QML_SINGLETON 25 | 26 | /** 27 | * @brief A QAbstractItemModel of all available color schemes. 28 | * 29 | * @sa QAbstractItemModel 30 | */ 31 | Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT) 32 | 33 | public: 34 | explicit ColorSchemer(QObject *parent = nullptr); 35 | ~ColorSchemer(); 36 | 37 | QAbstractItemModel *model() const; 38 | 39 | /** 40 | * @brief Activates the KColorScheme identified by the provided index. 41 | * 42 | * @sa KColorScheme 43 | */ 44 | Q_INVOKABLE void apply(int idx); 45 | 46 | /** 47 | * @brief Get the row for the current color scheme. 48 | * 49 | * @sa KColorScheme 50 | */ 51 | Q_INVOKABLE int indexForCurrentScheme(); 52 | }; 53 | -------------------------------------------------------------------------------- /src/settings/models/devicesproxymodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #include "devicesproxymodel.h" 5 | #include "devicesmodel.h" 6 | 7 | int DevicesProxyModel::type() const 8 | { 9 | return m_type; 10 | } 11 | void DevicesProxyModel::setType(int type) 12 | { 13 | m_type = type; 14 | Q_EMIT typeChanged(); 15 | } 16 | 17 | bool DevicesProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 18 | { 19 | Q_UNUSED(source_parent) 20 | return sourceModel()->data(sourceModel()->index(source_row, 0), DevicesModel::Type).toInt() == m_type; 21 | } 22 | DevicesProxyModel::DevicesProxyModel(QObject *parent) 23 | : QSortFilterProxyModel(parent) 24 | , m_type(0) 25 | { 26 | setSortRole(DevicesModel::LastTimestamp); 27 | sort(0, Qt::DescendingOrder); 28 | } 29 | 30 | #include "moc_devicesproxymodel.cpp" 31 | -------------------------------------------------------------------------------- /src/settings/models/devicesproxymodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class DevicesProxyModel : public QSortFilterProxyModel 10 | { 11 | Q_OBJECT 12 | QML_ELEMENT 13 | 14 | Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged) 15 | 16 | public: 17 | DevicesProxyModel(QObject *parent = nullptr); 18 | [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; 19 | 20 | void setType(int type); 21 | [[nodiscard]] int type() const; 22 | 23 | Q_SIGNALS: 24 | void typeChanged(); 25 | 26 | private: 27 | int m_type; 28 | }; 29 | -------------------------------------------------------------------------------- /src/settings/models/emoticonfiltermodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #include "emoticonfiltermodel.h" 5 | 6 | #include "accountemoticonmodel.h" 7 | #include "models/stickermodel.h" 8 | 9 | EmoticonFilterModel::EmoticonFilterModel(QObject *parent) 10 | : QSortFilterProxyModel(parent) 11 | { 12 | connect(this, &EmoticonFilterModel::sourceModelChanged, this, [this]() { 13 | if (dynamic_cast(sourceModel())) { 14 | m_stickerRole = StickerModel::IsStickerRole; 15 | m_emojiRole = StickerModel::IsEmojiRole; 16 | } else { 17 | m_stickerRole = AccountEmoticonModel::IsStickerRole; 18 | m_emojiRole = AccountEmoticonModel::IsEmojiRole; 19 | } 20 | }); 21 | } 22 | 23 | bool EmoticonFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 24 | { 25 | Q_UNUSED(sourceParent); 26 | auto stickerUsage = sourceModel()->data(sourceModel()->index(sourceRow, 0), m_stickerRole).toBool(); 27 | auto emojiUsage = sourceModel()->data(sourceModel()->index(sourceRow, 0), m_emojiRole).toBool(); 28 | return (stickerUsage && m_showStickers) || (emojiUsage && m_showEmojis); 29 | } 30 | 31 | bool EmoticonFilterModel::showStickers() const 32 | { 33 | return m_showStickers; 34 | } 35 | 36 | void EmoticonFilterModel::setShowStickers(bool showStickers) 37 | { 38 | beginResetModel(); 39 | m_showStickers = showStickers; 40 | endResetModel(); 41 | Q_EMIT showStickersChanged(); 42 | } 43 | 44 | bool EmoticonFilterModel::showEmojis() const 45 | { 46 | return m_showEmojis; 47 | } 48 | 49 | void EmoticonFilterModel::setShowEmojis(bool showEmojis) 50 | { 51 | beginResetModel(); 52 | m_showEmojis = showEmojis; 53 | endResetModel(); 54 | Q_EMIT showEmojisChanged(); 55 | } 56 | 57 | #include "moc_emoticonfiltermodel.cpp" 58 | -------------------------------------------------------------------------------- /src/settings/models/emoticonfiltermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class EmoticonFilterModel 11 | * 12 | * This class creates a custom QSortFilterProxyModel for filtering a emoticon by type 13 | * (Sticker or Emoji). 14 | */ 15 | class EmoticonFilterModel : public QSortFilterProxyModel 16 | { 17 | Q_OBJECT 18 | QML_ELEMENT 19 | 20 | /** 21 | * @brief Whether stickers should be shown 22 | */ 23 | Q_PROPERTY(bool showStickers READ showStickers WRITE setShowStickers NOTIFY showStickersChanged) 24 | 25 | /** 26 | * @brief Whether emojis show be shown 27 | */ 28 | Q_PROPERTY(bool showEmojis READ showEmojis WRITE setShowEmojis NOTIFY showEmojisChanged) 29 | 30 | public: 31 | explicit EmoticonFilterModel(QObject *parent = nullptr); 32 | 33 | /** 34 | * @brief Custom filter function checking the type of emoticon 35 | * 36 | * @note The filter cannot be modified and will always use the same filter properties. 37 | */ 38 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; 39 | 40 | [[nodiscard]] bool showStickers() const; 41 | void setShowStickers(bool showStickers); 42 | 43 | [[nodiscard]] bool showEmojis() const; 44 | void setShowEmojis(bool showEmojis); 45 | 46 | Q_SIGNALS: 47 | void showStickersChanged(); 48 | void showEmojisChanged(); 49 | 50 | private: 51 | bool m_showStickers = false; 52 | bool m_showEmojis = false; 53 | int m_stickerRole = 0; 54 | int m_emojiRole = 0; 55 | }; 56 | -------------------------------------------------------------------------------- /src/spaces/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 James Graham 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | qt_add_library(Spaces STATIC) 5 | ecm_add_qml_module(Spaces GENERATE_PLUGIN_SOURCE 6 | URI org.kde.neochat.spaces 7 | OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/spaces 8 | QML_FILES 9 | SpaceHomePage.qml 10 | SpaceHierarchyDelegate.qml 11 | RemoveChildDialog.qml 12 | SelectExistingRoomDialog.qml 13 | SOURCES 14 | models/spacechildrenmodel.cpp 15 | models/spacechildsortfiltermodel.cpp 16 | models/spacetreeitem.cpp 17 | ) 18 | 19 | target_include_directories(Spaces PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models) 20 | target_link_libraries(Spaces PRIVATE 21 | Qt::Core 22 | Qt::Quick 23 | KF6::Kirigami 24 | LibNeoChat 25 | ) 26 | -------------------------------------------------------------------------------- /src/spaces/RemoveChildDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | import org.kde.neochat.libneochat 11 | 12 | Kirigami.Dialog { 13 | id: root 14 | 15 | required property NeoChatRoom parentRoom 16 | 17 | required property string roomId 18 | 19 | required property string displayName 20 | 21 | required property string parentDisplayName 22 | 23 | required property bool canSetParent 24 | 25 | required property bool isDeclaredParent 26 | 27 | title: i18nc("@title", "Remove Child") 28 | 29 | width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24) 30 | 31 | standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel 32 | 33 | onAccepted: parentRoom.removeChild(root.roomId, removeOfficalCheck.checked) 34 | 35 | contentItem: ColumnLayout { 36 | spacing: 0 37 | FormCard.FormTextDelegate { 38 | text: i18n("The child %1 will be removed from the space %2", root.displayName, root.parentDisplayName) 39 | textItem.wrapMode: Text.Wrap 40 | } 41 | FormCard.FormCheckDelegate { 42 | id: removeOfficalCheck 43 | visible: root.isDeclaredParent 44 | enabled: root.canSetParent 45 | text: i18n("The current space is the official parent of this room, should this be cleared?") 46 | checked: root.canSetParent 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/spaces/models/spacechildsortfiltermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class SpaceChildSortFilterModel 11 | * 12 | * This class creates a custom QSortFilterProxyModel for filtering and sorting spaces 13 | * in a SpaceChildrenModel. 14 | * 15 | * @sa SpaceChildrenModel 16 | */ 17 | class SpaceChildSortFilterModel : public QSortFilterProxyModel 18 | { 19 | Q_OBJECT 20 | QML_ELEMENT 21 | 22 | /** 23 | * @brief The text to use to filter room names. 24 | */ 25 | Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged) 26 | 27 | public: 28 | SpaceChildSortFilterModel(QObject *parent = nullptr); 29 | 30 | void setFilterText(const QString &filterText); 31 | [[nodiscard]] QString filterText() const; 32 | 33 | protected: 34 | /** 35 | * @brief Returns true if the value of source_left is less than source_right. 36 | * 37 | * @sa QSortFilterProxyModel::lessThan 38 | */ 39 | bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; 40 | 41 | /** 42 | * @brief Custom filter function checking if an event type has been filtered out. 43 | * 44 | * The filter rejects a row if the room is known been replaced or if a search 45 | * string is set it will only return rooms that match. 46 | */ 47 | bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; 48 | 49 | Q_INVOKABLE void move(const QModelIndex ¤tIndex, const QModelIndex &targetIndex); 50 | 51 | Q_SIGNALS: 52 | void filterTextChanged(); 53 | 54 | private: 55 | QString m_filterText; 56 | }; 57 | -------------------------------------------------------------------------------- /src/timeline/EncryptedComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Tobias Fella 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | /** 12 | * @brief A component for an encrypted message that can't be decrypted. 13 | */ 14 | TextEdit { 15 | Layout.fillWidth: true 16 | Layout.maximumWidth: Message.maxContentWidth 17 | 18 | text: i18n("This message is encrypted and the sender has not shared the key with this device.") 19 | color: Kirigami.Theme.disabledTextColor 20 | selectedTextColor: Kirigami.Theme.highlightedTextColor 21 | selectionColor: Kirigami.Theme.highlightColor 22 | font.pointSize: Kirigami.Theme.defaultFont.pointSize 23 | selectByMouse: !Kirigami.Settings.isMobile 24 | readOnly: true 25 | wrapMode: Text.WordWrap 26 | textFormat: Text.RichText 27 | } 28 | -------------------------------------------------------------------------------- /src/timeline/FetchButtonComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.delegates as Delegates 10 | 11 | import org.kde.neochat 12 | import org.kde.neochat.chatbar 13 | 14 | /** 15 | * @brief A component to show a reply button for threads in a message bubble. 16 | */ 17 | Delegates.RoundedItemDelegate { 18 | id: root 19 | 20 | /** 21 | * @brief Request more events in the thread be loaded. 22 | */ 23 | signal fetchMoreEvents() 24 | 25 | Layout.fillWidth: true 26 | Layout.maximumWidth: Message.maxContentWidth 27 | 28 | leftInset: 0 29 | rightInset: 0 30 | 31 | highlighted: true 32 | 33 | icon.name: "arrow-up" 34 | icon.width: Kirigami.Units.iconSizes.sizeForLabels 35 | icon.height: Kirigami.Units.iconSizes.sizeForLabels 36 | text: i18nc("@action:button", "Fetch More Events") 37 | 38 | onClicked: { 39 | root.fetchMoreEvents() 40 | } 41 | 42 | contentItem: Kirigami.Icon { 43 | implicitWidth: root.icon.width 44 | implicitHeight: root.icon.height 45 | source: root.icon.name 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/timeline/FoodReservationComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import QtQuick.Controls as QQC2 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.formcard as FormCard 10 | 11 | /** 12 | * @brief A component for a food itinerary reservation. 13 | */ 14 | ItineraryReservationComponent { 15 | id: root 16 | 17 | /** 18 | * @brief The name of the reservation. 19 | */ 20 | required property string name 21 | 22 | /** 23 | * @brief The start time of the reservation. 24 | * 25 | * Includes date. 26 | */ 27 | required property string startTime 28 | 29 | /** 30 | * @brief The address of the hotel. 31 | */ 32 | required property string address 33 | 34 | headerItem: RowLayout { 35 | TransportIcon { 36 | source: "qrc:/qt/qml/org/kde/neochat/timeline/images/foodestablishment.svg" 37 | isMask: true 38 | size: Kirigami.Units.iconSizes.smallMedium 39 | } 40 | QQC2.Label { 41 | Layout.fillWidth: true 42 | text: root.name 43 | elide: Text.ElideRight 44 | 45 | Accessible.ignored: true 46 | } 47 | QQC2.Label { 48 | text: root.startTime 49 | } 50 | } 51 | 52 | contentItem: QQC2.Label { 53 | visible: text !== "" 54 | text: root.address 55 | wrapMode: Text.Wrap 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/timeline/ItineraryReservationComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.kirigamiaddons.formcard as FormCard 9 | 10 | /** 11 | * @brief A base component for an itinerary reservation. 12 | */ 13 | FormCard.FormCard { 14 | id: root 15 | 16 | /** 17 | * @brief An item with the header content. 18 | */ 19 | property alias headerItem: headerDelegate.contentItem 20 | 21 | /** 22 | * @brief An item with the main body content. 23 | */ 24 | property alias contentItem: content.contentItem 25 | 26 | Layout.fillWidth: true 27 | implicitWidth: Math.max(headerDelegate.implicitWidth, content.implicitWidth) 28 | 29 | Component.onCompleted: children[0].radius = Kirigami.Units.cornerRadius 30 | 31 | FormCard.AbstractFormDelegate { 32 | id: headerDelegate 33 | Layout.fillWidth: true 34 | background: Rectangle { 35 | color: Kirigami.Theme.backgroundColor 36 | Kirigami.Theme.colorSet: Kirigami.Theme.Header 37 | Kirigami.Theme.inherit: false 38 | } 39 | } 40 | FormCard.AbstractFormDelegate { 41 | id: content 42 | Layout.fillWidth: true 43 | background: null 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/timeline/LoadComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | import org.kde.neochat 11 | 12 | /** 13 | * @brief A component to show that part of a message is loading. 14 | */ 15 | RowLayout { 16 | id: root 17 | 18 | required property string display 19 | 20 | Layout.fillWidth: true 21 | Layout.maximumWidth: Message.maxContentWidth 22 | spacing: Kirigami.Units.smallSpacing 23 | 24 | QQC2.BusyIndicator {} 25 | Kirigami.Heading { 26 | id: loadingText 27 | Layout.fillWidth: true 28 | verticalAlignment: Text.AlignVCenter 29 | level: 2 30 | text: root.display.length > 0 ? root.display : i18n("Loading") 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/timeline/LoadingDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigami as Kirigami 7 | 8 | import org.kde.neochat 9 | 10 | TimelineDelegate { 11 | id: root 12 | 13 | width: parent?.width 14 | rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing 15 | 16 | alwaysFillWidth: NeoChatConfig.compactLayout 17 | 18 | contentItem: Kirigami.PlaceholderMessage { 19 | text: i18n("Loading…") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/timeline/MessageComponentChooser.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import Qt.labs.qmlmodels 6 | 7 | import org.kde.neochat 8 | 9 | /** 10 | * @brief Select a message component based on a MessageComponentType. 11 | */ 12 | BaseMessageComponentChooser { 13 | id: root 14 | 15 | DelegateChoice { 16 | roleValue: MessageComponentType.ThreadBody 17 | delegate: ThreadBodyComponent { 18 | onSelectedTextChanged: selectedText => { 19 | root.selectedTextChanged(selectedText); 20 | } 21 | onHoveredLinkChanged: hoveredLink => { 22 | root.hoveredLinkChanged(hoveredLink); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/timeline/MimeComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.coreaddons 9 | import org.kde.kirigami as Kirigami 10 | 11 | /** 12 | * @brief A component to show media based upon its mime type. 13 | */ 14 | RowLayout { 15 | property alias mimeIconSource: icon.source 16 | property alias label: nameLabel.text 17 | property string subLabel: "" 18 | property int size: 0 19 | property int duration: 0 20 | 21 | spacing: Kirigami.Units.largeSpacing 22 | 23 | Kirigami.Icon { 24 | id: icon 25 | fallback: "unknown" 26 | } 27 | ColumnLayout { 28 | Layout.alignment: Qt.AlignVCenter 29 | Layout.fillWidth: true 30 | 31 | spacing: 0 32 | 33 | QQC2.Label { 34 | id: nameLabel 35 | 36 | Layout.fillWidth: true 37 | Layout.alignment: caption.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter 38 | 39 | elide: Text.ElideRight 40 | } 41 | QQC2.Label { 42 | id: caption 43 | 44 | Layout.fillWidth: true 45 | 46 | text: (subLabel || size || duration || '') && [ 47 | subLabel, 48 | size && Format.formatByteSize(size), 49 | duration > 0 && Format.formatDuration(duration), 50 | ].filter(Boolean).join(" | ") 51 | 52 | elide: Text.ElideRight 53 | visible: text.length > 0 54 | opacity: 0.7 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/timeline/PdfPreviewComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-FileCopyrightText: 2024 James Graham 3 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 4 | 5 | import QtQuick 6 | import QtQuick.Layouts 7 | 8 | import org.kde.neochat 9 | 10 | Rectangle { 11 | id: root 12 | 13 | /** 14 | * @brief FileTransferInfo for any downloading files. 15 | */ 16 | required property var fileTransferInfo 17 | 18 | /** 19 | * @brief The attributes of the component. 20 | */ 21 | required property var componentAttributes 22 | 23 | Layout.preferredWidth: mediaSizeHelper.currentSize.width 24 | Layout.preferredHeight: mediaSizeHelper.currentSize.height 25 | 26 | color: "white" 27 | 28 | Image { 29 | anchors.fill: root 30 | source: root?.fileTransferInfo.localPath ?? "" 31 | 32 | MediaSizeHelper { 33 | id: mediaSizeHelper 34 | contentMaxWidth: root.Message.maxContentWidth 35 | mediaWidth: root.componentAttributes.size.width 36 | mediaHeight: root.componentAttributes.size.height 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/timeline/PredecessorDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | TimelineDelegate { 12 | id: root 13 | 14 | /** 15 | * @brief The current room that user is viewing. 16 | */ 17 | required property NeoChatRoom room 18 | 19 | width: parent?.width 20 | rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing 21 | 22 | alwaysFillWidth: NeoChatConfig.compactLayout 23 | 24 | contentItem: Kirigami.InlineMessage { 25 | visible: true 26 | text: i18n("This room continues another conversation.") 27 | type: Kirigami.MessageType.Information 28 | actions: Kirigami.Action { 29 | text: i18n("See older messages…") 30 | onTriggered: RoomManager.resolveResource(root.room.predecessorId) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/timeline/ReplyAuthorComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.labs.components as KirigamiComponents 10 | 11 | RowLayout { 12 | id: root 13 | 14 | /** 15 | * @brief The message author. 16 | * 17 | * A Quotient::RoomMember object. 18 | * 19 | * @sa Quotient::RoomMember 20 | */ 21 | required property var author 22 | 23 | /** 24 | * @brief The maximum width that the bubble's content can be. 25 | */ 26 | property real maxContentWidth: -1 27 | 28 | Layout.fillWidth: true 29 | Layout.maximumWidth: root.maxContentWidth 30 | 31 | implicitHeight: Math.max(replyAvatar.implicitHeight, replyName.implicitHeight) 32 | spacing: Kirigami.Units.largeSpacing 33 | 34 | KirigamiComponents.Avatar { 35 | id: replyAvatar 36 | 37 | implicitWidth: Kirigami.Units.iconSizes.small 38 | implicitHeight: Kirigami.Units.iconSizes.small 39 | 40 | source: root.author.avatarUrl 41 | name: root.author.displayName 42 | color: root.author.color 43 | asynchronous: true 44 | } 45 | QQC2.Label { 46 | id: replyName 47 | Layout.fillWidth: true 48 | 49 | color: root.author.color 50 | text: root.author.disambiguatedName 51 | elide: Text.ElideRight 52 | textFormat: Text.PlainText 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/timeline/ReplyButtonComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Controls as QQC2 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.kirigamiaddons.delegates as Delegates 10 | 11 | import org.kde.neochat 12 | import org.kde.neochat.chatbar 13 | 14 | /** 15 | * @brief A component to show a reply button for threads in a message bubble. 16 | */ 17 | Delegates.RoundedItemDelegate { 18 | id: root 19 | 20 | /** 21 | * @brief The thread root ID. 22 | */ 23 | required property string threadRoot 24 | 25 | Layout.fillWidth: true 26 | Layout.maximumWidth: Message.maxContentWidth 27 | 28 | leftInset: 0 29 | rightInset: 0 30 | 31 | highlighted: true 32 | 33 | icon.name: "mail-reply-custom" 34 | text: i18nc("@action:button", "Reply") 35 | 36 | onClicked: { 37 | Message.room.threadCache.replyId = ""; 38 | Message.room.threadCache.threadId = root.threadRoot; 39 | Message.room.mainCache.clearRelations(); 40 | Message.room.editCache.clearRelations(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/timeline/SectionDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Black Hat 2 | // SPDX-FileCopyrightText: 2020 Carl Schwan 3 | // SPDX-License-Identifier: GPL-3.0-only 4 | 5 | import QtQuick 6 | import QtQuick.Controls as QQC2 7 | import QtQuick.Layouts 8 | 9 | import org.kde.kirigami as Kirigami 10 | 11 | import org.kde.neochat 12 | 13 | QQC2.ItemDelegate { 14 | id: root 15 | 16 | property alias labelText: sectionLabel.text 17 | property var maxWidth: Number.POSITIVE_INFINITY 18 | 19 | property int colorSet: Kirigami.Theme.Window 20 | 21 | leftPadding: 0 22 | rightPadding: 0 23 | topPadding: Kirigami.Units.largeSpacing 24 | bottomPadding: 0 // Note not 0 by default 25 | 26 | leftInset: 0 27 | rightInset: 0 28 | topInset: -1 // This -1 is intentional to stretch the background one more pixel to prevent a visual bug when scrolling 29 | bottomInset: 0 30 | 31 | contentItem: ColumnLayout { 32 | spacing: Kirigami.Units.smallSpacing 33 | Layout.fillWidth: true 34 | 35 | Kirigami.Heading { 36 | id: sectionLabel 37 | level: 4 38 | color: Kirigami.Theme.disabledTextColor 39 | horizontalAlignment: Text.AlignHCenter 40 | verticalAlignment: Text.AlignVCenter 41 | Layout.fillWidth: true 42 | Layout.maximumWidth: maxWidth 43 | } 44 | Kirigami.Separator { 45 | Layout.fillWidth: true 46 | Layout.maximumWidth: maxWidth 47 | } 48 | } 49 | 50 | background: Rectangle { 51 | color: NeoChatConfig.blur ? "transparent" : Kirigami.Theme.backgroundColor 52 | Kirigami.Theme.inherit: false 53 | Kirigami.Theme.colorSet: root.colorSet 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/timeline/SpacerDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigami as Kirigami 7 | 8 | Item { 9 | width: parent?.width 10 | height: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2) 11 | } 12 | -------------------------------------------------------------------------------- /src/timeline/SuccessorDelegate.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | TimelineDelegate { 12 | id: root 13 | 14 | /** 15 | * @brief The current room that user is viewing. 16 | */ 17 | required property NeoChatRoom room 18 | 19 | width: parent?.width 20 | rightPadding: NeoChatConfig.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing 21 | 22 | alwaysFillWidth: NeoChatConfig.compactLayout 23 | 24 | contentItem: Kirigami.InlineMessage { 25 | visible: true 26 | text: i18n("This room has been replaced.") 27 | type: Kirigami.MessageType.Information 28 | actions: Kirigami.Action { 29 | text: i18n("See new room…") 30 | onTriggered: RoomManager.resolveResource(root.room.successorId) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/timeline/ThreadBodyComponent.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | 7 | import org.kde.kirigami as Kirigami 8 | 9 | import org.kde.neochat 10 | 11 | /** 12 | * @brief A component to visualize a ThreadModel. 13 | * 14 | * @sa ThreadModel 15 | */ 16 | ColumnLayout { 17 | id: root 18 | 19 | /** 20 | * @brief The Matrix ID of the root message in the thread, if any. 21 | */ 22 | required property string threadRoot 23 | 24 | /** 25 | * @brief The user selected text has changed. 26 | */ 27 | signal selectedTextChanged(string selectedText) 28 | 29 | /** 30 | * @brief The user hovered link has changed. 31 | */ 32 | signal hoveredLinkChanged(string hoveredLink) 33 | 34 | Layout.fillWidth: true 35 | Layout.fillHeight: true 36 | Layout.maximumWidth: Message.maxContentWidth 37 | spacing: Kirigami.Units.smallSpacing 38 | 39 | Repeater { 40 | id: threadRepeater 41 | model: root.Message.contentModel.modelForThread(root.threadRoot); 42 | 43 | delegate: BaseMessageComponentChooser { 44 | onSelectedTextChanged: selectedText => { 45 | root.selectedTextChanged(selectedText); 46 | } 47 | onHoveredLinkChanged: hoveredLink => { 48 | root.hoveredLinkChanged(hoveredLink); 49 | } 50 | onRemoveLinkPreview: index => threadRepeater.model.closeLinkPreview(index) 51 | onFetchMoreEvents: threadRepeater.model.fetchMoreEvents(5) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/timeline/TransportIcon.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2023 Volker Krause 3 | SPDX-License-Identifier: LGPL-2.0-or-later 4 | */ 5 | 6 | import QtQuick 7 | import QtQuick.Layouts 8 | import org.kde.kirigami as Kirigami 9 | 10 | /** Displays a transport line or mode logo/icon. 11 | * Mainly to hide ugly implementation details of Icon not 12 | * handling non-square SVG assets in the way we need it here. 13 | */ 14 | Item { 15 | id: root 16 | // properties match those of Icon 17 | property string source 18 | property alias isMask: __icon.isMask 19 | property alias color: __icon.color 20 | 21 | // icon size (height for non-square ones) 22 | property int size: Kirigami.Units.iconSizes.small 23 | 24 | property bool __isIcon: !source.startsWith("file:") 25 | 26 | 27 | implicitWidth: __isIcon ? root.size : Math.round(root.size * __image.implicitWidth / __image.implicitHeight) 28 | implicitHeight: root.size 29 | 30 | Layout.preferredWidth: root.implicitWidth 31 | Layout.preferredHeight: root.implicitHeight 32 | 33 | Kirigami.Icon { 34 | id: __icon 35 | source: root.__isIcon ? root.source : "" 36 | visible: source !== "" 37 | anchors.fill: parent 38 | } 39 | Image { 40 | id: __image 41 | source: root.__isIcon ? "" : root.source 42 | visible: source !== "" 43 | anchors.fill: parent 44 | fillMode: Image.PreserveAspectFit 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/timeline/config-neochat.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Tobias Fella 3 | SPDX-License-Identifier: LGPL-2.0-or-later 4 | */ 5 | 6 | #pragma once 7 | 8 | #define CMAKE_INSTALL_FULL_LIBEXECDIR_KF6 "${KDE_INSTALL_FULL_LIBEXECDIR_KF}" 9 | -------------------------------------------------------------------------------- /src/timeline/images/bike.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/bus.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/cablecar.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/car.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/coach.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/couchettecar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /src/timeline/images/couchettecar.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/elevator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/timeline/images/elevator.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: https://github.com/gravitystorm/openstreetmap-carto 3 | -------------------------------------------------------------------------------- /src/timeline/images/escalator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/timeline/images/escalator.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/ferry.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/flight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /src/timeline/images/flight.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/foodestablishment.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/funicular.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/longdistancetrain.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/rapidtransit.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/seat.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/shuttle.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/sleepingcar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/timeline/images/sleepingcar.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/stairs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/timeline/images/stairs.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/subway.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/taxi.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/train.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/tramway.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/transfer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /src/timeline/images/transfer.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/wait.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/images/walk.svg.license: -------------------------------------------------------------------------------- 1 | SPDX-License-Identifier: CC0-1.0 2 | SPDX-FileCopyrightText: Volker Krause 3 | -------------------------------------------------------------------------------- /src/timeline/locationhelper.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Volker Krause 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #include "locationhelper.h" 5 | 6 | #include 7 | 8 | QRectF LocationHelper::unite(const QRectF &r1, const QRectF &r2) 9 | { 10 | // this looks weird but is actually intentional as we need to handle point-like "rects" as well 11 | if ((!r1.isEmpty() || r1.isNull()) && (!r2.isEmpty() || r2.isNull())) { 12 | return r1 | r2; 13 | } 14 | return (!r1.isEmpty() || r1.isNull()) ? r1 : r2; 15 | } 16 | 17 | QPointF LocationHelper::center(const QRectF &r) 18 | { 19 | return r.center(); 20 | } 21 | 22 | constexpr inline double degToRad(double deg) 23 | { 24 | return deg / 180.0 * M_PI; 25 | } 26 | 27 | static QPointF mercatorProject(double lat, double lon, double zoom) 28 | { 29 | const auto x = (256.0 / (2.0 * M_PI)) * std::pow(2.0, zoom) * (degToRad(lon) + M_PI); 30 | const auto y = (256.0 / (2.0 * M_PI)) * std::pow(2.0, zoom) * (M_PI - std::log(std::tan(M_PI / 4.0 + degToRad(lat) / 2.0))); 31 | return QPointF(x, y); 32 | } 33 | 34 | float LocationHelper::zoomToFit(const QRectF &r, float mapWidth, float mapHeight) 35 | { 36 | const auto p1 = mercatorProject(r.bottomLeft().y(), r.bottomLeft().x(), 1.0); 37 | const auto p2 = mercatorProject(r.topRight().y(), r.topRight().x(), 1.0); 38 | 39 | const auto zx = std::log2((mapWidth / (p2.x() - p1.x()))); 40 | const auto zy = std::log2((mapHeight / (p2.y() - p1.y()))); 41 | const auto z = std::min(zx, zy); 42 | 43 | return z; 44 | } 45 | 46 | #include "moc_locationhelper.cpp" 47 | -------------------------------------------------------------------------------- /src/timeline/locationhelper.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Volker Krause 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /** Location related helper functions for QML. */ 11 | class LocationHelper : public QObject 12 | { 13 | Q_OBJECT 14 | QML_ELEMENT 15 | QML_SINGLETON 16 | 17 | public: 18 | /** Unite two rectanlges. */ 19 | Q_INVOKABLE static QRectF unite(const QRectF &r1, const QRectF &r2); 20 | /** Returns the center of @p r. */ 21 | Q_INVOKABLE static QPointF center(const QRectF &r); 22 | 23 | /** Returns the highest zoom level to fit @r into a map of size @p mapWidth x @p mapHeight. */ 24 | Q_INVOKABLE static float zoomToFit(const QRectF &r, float mapWidth, float mapHeight); 25 | }; 26 | 27 | Q_DECLARE_METATYPE(LocationHelper) 28 | -------------------------------------------------------------------------------- /src/timeline/models/itinerarymodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Tobias Fella 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class ItineraryModel : public QAbstractListModel 13 | { 14 | Q_OBJECT 15 | QML_ELEMENT 16 | QML_UNCREATABLE("") 17 | 18 | public: 19 | enum Roles { 20 | NameRole = Qt::DisplayRole, 21 | TypeRole, 22 | DepartureLocationRole, 23 | ArrivalLocationRole, 24 | DepartureTimeRole, 25 | DepartureAddressRole, 26 | ArrivalTimeRole, 27 | ArrivalAddressRole, 28 | AddressRole, 29 | StartTimeRole, 30 | EndTimeRole, 31 | DeparturePlatformRole, 32 | ArrivalPlatformRole, 33 | CoachRole, 34 | SeatRole, 35 | }; 36 | Q_ENUM(Roles) 37 | explicit ItineraryModel(QObject *parent = nullptr); 38 | 39 | QVariant data(const QModelIndex &index, int role) const override; 40 | int rowCount(const QModelIndex &parent = {}) const override; 41 | 42 | QHash roleNames() const override; 43 | 44 | QString path() const; 45 | void setPath(const QString &path); 46 | 47 | Q_INVOKABLE void sendToItinerary(); 48 | 49 | Q_SIGNALS: 50 | void loaded(); 51 | void loadErrorOccurred(); 52 | 53 | private: 54 | QJsonArray m_data; 55 | QString m_path; 56 | void loadData(); 57 | }; 58 | -------------------------------------------------------------------------------- /src/timeline/models/linemodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "linemodel.h" 5 | 6 | LineModel::LineModel(QObject *parent) 7 | : QAbstractListModel(parent) 8 | { 9 | } 10 | 11 | QQuickTextDocument *LineModel::document() const 12 | { 13 | return m_document; 14 | } 15 | 16 | void LineModel::setDocument(QQuickTextDocument *document) 17 | { 18 | if (document == m_document) { 19 | return; 20 | } 21 | 22 | m_document = document; 23 | Q_EMIT documentChanged(); 24 | 25 | resetModel(); 26 | } 27 | 28 | QVariant LineModel::data(const QModelIndex &index, int role) const 29 | { 30 | if (!index.isValid()) { 31 | return {}; 32 | } 33 | 34 | const auto &row = index.row(); 35 | if (row < 0 || row > rowCount()) { 36 | return {}; 37 | } 38 | 39 | if (role == LineHeightRole) { 40 | auto textDoc = m_document->textDocument(); 41 | return int(textDoc->documentLayout()->blockBoundingRect(textDoc->findBlockByNumber(row)).height()); 42 | } 43 | return {}; 44 | } 45 | 46 | int LineModel::rowCount(const QModelIndex &parent) const 47 | { 48 | Q_UNUSED(parent); 49 | if (m_document == nullptr) { 50 | return 0; 51 | } 52 | return m_document->textDocument()->blockCount(); 53 | } 54 | 55 | QHash LineModel::roleNames() const 56 | { 57 | return {{LineHeightRole, "docLineHeight"}}; 58 | } 59 | 60 | void LineModel::resetModel() 61 | { 62 | beginResetModel(); 63 | endResetModel(); 64 | } 65 | 66 | #include "moc_linemodel.cpp" 67 | -------------------------------------------------------------------------------- /src/timeline/models/messagecontentfiltermodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #include "messagecontentfiltermodel.h" 5 | #include "enums/messagecomponenttype.h" 6 | #include "models/messagecontentmodel.h" 7 | 8 | MessageContentFilterModel::MessageContentFilterModel(QObject *parent) 9 | : QSortFilterProxyModel(parent) 10 | { 11 | } 12 | 13 | bool MessageContentFilterModel::showAuthor() const 14 | { 15 | return m_showAuthor; 16 | } 17 | 18 | void MessageContentFilterModel::setShowAuthor(bool showAuthor) 19 | { 20 | if (showAuthor == m_showAuthor) { 21 | return; 22 | } 23 | 24 | m_showAuthor = showAuthor; 25 | Q_EMIT showAuthorChanged(); 26 | } 27 | 28 | bool MessageContentFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 29 | { 30 | if (m_showAuthor) { 31 | return true; 32 | } 33 | 34 | const auto index = sourceModel()->index(source_row, 0, source_parent); 35 | auto contentType = static_cast(index.data(MessageContentModel::ComponentTypeRole).toInt()); 36 | return contentType != MessageComponentType::Author; 37 | } 38 | -------------------------------------------------------------------------------- /src/timeline/models/messagecontentfiltermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | /** 10 | * @class MessageContentFilterModel 11 | * 12 | * This model filters a message's contents. 13 | */ 14 | class MessageContentFilterModel : public QSortFilterProxyModel 15 | { 16 | Q_OBJECT 17 | QML_ELEMENT 18 | 19 | /** 20 | * @brief Whether the author component should be shown. 21 | */ 22 | Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged) 23 | 24 | public: 25 | explicit MessageContentFilterModel(QObject *parent = nullptr); 26 | 27 | bool showAuthor() const; 28 | void setShowAuthor(bool showAuthor); 29 | 30 | Q_SIGNALS: 31 | void showAuthorChanged(); 32 | 33 | protected: 34 | /** 35 | * @brief Whether a row should be shown out or not. 36 | * 37 | * @sa QSortFilterProxyModel::filterAcceptsRow 38 | */ 39 | [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; 40 | 41 | private: 42 | bool m_showAuthor = true; 43 | }; 44 | -------------------------------------------------------------------------------- /src/timeline/models/pinnedmessagemodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Joshua Goins 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "messagemodel.h" 13 | #include "neochatroommember.h" 14 | 15 | namespace Quotient 16 | { 17 | class Connection; 18 | } 19 | 20 | class NeoChatRoom; 21 | 22 | /** 23 | * @class PinnedMessageModel 24 | * 25 | * This class defines the model for visualising a room's pinned messages. 26 | */ 27 | class PinnedMessageModel : public MessageModel 28 | { 29 | Q_OBJECT 30 | QML_ELEMENT 31 | 32 | /** 33 | * @brief Whether the model is currently loading. 34 | */ 35 | Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) 36 | 37 | public: 38 | explicit PinnedMessageModel(QObject *parent = nullptr); 39 | 40 | /** 41 | * @brief Number of rows in the model. 42 | * 43 | * @sa QAbstractItemModel::rowCount 44 | */ 45 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 46 | 47 | bool loading() const; 48 | 49 | Q_SIGNALS: 50 | void loadingChanged(); 51 | 52 | protected: 53 | std::optional> getEventForIndex(QModelIndex index) const override; 54 | 55 | private: 56 | void setLoading(bool loading); 57 | void fill(); 58 | 59 | bool m_loading = false; 60 | 61 | std::vector> m_pinnedEvents; 62 | }; 63 | -------------------------------------------------------------------------------- /src/timeline/models/pollanswermodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 James Graham 2 | // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | class PollHandler; 10 | 11 | /** 12 | * @class PollAnswerModel 13 | * 14 | * This class defines the model for visualising a list of answer to a poll. 15 | */ 16 | class PollAnswerModel : public QAbstractListModel 17 | { 18 | Q_OBJECT 19 | QML_ELEMENT 20 | QML_UNCREATABLE("") 21 | 22 | public: 23 | /** 24 | * @brief Defines the model roles. 25 | */ 26 | enum Roles { 27 | IdRole, /**< The ID of the answer. */ 28 | TextRole, /**< The answer text. */ 29 | CountRole, /**< The number of people who gave this answer. */ 30 | LocalChoiceRole, /**< Whether this option was selected by the local user */ 31 | IsWinnerRole, /**< Whether this option was selected by the local user */ 32 | }; 33 | Q_ENUM(Roles) 34 | 35 | explicit PollAnswerModel(PollHandler *parent); 36 | 37 | /** 38 | * @brief Get the given role value at the given index. 39 | * 40 | * @sa QAbstractItemModel::data 41 | */ 42 | QVariant data(const QModelIndex &index, int role) const override; 43 | 44 | /** 45 | * @brief Number of rows in the model. 46 | * 47 | * @sa QAbstractItemModel::rowCount 48 | */ 49 | int rowCount(const QModelIndex &parent = {}) const override; 50 | 51 | /** 52 | * @brief Returns a mapping from Role enum values to role names. 53 | * 54 | * @sa Roles, QAbstractItemModel::roleNames() 55 | */ 56 | QHash roleNames() const override; 57 | }; 58 | -------------------------------------------------------------------------------- /src/timeline/models/timelinemessagemodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2018-2019 Black Hat 2 | // SPDX-License-Identifier: GPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "messagemodel.h" 10 | 11 | class ReactionModel; 12 | 13 | namespace Quotient 14 | { 15 | class RoomEvent; 16 | } 17 | 18 | /** 19 | * @class TimelineMessageModel 20 | * 21 | * This class defines the model for visualising the room timeline. 22 | * 23 | * This model covers all event types in the timeline with many of the roles being 24 | * specific to a subset of events. This means the user needs to understand which 25 | * roles will return useful information for a given event type. 26 | * 27 | * @sa NeoChatRoom 28 | */ 29 | class TimelineMessageModel : public MessageModel 30 | { 31 | Q_OBJECT 32 | QML_ELEMENT 33 | 34 | public: 35 | explicit TimelineMessageModel(QObject *parent = nullptr); 36 | 37 | /** 38 | * @brief Number of rows in the model. 39 | * 40 | * @sa QAbstractItemModel::rowCount 41 | */ 42 | [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; 43 | 44 | private: 45 | void connectNewRoom(); 46 | 47 | std::optional> getEventForIndex(QModelIndex index) const override; 48 | 49 | int rowBelowInserted = -1; 50 | bool resetting = false; 51 | bool movingEvent = false; 52 | 53 | int timelineServerIndex() const override; 54 | 55 | bool canFetchMore(const QModelIndex &parent) const override; 56 | void fetchMore(const QModelIndex &parent) override; 57 | 58 | void moveReadMarker(const QString &toEventId); 59 | 60 | // Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows 61 | bool m_initialized = false; 62 | }; 63 | --------------------------------------------------------------------------------