merge upstream

This commit is contained in:
expectocode 2019-05-19 19:03:37 +01:00
commit dfdad76d97
30 changed files with 906 additions and 190 deletions

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/spectral.iml" filepath="$PROJECT_DIR$/.idea/spectral.iml" />
</modules>
</component>
</project>

8
.idea/spectral.iml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/vcs.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/include/SortFilterProxyModel" vcs="Git" />
<mapping directory="$PROJECT_DIR$/include/libqmatrixclient" vcs="Git" />
</component>
</project>

153
.idea/workspace.xml Normal file
View File

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeRunConfigurationManager" shouldGenerate="true" shouldDeleteObsolete="true">
<generated />
</component>
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="ChangeListManager">
<list default="true" id="1dc08437-54b4-4b19-9a54-6472ca11c95f" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/include/libqmatrixclient" beforeDir="false" afterPath="$PROJECT_DIR$/include/libqmatrixclient" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="FileEditorManager">
<leaf>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/spectraluser.cpp">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" lean-forward="true" selection-start-line="5" selection-end-line="5" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/src/spectraluser.cpp" />
</list>
</option>
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="KjZzvpQi" />
</component>
<component name="ProjectConfigurationFiles">
<option name="files">
<list>
<option value="$PROJECT_DIR$/.idea/spectral.iml" />
<option value="$PROJECT_DIR$/.idea/vcs.xml" />
<option value="$PROJECT_DIR$/.idea/modules.xml" />
</list>
</option>
</component>
<component name="ProjectFrameBounds">
<option name="x" value="1928" />
<option name="y" value="8" />
<option name="width" value="539" />
<option name="height" value="687" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="spectral" type="b2602c69:ProjectViewProjectNode" />
<item name="spectral" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="spectral" type="b2602c69:ProjectViewProjectNode" />
<item name="spectral" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="1dc08437-54b4-4b19-9a54-6472ca11c95f" name="Default Changelist" comment="" />
<created>1558287890866</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1558287890866</updated>
<workItem from="1558287893419" duration="176000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="176000" />
</component>
<component name="ToolWindowManager">
<frame x="1928" y="8" width="539" height="687" extended-state="0" />
<layout>
<window_info id="Favorites" side_tool="true" />
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.6136821" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info anchor="bottom" id="Database Changes" />
<window_info anchor="bottom" id="Version Control" />
<window_info anchor="bottom" id="Terminal" />
<window_info anchor="bottom" id="Event Log" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="right" id="Cargo" />
<window_info anchor="right" id="Database" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
</layout>
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/src/spectraluser.cpp">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" lean-forward="true" selection-start-line="5" selection-end-line="5" />
</state>
</provider>
</entry>
</component>
</project>

View File

@ -0,0 +1,71 @@
import QtQuick 2.12
Rectangle {
property alias topLeftRadius: topLeftRect.radius
property alias topRightRadius: topRightRect.radius
property alias bottomLeftRadius: bottomLeftRect.radius
property alias bottomRightRadius: bottomRightRect.radius
property alias topLeftVisible: topLeftRect.visible
property alias topRightVisible: topRightRect.visible
property alias bottomLeftVisible: bottomLeftRect.visible
property alias bottomRightVisible: bottomRightRect.visible
antialiasing: true
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
width: parent.width / 2
height: parent.height / 2
id: topLeftRect
antialiasing: true
color: parent.color
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
id: topRightRect
antialiasing: true
color: parent.color
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 2
height: parent.height / 2
id: bottomLeftRect
antialiasing: true
color: parent.color
}
Rectangle {
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
id: bottomRightRect
antialiasing: true
color: parent.color
}
}

View File

@ -2,6 +2,8 @@ import QtQuick 2.12
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.0
import Spectral.Setting 0.1
Item {
property string hint: "H"
property string source: ""
@ -35,7 +37,7 @@ Item {
visible: !realSource || image.status != Image.Ready
radius: height / 2
color: stringToColor(hint)
color: MPalette.accent
antialiasing: true
Label {
@ -50,16 +52,16 @@ Item {
}
}
function stringToColor(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
var colour = '#';
for (var j = 0; j < 3; j++) {
var value = (hash >> (j * 8)) & 0xFF;
colour += ('00' + value.toString(16)).substr(-2);
}
return colour;
}
// function stringToColor(str) {
// var hash = 0;
// for (var i = 0; i < str.length; i++) {
// hash = str.charCodeAt(i) + ((hash << 5) - hash);
// }
// var colour = '#';
// for (var j = 0; j < 3; j++) {
// var value = (hash >> (j * 8)) & 0xFF;
// colour += ('00' + value.toString(16)).substr(-2);
// }
// return colour;
// }
}

View File

@ -67,65 +67,25 @@ ColumnLayout {
Control {
Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48
verticalPadding: 8
horizontalPadding: 16
padding: 0
background: AutoRectangle {
readonly property int minorRadius: 2
id: bubbleBackground
background: Rectangle {
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 18
antialiasing: true
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
topLeftVisible: true
topRightVisible: true
bottomLeftVisible: true
bottomRightVisible: true
width: parent.width / 2
height: parent.height / 2
visible: true
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 5
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
visible: true
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 5
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.width / 2
height: parent.height / 2
visible: true
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 5
}
Rectangle {
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width / 2
height: parent.height / 2
visible: true
color: sentByMe ? MPalette.background : eventType === "notice" ? MPalette.primary : MPalette.accent
radius: 5
}
topLeftRadius: minorRadius
topRightRadius: minorRadius
bottomLeftRadius: minorRadius
bottomRightRadius: minorRadius
AutoMouseArea {
anchors.fill: parent
@ -166,11 +126,21 @@ ColumnLayout {
}
contentItem: ColumnLayout {
RowLayout {
spacing: 0
Control {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 8
Layout.rightMargin: 8
padding: 4
rightPadding: 12
visible: replyVisible
contentItem: RowLayout {
Avatar {
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@ -188,41 +158,40 @@ ColumnLayout {
}
}
Control {
Label {
Layout.fillWidth: true
visible: replyVisible
padding: 0
background: RippleEffect {
onClicked: goToEvent(replyEventId)
}
contentItem: Label {
color: darkBackground ? "white" : MPalette.lighter
text: "<style>a{color: " + (darkBackground ? "white" : MPalette.foreground) + ";} .user-pill{}</style>" + (replyDisplay || "")
color: !sentByMe ? MPalette.foreground : "white"
text: "<style>a{color: " + color + ";} .user-pill{}</style>" + (replyDisplay || "")
wrapMode: Label.Wrap
textFormat: Label.RichText
}
}
background: Rectangle {
color: sentByMe ? MPalette.accent : MPalette.background
radius: 18
AutoMouseArea {
anchors.fill: parent
onClicked: goToEvent(replyEventId)
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
visible: replyVisible
color: darkBackground ? "white" : MPalette.lighter
}
TextEdit {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 8
id: contentLabel
text: "<style>a{color: " + (darkBackground ? "white" : MPalette.foreground) + ";} .user-pill{}</style>" + display
text: "<style>a{color: " + color + ";} .user-pill{}</style>" + display
color: darkBackground ? "white" : MPalette.foreground

View File

@ -7,3 +7,4 @@ AutoListView 2.0 AutoListView.qml
AutoTextField 2.0 AutoTextField.qml
Avatar 2.0 Avatar.qml
FullScreenImage 2.0 FullScreenImage.qml
AutoRectangle 2.0 AutoRectangle.qml

View File

@ -27,8 +27,8 @@ Dialog {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
hint: user ? user.displayName : "No name"
source: user ? user.avatarMediaId : null
hint: user.displayName
source: user.avatarMediaId
RippleEffect {
anchors.fill: parent
@ -50,10 +50,19 @@ Dialog {
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: user ? user.displayName : "No Name"
text: user.displayName
color: MPalette.foreground
}
Label {
Layout.fillWidth: true
visible: user.bridgeName
text: user.bridgeName
color: MPalette.lighter
}
Label {
Layout.fillWidth: true
@ -89,7 +98,7 @@ Dialog {
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: user ? user.id : "No ID"
text: user.id
color: MPalette.accent
}

View File

@ -11,3 +11,4 @@ FontFamilyDialog 2.0 FontFamilyDialog.qml
AccountDetailDialog 2.0 AccountDetailDialog.qml
OpenFileDialog 2.0 OpenFileDialog.qml
OpenFolderDialog 2.0 OpenFolderDialog.qml
ImageClipboardDialog 2.0 ImageClipboardDialog.qml

View File

@ -31,7 +31,7 @@ Item {
connection: root.connection
onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon)
onNewMessage: if (!window.active && MSettings.showNotification) notificationsManager.postNotification(roomId, eventId, roomName, senderName, text, icon)
}
SortFilterProxyModel {
@ -54,6 +54,16 @@ Item {
sorters: [
RoleSorter { roleName: "category" },
ExpressionSorter {
expression: {
return modelLeft.highlightCount > 0;
}
},
ExpressionSorter {
expression: {
return modelLeft.notificationCount > 0;
}
},
RoleSorter {
roleName: "lastActiveTime"
sortOrder: Qt.DescendingOrder

View File

@ -3,10 +3,12 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.12
import Qt.labs.qmlmodels 1.0
import Qt.labs.platform 1.0
import Spectral.Component 2.0
import Spectral.Component.Emoji 2.0
import Spectral.Component.Timeline 2.0
import Spectral.Dialog 2.0
import Spectral.Effect 2.0
import Spectral 0.1
@ -23,6 +25,121 @@ Item {
room: currentRoom
}
DropArea {
anchors.fill: parent
enabled: currentRoom
onDropped: {
if (!drop.hasUrls) return
roomPanelInput.attach(drop.urls[0])
}
}
ImageClipboard {
id: imageClipboard
}
Popup {
anchors.centerIn: parent
id: attachDialog
padding: 16
contentItem: RowLayout {
Control {
Layout.preferredWidth: 160
Layout.fillHeight: true
padding: 16
contentItem: ColumnLayout {
spacing: 16
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "\ue2c8"
font.pixelSize: 64
color: MPalette.lighter
}
Label {
Layout.alignment: Qt.AlignHCenter
text: "Choose local file"
}
}
background: RippleEffect {
onClicked: {
attachDialog.close()
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
fileDialog.chosen.connect(function(path) {
if (!path) return
roomPanelInput.attach(path)
})
fileDialog.open()
}
}
}
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
color: MPalette.banner
}
Control {
Layout.preferredWidth: 160
Layout.fillHeight: true
padding: 16
contentItem: ColumnLayout {
spacing: 16
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
icon: "\ue410"
font.pixelSize: 64
color: MPalette.lighter
}
Label {
Layout.alignment: Qt.AlignHCenter
text: "Clipboard image"
color: MPalette.foreground
}
}
background: RippleEffect {
onClicked: {
var localPath = StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png"
if (!imageClipboard.saveImage(localPath)) return
roomPanelInput.attach(localPath)
attachDialog.close()
}
}
}
}
}
Component {
id: openFileDialog
OpenFileDialog {}
}
Column {
anchors.centerIn: parent

View File

@ -5,6 +5,7 @@ import QtQuick.Controls.Material 2.12
import Spectral.Component 2.0
import Spectral.Component.Emoji 2.0
import Spectral.Dialog 2.0
import Spectral.Effect 2.0
import Spectral.Setting 0.1
@ -21,6 +22,9 @@ Control {
property int autoCompleteBeginPosition
property int autoCompleteEndPosition
property bool hasAttachment: false
property url attachmentPath
id: root
padding: 0
@ -171,13 +175,13 @@ Control {
Layout.alignment: Qt.AlignBottom
id: uploadButton
visible: !isReply
visible: !isReply && !hasAttachment
contentItem: MaterialIcon {
icon: "\ue226"
}
onClicked: currentRoom.chooseAndUploadFile()
onClicked: attachDialog.open()
BusyIndicator {
anchors.fill: parent
@ -202,6 +206,51 @@ Control {
onClicked: clearReply()
}
Control {
Layout.margins: 6
Layout.preferredHeight: 36
Layout.alignment: Qt.AlignVCenter
visible: hasAttachment
rightPadding: 8
background: Rectangle {
color: MPalette.accent
radius: height / 2
antialiasing: true
}
contentItem: RowLayout {
spacing: 0
ToolButton {
Layout.preferredWidth: height
Layout.fillHeight: true
id: cancelAttachmentButton
contentItem: MaterialIcon {
icon: "\ue5cd"
color: "white"
font.pixelSize: 18
}
onClicked: {
hasAttachment = false
attachmentPath = ""
}
}
Label {
Layout.alignment: Qt.AlignVCenter
text: attachmentPath != "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
color: "white"
}
}
}
TextArea {
property real progress: 0
@ -252,7 +301,7 @@ Control {
Keys.onReturnPressed: {
if (event.modifiers & Qt.ShiftModifier) {
insert(cursorPosition, "\n")
} else if (text) {
} else {
postMessage(text)
text = ""
closeAll()
@ -304,9 +353,16 @@ Control {
}
function postMessage(text) {
if (text.trim().length === 0) { return }
if(!currentRoom) { return }
if (hasAttachment) {
currentRoom.uploadFile(attachmentPath, text)
clearAttachment()
return
}
if (text.trim().length === 0) { return }
var PREFIX_ME = '/me '
var PREFIX_NOTICE = '/notice '
var PREFIX_RAINBOW = '/rainbow '
@ -372,6 +428,10 @@ Control {
}
}
ImageClipboard {
id: imageClipboard
}
function insert(str) {
inputField.insert(inputField.cursorPosition, str)
}
@ -396,4 +456,14 @@ Control {
autoCompleteListView.visible = false
emojiPicker.visible = false
}
function attach(localPath) {
hasAttachment = true
attachmentPath = localPath
}
function clearAttachment() {
hasAttachment = false
attachmentPath = ""
}
}

@ -1 +1 @@
Subproject commit 52a81dfa8a5415be369d819837f445479b833cde
Subproject commit d6f39dcb0de69322479f287514a8c36afcb3fe7b

View File

@ -56,12 +56,17 @@ ApplicationWindow {
quitOnLastWindowClosed: !MSettings.showTray
onErrorOccured: errorControl.show(error + ": " + detail, 3000)
}
NotificationsManager {
id: notificationsManager
onNotificationClicked: {
roomListForm.enteredRoom = spectralController.connection.room(roomId)
roomForm.goToEvent(eventId)
showWindow()
}
onErrorOccured: errorControl.show(error + ": " + detail, 3000)
}
Shortcut {

View File

@ -58,5 +58,6 @@
<file>imports/Spectral/Dialog/OpenFileDialog.qml</file>
<file>imports/Spectral/Dialog/OpenFolderDialog.qml</file>
<file>imports/Spectral/Component/Timeline/VideoDelegate.qml</file>
<file>imports/Spectral/Component/AutoRectangle.qml</file>
</qresource>
</RCC>

View File

@ -117,7 +117,6 @@ mac {
HEADERS += \
src/controller.h \
src/roomlistmodel.h \
src/imageprovider.h \
src/messageeventmodel.h \
src/emojimodel.h \
src/spectralroom.h \
@ -125,19 +124,22 @@ HEADERS += \
src/accountlistmodel.h \
src/spectraluser.h \
src/notifications/manager.h \
src/utils.h
src/utils.h \
src/imageclipboard.h \
src/matriximageprovider.h
SOURCES += src/main.cpp \
src/controller.cpp \
src/roomlistmodel.cpp \
src/imageprovider.cpp \
src/messageeventmodel.cpp \
src/emojimodel.cpp \
src/spectralroom.cpp \
src/userlistmodel.cpp \
src/accountlistmodel.cpp \
src/spectraluser.cpp \
src/utils.cpp
src/utils.cpp \
src/imageclipboard.cpp \
src/matriximageprovider.cpp
unix:!mac {
SOURCES += src/notifications/managerlinux.cpp

223
spectral.pro.user Normal file
View File

@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.9.0, 2019-05-19T18:51:54. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7614c50b-52db-49bf-a585-31417a3c0f62}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Qt 5.12.1 (System)</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Qt 5.12.1 (System)</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{97a6fc6e-df2b-4272-9664-99a23fd81def}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/tanuj/code/spectral/build</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">qmake</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Make</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="bool" key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild">true</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy Configuration</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value>
<valuelist type="QVariantList" key="Analyzer.Perf.Events">
<value type="QString">cpu-cycles</value>
</valuelist>
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/>
<value type="int" key="Analyzer.Perf.Frequency">250</value>
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value>
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Perf.StackSize">4096</value>
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value>
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value>
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value>
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value>
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/>
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value>
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value>
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds">
<value type="int">0</value>
<value type="int">1</value>
<value type="int">2</value>
<value type="int">3</value>
<value type="int">4</value>
<value type="int">5</value>
<value type="int">6</value>
<value type="int">7</value>
<value type="int">8</value>
<value type="int">9</value>
<value type="int">10</value>
<value type="int">11</value>
<value type="int">12</value>
<value type="int">13</value>
<value type="int">14</value>
</valuelist>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">spectral</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/home/tanuj/code/spectral/spectral.pro</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/tanuj/code/spectral/build</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">21</value>
</data>
<data>
<variable>Version</variable>
<value type="int">21</value>
</data>
</qtcreator>

View File

@ -31,13 +31,9 @@
#include <QtNetwork/QAuthenticator>
#include <QtNetwork/QNetworkReply>
Controller::Controller(QObject* parent)
: QObject(parent), notificationsManager(this) {
Controller::Controller(QObject* parent) : QObject(parent) {
QApplication::setQuitOnLastWindowClosed(false);
connect(&notificationsManager, &NotificationsManager::notificationClicked,
this, &Controller::notificationClicked);
Connection::setRoomType<SpectralRoom>();
Connection::setUserType<SpectralUser>();
@ -232,10 +228,6 @@ void Controller::createDirectChat(Connection* c, const QString& userID) {
});
}
void Controller::copyToClipboard(const QString& text) {
m_clipboard->setText(text);
}
void Controller::playAudio(QUrl localFile) {
QMediaPlayer* player = new QMediaPlayer;
player->setMedia(localFile);
@ -243,16 +235,6 @@ void Controller::playAudio(QUrl localFile) {
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
}
void Controller::postNotification(const QString& roomId,
const QString& eventId,
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon) {
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
text, icon);
}
int Controller::dpi() {
return SettingsGroup("Interface").value("dpi", 100).toInt();
}

View File

@ -67,8 +67,6 @@ class Controller : public QObject {
}
private:
QClipboard* m_clipboard = QApplication::clipboard();
NotificationsManager notificationsManager;
QVector<Connection*> m_connections;
QPointer<Connection> m_connection;
@ -99,14 +97,7 @@ class Controller : public QObject {
void joinRoom(Connection* c, const QString& alias);
void createRoom(Connection* c, const QString& name, const QString& topic);
void createDirectChat(Connection* c, const QString& userID);
void copyToClipboard(const QString& text);
void playAudio(QUrl localFile);
void postNotification(const QString& roomId,
const QString& eventId,
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
};
#endif // CONTROLLER_H

40
src/imageclipboard.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "imageclipboard.h"
#include <QDir>
#include <QFileInfo>
#include <QGuiApplication>
#include <QUrl>
#include <QtDebug>
ImageClipboard::ImageClipboard(QObject* parent)
: QObject(parent), m_clipboard(QGuiApplication::clipboard()) {
connect(m_clipboard, &QClipboard::changed, this,
&ImageClipboard::imageChanged);
}
bool ImageClipboard::hasImage() {
return !image().isNull();
}
QImage ImageClipboard::image() {
return m_clipboard->image();
}
bool ImageClipboard::saveImage(const QUrl& localPath) {
if (!localPath.isLocalFile())
return false;
auto i = image();
if (i.isNull())
return false;
QString path = QFileInfo(localPath.toString()).absolutePath();
QDir dir;
if (!dir.exists(path))
dir.mkpath(path);
i.save(localPath.toLocalFile());
return true;
}

28
src/imageclipboard.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef IMAGECLIPBOARD_H
#define IMAGECLIPBOARD_H
#include <QClipboard>
#include <QImage>
#include <QObject>
class ImageClipboard : public QObject {
Q_OBJECT
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
public:
explicit ImageClipboard(QObject* parent = nullptr);
bool hasImage();
QImage image();
Q_INVOKABLE bool saveImage(const QUrl& localPath);
private:
QClipboard* m_clipboard;
signals:
void imageChanged();
};
#endif // IMAGECLIPBOARD_H

View File

@ -8,8 +8,10 @@
#include "accountlistmodel.h"
#include "controller.h"
#include "emojimodel.h"
#include "imageprovider.h"
#include "imageclipboard.h"
#include "matriximageprovider.h"
#include "messageeventmodel.h"
#include "notifications/manager.h"
#include "room.h"
#include "roomlistmodel.h"
#include "spectralroom.h"
@ -55,6 +57,9 @@ int main(int argc, char* argv[]) {
qmlRegisterType<UserListModel>("Spectral", 0, 1, "UserListModel");
qmlRegisterType<MessageEventModel>("Spectral", 0, 1, "MessageEventModel");
qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel");
qmlRegisterType<NotificationsManager>("Spectral", 0, 1,
"NotificationsManager");
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1,
"RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
@ -72,9 +77,10 @@ int main(int argc, char* argv[]) {
QQmlApplicationEngine engine;
engine.addImportPath("qrc:/imports");
ImageProvider* m_provider = new ImageProvider();
engine.rootContext()->setContextProperty("imageProvider", m_provider);
engine.addImageProvider(QLatin1String("mxc"), m_provider);
MatrixImageProvider* matrixImageProvider = new MatrixImageProvider();
engine.rootContext()->setContextProperty("imageProvider",
matrixImageProvider);
engine.addImageProvider(QLatin1String("mxc"), matrixImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty())

View File

@ -1,4 +1,4 @@
#include "imageprovider.h"
#include "matriximageprovider.h"
#include <QDir>
#include <QFileInfo>
@ -108,7 +108,7 @@ void ThumbnailResponse::cancel() {
Qt::QueuedConnection);
}
QQuickImageResponse* ImageProvider::requestImageResponse(
QQuickImageResponse* MatrixImageProvider::requestImageResponse(
const QString& id,
const QSize& requestedSize) {
return new ThumbnailResponse(m_connection.load(), id, requestedSize);

View File

@ -1,5 +1,5 @@
#ifndef IMAGEPROVIDER_H
#define IMAGEPROVIDER_H
#ifndef MatrixImageProvider_H
#define MatrixImageProvider_H
#pragma once
#include <QtQuick/QQuickAsyncImageProvider>
@ -43,12 +43,12 @@ class ThumbnailResponse : public QQuickImageResponse {
void cancel() override;
};
class ImageProvider : public QObject, public QQuickAsyncImageProvider {
class MatrixImageProvider : public QObject, public QQuickAsyncImageProvider {
Q_OBJECT
Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE
setConnection NOTIFY connectionChanged)
public:
explicit ImageProvider() = default;
explicit MatrixImageProvider() = default;
QQuickImageResponse* requestImageResponse(
const QString& id,
@ -67,4 +67,4 @@ class ImageProvider : public QObject, public QQuickAsyncImageProvider {
QAtomicPointer<QMatrixClient::Connection> m_connection;
};
#endif // IMAGEPROVIDER_H
#endif // MatrixImageProvider_H

View File

@ -21,17 +21,14 @@ class NotificationsManager : public QObject {
public:
NotificationsManager(QObject* parent = nullptr);
void postNotification(const QString &roomId, const QString &eventId,
const QString &roomName, const QString &senderName,
const QString &text, const QImage &icon);
signals:
void notificationClicked(const QString roomId, const QString eventId);
private:
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
QDBusInterface dbus;
uint showNotification(const QString summary, const QString text,
uint showNotification(const QString summary,
const QString text,
const QImage image);
#endif
@ -43,6 +40,13 @@ class NotificationsManager : public QObject {
public slots:
void actionInvoked(uint id, QString action);
void notificationClosed(uint id, uint reason);
void postNotification(const QString& roomId,
const QString& eventId,
const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)

View File

@ -44,10 +44,11 @@ inline QSize getImageSize(const QUrl& imageUrl) {
return reader.size();
}
void SpectralRoom::chooseAndUploadFile() {
auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as"));
if (!localFile.isEmpty()) {
QString txnID = postFile(localFile.fileName(), localFile, false);
void SpectralRoom::uploadFile(const QUrl& url, const QString& body) {
if (url.isEmpty())
return;
QString txnID = postFile(body.isEmpty() ? url.fileName() : body, url, false);
setHasFileUploading(true);
connect(this, &Room::fileTransferCompleted,
[=](QString id, QUrl localFile, QUrl mxcUrl) {
@ -71,7 +72,6 @@ void SpectralRoom::chooseAndUploadFile() {
}
});
}
}
void SpectralRoom::acceptInvitation() {
connection()->joinRoom(id());

View File

@ -254,7 +254,7 @@ class SpectralRoom : public Room {
void fileUploadingProgressChanged();
public slots:
void chooseAndUploadFile();
void uploadFile(const QUrl& url, const QString& body = "");
void acceptInvitation();
void forget();
void sendTypingNotification(bool isTyping);

View File

@ -1 +1,5 @@
#include "spectraluser.h"
QColor SpectralUser::color() {
return QColor::fromHslF(hueF(), 0.7, 0.5, 1);
}

View File

@ -10,9 +10,12 @@ using namespace QMatrixClient;
class SpectralUser : public User {
Q_OBJECT
Q_PROPERTY(QColor color READ color CONSTANT)
public:
SpectralUser(QString userId, Connection* connection)
: User(userId, connection) {}
QColor color();
};
#endif // SpectralUser_H