mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-12-27 16:39:29 -05:00
added a display of turtle search/tunnel requests over time, to help spotting potential spammers
git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@4216 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
parent
0429baf807
commit
3f21835114
@ -26,6 +26,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <list>
|
||||
@ -39,7 +41,7 @@ extern RsTurtle *rsTurtle ;
|
||||
typedef uint32_t TurtleRequestId ;
|
||||
|
||||
// This is the structure used to send back results of the turtle search
|
||||
// to the notifyBase class.
|
||||
// to the notifyBase class, or send info to the GUI.
|
||||
|
||||
struct TurtleFileInfo
|
||||
{
|
||||
@ -48,6 +50,14 @@ struct TurtleFileInfo
|
||||
uint64_t size ;
|
||||
};
|
||||
|
||||
struct TurtleRequestDisplayInfo
|
||||
{
|
||||
uint32_t request_id ; // Id of the request
|
||||
std::string source_peer_id ; // Peer that relayed the request
|
||||
uint32_t age ; // Age in seconds
|
||||
uint32_t depth ; // Depth of the request. Might be altered.
|
||||
};
|
||||
|
||||
// Interface class for turtle hopping.
|
||||
//
|
||||
// This class mainly interacts with the turtle router, that is responsible
|
||||
@ -61,7 +71,7 @@ class RsTurtle
|
||||
public:
|
||||
enum FileSharingStrategy { SHARE_ENTIRE_NETWORK, SHARE_FRIENDS_ONLY } ;
|
||||
|
||||
RsTurtle() { _sharing_strategy = SHARE_ENTIRE_NETWORK ;}
|
||||
RsTurtle() {}
|
||||
virtual ~RsTurtle() {}
|
||||
|
||||
// Lauches a search request through the pipes, and immediately returns
|
||||
@ -71,6 +81,11 @@ class RsTurtle
|
||||
virtual TurtleRequestId turtleSearch(const std::string& match_string) = 0 ;
|
||||
virtual TurtleRequestId turtleSearch(const LinearizedExpression& expr) = 0 ;
|
||||
|
||||
// Sets the file sharing strategy. It concerns all local files. It would
|
||||
// be better to handle this for each file, of course.
|
||||
|
||||
void setFileSharingStrategy(FileSharingStrategy f) { _sharing_strategy = f ; }
|
||||
|
||||
// Initiates tunnel handling for the given file hash. tunnels. Launches
|
||||
// an exception if an error occurs during the initialization process. The
|
||||
// turtle router itself does not initiate downloads, it only maintains
|
||||
@ -84,18 +99,13 @@ class RsTurtle
|
||||
//
|
||||
virtual void stopMonitoringFileTunnels(const std::string& file_hash) = 0 ;
|
||||
|
||||
// Sets the file sharing strategy. It concerns all local files. It would
|
||||
// be better to handle this for each file, of course.
|
||||
|
||||
void setFileSharingStrategy(FileSharingStrategy f) { _sharing_strategy = f ; }
|
||||
|
||||
// Get info from the turtle router. I use std strings to hide the internal structs.
|
||||
virtual void getInfo(std::vector<std::vector<std::string> >&,std::vector<std::vector<std::string> >&,
|
||||
std::vector<std::vector<std::string> >&,std::vector<std::vector<std::string> >&) const = 0;
|
||||
std::vector<TurtleRequestDisplayInfo>&,std::vector<TurtleRequestDisplayInfo>&) const = 0;
|
||||
|
||||
// Convenience function.
|
||||
virtual bool isTurtlePeer(const std::string& peer_id) const = 0 ;
|
||||
|
||||
|
||||
protected:
|
||||
FileSharingStrategy _sharing_strategy ;
|
||||
};
|
||||
|
@ -2040,8 +2040,8 @@ static std::string printNumber(uint64_t num,bool hex=false)
|
||||
|
||||
void p3turtle::getInfo( std::vector<std::vector<std::string> >& hashes_info,
|
||||
std::vector<std::vector<std::string> >& tunnels_info,
|
||||
std::vector<std::vector<std::string> >& search_reqs_info,
|
||||
std::vector<std::vector<std::string> >& tunnel_reqs_info) const
|
||||
std::vector<TurtleRequestDisplayInfo >& search_reqs_info,
|
||||
std::vector<TurtleRequestDisplayInfo >& tunnel_reqs_info) const
|
||||
{
|
||||
RsStackMutex stack(mTurtleMtx); /********** STACK LOCKED MTX ******/
|
||||
|
||||
@ -2091,35 +2091,28 @@ void p3turtle::getInfo( std::vector<std::vector<std::string> >& hashes_info,
|
||||
|
||||
for(std::map<TurtleSearchRequestId,TurtleRequestInfo>::const_iterator it(_search_requests_origins.begin());it!=_search_requests_origins.end();++it)
|
||||
{
|
||||
search_reqs_info.push_back(std::vector<std::string>()) ;
|
||||
std::vector<std::string>& search_req(search_reqs_info.back()) ;
|
||||
TurtleRequestDisplayInfo info ;
|
||||
|
||||
search_req.push_back(printNumber(it->first,true)) ;
|
||||
info.request_id = it->first ;
|
||||
info.source_peer_id = it->second.origin ;
|
||||
info.age = now - it->second.time_stamp ;
|
||||
info.depth = it->second.depth ;
|
||||
|
||||
RsPeerDetails sslDetails;
|
||||
if(rsPeers->getPeerDetails(it->second.origin,sslDetails))
|
||||
search_req.push_back(sslDetails.name + " - " + sslDetails.location) ;
|
||||
else
|
||||
search_req.push_back(it->second.origin) ;
|
||||
|
||||
search_req.push_back(printNumber(now - it->second.time_stamp) + " secs ago") ;
|
||||
search_reqs_info.push_back(info) ;
|
||||
}
|
||||
|
||||
tunnel_reqs_info.clear();
|
||||
|
||||
for(std::map<TurtleSearchRequestId,TurtleRequestInfo>::const_iterator it(_tunnel_requests_origins.begin());it!=_tunnel_requests_origins.end();++it)
|
||||
{
|
||||
tunnel_reqs_info.push_back(std::vector<std::string>()) ;
|
||||
std::vector<std::string>& tunnel_req(tunnel_reqs_info.back()) ;
|
||||
TurtleRequestDisplayInfo info ;
|
||||
|
||||
tunnel_req.push_back(printNumber(it->first,true)) ;
|
||||
RsPeerDetails sslDetails;
|
||||
if(rsPeers->getPeerDetails(it->second.origin,sslDetails))
|
||||
tunnel_req.push_back(sslDetails.name + " - " + sslDetails.location) ;
|
||||
else
|
||||
tunnel_req.push_back(it->second.origin) ;
|
||||
info.request_id = it->first ;
|
||||
info.source_peer_id = it->second.origin ;
|
||||
info.age = now - it->second.time_stamp ;
|
||||
info.depth = it->second.depth ;
|
||||
|
||||
tunnel_req.push_back(printNumber(now - it->second.time_stamp) + " secs ago") ;
|
||||
tunnel_reqs_info.push_back(info) ;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,8 +244,8 @@ class p3turtle: public p3Service, /*public pqiMonitor,*/ public RsTurtle,/* publ
|
||||
/// get info about tunnels
|
||||
virtual void getInfo(std::vector<std::vector<std::string> >&,
|
||||
std::vector<std::vector<std::string> >&,
|
||||
std::vector<std::vector<std::string> >&,
|
||||
std::vector<std::vector<std::string> >&) const ;
|
||||
std::vector<TurtleRequestDisplayInfo >&,
|
||||
std::vector<TurtleRequestDisplayInfo >&) const ;
|
||||
|
||||
#ifdef TO_REMOVE
|
||||
/************* from pqiMonitor *******************/
|
||||
|
@ -75,8 +75,8 @@ void FileTransferInfoWidget::updateDisplay()
|
||||
|
||||
//std::cout << "got details for file " << nfo.fname << std::endl ;
|
||||
|
||||
pixmap = QPixmap(size());
|
||||
pixmap.fill(this, 0, 0);
|
||||
// pixmap = QPixmap(size());
|
||||
// pixmap.fill(this, 0, 0);
|
||||
pixmap = QPixmap(maxWidth, maxHeight);
|
||||
pixmap.fill(this, 0, 0);
|
||||
setFixedHeight(maxHeight);
|
||||
|
@ -1,10 +1,104 @@
|
||||
#include <iostream>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <retroshare/rsturtle.h>
|
||||
#include <retroshare/rspeers.h>
|
||||
#include "TurtleRouterDialog.h"
|
||||
#include <QPainter>
|
||||
#include <QStylePainter>
|
||||
|
||||
static const int MAX_TUNNEL_REQUESTS_DISPLAY = 10 ;
|
||||
|
||||
class TRHistogram
|
||||
{
|
||||
public:
|
||||
TRHistogram(const std::vector<TurtleRequestDisplayInfo >& info) :_infos(info) {}
|
||||
|
||||
QColor colorScale(float f)
|
||||
{
|
||||
return QColor::fromHsv((int)((1.0-f)*280),200,255) ;
|
||||
}
|
||||
|
||||
virtual void draw(QPainter *painter,int& ox,int& oy,const QString& title)
|
||||
{
|
||||
static const int MaxTime = 61 ;
|
||||
static const int cellx = 6 ;
|
||||
static const int celly = 10 ;
|
||||
|
||||
int save_ox = ox ;
|
||||
painter->setPen(QColor::fromRgb(0,0,0)) ;
|
||||
painter->drawText(2+ox,celly+oy,title) ;
|
||||
oy+=2+2*celly ;
|
||||
|
||||
if(_infos.empty())
|
||||
return ;
|
||||
|
||||
ox += 10 ;
|
||||
std::map<std::string,std::vector<int> > hits ;
|
||||
std::map<std::string,std::vector<int> >::iterator it ;
|
||||
|
||||
int max_hits = 1;
|
||||
for(uint32_t i=0;i<_infos.size();++i)
|
||||
{
|
||||
std::vector<int>& h(hits[_infos[i].source_peer_id]) ;
|
||||
|
||||
if(h.size() <= _infos[i].age)
|
||||
h.resize(MaxTime,0) ;
|
||||
|
||||
if(_infos[i].age < h.size())
|
||||
{
|
||||
h[_infos[i].age]++ ;
|
||||
if(h[_infos[i].age] > max_hits)
|
||||
max_hits = h[_infos[i].age] ;
|
||||
}
|
||||
}
|
||||
|
||||
int p=0 ;
|
||||
|
||||
for(it=hits.begin();it!=hits.end();++it,++p)
|
||||
{
|
||||
int total = 0 ;
|
||||
|
||||
for(int i=0;i<MaxTime;++i)
|
||||
{
|
||||
painter->fillRect(ox+i*cellx,oy+p*celly,cellx,celly,colorScale(it->second[i]/(float)max_hits)) ;
|
||||
total += it->second[i] ;
|
||||
}
|
||||
|
||||
painter->setPen(QColor::fromRgb(0,0,0)) ;
|
||||
painter->drawText(ox+(MaxTime+1)*cellx,oy+(p+1)*celly,TurtleRouterDialog::getPeerName(it->first)) ;
|
||||
painter->drawText(ox+(MaxTime+1)*cellx+80,oy+(p+1)*celly,"("+QString::number(total)+")") ;
|
||||
}
|
||||
|
||||
painter->drawRect(ox,oy,MaxTime*cellx,p*celly) ;
|
||||
|
||||
for(int i=0;i<MaxTime;i+=5)
|
||||
painter->drawText(ox+i*cellx,oy+(p+1)*celly+4,QString::number(i)) ;
|
||||
|
||||
oy += (p+1)*celly+4 ;
|
||||
|
||||
painter->drawText(ox,oy+celly,QObject::tr("(Age in seconds)"));
|
||||
oy += 3*celly ;
|
||||
|
||||
// now, draw a scale
|
||||
|
||||
for(int i=0;i<=10;++i)
|
||||
{
|
||||
painter->fillRect(ox+i*(cellx+40),oy,cellx,celly,colorScale(i/10.0f)) ;
|
||||
painter->setPen(QColor::fromRgb(0,0,0)) ;
|
||||
painter->drawRect(ox+i*(cellx+40),oy,cellx,celly) ;
|
||||
painter->drawText(ox+i*(cellx+40)+cellx+4,oy+celly,QString::number((int)(max_hits*i/10.0))) ;
|
||||
}
|
||||
|
||||
oy += celly*2 ;
|
||||
|
||||
ox = save_ox ;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<TurtleRequestDisplayInfo>& _infos ;
|
||||
};
|
||||
|
||||
TurtleRouterDialog::TurtleRouterDialog(QWidget *parent)
|
||||
: RsAutoUpdatePage(2000,parent)
|
||||
{
|
||||
@ -26,17 +120,47 @@ TurtleRouterDialog::TurtleRouterDialog(QWidget *parent)
|
||||
_f2f_TW->insertTopLevelItem(n++,top_level_t_requests) ;
|
||||
|
||||
top_level_hashes.clear() ;
|
||||
|
||||
QVBoxLayout v(_tunnel_statistics_F) ;
|
||||
v.addWidget( _tst_CW = new TurtleRouterStatisticsWidget() ) ;
|
||||
}
|
||||
|
||||
void TurtleRouterDialog::updateDisplay()
|
||||
{
|
||||
std::vector<std::vector<std::string> > hashes_info ;
|
||||
std::vector<std::vector<std::string> > tunnels_info ;
|
||||
std::vector<std::vector<std::string> > search_reqs_info ;
|
||||
std::vector<std::vector<std::string> > tunnel_reqs_info ;
|
||||
std::vector<TurtleRequestDisplayInfo > search_reqs_info ;
|
||||
std::vector<TurtleRequestDisplayInfo > tunnel_reqs_info ;
|
||||
|
||||
rsTurtle->getInfo(hashes_info,tunnels_info,search_reqs_info,tunnel_reqs_info) ;
|
||||
|
||||
updateTunnelRequests(hashes_info,tunnels_info,search_reqs_info,tunnel_reqs_info) ;
|
||||
_tst_CW->updateTunnelStatistics(hashes_info,tunnels_info,search_reqs_info,tunnel_reqs_info) ;
|
||||
}
|
||||
|
||||
QString TurtleRouterDialog::getPeerName(const std::string& peer_id)
|
||||
{
|
||||
static std::map<std::string, QString> names ;
|
||||
|
||||
std::map<std::string,QString>::const_iterator it = names.find(peer_id) ;
|
||||
|
||||
if( it != names.end())
|
||||
return it->second ;
|
||||
else
|
||||
{
|
||||
RsPeerDetails detail ;
|
||||
if(!rsPeers->getPeerDetails(peer_id,detail))
|
||||
return "unknown peer";
|
||||
|
||||
return (names[peer_id] = QString::fromStdString(detail.name)) ;
|
||||
}
|
||||
}
|
||||
|
||||
void TurtleRouterDialog::updateTunnelRequests( const std::vector<std::vector<std::string> >& hashes_info,
|
||||
const std::vector<std::vector<std::string> >& tunnels_info,
|
||||
const std::vector<TurtleRequestDisplayInfo >& search_reqs_info,
|
||||
const std::vector<TurtleRequestDisplayInfo >& tunnel_reqs_info)
|
||||
{
|
||||
// now display this in the QTableWidgets
|
||||
|
||||
QStringList stl ;
|
||||
@ -73,7 +197,7 @@ void TurtleRouterDialog::updateDisplay()
|
||||
|
||||
for(uint i=0;i<search_reqs_info.size();++i)
|
||||
{
|
||||
QString str = QString::fromStdString( "Request id: " + search_reqs_info[i][0] + "\t from [" + search_reqs_info[i][1] + "]\t " + search_reqs_info[i][2]) ;
|
||||
QString str = "Request id: " + QString::number(search_reqs_info[i].request_id,16) + "\t from [" + getPeerName(search_reqs_info[i].source_peer_id) + "]\t " + QString::number(search_reqs_info[i].age)+" secs ago" ;
|
||||
|
||||
stl.clear() ;
|
||||
stl.push_back(str) ;
|
||||
@ -82,12 +206,10 @@ void TurtleRouterDialog::updateDisplay()
|
||||
}
|
||||
top_level_s_requests->setText(0, tr("Search requests") + "(" + QString::number(search_reqs_info.size()) + ")" ) ;
|
||||
|
||||
int reqs_size = tunnel_reqs_info.size() ;
|
||||
|
||||
for(uint i=0;i<reqs_size;++i)
|
||||
if(i+MAX_TUNNEL_REQUESTS_DISPLAY >= reqs_size || i < MAX_TUNNEL_REQUESTS_DISPLAY)
|
||||
for(uint i=0;i<tunnel_reqs_info.size();++i)
|
||||
if(i+MAX_TUNNEL_REQUESTS_DISPLAY >= tunnel_reqs_info.size() || i < MAX_TUNNEL_REQUESTS_DISPLAY)
|
||||
{
|
||||
QString str = QString::fromStdString( "Request id: " + tunnel_reqs_info[i][0] + "\t from [" + tunnel_reqs_info[i][1] + "]\t " + tunnel_reqs_info[i][2]) ;
|
||||
QString str = "Request id: " + QString::number(tunnel_reqs_info[i].request_id,16) + "\t from [" + getPeerName(tunnel_reqs_info[i].source_peer_id) + "]\t " + QString::number(tunnel_reqs_info[i].age)+" secs ago" ;
|
||||
|
||||
stl.clear() ;
|
||||
stl.push_back(str) ;
|
||||
@ -148,4 +270,47 @@ QTreeWidgetItem *TurtleRouterDialog::findParentHashItem(const std::string& hash)
|
||||
return items.front() ;
|
||||
}
|
||||
|
||||
TurtleRouterStatisticsWidget::TurtleRouterStatisticsWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
maxWidth = 200 ;
|
||||
maxHeight = 100 ;
|
||||
}
|
||||
|
||||
void TurtleRouterStatisticsWidget::updateTunnelStatistics(const std::vector<std::vector<std::string> >& hashes_info,
|
||||
const std::vector<std::vector<std::string> >& tunnels_info,
|
||||
const std::vector<TurtleRequestDisplayInfo >& search_reqs_info,
|
||||
const std::vector<TurtleRequestDisplayInfo >& tunnel_reqs_info)
|
||||
|
||||
{
|
||||
QPixmap tmppixmap(maxWidth, maxHeight);
|
||||
tmppixmap.fill(this, 0, 0);
|
||||
|
||||
QPainter painter(&tmppixmap);
|
||||
painter.initFrom(this);
|
||||
|
||||
// std::cerr << "Drawing into pixmap of size " << maxWidth << "x" << maxHeight << std::endl;
|
||||
// draw...
|
||||
int ox=5,oy=5 ;
|
||||
TRHistogram(search_reqs_info).draw(&painter,ox,oy,QObject::tr("Evolution of search requests:")) ;
|
||||
TRHistogram(tunnel_reqs_info).draw(&painter,ox,oy,QObject::tr("Evolution of tunnel requests:")) ;
|
||||
|
||||
// update the pixmap
|
||||
pixmap = tmppixmap;
|
||||
}
|
||||
|
||||
void TurtleRouterStatisticsWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QStylePainter(this).drawPixmap(0, 0, pixmap);
|
||||
}
|
||||
|
||||
void TurtleRouterStatisticsWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QRect TaskGraphRect = geometry();
|
||||
maxWidth = TaskGraphRect.width();
|
||||
maxHeight = TaskGraphRect.height();
|
||||
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,16 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <retroshare/rsturtle.h>
|
||||
#include "ui_TurtleRouterDialog.h"
|
||||
#include "RsAutoUpdatePage.h"
|
||||
|
||||
class TurtleRouterStatisticsWidget ;
|
||||
|
||||
class TurtleRouterDialog: public RsAutoUpdatePage, public Ui::TurtleRouterDialogForm
|
||||
{
|
||||
public:
|
||||
TurtleRouterDialog(QWidget *parent = NULL) ;
|
||||
|
||||
/** Default Constructor */
|
||||
// Cache for peer names.
|
||||
static QString getPeerName(const std::string& peer_id) ;
|
||||
|
||||
private:
|
||||
void updateTunnelRequests( const std::vector<std::vector<std::basic_string<char> > >&,
|
||||
const std::vector<std::vector<std::basic_string<char> > >&,
|
||||
const std::vector<TurtleRequestDisplayInfo >&,
|
||||
const std::vector<TurtleRequestDisplayInfo >&) ;
|
||||
|
||||
virtual void updateDisplay() ;
|
||||
QTreeWidgetItem *findParentHashItem(const std::string& hash) ;
|
||||
|
||||
@ -18,4 +28,25 @@ class TurtleRouterDialog: public RsAutoUpdatePage, public Ui::TurtleRouterDialog
|
||||
QTreeWidgetItem *top_level_unknown_hashes ;
|
||||
QTreeWidgetItem *top_level_s_requests ;
|
||||
QTreeWidgetItem *top_level_t_requests ;
|
||||
|
||||
TurtleRouterStatisticsWidget *_tst_CW ;
|
||||
} ;
|
||||
|
||||
class TurtleRouterStatisticsWidget: public QWidget
|
||||
{
|
||||
public:
|
||||
TurtleRouterStatisticsWidget(QWidget *parent = NULL) ;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) ;
|
||||
virtual void resizeEvent(QResizeEvent *event);
|
||||
|
||||
void updateTunnelStatistics( const std::vector<std::vector<std::basic_string<char> > >&,
|
||||
const std::vector<std::vector<std::basic_string<char> > >&,
|
||||
const std::vector<TurtleRequestDisplayInfo >&,
|
||||
const std::vector<TurtleRequestDisplayInfo >&) ;
|
||||
|
||||
private:
|
||||
QPixmap pixmap ;
|
||||
int maxWidth,maxHeight ;
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>865</width>
|
||||
<height>553</height>
|
||||
<height>525</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -17,17 +17,36 @@
|
||||
<iconset resource="images.qrc">
|
||||
<normaloff>:/images/rstray3.png</normaloff>:/images/rstray3.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="_f2f_TW">
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>F2F router information</string>
|
||||
<widget class="QTreeWidget" name="_f2f_TW">
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>F2F router information</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QFrame" name="_tunnel_statistics_F">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
Loading…
Reference in New Issue
Block a user