518 lines
15 KiB
C++
518 lines
15 KiB
C++
#include "stdafx.h"
|
|
#include "PopUpWindow.h"
|
|
#include <windows.h>
|
|
#include <QWindow>
|
|
#include "../MiscHelpers/Common/Common.h"
|
|
#include "../MiscHelpers/Common/Settings.h"
|
|
#include "../SbiePlusAPI.h"
|
|
|
|
bool CPopUpWindow__DarkMode = false;
|
|
|
|
CPopUpWindow::CPopUpWindow(QWidget* parent) : QMainWindow(parent)
|
|
{
|
|
m_HideAllMessages = false;
|
|
|
|
Qt::WindowFlags flags = windowFlags();
|
|
flags |= Qt::CustomizeWindowHint;
|
|
//flags &= ~Qt::WindowContextHelpButtonHint;
|
|
//flags &= ~Qt::WindowSystemMenuHint;
|
|
//flags &= ~Qt::WindowMinMaxButtonsHint;
|
|
flags &= ~Qt::WindowMaximizeButtonHint;
|
|
//flags &= ~Qt::WindowCloseButtonHint;
|
|
setWindowFlags(flags);
|
|
|
|
this->setWindowTitle(tr("Sandboxie-Plus Notifications"));
|
|
|
|
QWidget* centralWidget = new QWidget();
|
|
ui.setupUi(centralWidget);
|
|
this->setCentralWidget(centralWidget);
|
|
|
|
//setWindowFlags(Qt::Tool);
|
|
|
|
ui.table->verticalHeader()->hide();
|
|
ui.table->horizontalHeader()->hide();
|
|
ui.table->setColumnCount(1);
|
|
ui.table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
|
|
|
m_pActionCopy = new QAction();
|
|
connect(m_pActionCopy, SIGNAL(triggered(bool)), this, SLOT(OnCopy()));
|
|
m_pActionCopy->setShortcut(QKeySequence::Copy);
|
|
m_pActionCopy->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
|
this->addAction(m_pActionCopy);
|
|
|
|
m_iTopMost = 0;
|
|
SetWindowPos((HWND)this->winId(), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
m_uTimerID = startTimer(1000);
|
|
|
|
m_ResetPosition = !restoreGeometry(theConf->GetBlob("PopUpWindow/Window_Geometry"));
|
|
}
|
|
|
|
CPopUpWindow::~CPopUpWindow()
|
|
{
|
|
killTimer(m_uTimerID);
|
|
|
|
theConf->SetBlob("PopUpWindow/Window_Geometry", saveGeometry());
|
|
}
|
|
|
|
void CPopUpWindow::AddEntry(CPopUpEntry* pEntry)
|
|
{
|
|
int RowCounter = ui.table->rowCount();
|
|
ui.table->insertRow(RowCounter);
|
|
ui.table->setCellWidget(RowCounter, 0, pEntry);
|
|
ui.table->verticalHeader()->setSectionResizeMode(RowCounter, QHeaderView::ResizeToContents);
|
|
|
|
ui.table->scrollToBottom();
|
|
|
|
Show();
|
|
}
|
|
|
|
void CPopUpWindow::RemoveEntry(CPopUpEntry* pEntry)
|
|
{
|
|
for (int i = 0; i < ui.table->rowCount(); i++)
|
|
{
|
|
CPopUpEntry* pCurEntry = qobject_cast<CPopUpEntry*>(ui.table->cellWidget(i, 0));
|
|
if (pCurEntry && pCurEntry == pEntry) {
|
|
ui.table->removeRow(i--);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ui.table->rowCount() == 0)
|
|
this->hide();
|
|
}
|
|
|
|
void CPopUpWindow::Show()
|
|
{
|
|
Poke();
|
|
|
|
QScreen *screen = this->windowHandle()->screen();
|
|
QRect scrRect = screen->availableGeometry();
|
|
|
|
if (!m_ResetPosition)
|
|
{
|
|
QRect wndRect = this->frameGeometry();
|
|
if (wndRect.bottom() > scrRect.height() || wndRect.right() > scrRect.width() || wndRect.top() < 0 || wndRect.left() < 0)
|
|
m_ResetPosition = true;
|
|
}
|
|
|
|
if (m_ResetPosition)
|
|
{
|
|
m_ResetPosition = false;
|
|
|
|
this->resize(600, 200);
|
|
this->move(scrRect.width() - 600 - 20, scrRect.height() - 200 - 50);
|
|
}
|
|
|
|
CSandMan::SafeShow(this);
|
|
}
|
|
|
|
void CPopUpWindow::Poke()
|
|
{
|
|
if (!this->isVisible() || m_iTopMost <= -5) {
|
|
SetWindowPos((HWND)this->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
m_iTopMost = 5;
|
|
}
|
|
}
|
|
|
|
void CPopUpWindow::closeEvent(QCloseEvent *e)
|
|
{
|
|
for (int i = 0; i < ui.table->rowCount(); i++)
|
|
{
|
|
CPopUpPrompt* pEntry = qobject_cast<CPopUpPrompt*>(ui.table->cellWidget(i, 0));
|
|
if (pEntry)
|
|
SendPromptResult(pEntry, 0);
|
|
}
|
|
|
|
ui.table->clear();
|
|
ui.table->setRowCount(0);
|
|
|
|
e->ignore();
|
|
|
|
this->hide();
|
|
}
|
|
|
|
void CPopUpWindow::timerEvent(QTimerEvent* pEvent)
|
|
{
|
|
if (pEvent->timerId() != m_uTimerID)
|
|
return;
|
|
|
|
if (m_iTopMost > 0)
|
|
{
|
|
QWidget *topMostWindow = nullptr;
|
|
for (QWidget *widget : QApplication::topLevelWidgets()) {
|
|
if (widget->isVisible() && widget->isActiveWindow()) {
|
|
topMostWindow = widget;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(topMostWindow && (topMostWindow->inherits("CCheckableMessageBox") || topMostWindow->inherits("QMessageBox")))
|
|
{
|
|
m_iTopMost = 0;
|
|
SetWindowPos((HWND)this->winId(), HWND_NOTOPMOST , 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
|
|
SetWindowPos((HWND)topMostWindow->winId(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
SetWindowPos((HWND)topMostWindow->winId(), HWND_NOTOPMOST , 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
|
|
if (m_iTopMost > -5 && (--m_iTopMost == 0))
|
|
{
|
|
SetWindowPos((HWND)this->winId(), HWND_NOTOPMOST , 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
|
|
void CPopUpWindow::AddLogMessage(quint32 MsgCode, const QStringList& MsgData, quint32 ProcessId)
|
|
{
|
|
if (IsMessageHidden(MsgCode, MsgData))
|
|
return;
|
|
|
|
CBoxedProcessPtr pProcess;
|
|
QString ProcessName;
|
|
QString BoxName;
|
|
if (ProcessId == 4)
|
|
ProcessName = "System";
|
|
else {
|
|
pProcess = theAPI->GetProcessById(ProcessId);
|
|
if (!pProcess.isNull()) {
|
|
ProcessName = pProcess->GetProcessName();
|
|
BoxName = pProcess->GetBoxName();
|
|
}
|
|
else
|
|
ProcessName = QString("PID %1").arg(ProcessId);
|
|
}
|
|
|
|
QString Message = theGUI->FormatSbieMessage(MsgCode, MsgData, ProcessName);
|
|
QString Link = theGUI->MakeSbieMsgLink(MsgCode, MsgData, ProcessName);
|
|
|
|
int RowCounter = ui.table->rowCount();
|
|
if (RowCounter > 0) {
|
|
CPopUpMessage* pEntry = qobject_cast<CPopUpMessage*>(ui.table->cellWidget(RowCounter-1, 0));
|
|
if (pEntry && pEntry->GetMsgString() == Message) {
|
|
pEntry->Repeat();
|
|
return;
|
|
}
|
|
}
|
|
|
|
CPopUpMessage* pEntry = new CPopUpMessage(Message, Link, MsgCode, MsgData, ProcessName, BoxName, this);
|
|
QObject::connect(pEntry, SIGNAL(Dismiss()), this, SLOT(OnDismissMessage()));
|
|
QObject::connect(pEntry, SIGNAL(Hide()), this, SLOT(OnHideMessage()));
|
|
AddEntry(pEntry);
|
|
|
|
if ((MsgCode & 0xFFFF) == 1319) // Blocked spooler print to file
|
|
{
|
|
if (pProcess.isNull() || pProcess->IsTerminated())
|
|
return;
|
|
|
|
QString Message2 = tr("Do you want to allow the print spooler to write outside the sandbox for %1 (%2)?").arg(pProcess->GetProcessName()).arg(pProcess->GetProcessId());
|
|
|
|
QVariantMap Result;
|
|
Result["id"] = (int)CSbieAPI::ePrintSpooler;
|
|
|
|
CPopUpPrompt* pEntry = new CPopUpPrompt(Message2, 0, Result, pProcess, this);
|
|
pEntry->m_pRemember->setVisible(false);
|
|
connect(pEntry, SIGNAL(PromptResult(int)), this, SLOT(OnPromptResult(int)));
|
|
AddEntry(pEntry);
|
|
}
|
|
|
|
// 2219 // elevation is disabled
|
|
|
|
// 1307 // internet denied
|
|
|
|
// 1308 // start/run denied
|
|
}
|
|
|
|
void CPopUpWindow::ReloadHiddenMessages()
|
|
{
|
|
m_HiddenMessages.clear();
|
|
|
|
if (theAPI->GetUserSettings() == NULL)
|
|
return;
|
|
|
|
QStringList HiddenMessages = theAPI->GetUserSettings()->GetTextList("SbieCtrl_HideMessage", true);
|
|
foreach(const QString& HiddenMessage, HiddenMessages)
|
|
{
|
|
if (HiddenMessage == "*") {
|
|
m_HideAllMessages = true;
|
|
m_HiddenMessages.clear();
|
|
break;
|
|
}
|
|
|
|
StrPair CodeDetail = Split2(HiddenMessage, ",");
|
|
if (CodeDetail.first.left(4) == "SBIE" || CodeDetail.first.left(4) == "SBOX")
|
|
CodeDetail.first = CodeDetail.first.mid(4);
|
|
m_HiddenMessages.insert(CodeDetail.first.toInt(), CodeDetail.second);
|
|
}
|
|
}
|
|
|
|
void CPopUpWindow::OnDismissMessage()
|
|
{
|
|
CPopUpEntry* pEntry = qobject_cast<CPopUpEntry*>(sender());
|
|
RemoveEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::OnHideMessage()
|
|
{
|
|
CPopUpMessage* pEntry = qobject_cast<CPopUpMessage*>(sender());
|
|
|
|
if (QMessageBox("Sandboxie-Plus", theAPI->GetSbieMsgStr(3647, theGUI->m_LanguageId).arg(pEntry->GetMsgId()).arg("")
|
|
, QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::NoButton, this).exec() != QMessageBox::Yes)
|
|
return;
|
|
|
|
m_HiddenMessages.insert(pEntry->GetMsgId(), pEntry->GetMsgData(1));
|
|
if (theAPI->GetUserSettings() != NULL)
|
|
theAPI->GetUserSettings()->AppendText("SbieCtrl_HideMessage", QString("%1,%2").arg(pEntry->GetMsgId()).arg(pEntry->GetMsgData(1)));
|
|
|
|
for (int i = 0; i < ui.table->rowCount(); i++)
|
|
{
|
|
CPopUpMessage* pEntry = qobject_cast<CPopUpMessage*>(ui.table->cellWidget(i, 0));
|
|
if (pEntry && IsMessageHidden(pEntry->GetMsgCode(), pEntry->GetMsgData()))
|
|
ui.table->removeRow(i--);
|
|
}
|
|
|
|
if(ui.table->rowCount() == 0)
|
|
this->hide();
|
|
}
|
|
|
|
bool CPopUpWindow::IsMessageHidden(quint32 MsgCode, const QStringList& MsgData)
|
|
{
|
|
if (m_HideAllMessages)
|
|
return true;
|
|
|
|
foreach(const QString& Details, m_HiddenMessages.values(MsgCode & 0xFFFF))
|
|
{
|
|
if(Details.isEmpty())
|
|
return true;
|
|
|
|
QRegularExpression exp("^" + QRegularExpression::escape(Details).replace("\\*",".*").replace("\\?","."));
|
|
if(MsgData.size() >= 2 && exp.match(MsgData[1]).hasMatch())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CPopUpWindow::AddUserPrompt(quint32 RequestId, const QVariantMap& Data, quint32 ProcessId)
|
|
{
|
|
CBoxedProcessPtr pProcess = theAPI->GetProcessById(ProcessId);
|
|
if (pProcess.isNull() || pProcess->IsTerminated())
|
|
return;
|
|
|
|
QVariantMap Result;
|
|
Result["id"] = Data["id"];
|
|
|
|
int retval = pProcess.objectCast<CSbieProcess>()->GetRememberedAction(Result["id"].toInt());
|
|
if (retval != -1)
|
|
{
|
|
Result["retval"] = retval;
|
|
theAPI->SendQueueRpl(RequestId, Result);
|
|
return;
|
|
}
|
|
|
|
QString Message;
|
|
switch (Data["id"].toInt())
|
|
{
|
|
case CSbieAPI::eFileMigration:
|
|
Message = tr("Do you want to allow %4 (%5) to copy a %1 large file into sandbox: %2?\nFile name: %3")
|
|
.arg(FormatSize(Data["fileSize"].toULongLong())).arg(pProcess->GetBoxName())
|
|
.arg(Data["fileName"].toString())
|
|
.arg(pProcess->GetProcessName()).arg(pProcess->GetProcessId());
|
|
break;
|
|
case CSbieAPI::eInetBlockade:
|
|
Message = tr("Do you want to allow %1 (%2) access to the internet?\nFull path: %3")
|
|
.arg(pProcess->GetProcessName()).arg(pProcess->GetProcessId())
|
|
.arg(pProcess->GetFileName());
|
|
break;
|
|
}
|
|
|
|
CPopUpPrompt* pEntry = new CPopUpPrompt(Message, RequestId, Result, pProcess, this);
|
|
switch (pEntry->m_Result["id"].toInt())
|
|
{
|
|
case CSbieAPI::eInetBlockade:
|
|
pEntry->AddAddToList();
|
|
pEntry->m_pRemember->setChecked(true);
|
|
break;
|
|
}
|
|
connect(pEntry, SIGNAL(PromptResult(int)), this, SLOT(OnPromptResult(int)));
|
|
AddEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::OnPromptResult(int retval)
|
|
{
|
|
CPopUpPrompt* pEntry = qobject_cast<CPopUpPrompt*>(sender());
|
|
if (retval == -1)
|
|
pEntry->m_pProcess->Terminate();
|
|
else
|
|
SendPromptResult(pEntry, retval);
|
|
RemoveEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::SendPromptResult(CPopUpPrompt* pEntry, int retval)
|
|
{
|
|
if (retval == 1)
|
|
{
|
|
switch (pEntry->m_Result["id"].toInt())
|
|
{
|
|
case CSbieAPI::ePrintSpooler:
|
|
theAPI->SetProcessExemption(pEntry->m_pProcess->GetProcessId(), 'splr', true);
|
|
break;
|
|
case CSbieAPI::eInetBlockade:
|
|
if (pEntry->m_bAddToList)
|
|
pEntry->m_pProcess.objectCast<CSbieProcess>()->SetInternetAccess(true);
|
|
theAPI->SetProcessExemption(pEntry->m_pProcess->GetProcessId(), 'inet', true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pEntry->m_RequestId == 0)
|
|
return;
|
|
|
|
pEntry->m_Result["retval"] = retval;
|
|
theAPI->SendQueueRpl(pEntry->m_RequestId, pEntry->m_Result);
|
|
|
|
if (pEntry->m_pRemember->isChecked())
|
|
pEntry->m_pProcess.objectCast<CSbieProcess>()->SetRememberedAction(pEntry->m_Result["id"].toInt(), retval);
|
|
}
|
|
|
|
void CPopUpWindow::AddFileToRecover(const QString& FilePath, QString BoxPath, const CSandBoxPtr& pBox, quint32 ProcessId)
|
|
{
|
|
CBoxedProcessPtr pProcess = theAPI->GetProcessById(ProcessId);
|
|
|
|
QString Message = tr("%1 is eligible for quick recovery from %2.\nThe file was written by: %3")
|
|
.arg(FilePath.mid(FilePath.lastIndexOf("\\") + 1)).arg(QString(pBox->GetName()).replace("_", " "))
|
|
.arg(pProcess.isNull() ? tr("an UNKNOWN process.") : tr("%1 (%2)").arg(pProcess->GetProcessName()).arg(pProcess->GetProcessId()));
|
|
|
|
if (BoxPath.isEmpty()) // legacy case, no BoxName, no support for driver serial numbers
|
|
BoxPath = theAPI->GetBoxedPath(pBox->GetName(), FilePath);
|
|
|
|
CPopUpRecovery* pEntry = new CPopUpRecovery(Message, FilePath, theAPI->GetBoxedPath(pBox.data(), FilePath), pBox->GetName(), this);
|
|
|
|
QStringList RecoverTargets = theAPI->GetUserSettings()->GetTextList("SbieCtrl_RecoverTarget", true);
|
|
pEntry->m_pTarget->insertItems(pEntry->m_pTarget->count()-1, RecoverTargets);
|
|
|
|
connect(pEntry, SIGNAL(Dismiss(int)), this, SLOT(OnDismiss(int)));
|
|
connect(pEntry, SIGNAL(RecoverFile(int)), this, SLOT(OnRecoverFile(int)));
|
|
connect(pEntry, SIGNAL(OpenRecovery()), this, SLOT(OnOpenRecovery()));
|
|
AddEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::OnDismiss(int iFlag)
|
|
{
|
|
CPopUpRecovery* pEntry = qobject_cast<CPopUpRecovery*>(sender());
|
|
|
|
if (iFlag == 0)
|
|
RemoveEntry(pEntry);
|
|
|
|
if ((iFlag & 0x02) != 0) // disable for this box
|
|
{
|
|
CSandBoxPtr pBox = theAPI->GetBoxByName(pEntry->m_BoxName);
|
|
if (!pBox.isNull())
|
|
pBox.objectCast<CSandBoxPlus>()->SetSuspendRecovery();
|
|
}
|
|
|
|
if ((iFlag & 0x01) != 0) // dismiss all from this box
|
|
{
|
|
for (int i = 0; i < ui.table->rowCount(); i++)
|
|
{
|
|
CPopUpRecovery* pCurEntry = qobject_cast<CPopUpRecovery*>(ui.table->cellWidget(i, 0));
|
|
if (pCurEntry && pCurEntry->m_BoxName == pEntry->m_BoxName)
|
|
ui.table->removeRow(i--);
|
|
}
|
|
|
|
if (ui.table->rowCount() == 0)
|
|
this->hide();
|
|
}
|
|
}
|
|
|
|
void CPopUpWindow::OnRecoverFile(int Action)
|
|
{
|
|
CPopUpRecovery* pEntry = qobject_cast<CPopUpRecovery*>(sender());
|
|
|
|
QString RecoveryFolder = pEntry->m_pTarget->currentText();
|
|
|
|
if (pEntry->m_pTarget->currentIndex() != 0 || pEntry->m_ListCleared) {
|
|
QStringList RecoverTargets;
|
|
for (int i = 2; i < pEntry->m_pTarget->count() - 1; i++)
|
|
RecoverTargets.append(pEntry->m_pTarget->itemText(i));
|
|
theAPI->GetUserSettings()->UpdateTextList("SbieCtrl_RecoverTarget", RecoverTargets, true);
|
|
}
|
|
|
|
QString FileName = pEntry->m_FilePath.mid(pEntry->m_FilePath.lastIndexOf("\\") + 1);
|
|
//QString BoxedFilePath = theAPI->GetBoxedPath(pEntry->m_BoxName, pEntry->m_FilePath); // pEntry->m_BoxPath
|
|
|
|
QList<QPair<QString, QString>> FileList;
|
|
FileList.append(qMakePair(pEntry->m_BoxPath, RecoveryFolder + "\\" + FileName));
|
|
|
|
SB_PROGRESS Status = theGUI->RecoverFiles(pEntry->m_BoxName, FileList, theGUI, Action);
|
|
if (Status.GetStatus() == OP_ASYNC)
|
|
theGUI->AddAsyncOp(Status.GetValue());
|
|
|
|
RemoveEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::OnOpenRecovery()
|
|
{
|
|
CPopUpRecovery* pEntry = qobject_cast<CPopUpRecovery*>(sender());
|
|
|
|
CSandBoxPtr pBox = theAPI->GetBoxByName(pEntry->m_BoxName);
|
|
if (pBox)
|
|
theGUI->ShowRecovery(pBox);
|
|
|
|
// since we opened the recovery dialog, we can dismiss all the notifications for this box
|
|
OnDismiss(0x01);
|
|
}
|
|
|
|
void CPopUpWindow::ShowProgress(quint32 MsgCode, const QStringList& MsgData, quint32 ProcessId)
|
|
{
|
|
QString BoxName = MsgData.size() >= 2 ? MsgData[1] : tr("UNKNOWN");
|
|
QString FilePath = MsgData.size() >= 3 ? theAPI->Nt2DosPath(MsgData[2]) : tr("UNKNOWN");
|
|
quint64 SizeLeft = MsgData.size() >= 4 ? MsgData[3].toULongLong() : 0;
|
|
|
|
if (m_HiddenMessages.contains(0, FilePath))
|
|
return;
|
|
|
|
QString Message = tr("Migrating a large file %1 into the sandbox %2, %3 left.\nFull path: %4")
|
|
.arg(FilePath.mid(FilePath.lastIndexOf("\\") + 1)).arg(BoxName).arg(FormatSize(SizeLeft))
|
|
.arg(FilePath);
|
|
|
|
CPopUpProgress* pEntry = NULL;
|
|
for (int i = 0; i < ui.table->rowCount(); i++)
|
|
{
|
|
CPopUpProgress* pCurEntry = qobject_cast<CPopUpProgress*>(ui.table->cellWidget(i, 0));
|
|
if (pCurEntry && pCurEntry->m_ID == FilePath) {
|
|
pEntry = pCurEntry;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pEntry)
|
|
{
|
|
pEntry = new CPopUpProgress(Message, FilePath, SizeLeft, this);
|
|
QObject::connect(pEntry, SIGNAL(Dismiss(bool)), this, SLOT(OnDismissProgress(bool)));
|
|
AddEntry(pEntry);
|
|
}
|
|
else
|
|
pEntry->UpdateProgress(Message, SizeLeft);
|
|
}
|
|
|
|
void CPopUpWindow::OnDismissProgress(bool bHide)
|
|
{
|
|
CPopUpProgress* pEntry = qobject_cast<CPopUpProgress*>(sender());
|
|
|
|
if (bHide)
|
|
m_HiddenMessages.insert(0, pEntry->m_ID);
|
|
|
|
RemoveEntry(pEntry);
|
|
}
|
|
|
|
void CPopUpWindow::OnCopy()
|
|
{
|
|
QStringList Messages;
|
|
foreach(const QModelIndex& Index, ui.table->selectionModel()->selectedIndexes())
|
|
{
|
|
CPopUpEntry* pCurEntry = qobject_cast<CPopUpEntry*>(ui.table->cellWidget(Index.row(), 0));
|
|
Messages.append(pCurEntry->GetMessageText());
|
|
}
|
|
QApplication::clipboard()->setText(Messages.join("\n"));
|
|
}
|