Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F27325953
messageviewer.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
16 KB
Subscribers
None
messageviewer.cpp
View Options
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include
"messageviewer.h"
using
namespace
Qt
::
Literals
::
StringLiterals
;
#include
"attachmentview_p.h"
#include
"messagecontainerwidget_p.h"
#include
"mimetreeparser_widgets_debug.h"
#include
"partmodel.h"
#include
"urlhandler.h"
#include
<KCalendarCore/Event>
#include
<KCalendarCore/ICalFormat>
#include
<KLocalizedString>
#include
<KMessageWidget>
#include
<MimeTreeParserCore/AttachmentModel>
#include
<MimeTreeParserCore/MessageParser>
#include
<MimeTreeParserCore/ObjectTreeParser>
#include
<MimeTreeParserCore/PartModel>
#include
<QAction>
#include
<QDesktopServices>
#include
<QFileDialog>
#include
<QFontDatabase>
#include
<QFormLayout>
#include
<QGroupBox>
#include
<QLabel>
#include
<QMenu>
#include
<QScrollArea>
#include
<QStandardPaths>
#include
<QVBoxLayout>
#include
<qnamespace.h>
using
namespace
MimeTreeParser
::
Widgets
;
class
MessageViewer
::
Private
{
public
:
explicit
Private
(
MessageViewer
*
q_ptr
)
:
q
{
q_ptr
}
,
messageWidget
(
new
KMessageWidget
(
q_ptr
))
{
createActions
();
messageWidget
->
setCloseButtonVisible
(
true
);
messageWidget
->
hide
();
}
MessageViewer
*
const
q
;
QVBoxLayout
*
layout
=
nullptr
;
KMime
::
Message
::
Ptr
message
;
MessageParser
parser
;
QScrollArea
*
scrollArea
=
nullptr
;
QFormLayout
*
formLayout
=
nullptr
;
AttachmentView
*
attachmentView
=
nullptr
;
MimeTreeParser
::
MessagePart
::
List
selectedParts
;
UrlHandler
*
urlHandler
=
nullptr
;
KMessageWidget
*
const
messageWidget
=
nullptr
;
QAction
*
saveAttachmentAction
=
nullptr
;
QAction
*
openAttachmentAction
=
nullptr
;
QAction
*
importPublicKeyAction
=
nullptr
;
bool
fixedFont
=
false
;
void
createActions
()
{
saveAttachmentAction
=
new
QAction
(
QIcon
::
fromTheme
(
u
"document-save-as-symbolic"
_s
),
i18n
(
"&Save Attachment As…"
),
q
);
connect
(
saveAttachmentAction
,
&
QAction
::
triggered
,
q
,
[
this
]()
{
saveSelectedAttachments
();
});
openAttachmentAction
=
new
QAction
(
QIcon
::
fromTheme
(
u
"document-open-symbolic"
_s
),
i18nc
(
"to open"
,
"Open"
),
q
);
connect
(
openAttachmentAction
,
&
QAction
::
triggered
,
q
,
[
this
]()
{
openSelectedAttachments
();
});
importPublicKeyAction
=
new
QAction
(
QIcon
::
fromTheme
(
u
"document-import-key-symbolic"
_s
),
i18nc
(
"@action:inmenu"
,
"Import public key"
),
q
);
connect
(
importPublicKeyAction
,
&
QAction
::
triggered
,
q
,
[
this
]()
{
importPublicKey
();
});
}
void
openSelectedAttachments
();
void
saveSelectedAttachments
();
void
selectionChanged
();
void
showContextMenu
();
void
importPublicKey
();
void
recursiveBuildViewer
(
PartModel
*
parts
,
QVBoxLayout
*
layout
,
const
QModelIndex
&
parent
);
};
void
MessageViewer
::
Private
::
openSelectedAttachments
()
{
Q_ASSERT
(
selectedParts
.
count
()
>=
1
);
for
(
const
auto
&
part
:
std
::
as_const
(
selectedParts
))
{
parser
.
attachments
()
->
openAttachment
(
part
);
}
}
void
MessageViewer
::
Private
::
saveSelectedAttachments
()
{
Q_ASSERT
(
selectedParts
.
count
()
>=
1
);
for
(
const
auto
&
part
:
std
::
as_const
(
selectedParts
))
{
QString
pname
=
part
->
filename
();
if
(
pname
.
isEmpty
())
{
pname
=
i18nc
(
"Fallback when file has no name"
,
"unnamed"
);
}
const
QString
path
=
QFileDialog
::
getSaveFileName
(
q
,
i18n
(
"Save Attachment As"
),
pname
);
parser
.
attachments
()
->
saveAttachmentToPath
(
part
,
path
);
}
}
void
MessageViewer
::
Private
::
importPublicKey
()
{
Q_ASSERT
(
selectedParts
.
count
()
==
1
);
parser
.
attachments
()
->
importPublicKey
(
selectedParts
[
0
]);
}
void
MessageViewer
::
Private
::
showContextMenu
()
{
const
int
numberOfParts
(
selectedParts
.
count
());
QMenu
menu
;
if
(
numberOfParts
==
1
)
{
const
QString
mimetype
=
QString
::
fromLatin1
(
selectedParts
.
first
()
->
mimeType
());
if
(
mimetype
==
QLatin1StringView
(
"application/pgp-keys"
))
{
menu
.
addAction
(
importPublicKeyAction
);
}
}
menu
.
addAction
(
openAttachmentAction
);
menu
.
addAction
(
saveAttachmentAction
);
menu
.
exec
(
QCursor
::
pos
());
}
void
MessageViewer
::
Private
::
selectionChanged
()
{
const
QModelIndexList
selectedRows
=
attachmentView
->
selectionModel
()
->
selectedRows
();
MimeTreeParser
::
MessagePart
::
List
selectedParts
;
selectedParts
.
reserve
(
selectedRows
.
count
());
for
(
const
QModelIndex
&
index
:
selectedRows
)
{
auto
part
=
attachmentView
->
model
()
->
data
(
index
,
AttachmentModel
::
AttachmentPartRole
).
value
<
MimeTreeParser
::
MessagePart
::
Ptr
>
();
selectedParts
.
append
(
part
);
}
this
->
selectedParts
=
selectedParts
;
}
MessageViewer
::
MessageViewer
(
QWidget
*
parent
)
:
QSplitter
(
Qt
::
Vertical
,
parent
)
,
d
(
std
::
make_unique
<
MessageViewer
::
Private
>
(
this
))
{
setObjectName
(
QLatin1StringView
(
"MessageViewerSplitter"
));
setChildrenCollapsible
(
false
);
setSizes
({
0
});
addWidget
(
d
->
messageWidget
);
auto
mainWidget
=
new
QWidget
(
this
);
auto
mainLayout
=
new
QVBoxLayout
(
mainWidget
);
mainLayout
->
setContentsMargins
({});
mainLayout
->
setSpacing
(
0
);
auto
headersArea
=
new
QWidget
(
mainWidget
);
headersArea
->
setSizePolicy
(
QSizePolicy
::
Expanding
,
QSizePolicy
::
Maximum
);
mainLayout
->
addWidget
(
headersArea
);
d
->
urlHandler
=
new
UrlHandler
(
this
);
d
->
formLayout
=
new
QFormLayout
(
headersArea
);
auto
widget
=
new
QWidget
(
this
);
d
->
layout
=
new
QVBoxLayout
(
widget
);
d
->
layout
->
setSizeConstraint
(
QLayout
::
SetMinAndMaxSize
);
d
->
layout
->
setObjectName
(
QLatin1StringView
(
"PartLayout"
));
d
->
scrollArea
=
new
QScrollArea
(
this
);
d
->
scrollArea
->
setWidget
(
widget
);
d
->
scrollArea
->
setWidgetResizable
(
true
);
d
->
scrollArea
->
setBackgroundRole
(
QPalette
::
Base
);
mainLayout
->
addWidget
(
d
->
scrollArea
);
mainLayout
->
setStretchFactor
(
d
->
scrollArea
,
2
);
setStretchFactor
(
1
,
2
);
d
->
attachmentView
=
new
AttachmentView
(
this
);
d
->
attachmentView
->
setProperty
(
"_breeze_borders_sides"
,
QVariant
::
fromValue
(
QFlags
{
Qt
::
BottomEdge
}));
d
->
attachmentView
->
setSizeAdjustPolicy
(
QAbstractScrollArea
::
AdjustToContents
);
addWidget
(
d
->
attachmentView
);
connect
(
d
->
attachmentView
,
&
AttachmentView
::
contextMenuRequested
,
this
,
[
this
]
{
d
->
selectionChanged
();
d
->
showContextMenu
();
});
connect
(
d
->
parser
.
attachments
(),
&
AttachmentModel
::
info
,
this
,
[
this
](
const
QString
&
message
)
{
d
->
messageWidget
->
setMessageType
(
KMessageWidget
::
Information
);
d
->
messageWidget
->
setText
(
message
);
d
->
messageWidget
->
animatedShow
();
});
connect
(
d
->
parser
.
attachments
(),
&
AttachmentModel
::
errorOccurred
,
this
,
[
this
](
const
QString
&
message
)
{
d
->
messageWidget
->
setMessageType
(
KMessageWidget
::
Error
);
d
->
messageWidget
->
setText
(
message
);
d
->
messageWidget
->
animatedShow
();
});
connect
(
d
->
attachmentView
->
selectionModel
(),
&
QItemSelectionModel
::
selectionChanged
,
this
,
[
this
]
{
d
->
selectionChanged
();
});
connect
(
d
->
attachmentView
,
&
QAbstractItemView
::
doubleClicked
,
this
,
[
this
](
const
QModelIndex
&
)
{
// Since this is only emitted if a valid index is double clicked we can assume
// that the first click of the double click set the selection accordingly.
d
->
openSelectedAttachments
();
});
}
MessageViewer
::~
MessageViewer
()
{
QLayoutItem
*
child
;
while
((
child
=
d
->
layout
->
takeAt
(
0
))
!=
nullptr
)
{
delete
child
->
widget
();
delete
child
;
}
}
QString
MessageViewer
::
subject
()
const
{
return
d
->
parser
.
subject
();
}
KMime
::
Message
::
Ptr
MessageViewer
::
message
()
const
{
return
d
->
parser
.
message
();
}
void
MessageViewer
::
Private
::
recursiveBuildViewer
(
PartModel
*
parts
,
QVBoxLayout
*
layout
,
const
QModelIndex
&
parent
)
{
for
(
int
i
=
0
,
count
=
parts
->
rowCount
(
parent
);
i
<
count
;
i
++
)
{
const
auto
idx
=
parts
->
index
(
i
,
0
,
parent
);
const
auto
type
=
idx
.
data
(
PartModel
::
TypeRole
).
value
<
PartModel
::
Types
>
();
const
auto
content
=
idx
.
data
(
PartModel
::
ContentRole
).
toString
();
// signature
const
auto
signatureSecurityLevel
=
idx
.
data
(
PartModel
::
SignatureSecurityLevelRole
).
value
<
PartModel
::
SecurityLevel
>
();
const
auto
signatureInfo
=
idx
.
data
(
PartModel
::
SignatureDetailsRole
).
toString
();
const
auto
signatureIconName
=
idx
.
data
(
PartModel
::
SignatureIconNameRole
).
toString
();
// encryption
const
auto
encryptionSecurityLevel
=
idx
.
data
(
PartModel
::
EncryptionSecurityLevelRole
).
value
<
PartModel
::
SecurityLevel
>
();
const
auto
encryptionInfo
=
idx
.
data
(
PartModel
::
EncryptionDetails
).
value
<
SignatureInfo
>
();
const
auto
encryptionIconName
=
idx
.
data
(
PartModel
::
EncryptionIconNameRole
).
toString
();
// sidebar
const
auto
sidebarSecurityLevel
=
idx
.
data
(
PartModel
::
SidebarSecurityLevelRole
).
value
<
PartModel
::
SecurityLevel
>
();
switch
(
type
)
{
case
PartModel
::
Types
::
Plain
:
{
auto
container
=
new
MessageWidgetContainer
(
signatureInfo
,
signatureIconName
,
signatureSecurityLevel
,
encryptionInfo
,
encryptionIconName
,
encryptionSecurityLevel
,
sidebarSecurityLevel
,
urlHandler
);
auto
label
=
new
QLabel
(
content
);
label
->
setTextInteractionFlags
(
Qt
::
TextBrowserInteraction
);
label
->
setOpenExternalLinks
(
true
);
label
->
setWordWrap
(
true
);
if
(
fixedFont
)
{
label
->
setFont
(
QFontDatabase
::
systemFont
(
QFontDatabase
::
FixedFont
));
}
container
->
layout
()
->
addWidget
(
label
);
layout
->
addWidget
(
container
);
break
;
}
case
PartModel
::
Types
::
Ical
:
{
auto
container
=
new
MessageWidgetContainer
(
signatureInfo
,
signatureIconName
,
signatureSecurityLevel
,
encryptionInfo
,
encryptionIconName
,
encryptionSecurityLevel
,
sidebarSecurityLevel
,
urlHandler
);
KCalendarCore
::
ICalFormat
format
;
auto
incidence
=
format
.
fromString
(
content
);
auto
widget
=
new
QGroupBox
(
container
);
widget
->
setTitle
(
i18n
(
"Invitation"
));
auto
incidenceLayout
=
new
QFormLayout
(
widget
);
incidenceLayout
->
addRow
(
i18n
(
"&Summary:"
),
new
QLabel
(
incidence
->
summary
()));
incidenceLayout
->
addRow
(
i18n
(
"&Organizer:"
),
new
QLabel
(
incidence
->
organizer
().
fullName
()));
if
(
incidence
->
location
().
length
()
>
0
)
{
incidenceLayout
->
addRow
(
i18n
(
"&Location:"
),
new
QLabel
(
incidence
->
location
()));
}
incidenceLayout
->
addRow
(
i18n
(
"&Start date:"
),
new
QLabel
(
incidence
->
dtStart
().
toLocalTime
().
toString
()));
if
(
const
auto
event
=
incidence
.
dynamicCast
<
KCalendarCore
::
Event
>
())
{
incidenceLayout
->
addRow
(
i18n
(
"&End date:"
),
new
QLabel
(
event
->
dtEnd
().
toLocalTime
().
toString
()));
}
if
(
incidence
->
description
().
length
()
>
0
)
{
incidenceLayout
->
addRow
(
i18n
(
"&Details:"
),
new
QLabel
(
incidence
->
description
()));
}
container
->
layout
()
->
addWidget
(
widget
);
layout
->
addWidget
(
container
);
break
;
}
case
PartModel
::
Types
::
Encapsulated
:
{
auto
container
=
new
MessageWidgetContainer
(
signatureInfo
,
signatureIconName
,
signatureSecurityLevel
,
encryptionInfo
,
encryptionIconName
,
encryptionSecurityLevel
,
sidebarSecurityLevel
,
urlHandler
);
auto
groupBox
=
new
QGroupBox
(
container
);
groupBox
->
setSizePolicy
(
QSizePolicy
::
MinimumExpanding
,
q
->
sizePolicy
().
verticalPolicy
());
groupBox
->
setTitle
(
i18n
(
"Encapsulated email"
));
auto
encapsulatedLayout
=
new
QVBoxLayout
(
groupBox
);
auto
header
=
new
QWidget
(
groupBox
);
auto
headerLayout
=
new
QFormLayout
(
header
);
const
auto
from
=
parts
->
data
(
parts
->
index
(
i
,
0
,
parent
),
PartModel
::
SenderRole
).
toString
();
const
auto
date
=
parts
->
data
(
parts
->
index
(
i
,
0
,
parent
),
PartModel
::
DateRole
).
toDateTime
();
headerLayout
->
addRow
(
i18n
(
"From:"
),
new
QLabel
(
from
));
headerLayout
->
addRow
(
i18n
(
"Date:"
),
new
QLabel
(
date
.
toLocalTime
().
toString
()));
encapsulatedLayout
->
addWidget
(
header
);
recursiveBuildViewer
(
parts
,
encapsulatedLayout
,
parts
->
index
(
i
,
0
,
parent
));
container
->
layout
()
->
addWidget
(
groupBox
);
layout
->
addWidget
(
container
);
break
;
}
case
PartModel
::
Types
::
Error
:
{
const
auto
errorString
=
idx
.
data
(
PartModel
::
ErrorString
).
toString
();
auto
errorWidget
=
new
KMessageWidget
(
errorString
);
errorWidget
->
setCloseButtonVisible
(
false
);
errorWidget
->
setMessageType
(
KMessageWidget
::
MessageType
::
Error
);
QObject
::
connect
(
errorWidget
,
&
KMessageWidget
::
linkActivated
,
errorWidget
,
[
this
,
errorWidget
](
const
QString
&
link
)
{
QUrl
url
(
link
);
if
(
url
.
path
()
==
QLatin1StringView
(
"showCertificate"
))
{
urlHandler
->
handleClick
(
QUrl
(
link
),
errorWidget
->
window
()
->
windowHandle
());
}
});
errorWidget
->
setWordWrap
(
true
);
layout
->
addWidget
(
errorWidget
);
break
;
}
default
:
qCWarning
(
MIMETREEPARSER_WIDGET_LOG
)
<<
parts
->
data
(
parts
->
index
(
i
,
0
,
parent
),
PartModel
::
ContentRole
)
<<
type
;
}
}
}
class
HeaderLabel
:
public
QLabel
{
public
:
explicit
HeaderLabel
(
const
QString
&
content
)
:
QLabel
(
content
)
{
setWordWrap
(
true
);
setTextFormat
(
Qt
::
PlainText
);
setSizePolicy
(
QSizePolicy
::
Expanding
,
QSizePolicy
::
Maximum
);
}
void
resizeEvent
(
QResizeEvent
*
event
)
override
{
int
height
=
heightForWidth
(
width
());
setMaximumHeight
(
height
);
setMinimumHeight
(
height
);
QLabel
::
resizeEvent
(
event
);
}
};
void
MessageViewer
::
setMessage
(
const
KMime
::
Message
::
Ptr
message
)
{
setUpdatesEnabled
(
false
);
d
->
parser
.
setMessage
(
message
);
for
(
int
i
=
d
->
formLayout
->
rowCount
()
-
1
;
i
>=
0
;
i
--
)
{
d
->
formLayout
->
removeRow
(
i
);
}
if
(
!
d
->
parser
.
subject
().
isEmpty
())
{
const
auto
label
=
new
QLabel
(
d
->
parser
.
subject
());
label
->
setTextFormat
(
Qt
::
PlainText
);
d
->
formLayout
->
addRow
(
i18n
(
"&Subject:"
),
label
);
}
if
(
!
d
->
parser
.
from
().
isEmpty
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&From:"
),
new
HeaderLabel
(
d
->
parser
.
from
()));
}
if
(
!
d
->
parser
.
sender
().
isEmpty
()
&&
d
->
parser
.
from
()
!=
d
->
parser
.
sender
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&Sender:"
),
new
HeaderLabel
(
d
->
parser
.
sender
()));
}
if
(
!
d
->
parser
.
to
().
isEmpty
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&To:"
),
new
HeaderLabel
(
d
->
parser
.
to
()));
}
if
(
!
d
->
parser
.
cc
().
isEmpty
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&CC:"
),
new
HeaderLabel
(
d
->
parser
.
cc
()));
}
if
(
!
d
->
parser
.
bcc
().
isEmpty
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&BCC:"
),
new
HeaderLabel
(
d
->
parser
.
bcc
()));
}
if
(
!
d
->
parser
.
date
().
isNull
())
{
d
->
formLayout
->
addRow
(
i18n
(
"&Date:"
),
new
HeaderLabel
(
QLocale
::
system
().
toString
(
d
->
parser
.
date
().
toLocalTime
(),
QLocale
::
ShortFormat
)));
}
const
auto
parts
=
d
->
parser
.
parts
();
QLayoutItem
*
child
;
while
((
child
=
d
->
layout
->
takeAt
(
0
))
!=
nullptr
)
{
delete
child
->
widget
();
delete
child
;
}
d
->
recursiveBuildViewer
(
parts
,
d
->
layout
,
{});
d
->
layout
->
addStretch
();
d
->
attachmentView
->
setModel
(
d
->
parser
.
attachments
());
d
->
attachmentView
->
setVisible
(
d
->
parser
.
attachments
()
->
rowCount
()
>
0
);
setUpdatesEnabled
(
true
);
}
void
MessageViewer
::
print
(
QPainter
*
painter
,
int
width
)
{
const
auto
oldSize
=
size
();
resize
(
width
-
30
,
oldSize
.
height
());
d
->
scrollArea
->
setFrameShape
(
QFrame
::
NoFrame
);
render
(
painter
);
d
->
scrollArea
->
setFrameShape
(
QFrame
::
StyledPanel
);
resize
(
oldSize
);
}
bool
MessageViewer
::
fixedFont
()
const
{
return
d
->
fixedFont
;
}
void
MessageViewer
::
setFixedFont
(
bool
checked
)
{
d
->
fixedFont
=
checked
;
setMessage
(
message
());
// rebuild UI
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Fri, Aug 29, 7:56 AM (1 d, 7 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
13/0c/47be1d44dc7a3f6b03f1db45cf44
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment