Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F33432223
certificatelineedit.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
31 KB
Subscribers
None
certificatelineedit.cpp
View Options
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
"certificatelineedit.h"
#include
"commands/detailscommand.h"
#include
"dialogs/groupdetailsdialog.h"
#include
"utils/accessibility.h"
#include
"view/errorlabel.h"
#include
<QAccessible>
#include
<QAction>
#include
<QCompleter>
#include
<QPushButton>
#include
<QSignalBlocker>
#include
"kleopatra_debug.h"
#include
<Libkleo/Debug>
#include
<Libkleo/Formatting>
#include
<Libkleo/KeyCache>
#include
<Libkleo/KeyFilter>
#include
<Libkleo/KeyGroup>
#include
<Libkleo/KeyList>
#include
<Libkleo/KeyListModel>
#include
<Libkleo/KeyListSortFilterProxyModel>
#include
<Libkleo/UserIDProxyModel>
#include
<KLocalizedString>
#include
<gpgme++/key.h>
#include
<gpgme++/keylistresult.h>
#include
<QGpgME/KeyListJob>
#include
<QGpgME/Protocol>
#include
<QHBoxLayout>
#include
<QLineEdit>
#include
<QMenu>
#include
<QToolButton>
using
namespace
Kleo
;
using
namespace
GpgME
;
Q_DECLARE_METATYPE
(
GpgME
::
Key
)
Q_DECLARE_METATYPE
(
GpgME
::
UserID
)
Q_DECLARE_METATYPE
(
KeyGroup
)
static
QStringList
s_lookedUpKeys
;
namespace
{
class
CompletionProxyModel
:
public
KeyListSortFilterProxyModel
{
Q_OBJECT
public
:
CompletionProxyModel
(
QObject
*
parent
=
nullptr
)
:
KeyListSortFilterProxyModel
(
parent
)
{
}
int
columnCount
(
const
QModelIndex
&
parent
=
QModelIndex
())
const
override
{
Q_UNUSED
(
parent
)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// completion pop-up
return
1
;
}
QVariant
data
(
const
QModelIndex
&
idx
,
int
role
)
const
override
{
if
(
!
idx
.
isValid
())
{
return
QVariant
();
}
switch
(
role
)
{
case
Qt
::
DecorationRole
:
{
const
auto
key
=
KeyListSortFilterProxyModel
::
data
(
idx
,
KeyList
::
KeyRole
).
value
<
GpgME
::
Key
>
();
if
(
!
key
.
isNull
())
{
return
Kleo
::
Formatting
::
iconForUid
(
key
.
userID
(
0
));
}
const
auto
userID
=
KeyListSortFilterProxyModel
::
data
(
idx
,
KeyList
::
UserIDRole
).
value
<
GpgME
::
UserID
>
();
if
(
!
userID
.
isNull
())
{
return
Kleo
::
Formatting
::
iconForUid
(
userID
);
}
const
auto
group
=
KeyListSortFilterProxyModel
::
data
(
idx
,
KeyList
::
GroupRole
).
value
<
KeyGroup
>
();
if
(
!
group
.
isNull
())
{
return
QIcon
::
fromTheme
(
QStringLiteral
(
"group"
));
}
Q_ASSERT
(
!
key
.
isNull
()
||
!
userID
.
isNull
()
||
!
group
.
isNull
());
return
QVariant
();
}
default
:
return
KeyListSortFilterProxyModel
::
data
(
index
(
idx
.
row
(),
KeyList
::
Summary
),
role
);
}
}
private
:
bool
lessThan
(
const
QModelIndex
&
left
,
const
QModelIndex
&
right
)
const
override
{
const
auto
leftKey
=
sourceModel
()
->
data
(
left
,
KeyList
::
KeyRole
).
value
<
GpgME
::
Key
>
();
const
auto
leftGroup
=
leftKey
.
isNull
()
?
sourceModel
()
->
data
(
left
,
KeyList
::
GroupRole
).
value
<
KeyGroup
>
()
:
KeyGroup
{};
const
auto
leftUserID
=
sourceModel
()
->
data
(
left
,
KeyList
::
UserIDRole
).
value
<
GpgME
::
UserID
>
();
const
auto
rightUserID
=
sourceModel
()
->
data
(
right
,
KeyList
::
UserIDRole
).
value
<
GpgME
::
UserID
>
();
const
auto
rightKey
=
sourceModel
()
->
data
(
right
,
KeyList
::
KeyRole
).
value
<
GpgME
::
Key
>
();
const
auto
rightGroup
=
rightKey
.
isNull
()
?
sourceModel
()
->
data
(
right
,
KeyList
::
GroupRole
).
value
<
KeyGroup
>
()
:
KeyGroup
{};
// shouldn't happen, but still put null entries at the end
if
(
leftKey
.
isNull
()
&&
leftUserID
.
isNull
()
&&
leftGroup
.
isNull
())
{
return
false
;
}
if
(
rightKey
.
isNull
()
&&
rightUserID
.
isNull
()
&&
rightGroup
.
isNull
())
{
return
true
;
}
// first sort by the displayed name and/or email address
const
auto
leftNameAndOrEmail
=
leftGroup
.
isNull
()
?
(
leftKey
.
isNull
()
?
Formatting
::
nameAndEmailForSummaryLine
(
leftUserID
)
:
Formatting
::
nameAndEmailForSummaryLine
(
leftKey
))
:
leftGroup
.
name
();
const
auto
rightNameAndOrEmail
=
rightGroup
.
isNull
()
?
(
rightKey
.
isNull
()
?
Formatting
::
nameAndEmailForSummaryLine
(
rightUserID
)
:
Formatting
::
nameAndEmailForSummaryLine
(
rightKey
))
:
rightGroup
.
name
();
const
int
cmp
=
QString
::
localeAwareCompare
(
leftNameAndOrEmail
,
rightNameAndOrEmail
);
if
(
cmp
)
{
return
cmp
<
0
;
}
// then sort groups before certificates
if
(
!
leftGroup
.
isNull
()
&&
(
!
rightKey
.
isNull
()
||
!
rightUserID
.
isNull
()))
{
return
true
;
// left is group, right is certificate
}
if
((
!
leftKey
.
isNull
()
||
!
rightKey
.
isNull
())
&&
!
rightGroup
.
isNull
())
{
return
false
;
// left is certificate, right is group
}
// if both are groups (with identical names) sort them by their ID
if
(
!
leftGroup
.
isNull
()
&&
!
rightGroup
.
isNull
())
{
return
leftGroup
.
id
()
<
rightGroup
.
id
();
}
// sort certificates with same name/email by validity and creation time
const
auto
lUid
=
leftUserID
.
isNull
()
?
leftKey
.
userID
(
0
)
:
leftUserID
;
const
auto
rUid
=
rightUserID
.
isNull
()
?
rightKey
.
userID
(
0
)
:
rightUserID
;
if
(
lUid
.
validity
()
!=
rUid
.
validity
())
{
return
lUid
.
validity
()
>
rUid
.
validity
();
}
/* Both have the same validity, check which one is newer. */
time_t
leftTime
=
0
;
for
(
const
GpgME
::
Subkey
&
s
:
(
leftUserID
.
isNull
()
?
leftKey
:
leftUserID
.
parent
()).
subkeys
())
{
if
(
s
.
isBad
())
{
continue
;
}
if
(
s
.
creationTime
()
>
leftTime
)
{
leftTime
=
s
.
creationTime
();
}
}
time_t
rightTime
=
0
;
for
(
const
GpgME
::
Subkey
&
s
:
(
rightUserID
.
isNull
()
?
rightKey
:
rightUserID
.
parent
()).
subkeys
())
{
if
(
s
.
isBad
())
{
continue
;
}
if
(
s
.
creationTime
()
>
rightTime
)
{
rightTime
=
s
.
creationTime
();
}
}
if
(
rightTime
!=
leftTime
)
{
return
leftTime
>
rightTime
;
}
// as final resort we compare the fingerprints
return
strcmp
((
leftUserID
.
isNull
()
?
leftKey
:
leftUserID
.
parent
()).
primaryFingerprint
(),
(
rightUserID
.
isNull
()
?
rightKey
:
rightUserID
.
parent
()).
primaryFingerprint
())
<
0
;
}
};
auto
createSeparatorAction
(
QObject
*
parent
)
{
auto
action
=
new
QAction
{
parent
};
action
->
setSeparator
(
true
);
return
action
;
}
}
// namespace
class
CertificateLineEdit
::
Private
{
CertificateLineEdit
*
q
;
public
:
enum
class
Status
{
Empty
,
//< text is empty
Success
,
//< a certificate or group is set
None
,
//< entered text does not match any certificates or groups
Ambiguous
,
//< entered text matches multiple certificates or groups
Revoked
,
//< the selected cert is revoked
Expired
,
//< the selected cert is expired
};
enum
class
CursorPositioning
{
MoveToEnd
,
KeepPosition
,
MoveToStart
,
Default
=
MoveToEnd
,
};
explicit
Private
(
CertificateLineEdit
*
qq
,
AbstractKeyListModel
*
model
,
KeyUsage
::
Flags
usage
,
KeyFilter
*
filter
);
QString
text
()
const
;
void
setKey
(
const
GpgME
::
Key
&
key
);
void
setGroup
(
const
KeyGroup
&
group
);
void
setUserID
(
const
GpgME
::
UserID
&
userID
);
void
setKeyFilter
(
const
std
::
shared_ptr
<
KeyFilter
>
&
filter
);
void
setAccessibleName
(
const
QString
&
s
);
private
:
void
updateKey
(
CursorPositioning
positioning
);
void
editChanged
();
void
editFinished
();
void
checkLocate
();
void
onLocateJobResult
(
QGpgME
::
Job
*
job
,
const
QString
&
email
,
const
KeyListResult
&
result
,
const
std
::
vector
<
GpgME
::
Key
>
&
keys
);
void
openDetailsDialog
();
void
setTextWithBlockedSignals
(
const
QString
&
s
,
CursorPositioning
positioning
);
void
showContextMenu
(
const
QPoint
&
pos
);
QString
errorMessage
()
const
;
QIcon
statusIcon
()
const
;
QString
statusToolTip
()
const
;
void
updateStatusAction
();
void
updateErrorLabel
();
void
updateAccessibleNameAndDescription
();
public
:
Status
mStatus
=
Status
::
Empty
;
bool
mEditingInProgress
=
false
;
GpgME
::
Key
mKey
;
KeyGroup
mGroup
;
GpgME
::
UserID
mUserId
;
struct
Ui
{
explicit
Ui
(
QWidget
*
parent
)
:
lineEdit
{
parent
}
,
button
{
parent
}
,
errorLabel
{
parent
}
{
}
QLineEdit
lineEdit
;
QToolButton
button
;
ErrorLabel
errorLabel
;
}
ui
;
private
:
QString
mAccessibleName
;
UserIDProxyModel
*
const
mUserIdProxyModel
;
KeyListSortFilterProxyModel
*
const
mFilterModel
;
CompletionProxyModel
*
const
mCompleterFilterModel
;
QCompleter
*
mCompleter
=
nullptr
;
std
::
shared_ptr
<
KeyFilter
>
mFilter
;
QAction
*
const
mStatusAction
;
QAction
*
const
mShowDetailsAction
;
QPointer
<
QGpgME
::
Job
>
mLocateJob
;
Formatting
::
IconProvider
mIconProvider
;
};
CertificateLineEdit
::
Private
::
Private
(
CertificateLineEdit
*
qq
,
AbstractKeyListModel
*
model
,
KeyUsage
::
Flags
usage
,
KeyFilter
*
filter
)
:
q
{
qq
}
,
ui
{
qq
}
,
mUserIdProxyModel
{
new
UserIDProxyModel
{
qq
}}
,
mFilterModel
{
new
KeyListSortFilterProxyModel
{
qq
}}
,
mCompleterFilterModel
{
new
CompletionProxyModel
{
qq
}}
,
mCompleter
{
new
QCompleter
{
qq
}}
,
mFilter
{
std
::
shared_ptr
<
KeyFilter
>
{
filter
}}
,
mStatusAction
{
new
QAction
{
qq
}}
,
mShowDetailsAction
{
new
QAction
{
qq
}}
,
mIconProvider
{
usage
}
{
ui
.
lineEdit
.
setPlaceholderText
(
i18n
(
"Please enter a name or email address..."
));
ui
.
lineEdit
.
setClearButtonEnabled
(
true
);
ui
.
lineEdit
.
setContextMenuPolicy
(
Qt
::
CustomContextMenu
);
ui
.
lineEdit
.
addAction
(
mStatusAction
,
QLineEdit
::
LeadingPosition
);
mUserIdProxyModel
->
setSourceModel
(
model
);
mCompleterFilterModel
->
setKeyFilter
(
mFilter
);
mCompleterFilterModel
->
setSourceModel
(
mUserIdProxyModel
);
// initialize dynamic sorting
mCompleterFilterModel
->
sort
(
0
);
mCompleter
->
setModel
(
mCompleterFilterModel
);
mCompleter
->
setFilterMode
(
Qt
::
MatchContains
);
mCompleter
->
setCaseSensitivity
(
Qt
::
CaseInsensitive
);
ui
.
lineEdit
.
setCompleter
(
mCompleter
);
ui
.
button
.
setIcon
(
QIcon
::
fromTheme
(
QStringLiteral
(
"resource-group-new"
)));
ui
.
button
.
setToolTip
(
i18n
(
"Show certificate list"
));
ui
.
button
.
setAccessibleName
(
i18n
(
"Show certificate list"
));
ui
.
errorLabel
.
setVisible
(
false
);
auto
vbox
=
new
QVBoxLayout
{
q
};
vbox
->
setContentsMargins
(
0
,
0
,
0
,
0
);
auto
l
=
new
QHBoxLayout
;
l
->
setContentsMargins
(
0
,
0
,
0
,
0
);
l
->
addWidget
(
&
ui
.
lineEdit
);
l
->
addWidget
(
&
ui
.
button
);
vbox
->
addLayout
(
l
);
vbox
->
addWidget
(
&
ui
.
errorLabel
);
q
->
setFocusPolicy
(
ui
.
lineEdit
.
focusPolicy
());
q
->
setFocusProxy
(
&
ui
.
lineEdit
);
mShowDetailsAction
->
setIcon
(
QIcon
::
fromTheme
(
QStringLiteral
(
"help-about"
)));
mShowDetailsAction
->
setText
(
i18nc
(
"@action:inmenu"
,
"Show Details"
));
mShowDetailsAction
->
setEnabled
(
false
);
mFilterModel
->
setSourceModel
(
mUserIdProxyModel
);
mFilterModel
->
setFilterKeyColumn
(
KeyList
::
Summary
);
if
(
filter
)
{
mFilterModel
->
setKeyFilter
(
mFilter
);
}
connect
(
KeyCache
::
instance
().
get
(),
&
Kleo
::
KeyCache
::
keysMayHaveChanged
,
q
,
[
this
]()
{
updateKey
(
CursorPositioning
::
KeepPosition
);
});
connect
(
KeyCache
::
instance
().
get
(),
&
Kleo
::
KeyCache
::
groupUpdated
,
q
,
[
this
](
const
KeyGroup
&
group
)
{
if
(
!
mGroup
.
isNull
()
&&
mGroup
.
source
()
==
group
.
source
()
&&
mGroup
.
id
()
==
group
.
id
())
{
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
group
),
CursorPositioning
::
KeepPosition
);
// queue the update to ensure that the model has been updated
QMetaObject
::
invokeMethod
(
q
,
[
this
]()
{
updateKey
(
CursorPositioning
::
KeepPosition
);
},
Qt
::
QueuedConnection
);
}
});
connect
(
KeyCache
::
instance
().
get
(),
&
Kleo
::
KeyCache
::
groupRemoved
,
q
,
[
this
](
const
KeyGroup
&
group
)
{
if
(
!
mGroup
.
isNull
()
&&
mGroup
.
source
()
==
group
.
source
()
&&
mGroup
.
id
()
==
group
.
id
())
{
mGroup
=
KeyGroup
();
QSignalBlocker
blocky
{
&
ui
.
lineEdit
};
ui
.
lineEdit
.
clear
();
// queue the update to ensure that the model has been updated
QMetaObject
::
invokeMethod
(
q
,
[
this
]()
{
updateKey
(
CursorPositioning
::
KeepPosition
);
},
Qt
::
QueuedConnection
);
}
});
connect
(
&
ui
.
lineEdit
,
&
QLineEdit
::
editingFinished
,
q
,
[
this
]()
{
// queue the call of editFinished() to ensure that QCompleter::activated is handled first
QMetaObject
::
invokeMethod
(
q
,
[
this
]()
{
editFinished
();
},
Qt
::
QueuedConnection
);
});
connect
(
&
ui
.
lineEdit
,
&
QLineEdit
::
textChanged
,
q
,
[
this
]()
{
editChanged
();
});
connect
(
&
ui
.
lineEdit
,
&
QLineEdit
::
customContextMenuRequested
,
q
,
[
this
](
const
QPoint
&
pos
)
{
showContextMenu
(
pos
);
});
connect
(
mStatusAction
,
&
QAction
::
triggered
,
q
,
[
this
]()
{
openDetailsDialog
();
});
connect
(
mShowDetailsAction
,
&
QAction
::
triggered
,
q
,
[
this
]()
{
openDetailsDialog
();
});
connect
(
&
ui
.
button
,
&
QToolButton
::
clicked
,
q
,
&
CertificateLineEdit
::
certificateSelectionRequested
);
connect
(
mCompleter
,
qOverload
<
const
QModelIndex
&>
(
&
QCompleter
::
activated
),
q
,
[
this
](
const
QModelIndex
&
index
)
{
Key
key
=
mCompleter
->
completionModel
()
->
data
(
index
,
KeyList
::
KeyRole
).
value
<
Key
>
();
auto
group
=
mCompleter
->
completionModel
()
->
data
(
index
,
KeyList
::
GroupRole
).
value
<
KeyGroup
>
();
auto
userID
=
mCompleter
->
completionModel
()
->
data
(
index
,
KeyList
::
UserIDRole
).
value
<
UserID
>
();
if
(
!
userID
.
isNull
())
{
q
->
setUserID
(
userID
);
}
else
if
(
!
key
.
isNull
())
{
q
->
setKey
(
key
);
}
else
if
(
!
group
.
isNull
())
{
q
->
setGroup
(
group
);
}
else
{
qCDebug
(
KLEOPATRA_LOG
)
<<
"Activated item is neither key, nor userid, or group"
;
}
// queue the call of editFinished() to ensure that QLineEdit finished its own work
QMetaObject
::
invokeMethod
(
q
,
[
this
]()
{
editFinished
();
},
Qt
::
QueuedConnection
);
});
updateKey
(
CursorPositioning
::
Default
);
}
void
CertificateLineEdit
::
Private
::
openDetailsDialog
()
{
if
(
!
q
->
key
().
isNull
()
||
!
q
->
userID
().
isNull
())
{
const
Key
key
=
!
q
->
key
().
isNull
()
?
q
->
key
()
:
q
->
userID
().
parent
();
auto
cmd
=
new
Commands
::
DetailsCommand
{
key
};
cmd
->
setParentWidget
(
q
);
cmd
->
start
();
}
else
if
(
!
q
->
group
().
isNull
())
{
auto
dlg
=
new
Dialogs
::
GroupDetailsDialog
{
q
};
dlg
->
setAttribute
(
Qt
::
WA_DeleteOnClose
);
dlg
->
setGroup
(
q
->
group
());
dlg
->
show
();
}
}
void
CertificateLineEdit
::
Private
::
setTextWithBlockedSignals
(
const
QString
&
s
,
CursorPositioning
positioning
)
{
QSignalBlocker
blocky
{
&
ui
.
lineEdit
};
const
auto
cursorPos
=
ui
.
lineEdit
.
cursorPosition
();
ui
.
lineEdit
.
setText
(
s
);
switch
(
positioning
)
{
case
CursorPositioning
::
KeepPosition
:
ui
.
lineEdit
.
setCursorPosition
(
cursorPos
);
break
;
case
CursorPositioning
::
MoveToStart
:
ui
.
lineEdit
.
setCursorPosition
(
0
);
break
;
case
CursorPositioning
::
MoveToEnd
:
default
:
;
// setText() already moved the cursor to the end of the line
};
}
void
CertificateLineEdit
::
Private
::
showContextMenu
(
const
QPoint
&
pos
)
{
if
(
QMenu
*
menu
=
ui
.
lineEdit
.
createStandardContextMenu
())
{
auto
*
const
firstStandardAction
=
menu
->
actions
().
value
(
0
);
menu
->
insertActions
(
firstStandardAction
,
{
mShowDetailsAction
,
createSeparatorAction
(
menu
)});
menu
->
setAttribute
(
Qt
::
WA_DeleteOnClose
);
menu
->
popup
(
ui
.
lineEdit
.
mapToGlobal
(
pos
));
}
}
CertificateLineEdit
::
CertificateLineEdit
(
AbstractKeyListModel
*
model
,
KeyUsage
::
Flags
usage
,
KeyFilter
*
filter
,
QWidget
*
parent
)
:
QWidget
{
parent
}
,
d
{
new
Private
{
this
,
model
,
usage
,
filter
}}
{
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model
->
setParent
(
parent
?
parent
:
this
);
}
CertificateLineEdit
::~
CertificateLineEdit
()
=
default
;
void
CertificateLineEdit
::
Private
::
editChanged
()
{
const
bool
editingStarted
=
!
mEditingInProgress
;
mEditingInProgress
=
true
;
updateKey
(
CursorPositioning
::
Default
);
if
(
editingStarted
)
{
Q_EMIT
q
->
editingStarted
();
}
if
(
q
->
isEmpty
())
{
Q_EMIT
q
->
cleared
();
}
}
void
CertificateLineEdit
::
Private
::
editFinished
()
{
// perform a first update with the "editing in progress" flag still set
updateKey
(
CursorPositioning
::
MoveToStart
);
mEditingInProgress
=
false
;
checkLocate
();
// perform another update with the "editing in progress" flag cleared
// after a key locate may have been started; this makes sure that displaying
// an error is delayed until the key locate job has finished
updateKey
(
CursorPositioning
::
MoveToStart
);
}
void
CertificateLineEdit
::
Private
::
checkLocate
()
{
if
(
mStatus
!=
Status
::
None
)
{
// try to locate key only if text matches no local certificates, user ids, or groups
return
;
}
// Only check once per mailbox
const
auto
mailText
=
ui
.
lineEdit
.
text
().
trimmed
();
if
(
mailText
.
isEmpty
()
||
s_lookedUpKeys
.
contains
(
mailText
))
{
return
;
}
s_lookedUpKeys
<<
mailText
;
if
(
mLocateJob
)
{
mLocateJob
->
slotCancel
();
mLocateJob
.
clear
();
}
auto
job
=
QGpgME
::
openpgp
()
->
locateKeysJob
();
connect
(
job
,
&
QGpgME
::
KeyListJob
::
result
,
q
,
[
this
,
job
,
mailText
](
const
KeyListResult
&
result
,
const
std
::
vector
<
GpgME
::
Key
>
&
keys
)
{
onLocateJobResult
(
job
,
mailText
,
result
,
keys
);
});
if
(
auto
err
=
job
->
start
({
mailText
},
/*secretOnly=*/
false
))
{
qCDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Error: Starting"
<<
job
<<
"for"
<<
mailText
<<
"failed with"
<<
Formatting
::
errorAsString
(
err
);
}
else
{
mLocateJob
=
job
;
qCDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Started"
<<
job
<<
"for"
<<
mailText
;
}
}
void
CertificateLineEdit
::
Private
::
onLocateJobResult
(
QGpgME
::
Job
*
job
,
const
QString
&
email
,
const
KeyListResult
&
result
,
const
std
::
vector
<
GpgME
::
Key
>
&
keys
)
{
if
(
mLocateJob
!=
job
)
{
qCDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Ignoring outdated finished"
<<
job
<<
"for"
<<
email
;
return
;
}
qCDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
job
<<
"for"
<<
email
<<
"finished with"
<<
Formatting
::
errorAsString
(
result
.
error
())
<<
"and keys"
<<
keys
;
mLocateJob
.
clear
();
if
(
!
keys
.
empty
()
&&
!
keys
.
front
().
isNull
())
{
KeyCache
::
mutableInstance
()
->
insert
(
keys
.
front
());
// inserting the key implicitly triggers an update
}
else
{
// explicitly trigger an update to display "no key" error
updateKey
(
CursorPositioning
::
MoveToStart
);
}
}
void
CertificateLineEdit
::
Private
::
updateKey
(
CursorPositioning
positioning
)
{
static
const
_detail
::
ByFingerprint
<
std
::
equal_to
>
keysHaveSameFingerprint
;
const
auto
mailText
=
ui
.
lineEdit
.
text
().
trimmed
();
auto
newKey
=
Key
();
auto
newGroup
=
KeyGroup
();
auto
newUserId
=
UserID
();
if
(
mailText
.
isEmpty
())
{
mStatus
=
Status
::
Empty
;
}
else
{
mFilterModel
->
setFilterRegularExpression
(
QRegularExpression
::
escape
(
mailText
));
if
(
mFilterModel
->
rowCount
()
>
1
)
{
// keep current key, user id, or group if they still match
if
(
!
mKey
.
isNull
())
{
for
(
int
row
=
0
;
row
<
mFilterModel
->
rowCount
();
++
row
)
{
const
QModelIndex
index
=
mFilterModel
->
index
(
row
,
0
);
Key
key
=
mFilterModel
->
key
(
index
);
if
(
!
key
.
isNull
()
&&
keysHaveSameFingerprint
(
key
,
mKey
))
{
newKey
=
mKey
;
break
;
}
}
}
else
if
(
!
mGroup
.
isNull
())
{
newGroup
=
mGroup
;
for
(
int
row
=
0
;
row
<
mFilterModel
->
rowCount
();
++
row
)
{
const
QModelIndex
index
=
mFilterModel
->
index
(
row
,
0
);
KeyGroup
group
=
mFilterModel
->
group
(
index
);
if
(
!
group
.
isNull
()
&&
group
.
source
()
==
mGroup
.
source
()
&&
group
.
id
()
==
mGroup
.
id
())
{
newGroup
=
mGroup
;
break
;
}
}
}
else
if
(
!
mUserId
.
isNull
())
{
for
(
int
row
=
0
;
row
<
mFilterModel
->
rowCount
();
++
row
)
{
const
QModelIndex
index
=
mFilterModel
->
index
(
row
,
0
);
UserID
userId
=
index
.
data
(
KeyList
::
UserIDRole
).
value
<
UserID
>
();
if
(
!
userId
.
isNull
()
&&
keysHaveSameFingerprint
(
userId
.
parent
(),
mUserId
.
parent
())
&&
!
strcmp
(
userId
.
id
(),
mUserId
.
id
()))
{
newUserId
=
mUserId
;
}
}
}
if
(
newKey
.
isNull
()
&&
newGroup
.
isNull
()
&&
newUserId
.
isNull
())
{
mStatus
=
Status
::
Ambiguous
;
}
}
else
if
(
mFilterModel
->
rowCount
()
==
1
)
{
const
auto
index
=
mFilterModel
->
index
(
0
,
0
);
newUserId
=
mFilterModel
->
data
(
index
,
KeyList
::
UserIDRole
).
value
<
UserID
>
();
if
(
newUserId
.
isNull
())
{
newKey
=
mFilterModel
->
data
(
index
,
KeyList
::
KeyRole
).
value
<
Key
>
();
}
newGroup
=
mFilterModel
->
data
(
index
,
KeyList
::
GroupRole
).
value
<
KeyGroup
>
();
Q_ASSERT
(
!
newKey
.
isNull
()
||
!
newGroup
.
isNull
()
||
!
newUserId
.
isNull
());
if
(
newKey
.
isNull
()
&&
newGroup
.
isNull
()
&&
newUserId
.
isNull
())
{
mStatus
=
Status
::
None
;
}
}
else
{
if
(
!
mUserId
.
isNull
()
&&
(
mUserId
.
isRevoked
()
||
mUserId
.
parent
().
isRevoked
()))
{
mStatus
=
Status
::
Revoked
;
}
else
if
(
!
mUserId
.
isNull
()
&&
mUserId
.
parent
().
isExpired
())
{
mStatus
=
Status
::
Expired
;
}
else
{
mStatus
=
Status
::
None
;
}
}
}
mKey
=
newKey
;
mGroup
=
newGroup
;
mUserId
=
newUserId
;
if
(
!
mKey
.
isNull
())
{
/* FIXME: This needs to be solved by a multiple UID supporting model */
mStatus
=
Status
::
Success
;
ui
.
lineEdit
.
setToolTip
(
Formatting
::
toolTip
(
mKey
,
Formatting
::
ToolTipOption
::
AllOptions
));
if
(
!
mEditingInProgress
)
{
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
mKey
),
positioning
);
}
}
else
if
(
!
mGroup
.
isNull
())
{
mStatus
=
Status
::
Success
;
ui
.
lineEdit
.
setToolTip
(
Formatting
::
toolTip
(
mGroup
,
Formatting
::
ToolTipOption
::
AllOptions
));
if
(
!
mEditingInProgress
)
{
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
mGroup
),
positioning
);
}
}
else
if
(
!
mUserId
.
isNull
())
{
mStatus
=
Status
::
Success
;
ui
.
lineEdit
.
setToolTip
(
Formatting
::
toolTip
(
mUserId
,
Formatting
::
ToolTipOption
::
AllOptions
));
if
(
!
mEditingInProgress
)
{
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
mUserId
),
positioning
);
}
}
else
{
ui
.
lineEdit
.
setToolTip
({});
}
mShowDetailsAction
->
setEnabled
(
mStatus
==
Status
::
Success
);
updateStatusAction
();
updateErrorLabel
();
Q_EMIT
q
->
keyChanged
();
}
QString
CertificateLineEdit
::
Private
::
errorMessage
()
const
{
switch
(
mStatus
)
{
case
Status
::
Empty
:
case
Status
::
Success
:
return
{};
case
Status
::
None
:
return
i18n
(
"No matching certificates or groups found"
);
case
Status
::
Ambiguous
:
return
i18n
(
"Multiple matching certificates or groups found"
);
case
Status
::
Expired
:
return
i18n
(
"This certificate is expired"
);
case
Status
::
Revoked
:
return
i18n
(
"This certificate is revoked"
);
default
:
qDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Invalid status:"
<<
static_cast
<
int
>
(
mStatus
);
Q_ASSERT
(
!
"Invalid status"
);
};
return
{};
}
QIcon
CertificateLineEdit
::
Private
::
statusIcon
()
const
{
switch
(
mStatus
)
{
case
Status
::
Empty
:
return
QIcon
::
fromTheme
(
QStringLiteral
(
"emblem-unavailable"
));
case
Status
::
Success
:
if
(
!
mKey
.
isNull
())
{
return
mIconProvider
.
icon
(
mKey
);
}
else
if
(
!
mGroup
.
isNull
())
{
return
mIconProvider
.
icon
(
mGroup
);
}
else
if
(
!
mUserId
.
isNull
())
{
return
mIconProvider
.
icon
(
mUserId
);
}
else
{
qDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Success, but neither key, nor user id, or group."
;
return
{};
}
case
Status
::
None
:
case
Status
::
Ambiguous
:
if
(
mEditingInProgress
||
mLocateJob
)
{
return
QIcon
::
fromTheme
(
QStringLiteral
(
"emblem-question"
));
}
else
{
return
QIcon
::
fromTheme
(
QStringLiteral
(
"emblem-error"
));
}
case
Status
::
Expired
:
case
Status
::
Revoked
:
return
QIcon
::
fromTheme
(
QStringLiteral
(
"emblem-error"
));
default
:
qDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Invalid status:"
<<
static_cast
<
int
>
(
mStatus
);
Q_ASSERT
(
!
"Invalid status"
);
};
return
{};
}
QString
CertificateLineEdit
::
Private
::
statusToolTip
()
const
{
switch
(
mStatus
)
{
case
Status
::
Empty
:
return
{};
case
Status
::
Success
:
if
(
!
mUserId
.
isNull
())
{
return
Formatting
::
validity
(
mUserId
);
}
if
(
!
mKey
.
isNull
())
{
return
Formatting
::
validity
(
mKey
.
userID
(
0
));
}
else
if
(
!
mGroup
.
isNull
())
{
return
Formatting
::
validity
(
mGroup
);
}
else
{
qDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Success, but neither key, nor user id, or group."
;
return
{};
}
case
Status
::
None
:
case
Status
::
Ambiguous
:
return
errorMessage
();
default
:
qDebug
(
KLEOPATRA_LOG
)
<<
__func__
<<
"Invalid status:"
<<
static_cast
<
int
>
(
mStatus
);
Q_ASSERT
(
!
"Invalid status"
);
};
return
{};
}
void
CertificateLineEdit
::
Private
::
updateStatusAction
()
{
mStatusAction
->
setIcon
(
statusIcon
());
mStatusAction
->
setToolTip
(
statusToolTip
());
}
namespace
{
QString
decoratedError
(
const
QString
&
text
)
{
return
text
.
isEmpty
()
?
QString
()
:
i18nc
(
"@info"
,
"Error: %1"
,
text
);
}
}
void
CertificateLineEdit
::
Private
::
updateErrorLabel
()
{
const
auto
currentErrorMessage
=
ui
.
errorLabel
.
text
();
const
auto
newErrorMessage
=
decoratedError
(
errorMessage
());
if
(
newErrorMessage
==
currentErrorMessage
)
{
return
;
}
if
(
currentErrorMessage
.
isEmpty
()
&&
(
mEditingInProgress
||
mLocateJob
))
{
// delay showing the error message until editing is finished, so that we
// do not annoy the user with an error message while they are still
// entering the recipient;
// on the other hand, we clear the error message immediately if it does
// not apply anymore and we update the error message immediately if it
// changed
return
;
}
ui
.
errorLabel
.
setVisible
(
!
newErrorMessage
.
isEmpty
());
ui
.
errorLabel
.
setText
(
newErrorMessage
);
updateAccessibleNameAndDescription
();
}
void
CertificateLineEdit
::
Private
::
setAccessibleName
(
const
QString
&
s
)
{
mAccessibleName
=
s
;
updateAccessibleNameAndDescription
();
}
void
CertificateLineEdit
::
Private
::
updateAccessibleNameAndDescription
()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if
(
mAccessibleName
.
isEmpty
())
{
mAccessibleName
=
getAccessibleName
(
&
ui
.
lineEdit
);
}
const
bool
errorShown
=
ui
.
errorLabel
.
isVisible
();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by setting the error message as accessible description of the input field
const
auto
description
=
errorShown
?
ui
.
errorLabel
.
text
()
:
QString
{};
if
(
ui
.
lineEdit
.
accessibleDescription
()
!=
description
)
{
ui
.
lineEdit
.
setAccessibleDescription
(
description
);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid data" if this state is set;
// emulate this by adding "invalid data" to the accessible name of the input field
const
auto
name
=
errorShown
?
mAccessibleName
+
QLatin1String
{
", "
}
+
invalidEntryText
()
//
:
mAccessibleName
;
if
(
ui
.
lineEdit
.
accessibleName
()
!=
name
)
{
ui
.
lineEdit
.
setAccessibleName
(
name
);
}
}
Key
CertificateLineEdit
::
key
()
const
{
if
(
isEnabled
())
{
return
d
->
mKey
;
}
else
{
return
Key
();
}
}
KeyGroup
CertificateLineEdit
::
group
()
const
{
if
(
isEnabled
())
{
return
d
->
mGroup
;
}
else
{
return
KeyGroup
();
}
}
UserID
CertificateLineEdit
::
userID
()
const
{
if
(
isEnabled
())
{
return
d
->
mUserId
;
}
else
{
return
UserID
();
}
}
QString
CertificateLineEdit
::
Private
::
text
()
const
{
return
ui
.
lineEdit
.
text
().
trimmed
();
}
QString
CertificateLineEdit
::
text
()
const
{
return
d
->
text
();
}
void
CertificateLineEdit
::
Private
::
setKey
(
const
Key
&
key
)
{
mKey
=
key
;
mGroup
=
KeyGroup
();
mUserId
=
UserID
();
qCDebug
(
KLEOPATRA_LOG
)
<<
"Setting Key. "
<<
Formatting
::
summaryLine
(
key
);
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
key
),
CursorPositioning
::
MoveToStart
);
updateKey
(
CursorPositioning
::
MoveToStart
);
}
void
CertificateLineEdit
::
setKey
(
const
Key
&
key
)
{
d
->
setKey
(
key
);
}
void
CertificateLineEdit
::
Private
::
setUserID
(
const
UserID
&
userID
)
{
mUserId
=
userID
;
mKey
=
Key
();
mGroup
=
KeyGroup
();
qCDebug
(
KLEOPATRA_LOG
)
<<
"Setting UserID. "
<<
Formatting
::
summaryLine
(
userID
);
// position cursor, so that the start of the summary is visible
setTextWithBlockedSignals
(
Formatting
::
summaryLine
(
userID
),
CursorPositioning
::
MoveToStart
);
updateKey
(
CursorPositioning
::
MoveToStart
);
}
void
CertificateLineEdit
::
setUserID
(
const
UserID
&
userID
)
{
d
->
setUserID
(
userID
);
}
void
CertificateLineEdit
::
Private
::
setGroup
(
const
KeyGroup
&
group
)
{
mGroup
=
group
;
mKey
=
Key
();
mUserId
=
UserID
();
const
QString
summary
=
Formatting
::
summaryLine
(
group
);
qCDebug
(
KLEOPATRA_LOG
)
<<
"Setting KeyGroup. "
<<
summary
;
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals
(
summary
,
CursorPositioning
::
MoveToStart
);
updateKey
(
CursorPositioning
::
MoveToStart
);
}
void
CertificateLineEdit
::
setGroup
(
const
KeyGroup
&
group
)
{
d
->
setGroup
(
group
);
}
bool
CertificateLineEdit
::
isEmpty
()
const
{
return
d
->
mStatus
==
Private
::
Status
::
Empty
;
}
bool
CertificateLineEdit
::
isEditingInProgress
()
const
{
return
d
->
mEditingInProgress
;
}
bool
CertificateLineEdit
::
hasAcceptableInput
()
const
{
return
d
->
mStatus
==
Private
::
Status
::
Empty
//
||
d
->
mStatus
==
Private
::
Status
::
Success
;
}
void
CertificateLineEdit
::
Private
::
setKeyFilter
(
const
std
::
shared_ptr
<
KeyFilter
>
&
filter
)
{
mFilter
=
filter
;
mFilterModel
->
setKeyFilter
(
filter
);
mCompleterFilterModel
->
setKeyFilter
(
mFilter
);
updateKey
(
CursorPositioning
::
Default
);
}
void
CertificateLineEdit
::
setKeyFilter
(
const
std
::
shared_ptr
<
KeyFilter
>
&
filter
)
{
d
->
setKeyFilter
(
filter
);
}
void
CertificateLineEdit
::
setAccessibleNameOfLineEdit
(
const
QString
&
name
)
{
d
->
setAccessibleName
(
name
);
}
#include
"certificatelineedit.moc"
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Sat, Nov 22, 1:28 PM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
77/8b/109124693d0423261c9b88ceb70a
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment