From 8c0babf511d78c5bc93f4ec3dcca1863139fd6c0 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 17 Nov 2022 14:00:24 -0500 Subject: [PATCH 01/42] Refs #19. Added session parameters display. Should handle in a better way "json" fields... --- CMakeLists.txt | 2 +- client/resources/translations/openteraplus_en.ts | 16 ++++++++-------- client/resources/translations/openteraplus_fr.ts | 16 ++++++++-------- client/src/editors/TeraForm.cpp | 12 ++++++++++-- .../VideoRehabService/VideoRehabWidget.cpp | 1 + 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eba9c87..c6a696bb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_policy(SET CMP0043 NEW) # Software version SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "1") -SET(CPACK_PACKAGE_VERSION_PATCH "2") +SET(CPACK_PACKAGE_VERSION_PATCH "3") SET(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}) add_definitions(-DOPENTERAPLUS_VERSION="${CPACK_PACKAGE_VERSION}" ) add_definitions(-DOPENTERAPLUS_VERSION_MAJOR="${CPACK_PACKAGE_VERSION_MAJOR}" ) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 27580219..319dd28d 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -4305,7 +4305,7 @@ realized sessions TeraForm - + Choisir la couleur Chose a color @@ -5411,37 +5411,37 @@ Vous devez spécifier au moins un groupe utilisateur Connecting... - + Fichier disponible File available - + Le fichier File - + est disponible dans le répertoire is available in the folder - + Impossible de charger la page Unable to load page - + Problème vidéo Video Problem - + Problème audio Audio Problem - + Erreur Error diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index 0ed780d1..0bf0e628 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -4234,7 +4234,7 @@ Souhaitez-vous continuer? TeraForm - + Choisir la couleur @@ -5335,37 +5335,37 @@ Vous devez spécifier au moins un groupe utilisateur - + Fichier disponible - + Le fichier - + est disponible dans le répertoire - + Impossible de charger la page - + Problème vidéo - + Problème audio - + Erreur diff --git a/client/src/editors/TeraForm.cpp b/client/src/editors/TeraForm.cpp index 1f9a9c1c..f274a12a 100644 --- a/client/src/editors/TeraForm.cpp +++ b/client/src/editors/TeraForm.cpp @@ -467,7 +467,7 @@ void TeraForm::buildFormFromStructure(QWidget *page, const QVariantList &structu else if (item_type == "checklist"){ item_widget = createListWidget(item_data); } - else if (item_type == "longtext"){ + else if (item_type == "longtext" || item_type == "json"){ // Json here, for now... item_widget = createLongTextWidget(item_data); } else if (item_type == "label"){ @@ -1051,7 +1051,6 @@ QVariant TeraForm::getWidgetValue(QWidget *widget) void TeraForm::setWidgetValue(QWidget *widget, const QVariant &value) { - widget->setProperty("last_value", value); if (QComboBox* combo = dynamic_cast(widget)){ int index = combo->findText(value.toString()); @@ -1083,6 +1082,15 @@ void TeraForm::setWidgetValue(QWidget *widget, const QVariant &value) } if (QTextEdit* text = dynamic_cast(widget)){ + if (value.canConvert(QMetaType::QVariantMap) || value.canConvert(QMetaType::QVariantHash)){ + QVariantHash data; + if (value.convert(QMetaType::QVariantHash, &data)){ + QJsonDocument doc; + doc.setObject(QJsonObject::fromVariantHash(data)); + text->setText(doc.toJson(QJsonDocument::Compact)); + return; + } + } text->setText(value.toString()); return; } diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.cpp b/client/src/services/VideoRehabService/VideoRehabWidget.cpp index 397d1372..bf78c018 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabWidget.cpp @@ -52,6 +52,7 @@ void VideoRehabWidget::initUI() m_loadingIcon->start(); QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true); + //QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, false); m_webEngine = new QWebEngineView(ui->wdgWebEngine); From fc765492845914ceec1728ff265d52805b8017be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20L=C3=A9tourneau?= Date: Thu, 17 Nov 2022 14:26:47 -0500 Subject: [PATCH 02/42] Refs #79 Fixed to Hash --- client/src/editors/TeraForm.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/src/editors/TeraForm.cpp b/client/src/editors/TeraForm.cpp index f274a12a..df8a5bbc 100644 --- a/client/src/editors/TeraForm.cpp +++ b/client/src/editors/TeraForm.cpp @@ -1083,13 +1083,11 @@ void TeraForm::setWidgetValue(QWidget *widget, const QVariant &value) if (QTextEdit* text = dynamic_cast(widget)){ if (value.canConvert(QMetaType::QVariantMap) || value.canConvert(QMetaType::QVariantHash)){ - QVariantHash data; - if (value.convert(QMetaType::QVariantHash, &data)){ - QJsonDocument doc; - doc.setObject(QJsonObject::fromVariantHash(data)); - text->setText(doc.toJson(QJsonDocument::Compact)); - return; - } + QVariantHash data = value.toHash(); + QJsonDocument doc; + doc.setObject(QJsonObject::fromVariantHash(data)); + text->setText(doc.toJson(QJsonDocument::Compact)); + return; } text->setText(value.toString()); return; From 019525438d71ad811c2a3c37b8db3437de06b740 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 22 Nov 2022 15:16:51 -0500 Subject: [PATCH 03/42] Refs #80. Added LogViewWidget. Work in progress... --- client/resources/TeraClient.qrc | 1 + client/resources/icons/log_icon.png | Bin 0 -> 47686 bytes .../resources/translations/openteraplus_en.ts | 211 +++++++++---- .../resources/translations/openteraplus_fr.ts | 211 +++++++++---- client/src/CMakeLists.txt | 5 +- client/src/managers/ComManager.cpp | 7 +- client/src/managers/ComManager.h | 2 + client/src/widgets/ConfigWidget.cpp | 14 +- client/src/widgets/ConfigWidget.h | 3 +- client/src/widgets/ConfigWidget.ui | 280 +++++++++++------- client/src/widgets/LogViewWidget.cpp | 235 +++++++++++++++ client/src/widgets/LogViewWidget.h | 59 ++++ client/src/widgets/LogViewWidget.ui | 231 +++++++++++++++ shared/src/WebAPI.h | 6 + shared/src/data/TeraData.cpp | 15 + shared/src/data/TeraData.h | 4 +- 16 files changed, 1075 insertions(+), 209 deletions(-) create mode 100644 client/resources/icons/log_icon.png create mode 100644 client/src/widgets/LogViewWidget.cpp create mode 100644 client/src/widgets/LogViewWidget.h create mode 100644 client/src/widgets/LogViewWidget.ui diff --git a/client/resources/TeraClient.qrc b/client/resources/TeraClient.qrc index 79bffb22..298a3467 100755 --- a/client/resources/TeraClient.qrc +++ b/client/resources/TeraClient.qrc @@ -134,5 +134,6 @@ translations/openteraplus_fr.qm icons/test_type.png icons/qr.png + icons/log_icon.png diff --git a/client/resources/icons/log_icon.png b/client/resources/icons/log_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e7ee9969120d88bdbc42502a1135ffdc7b1611c GIT binary patch literal 47686 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

2An2yeS z^C`3}pd(q~dLW7^beJf=ih8Z#R?zD8MTc+dL$y+5{x6dU8dDvgbtvsQ#-qtT>#wz^ zvsgCT&e_ez4v&%xirkh?zH621mk_Tb=l+`nFNbABm*Wy?-1~SA7fN)*l|aC1)}9_n z7`cA{C9o#?;K~)-MCj_olYFe5cCjG%~l{@0~i$CD$a&!N9Ut$)2B5hR3{}7yz`uPCf&TGk`f>cAp-AosLUGmUa(iKkWSowVu<2E&s(29V9o9V%QPW&)$D2vt}6wEh~3D=czHP1k`~(ly^~-uRWb z5@I{OsaMA`f7Uxd5pG(tYjm8U9-&l!{}^bWroQX+Yr3opsZdfo2}es|YHN{egtYko z#gjhD4-;dSOKeHozw5Kts=R%Y9owrsIj`e_ z)>mlX6cXKR4Ekp0I%oHJUH_y-#~^2FgvM|pL!MT?mQF~_(jl;J)a*$KUBKt^M}3mO zK+l+k@BUKU-VN8Um8LG5GIvfY$Ab}DDX zH^fb%zHEs5*YzFmaIuv>OU&fW9G02%Odriv(<@xMRur4+QJ(qv^0!X?F3g7j@hIX< zoo@v2VfQ}Xi?+OXEFGghKX__z)5v!qnDvWXS33d=Nn6lt_=fd4XmX5$@Go}Db=mLK zm{(?HTo;e@Y6VWZj--YwQ7Boy^u_fQ3Zotlz*{IcAWTM%bWLAL zC42nG$o+DVu+x3awJD zc8(k87i8`pEIU2ROx;b)WjdcvK;V&x$Vk$jQM=~>p~k6*0sXxn=c#GxQQJ75pDI0+ zty8t34JIfAZ7HPA5WY&+r%Qc-BF7&A1{lyYwUAFLg69>YP4&CKq#b^C^o_bx5JEGg@b>z+%G8^Q-lcqA?U@KO) zxAwV%-^sN2B`LKT3wB+#pk3G1*hYtphQ^cNu(!9 zZe1oF6O+SHVTjuA4-A<vJ{LSC*B`^R;sr>8BGLKj`&9lpUV3j;+`{C zkiW@DM`&2ez-$N7M)Z^qk4b$Doz6wUxWR{ z=gQ}>{V4Qe)vMk~Pe0TL;Hl%7M8zGJn#I)&0YO2)6eM`zNLEbJ7OepR_(8GAs&U5c|GIjC+VR?~~8T_gz&m!=bCh>zV zR{PkFLP?K;j&AAbmgP(JG#+aFdJl3%nNlgN1v26rn;G|94o6vkOUf7XH1{*v$eljR zr-n2FpzwpPC%-NzYy$NDCoobs7c^_WKi?FiUam>@CdwKu3QA|+GVB2Of4D9{dJ_U` zzuxsit=-~`6(9G;24cyvgv9;a;@eE1i7WGf^EN*z-ggoMv6!^|3=T{E)S$Ryl$u=a zf~DH_eNPr7dv)k_*M~-;%M5t(s5!RAfP!kFp!4Dy&?p3$TG4xEw@^XLFGAv>VUrQE z3ztly)r!{7R&%aU)ZtDK?z6YQAmbR!6Tb5r0=Q5c7tM&0bRy_ME zOYcpLUsKSS3^3U>JeSO1z{7?Q28zauU1t<5*xlRjL1U+$=A?r@O<&N#0du2armdX2 zEo6h9`4CVoA9(ism9(G}UhUqNWJp~6*q19fP`2mEVlgvnbeVO(I9wcD>AI~N8Kb}> ziqcb*#(xEspEq*)xz{K|H$P^wvm6#}$T0#Nmw641iIPt$?w)G|sa77BN4T``$f8iE zQm2T!bU+mtA)%yKITbj&$}B+BY-+XWZ8lc1Mg3{%QOImF?Vdt^+RR$M#9zyP{wBM! zo@cs0wr)tX7|N#Y!xWuye`fIIHmJj@M5}(;Ms0|6m-g-)BKH?sh-S|}%KJDVjA=0l2*bAxI@_EOT(f`J!=qR#ee&l{vHq|_3?YT{?`9fxi|!BUr11FYd8c!e$=@$PJ4S>S!Fg;{dA6bTrF;aQ~$W6n&=+BVAmDo z_OLr2m>{iC0-MvX>4b*?l?ZLZN)UT^7{UQYGJe1*h#C7x^vOW^mtsmgaW>LULuJKr zM|%;n&p2Wl>m{#otS4)>Mfa>JxgW?Yvc(=tsRcO3o-{BdCMK?TFzQiqYN@Uprlq02 z>>74`4Zdx(^+`ot6mBOMUk5_dCkl1byt`0=H?+#H9I(){(|k z-4$B&!0=S$k<4Hv`ZpSu`Fgbo)1%RxfIx^ULB;t%;(s4%wP=eSkFW5F>YRwW%w&xyZUG@sR8|YuIPZ2E`yJ0vQhPc~Tz>g}W zZ^6eV>G-6vQZTOt#_t;kW^7Z>0`dEVIv*}%e%-%u>rW6@AiI3(u1L5X4oWLKdl(;| z36ui8YZ9gp!l!J^ha`V8Ec;}CN#0L=iOv2CjK$EIZ%l0CCJ*gd9RlVQfRV_*(5$K^hzNKa_*ZnUvP4(0RNl&tE=<)IqvEdI(l5$EOk`^lFlSm?P9c8AO z+I&vtk|Nl6H7-t1%};n4+V2T98yx9)U7q36eGyte@nT7FW{UY9jIuqoe0nqA@~#&0 zNqW}+G>LGkz)y9{Xxf|8UHhQA?xEilY`8S&nVMXB`Q)#sww(R=q6s^s$)aJKNB;PV)MMr66`HH0ozF6y)*c z=Fxw{Q5&N}+#sIai*1QoQ3Quh+iBJwk|Dv{DD`Trg{6T%zT=4+l}oxRKL{AnFPn8F z!^V0}rtX7%KGggfK{gao*ugy$D5JGPwN_SHwwznMovPXiwu$4@CR$lU=#*bgTq1@A z(H`hZZ7R{RBMxx5m?jYfM;|mUiRRQI5)Rp;_UJ}^bP^VgRo6r_R7i3IrA2c6Hgb-4 z#HL}3qv=bW1%^qUTA>X(OZU_lnG+hHekY{e@^uRkgV$nx3K{xw{g5auP@3{>bGm&^ zx^}^hs_lIk73f84zs@378{)Dx?0r<-2}@Qo>5tA)SSSRCM?vYm;SHWD7=$T|ZWW<& z|5q5pX%4z2#WaQSDHXx0FeEC?=_xScbc{qW%qhvwF69KCr1ZA#gsBq3KX**mZ+|b7 zBRP1{HzVs<4GJJviwAA_p@H@@c6bZO_4z*m_!r3d3{%O@Y&0yg7VTRpM}wQb(=2E7 z*RVHkkmWg-w|UyPlGxr8!YOZu5sc4^VGPaeyf_f!C+zXi28L!U0l!BpDDWH~2XsIq zd|3AM-yeD`w;Ll*Yz4dnu3zzl&i$eUQY^>j4A+8ob@Dm_DqKZ`xztoc5hYCl4pcFv zrItBGCR{-JKqVBd`(npFMb$&GAGw7Ia1c%OHh zJgB&fAQ9pI*8yGxxOwxI@fStzn$94{c1kU%=*KM zJ#m=TZg00Mwv2O&Hh|e2QT~B9v85mub{>+9QM&_;W;X8wv;i2L|Dv8_Bo zNMgv()$EAY_^|P}OQ)S^Z@f7-1rH+pCSvxsTau|GSuKK*&ZvL!Hrr2c81ZwFg|If$ z=nzNKmd=lxZP(I!t-XPvWF-lxx@HaGqY?X1_w2sEmw@f&x#(3NJ1V9vanKly}dnG&hYe}&zqPSItB(ie}CGs(?-#+?_QhYSPAXkk(^eb zluN&^uiN;VAwu7^w?8f60y<<(w!sDFl75dwWs-B6^G10=%4VQVGmu(Hf;%~?Hj&Ys zK~6R`NPD||S9Oo3IS({>9Nx^VTl6>ZB9nf$-gAA&RO;gDRx<%2!De;)|E4q$*3NgC zIiYuK50ZdJkZ8=yaOHkCwIDhOG>r=+S0J!ojjX_XiF7dmLCwZ0AFi@ntXCJIfAjf)Md z^{RiR>Nh%!F&=hZT3j5gjwPSGBYyElmq*(MM-!1R>5r}%A>(`9X_GV;+B5avBy|Ad+ z7xj@!sbKZ#OFBIl_4E5RQb}i5A%Chr^K?CbKu7nx`}UA6(KdL@?q-Jn4-HLTetzF& zh`Jhq?bvBHLlx_qIMNaAJn#DN4~iKd=vqC_*DB$9n5Yn8p8=}{4v7G>I==gJU%NwY z{f`-d^BmnF#P?pL`+jv|wuW)`Md|0F`F+H9rL8DBs00itOf0M z>rmI`PaR-(tj3b`r_O+9e-&YQv|s-1RH}s62#|27WSr1h-47k$BKLsaijH`pT5kYb zK9?U=#ait5skVk}%!`hm2^dfCJ59!8Ttnd-?G^Je^ zS-s4F>%?t4^QL4{AU3^byF-?`JY+9df|&f4TXNLPBlaYS2Y$KF1n@tW!?$k6i89(8;zhLi_yR^G3N>?1o9){X1&xju#H9wF$ zaneZ?yoPW#>;v_Ic7qZFZasmlD&l*3|IC6B-{$6MZEbB6latqPW%T1U!&Qe(dF`|b zD6fm9CfuWjL(`qFt?TC&n}89#v_6mHZc-^HSmrbj%JSyv2S9LAp)a>#Tm4L=!J`Q7 znMTe>(kI@^hqv8>b>@=uEiRm%&?#3@A422W0sR*B7MLA(9De;TXTVoHNK!V0;@&V_ zyP3#dbA`P7_gBBpfQ!?IMOj6KmXWbSAJaXDNb$uwt2KXxpn`LTTkf9bx?u)mnksejDBpvc^{-)bl<1NFrc>%tU#g_KEYODw`Pa9Pi?E;uAypdX*(N(L5s zAMQ`7>CS?`FQQEazvpmL<<&N#O+fRvRKw`nbo~l>IqC@pC1DD#07eS=p;I6p%ygoJ zSQFU{LRmhEc$^H)9>nk^^m&?@&3-*?mhAB{14isvJSiiYck3JkX2X2`$#+gAy??iB3!~S7it+X*bCCXCR`f<@ZdF6fWU_Mtdp9nld=sjQg z<+?Bb)>E!K(QCl$>xPB5a?krv;u#&BH9w)9+mw=2U^tNo#@lc3*BquKYe6OF*I zK+Bs4oy^1!7hvTHHmwp01D@3%w0PTO`$K0J^MGpY>%*T;jKSM|6-w9JuEIE0f^SYy zDc#o(fBj8YBTt;7=3nxZ-CPDgMLQf8<? z;O6nnBOo|?bf0{z`rRfJ=|ZDJ6K1*>)@hCK82$nH63>5gX{=`iR}44BptENqRUpKC zjHlt|PQC}(h<(Jdnl@rH%rfzbkgZ~u1Mv@;pG`KSCd(_PWmYd8Z((QY7 z=8v`Pg0to&Erb3@`7P%;*Xym|g`=pt?2wj8f)3VwYto>zyH9745ys%q`?R~ihJva< z!BS5Xb!!DMp}XQu8*uP`7z7;N69))$ML#Xx!yuo37Ca!tRG-&swwOgoo_MlYD60m2 zbt^+#{{4jh#%RnhfoX#&n>zt%o|4&WJ~)!r^`;3pkGOk(Q0tzh*@8KG&joLZP9nAN z85h~dS|+(54-jxA`9DPYjK{G3{8?o2JZ3MBffG5-`iXesc4vq$^PGoSIBQ};#uu}P zD1HE@Mmhj|q4U7yW?Jd0@+l%B_!O`8tU^o2xA`eft$VWKT(MKwVKZ|iwB-pKz=^P?lle_zGWv{F=Y=Q~j>9tjL?FU(duh={48_1Ix zL;DU7QQni z#B^4fIVm4TFx&~QKpP&^4^a`rWR!tJhYGQ-6Db?HT=9=^n7vfy;=&eLqk1rNyH3v` zt=Z|h=%jg@9^@b3WZCiD#*s*s)gcaWYBsb<`|~_u4|YeO-fCP)s%`5ZbT(%_s7xI= z$-(DzYseC{Ygm`f|6_8U6*xJ#`7Y(la2ag|r7>8@9wqDb?;1*B zHg0k#tN}!CFG2;f?L-3;{z5=u*7@oyYn$UX|F)ZC`J&s~*ZpwjoVS^q+~*Rs8tQr1 zfoz?9O(1QXn;ZpZui0h8PlP_yXtgPDbmKBu^VR-H-$N?Uc-=+VJw7+T&V0rc9TbQos08;0$Z`zGi}>a3Vj%eiyG&hjDu-Mau5c#%9xw*5w;# zi0d23G3d<#ilMI{9V+Ba`&q6yHQ|H+L!Nug&}(|W<<{cV8HTtuMn(Ly^MG!SGBV9_ zaNCG`M!d<$$?E*h6TsDfs)1*3{-o5f@U=7hw!EFM3i-xLro@cr2nbgzgF>fqs#f9K z6=6p?vs>%|3!AQXuP3!5!eySLQL^+m)mEB6UFz$`;)|LI*^8K4|6uID+Zq<@y#SAs z`K-mwJA(f~6tmY^;=Xeizia4Aw;1w%oL=i-VbCQ>>w|qmsnIRPvdWSfI71It_p;{b z`nrkzdd^PQCNZ8Jw3^tJ_PXr*in)Jfyv- zZG8V`sbW+)U~LMd8=0Pmzi`A|k9@K>;F*>A0^QC5N-#xkzptLs_;ktS%ldq}TL%-& zhQva?jJ5)=Gzz%ic&a*~a}Fdw7Tu19EqS2I6Q#2H0BflNIGy`sNR`IR3xmGN_3`~z z;3;6Ay4L_IN&y;O^b@ z`@i`pG@uVjH>#>!DLb7N2GGMDWC@@uko%%rpC`ST4fnZBXvkeeen7y01}29KkBQn2bq-F5tZ^Kp(Q zxzl#bwYenHbGOnZ$!)%oK~Z;R;75MHV0=OhlXo=4fXS#Hcs&|Vw*?s6%SIFsOMFZV z<`?vakt$CL3W|lJlIoN~$R1#r@u>Pe^Acp3(caE8_8H6&x6%SDi}&z@B{dSHF~xt^ z!5~Z`?r?gRu;Xti)aIn#mwqn#7u!39L?^o$&#bHw=4Fkg$HCOio|iFoX^r*WdR;l# z#@=qpe3Sjwx+#Ws6#f<=Hj`?tZXCm>Cxfz2%P<+!md@YQ6JyVsCenk(=*$F+a?#HFYK6BCFE^_r(-y zA~>q&92lPll?clRx0{X)An}+O*oG*WO>8WFalY%lF?{M;wsxu8XF44(&ms~tTTQi) zbUM6`QK^iy>dP^Zs~^obY!}2}qXufl+Zw`D%k}L!y7-i+o0K8ix_56~V$*4&yFRwJ zFd!9EZikYaRlm$86TCL?7~-)(2-y?bysUb312oJ!(k zbN7tnY0+}ZTFfnzKt1rvM)fY5e@hYDC>GI{kJg`jYkb0)G9ZVubxKPD3JHMB0H!G~gH%ifBUvqpCk>8q~1W^UC0LugjvcC3fHQm%K{)c*xF*G|N_I#ZR!=o#2sr zkKc}ouFw#)Xw*kM^$K>D6yfc*+3pJ-I%|cMuJ>tt`FqSD zvPQPBbCgeZ*74GiU4(q-fpP8RX97JR#M1KZQq!pVt4UhYOQb^YBJ8@FGy@P#z*&N8 zz_jn5G%UhfGV#D}Yq>8+ulvMw_q04k^ozQ?R?EKf`oBr;*V8N_!=*2+8)gC2hx0b` z_HJHxebFI-5peFMZcqgOR~6yya(rNw&BD)flp;%hq=cLLRY!QgqGGwvxylM=IfmMB zI|^~yM|i1QPU9coLXWNTdCH9P1)u{rugAv}sUxFW;T?(@dT)p(AYlbMR*#9NWawpK&FKqHX!_N8)tqL|d; zKFB~XvoO480FEfNBv>BF51#Jh$?6I{k@wYpX@Z~KjL4boPT6y1z3Q+p!d^;X#iFM9 z^+~y-G4uq&oIv@PYf|6Thp&7yxS6qJZ;=f%cZQ~K(H6v?f_rNYXl1$$Rnes@-)!Oh zX@^l;ew_P^be{IVj6YfBtd=n@o*rwm(dYi?sczb3K9`o9-cx>``?)r+8#qe7C&8Cc zTPMXc4_!EMk@pT;zf0q$z!RKLi;@_C^M?Cz1%Mq9_n4}Ty1KYerSfiASLmLYx$Q)b z(4E-)!UXr`xK%~)+WAm(W2rU9uVs^2O;eDzwcK7#1qnj8@?qF>|Y0}{`?hO+D7g}`nz z8b;h95$1)EWqfQO(&<+X(ZPzc_*9 znpVxWwmld^WO-KtWL>UPa?N}j6AfQDGJrw2bM+ssH3qKaqBk0vDy6t?tKrI4X+}Hk zAl(y`#N>L-RX1|_}A?b5C9=EL6` zaW5Tc<=7XzgMI zivnMr1Q2c57be|jz=wTJ<90c$IczFfR9vdnm!GUdW!7cim{B&sR9~N{EfmJ^67xU@ z+6t~d3Jus+D)3}$mOwWRbeXJ&hJW`QkaAuP9^UyrL4sr+R+coa_ZMtMXm@7$Gj?&=!*nUj`>yUV@z)pBxWZ)3iur96kdj?EvTBTpMTj`-xM`> z^{GZy|Hd?^c9AmKW1S$Z)NS2Mx3GS~m3jp)B=2;!>}ySAz(-w7RQ0U4vCtuU-Kt7Y z@)YqgE8QxfOO3drOs}u2Ig$JrH;+Wfd2Zvhd^YL(XVfUJV|kNjk(K?6s!j>@1qD<7 zX^zhSS*^jD>-wQI$dH?cLJ`^Jiycm;2jYPvK|;ulf2U>5I7@%Jpxhr3ixPwSJV{zO zc^W#DzqE1}?Pf5Uvz$_C%4;c0H~ zyv~!9^NM9m|GKAYkqxdcY;i#5Wk(^Fe^S034=C7RP;Um9F(Q^s%$gRp)Y31J91rr{ z;R^xmNG;`B3<4RdW03*6;WS7ymlU#x{n7$*wz0{k(sYDo-WuwV2PUTlailo)KUYu3 z#|-%F$pWc=jC1)jsY={op!0=g?Dgbb4P~RQ9zgYokx9yh8z9W=<8`5?Tt3r=v(vwg zyUztUAGaGcV#TxIrln`x5?`3Y_g;^9PaUqBWG7k7G(dhT3J;MdT`6HVwG!1U)%Dr! zB~wSXI=uzP^)aC=$^VRm_|OH*`ZY9#_6mv-kE{%Pjf1`|JokQ~`xG}9-_ok~TVxx` zb?Z-bhz4{)Q&`R|p+JFoP2)8?qKd7@dDo?rZ#64X5OX7MuAX0#l`(U#fbeK5r;>t+ z)_k$ZqmhC0D5a$9sKik0>TCR$sXXXt9$^OdYdlBt*wZtbYRS&|hpO&qw`{zSXxZZ* z9&SkGNdLawUsd>pRz?@$uw`RRf!79TG9g2jm+?aL>x~ziT`7x1eI}{f4M~b>!zMUi zQ%f`t2apak6DN78i{1crz(ooJzi1P%C%r&&aNidp>7^uU#9Gs7fXQKR_0x!M ziFYeV2uyIesJxFPb_OcURuZf@iSl8Fct|V09*|Z&po_kvXSc6Tid=BAEPm5S%3Zfm zq6G6nEou!6=p#@Ct9X}RufEJ^U5Hjf2FGv_<3o#4gd z=#zsfz1nt}3|wbtdn`S?HXSoN*Y8Pvqu3`Hx?`duAQzd(`C0~GR!884Rhi{75A$mC z#q=nlsk`!JW5r?dw}2^n>zGrF+6vwom6vpNFosf8a}1v#lW+2;>JpFg!QRl8Zh2Z7 zykt>p#)`O@m%9##YC$p&Wa&)d!N`{+$ZK29FJ-@|plb%_ap!A$Un(=doJKFyOjeR; z-ybjNxzgsy0V6ujkZwJt|B;gM4ocJ_3?0=vp8V5S}N>9ur z04Pr1)Jei_Ya+_Rfr4E(pX0 z2@bFaITrN~K~c5vOH>}j!8tu29M9TI0v1!j=@ezgfW=zk;!YOdJU&{?Ur}6PDJ5Z2 zMW_FPiLj4Y|1j9K+v}?K4nDt9O6+_?N0nlVW83RZq+J1Uycsh6s=!Jii>`-eCePFm zX3nj7Jhr&sj*NL39XpR}ULTsn>v`7l(4= zaYz87RN7#Zqqd;I(qYAr15dJ9N8ngW59_@LT-)vuO7>3qjgHEb&Ej3}Mu0le$8)8A zQ8vYz+ILOkN1gF^@$DM8x-rQ9Wf63AH~9(u%(u%uEo{lvzW~M;kG}A#m*BY9`enrlJJ;4F|@MOup75$B7uf=kcZ_)uwN6- zN`H?VA_&bc6Sfd8jh+T2ZVL4I4<|-)+AOAJlbTyNp5^M4@V2}vi1^aT1{K<&T4~<^ zTG%tc7epF1(5iA7Zuc8l38HNjZ!}DTbANW$0G+ZUTcX1!;88Q;-8et}1ds!51yuua zX5Uz0bp!}W+8)A+$L|iq-Wz(eRJUJn^&l{m-~JNgq~kkODP(@d8WAW_3D@Ije(VWT zEQ~7nlZ-TB$IPC9+OV!2>8U(l;kNnsb?kWAEQ%xLJI1liC?!X!AOhr;(~$?LVp`yb z?(SLWL4O6d(0j~w59pdh_r4-H8u+pL3t*8kByC*P$iyJ4i&j-NjBOoH&IePUEIFV3 zwB(RnfYZ5|H>G^fA7M#>4}0TD66qbbQd;;qsci3Wie6S3au`z9@4wK)l^^?zXrkak zqU?+zCcD`-a?bYFP^XEhTb`?i-KVJFzR8Owi)n9iWHdmcf?~7R4>Kr07f;gaK_E0s zK5weGKYwE2vtbFfJ=(lGy)zgD zfC;x`Sh6Udb~P0JKdYbl<$l<;JrC&#x*0- zL!cH{KS6)p+U9X#YgwNcNE~f8ESA;pCutw({+$Kq`)YN}Bu^D%j#u`}x8t%RJk*{( z*Eqi1Z@WsM9h!92Amw z6cwJLk81RO7rr@R_R7%l|CsPOYdD7`d)nbXCd{0|S{dnx-PF8s)smNV)}mf-^z-z3 zo2Lj9bMAUqVa|3f(N#Gb&%0z9VeknEeCiN%_arlr{n>mCBr6+o$v;L1RV?aE`t|X4e>@ z!}P+A10ls4_CI_DsDS%B@Fhw62kO^QOvm05dk1jmAwxSPdp+jGV-wyxqA{WWqg|}~ zI>zeP(4IeR-D*gKn~};yt(^cu7cV!?mlXo)E-9u-sLjC~9;JB@)s$4R(-IBd-vfJN zm6Q^|m8wDXU^0!eNg8_{8FNBAEia96SUB%Ip2>_4e|*ebhoY_ZI%GvOM z47uGREU)Nm6KQ@UW=y+_uAV$GthfttroRV2^$4TMTu1VA5jLubg5ckS3I~z21Rw0O zmv&Wwex;WC$yx>~NUI|5)CI8qM_s2#dtq+tMJi}Ez?4}lmnDEE3JYdVowF=l;`mv0 zA!SF#CROY@R#X?~MbSr0YQUGEHkxvbdcuN#;zsLZG{q+xwClBacn|k9bViLs{l@g< z<9&7tJl;hq>$5$(hmb8RaG&4fPcIRH?8;NMCP~ZhPJ19ESuG@SL-sC{0*>KiNLRqHEjCiZGx|=6>lbHVBYD+GbI?!jd8O)yZ593@;G z;Vvb3sIscgyHEPy-SN-ltjhWt&?QHoJU-BPVkBwZ52rV$&?=hvIO+N}?0+%XObd_lSlqm8uY;Nr3JKdr zANe(77EnUwM8gh(m2V`0Ch-U}ui?kfs{aUomd=vJeOdsVK7z&1cuiok^TlSKAL@GSRop`vwZVN2=y(-6VnZL{!xM1s z`BTC+EZ9l1U~6!yv?RC~>u+#|rwJU$K?p9TZl6?Jjp0SDoToL3E38ISsSl-)_N4Dk z&-L*O40-F|ZCeRRkL+hzakG9xnC%j%RnaTwRegl>r-QNW`RR+*R$MKeK8IY}vDgbcq@#U60o=v&A@dALxPtui z0G!!G9mmBhRaD^4;NQO<7f?l2$f_bo+#7)F5ONRM<*2e?cI)%oXfipN8lhu}@2h}3 z>hBt#{&%))*uX93;4p7*xDw3{$pDz)1LO6`nB6vGn(UWX%%_qVEgMGeGtQRj(X#l`a9Y;HO_p99-sxSAAQnn-43$El z921?5U=YQ_2SUf@dRii0M_U1P8QDifHF`B7C*Fc_A3nsSQ8hS5?|H!ZN2K7Fm#iZj zzv?)##$~j|0$OfbOBZt*YEF%LTDS)2^sI7Cj^VaF);3~&f3%Lw+N|cGl1j%V%7^Uv zaA-z$SQlXy5b!xFU!0&%a>TKM>jKk>mF9e0t4Q16B4W!I|1M=8Tu%UpXo=+C?;vH% zVd{!z$~NCJ!zQps0?=Qx&Zzi(0E#?H=W%aDtJty!%L^fv99r|JD67+;Ynke+bM}vi zFAdv+Z)(J6=5aPGB;RC`!bf=vV=P#_Mrw<*jvAC^;p>2P*lZ%&HIM4=D-EPiDOk`m zwx*+?R$qLZ8@ykxY-X0F7+4J!QMr4!yev(IbgGXPrDX1_ zr1Gqcd0Ka-Jb}??dWB4-i~^9j$A|j_PuV-6qYS_?a03hlVi{-l3Z1sd6OZOu)u3}7 zh&9l_`OtU{yq5o8knSiSpt^;k?^J+=2trH4c)^1gJaiL(hYX)&d#+-@g+d42@6g9-Sv!V+T{(Sr;OY=|vS)St&uKzA zkN$BKcTrFI|aa(jfVIHu0FkiU<6~%h_=tBmWBz{7z_Pr>cwZV15cZY{s$X7 zYgr`gaRe7)0F=uud$j@R$F7f!Go5!ZQU-pY&e3A!zkJt~aogg9RBGL8zY$tr^0aN` z0=m^9k@OPdr>g%A3pM@)?TF``u=1x8cw1WK#Xq^+w~VF)nz?b?00w8xEzPfaVed=r zlXPC)W1A7x&{{C{u6j{*eQY6ZyfW00D=dcPdr(ue#!J&Z#9pTGeTQplLml}N&cSZ( zlH*@Dn-Qi7L0-EH9my&+EG}h4Q1--M?>uSAt*5VQ(SnT+G}hmKdWk_jPVE#$nbg~m zWkm{0mX^C$yad<7D|GuLvG*`yYE68^)&6{s1O2+lha|(e@#G^_gS{X6wG=XN2=iBS z7gz#zS3|GCenw<=zjM)x2c&vdZ56nVeOB4p`PGkiHv;O9w1G6YO?!4>ZuIWZsj5!1 zaVj1vBIG#|K;%!(`KE&`LxR)4X_}h%WmiIvr5y%i9%tB&9kl|bXo*f=Bup;iX@>y@ zM^S?8l}zJHyJn`#fJgLGp7B~*{|kqb`qr$bS+^L2Zzkhgu*Yb#*VLw)yEZOrxX7V4 z`y*pmc?h!kF*}^!EY~g=R6^zMMf+uR<}v;~_S2!%V72l#Nog4_gmUG=ZYg6cf_b;r^J)d4Nk)^eanp(k4zribB~?q88#s+8Tz_nGza?rX3yHMZ}BiN}Fo zDJMauswy_v?3sX)eoDMr|kZ=A7uR81nV*s!zJ z>VPe*V(PSwPp~CNz~(q>O2^Mk0AWU8{A?p6NVx&X@~)`|dFfV}y9WOTv!#YxeQV1A z1SQoFor9HvDU(k(Q|Ap6gBc5IoY?cb&JWlFOS%it`dHD8CzP|-9c<~o-glpD7s1N` zz1r~kEB5k;Lv0aFdCeqGjOTpN!GJMG3Q2WH)6L+a$IZZ^+6pvE(iT8vCR7 zm^b7cd$D%~;{*|LXvad}Pr(}9#2j{eO-OuJ-R{|<&3dUZ@YhcanR7mnUMH!!uI-WY z8+BiM0pnw&OH10TN=UbIu`|R;G}B1-cbJY5scqczLRTEt?wpLZwM-65(Bf?Pn&_C+ z9e$oCK{hc$29Zt|x*nR3kw*9EW+`noxNTuRuGQGi1yGZj^m60L;w#XvK$o|ZLE_6F zno5UP4J4rko1gT~4KyQOvOZhu#g)7fJMU~9dmR_NutS18I+n(fJqUW6)VGjM0%t9j zF(U#3^$*c|+yYmGI{yCvOE5|jQ}{Z`XK~)!eY>RNfkb>>Ee_FFqsztFG@d<|k1;)C zp2h(~W1b)0B7fYab9jh!R4|mv5;jS>SK)B_`EY8zY$(Of^vTIgbJ@(YYE_6-H7iUj zGI!%;pLF$19k?pBQ)%ih14_kLMx#mXmfuai2ryaf30c^%a?s|XxNQs#Z-x_ZPvFM? z;^}8AsQc1zML0jH%6-6~enteEO}LVNuxQS0+pv)ze0cBXylaLOd9u_z*wz-!0!^PM ztoI4}c9Rh0E$0>&RImq!@O(s@t_W^kf$cEKE|}P~dZ9|*eNnGn3oq-2tPTU*=ORk^ zH!r(f4TuZueslb^jXG%!O=z*3F=_bMtC0jva=bEP8q9mV}sEvEP7c$DQFOTC-BZETboyGcTH49uFIlk4ll z;`*e?MP6X67`f5|l@Hh?0o)CuB8p`GrE-aP%fE%W8c!+MVF9xgE#P<(dL8b98JD}k zfV)h!sqDZ?mtj5WB&Sy_U4H%RJv@%45ROU?!O2^erGH;JyU)ufw$UkJlDK6qfRNm~ zXw-v79gvR^I=E64;p2ZjK^0Zs;J;CPIAk=XWMQ`Lj8fcgU8kY8;p}rzuiKevChLjG zImEgA+kFepHzaI>SJfKd?TGWsk-7yQ$viwuX2TG~ak>14g+Li@&Nualx3(2XbgKW*R z+MNXlp|+hK1Hn7&Yu5#hrvd*=YIVNvM7dwgdFT?xJc=JxB3oVyteNG6*b2s9r^*CX zj@eE0cCqk>GT}u<^$!m6N=T^ZEJ#W&la1jbjc=kl0KQ#7;K+5UM*OIn4H_D=K|o6j zmy7@F9+E~WxPwX2^+RdL)1GrCvv|#a&uIKmVSU9n@us&Il5Dt4{qaq*SNN%=6OVIG zoX}-02W0n~Hcby>RG2)u9tJ!hw-p}x!^6Y52G6JmwNAXELJGshzw0#;xLUU8;+~R+ zbw2KR^awNs=^OuN?>1ki0K^_xW6T`h&tJpm=O|eg@xSu&za@F2Tn}(~`Zu+}D#^53 zbX}5t<|(+@vv-p7d9+Sz#9tPZs_Kvr5ZcNKk?Cwux-Qmq1rPoddQls(gfsJbSl81U zH2VMr0Dh|IAOd@R!XCfH5}aXc&hGO&3x{I4E8N;##Oz^;V{PTX)n~){0~WuPdncUc zVWhD>+hHBSy|SNIBMsGYq0>j1vOjb2{X(ZWt0U7Wte??ate29+#!8pF0t1jEPnv5A z*?2Wp{Ej$oT;LG+n3-t}G%7%yMios}O0Ib##>N=I&d89OY{t{qeA?Qv`p^EqbaPi-b#6dJYSHl+Z8-e93z*)Tx)W-1(4^bQd(2a=gkMkS@*+L4+-b=C`Zlm?oy} zVFp1;_9!QBaP_*E{H1r)&0c`Xe;hx)_15EC01hNBX8d*Iv&^LvTx5_*!&0agufOAk z5Dvq#aP~Al&Ko9a+&6R#IFEpd1vL#&eP8u{zvqp~$w6oTGJqo6z`*?uvagP^X{%Nm z#8NubLeKo^!n>6^Sxs9DAC>RjOorvjLp7{`eIhVb_UCo)`o%i1$#-7_(4&rSj-D1; z44ZIrt9fmJ#CjKZqZF!|vxr+Pu%FJE3l!nzj%1*U7MXaREZ-dCLY=KQ;xHbO&7>tK zEdBe>oO2DAwQJo_@i~wp2QK@OAS>H^mqLPCh`U^YCh8fG#kAZ`$T2bBD-!3uEc3LR zomQ`JG8DiTI5El$lN&!yUnYp2~tGzi^U zNY2xkrc#YOo^qtn#CdfZMmt4*f88rhHL#d@Z}_?!8>?mpbKU!-FllF{MuZ?dO_Uzz z(_8bmNeRDy)8Eo_K|8LM4a?p#CiY6bR z2pz}m#$Ow?x}|ODa`A4&3T+bK9fJ@CP3ns~+u6fE|Q?fOF-cgxFl^%F^SB(UD>h;! zjz#SShNwMGm8BbX#7f<5?onT-B*WgCJ8O{IXQj-T1-yA)J9A1_wm5laK|vu8fLUSd z;w71l^F1N5e`2X$MsZ}U2t(A~ec0(WrPvMb3T1j{s=JqY%m$5Z0PyzZb?L3K0c2Qu zHS}>cKBUj7&Sg**@?d7l5xe_PPuZug-gj>3hWIDw~DKvN5 z7TR>yPI&pO*oyQA6+x60-}}VKSo*5<^7QO|D+k(^SRF z=a^FE{i-N*cpn@mhh;EBBGBlboB^wbQn4h}`CfxxgJ*6oU^k2K_5wr&1z>l4HOlAJ zHutf_96J2i;u;DXRDBllOaNUrz6raBB@k;;mT@nO3dayoSJ?-DtJ{O+d=EC2XNTGU zQ6oi4{5=(Y0Gh9q240qsf*myZ+=(lYVt8#dob@ow-Ag0n;5#h9MVjQUtm5#!2(qCv z^Y2IY23)71C0KEjyckHNmwfGxS};8V-aN;>&TY9?&XX=Q0{QXVTSg$SSbNOFl@HO; zTV2ZHF)Kvj^ife!KoXLCYc$*G^|h?)-l!XjG0&=CcYRo7LLLt+!{e*}whqu8Ac%j* z6Gwn^(NPVW`c*trWM2ydTyB%~$$|8_Pnp){=JZ8~@?sR>#vcv4o<+;lEW*EIP9a}a zG_nl5L>DpW9LFEMmF)hkzR0r{*+(xCb0s73MvA!pvet(4NUsjph`^j2lh^H!I8$D8B-qe@{g`*lU@ie2ru zD+`0p;Qxj$uL&0S2j4jgf|KmrJX(Bbt_aLqe%9f@k;>g#j#_~m)o#HT^ry=zDK-3d z2iMoT5EW$OB?Li!LQfJQ@u9>0z$~r8AHy19%7sa1#|jl;%|?Yw{xq3e0FfHAWgQu< zJjnTwoE`yozYS}hT(2+g4rV+XsAea(;}~4Jr?GF|?agw@1;@!Azi-Zp8}5yMQXYQN znlV=h|MwD~1?k9z1bSntYB}#iB znYRxqNw+ejGEj7Vo%2jw=zVf!_sPUxw`Kf3y$A^eWN?-3&ywBJ5=`3=F_Pn*oH|a zh)J!3QDaZ38pEB6>|XL(zqac}GmW0IJbc&-tRi(m*CGY#S$~cqh%r@aDcHYn=!GdO zEej`bYq6N$Ma!*#mqL{c4SNXHIRWYO`(Y2L}> zPTmXK{A*~ln=x#ssZPL!8v89uv#hebuDkt8448O9WRL7`6$dzYKyc08|B|C18`PCm z3B6zCiLXiohGSnKu-!Qc7vIb-yVT%%LjRfL;=HqU{$( ztoCNQ;pb>@?bO~Ljeo-NZ_TP_wMCx)Jz8jUHCkp_zBt~Z7QYe!#xdN=sV7<~Fj22q zUTrs)WHIBlCvPZJ?)m=v@=mqp265IW8u(itEO(BGj~l0r^UXF-fU10La^S%=&5XtS zCOo?5SM6Xa?dww`#{_aqHYnOU(J$vq>lTRc;Gf5@Wh~^?mk|exX!-0Fxj~y&D)#y; zofpzC$YrRz_&=$>t`^ZPGx~GXv+`N&gp7PJLl!G)dq%W3^I=Dl7vEwpr>5q7&aNFc6-`VJU!~<vTwP{qbb@)cKa?Z2PP#{a>{sWL4oKLD$EnIbpWP_X9a8%%a z%L@dG+)^$$iNRJd4NuzI#zxrt1Ru;qzqbo>I0oj;y2^(JzwyenptKKMa+_14Vpi&a z4sK)vJ%zlB=y24AK_wxe`UdUzgBG8@qlMKr14y?&GyDp3@f8ztGFI;*nd7yP28;d`NB_IX%)N9HbD75AU(}#!EXCiuDPAI0Yq)h*8N2Q(l_dxk8&eO9rHUybLR zvB8!WwklbQ7!{a)7X1eG(t`U1v4&W*T@%N<_SIA%ls72JFM@^B^J>CT&vaXD!6Bs^ z^8w!%?-0+Z$t47|EO5X00oqkPjm`P{A<3+2l~2oSpE|+=)726*WdL=&&|4@7t8~Nu z0#SM%MBN?-|2O<-Z#g-u#WoaH%e^T}%X~c#j#X{IxmmyP`i!9%&rBt*!55g0-QC^$ z2L?1tCf=_Ivu2JdkMuNzEwRoiQ z*{Hd1F3&z+Y*MZi>C%(q)!CZ_u21?55lE!=5sL_x4Yq|7U#|;!||E8rtLY zatcz-_@2HxObS>d<CJs0cFc$W^IckmB?kplt$-~N(Dl;&d^s$Luz8?I zeYyD|qcW54;;Corv6fO7Z1ry$`2?-Q$ZEQ9Nj~IL8b$O57|~W>xj=Ts@D}Lbt&C4A zIFK=CgcCa=dhZg3Q%vf9y5n?{Byr;x3jGEB16|h+UM>V(QiGzh)t^5v!{^@bn`hly z%vlAl3rz=qeS92goQ1=MQ{aPcb(oA0d;$9?BgA-C4bBc#0-!w`e2xOa9$i%?Byi=x zqQ}*X##5aAr9%~hB8^W=!`FRr==F>YdZ|wOMF})X0A2pX=R&U-Td$F0=Z)jcrAjb{ z6rY(_Pz*3PxZmu!8#VjHpKosc7+?$!2UIOqzSS+WpTasnyt*W~=ls#4w|jN0?W{}Z z10vJbY!FI-_z`Z~ZKdEWCIei|M~a2fR|i%aFSXDTvDFYs)D=Tk@*b&l`ss_Y8{9J0 zS5MhAW%G=Fs^h}K%)h3iSCui+nUVzpTg@qWgd~Q}51rlruHI3jk!UZmU;k3y9altg z@YqV-`z(qbP?J=BL;Z-_IylqouOo6JABL^8+{|@I|v#Tx+n_I(iqu4EawikT50$! zVrTMmwx@;^rTey)w=`QL_EAJ=;c! z`R1Z=++ja+vb`@!uk|4jGIUMOt8J&(AqGnNb;gMASN^DHk{USiM|ngXTsrk$J5-=4 zS=as}XSe-1)=yEjEMt~GXf3+E3(siGN$$z=e5;TO4*dbf$L;ue9<`q(c=@(j19Czc zl2F+b2Y_B_z_rd4zG&@{Vn*862zR=_Nni@hpX-`jsqv*M)N+oohLH+?(Wim5L#Lhs zt;tuX_0XvH_bB(+2qR+DWM0jFB(fF{-^eSLhz>D2@)GaCvB>&R*81_3$;#1w$N0K4 zuSq<9Yz(g!7kwVxhwS6WkMwYVfF^;kvBN&rrTPJJyC*`A?9JtxEv3Ty+=XcxU6GRjOoImsTZoYWq zw&88)gsGowTooN;o{E2u7g_`z3{>%*H}5I48as4?y|9{oM^0S2<#`-(AszOIc5bW&N#mMp5=l#AXRYpd1is|*`VR1CQ;d-eT&4{_ z*vp#w_iGnWBL30$B&??vc*5CfN>}d7D+?ibuNn1r--9C0m@!(i;O01Y&G88-@(C1- zIXBzEqw~W6cA+3~Vxj*K`Iv>HS1!yDE8RoNn_A(?b`?qQ^Wra5*bwwoOi5M>Ag4%L zs|ecsI3ad%|LAq4t`938t@%(3`1O8kxHf7TORL|6>RnDSWt_T8pXGcYjQ(A3Fdg9* z9mNr<0P{ja=oVe(-8}g4b4;Ox`y1!td%k`C?z3oEHZMcD*gGBO=Wc@H27{r=N5)Eb2o+;ZVJV`dP4`uj1_ftAp2*I%;r#Nf<84^k&-mj z5mqTYf=@;!T5YAPQ4M0$7TX?xrg4?|PS>?C01h`VhtMry9MIlW8kR|fTeNxsEG2ph8| zD~sLyNNl~nF$dnEs3MgDLo_k~U$U&Hi=F?0 zYBu-i{Pp1HRnRi@srwbq?@|87@Q3nUy-Cy|>rZjMToV8440fS>_~1$VE*fVBKFkMT z6Ua%kAZ!;wziiRp^Rd9zsj!fHYahNkVaU2{O&dVkBH(_R3VB*mI8u39^pV*P1$u91 z{-w;uaUIliK#PYIlr;I;Y}NG;z_;irPU`?za$yOq%J6dvXPrIEP2!WIF)z{jNwJ|v zI+69$PxEc^pL!lV?uu>cJnnNIGy42JN3G{!wwBw!TwtvEUeX5WWAO|QEPf4_?`~M2 z^^vfUplZTboWY@)UBv1^?{C3IU@|i~>gG(>d5bYm@XOU0@U0w)mdK=(BhqCj*U2sD zWeva6rR5TF*<)6zSyj&R&065{+RH5aMKa{uSPQ!yvE0J_QWwPK0?j|gC!n^Iz9xx@ zb#>k&DqM3VNcnCtwL33%Huz38$~9Tl>1r%Ghy?^n68IooiWNI?0`0J=qVVwYWy>An z`lq&p>;4G1_c0}~`9#wpt@&8X@T&bX3`$ZE0c?3L)XXp7g2n_1BobXC^_NnzIr?jp zB@aNH_W70b!#_v+Li(TU4q4($dn*oq}kR01BTUuX`)GvFmmR^-&}#09G3{foQcr9rSB7G!pav^mp6SQZH}S zAOD<&bn$zUH%uMH;*gCXj^Mk@ydnaU{$h%a>U%!1ZeS%1n}4e1N3HrE3#mR#b+Sk@~`9~_S=SBZGi^$94_o=ZJ6fH=%sEDJn zgi05e&b~dOmN;QrSEsdcyZ*0ruv+Ws>PEx3P zkj}RZtAh=>MT@Uu+#5?Vw=h;U>K%P!gvyg{E3({qXv{bp1lweGN6kcUtnU-Xk=?i^ z$ZOa=K{`cVDeFp3;fGW;pjI>v0wL>lGU4ETG3FTBl^-KQ^+B7|uEu*;3Syuw0*JBW zVr=pBs_+Ft(G3iVBs(6-f`QY4=kv~prNg#8SnRRy+y}ydjhxR<@fq3}{SexNWB8l)yVA>Hd>%kF~dW$e$Q1_1`$V zoVQbIJ7&@gS$tgu)t!IOBG~i9Rf`?E(PYaxw3o8?B5(LZB(_gcz(leFq+N`(*T0x= zn<9R^|K07KW%ANwL0M3(%S)F=iLN}=W ze5@|Ew&kG*dj5P1j^ZX5=o0})?JbfYsz_%Wm!J^E)tGcx z4uUSrLeifRp-}2?5s0Ehu=3Lu%Zt7n#Zpt07S#~m$dr`y^?!h6{9;!g!2KZ3zRbws zcgL7c>%*nOS5p0=uct-%nlE8ml~m~B->CTvW!K#RK!dbNe*UKjX7_i+$2P|uo=9A7 zVDxJa)+Pl~U>Qf0-1dQ{vZmCMlJzt9G6!%yR$C?r`Y{5jRETyF-(&>sxi)dzxw$$C zIFtu!9P&GoBBkI(G&x?sVr#v7B@Fm%_4vGiL#wv#Ht(|w$*u|7ZbW8MevzbaGN&qd z92pdfbM-pHCf*!-EaZj3>aH=>Ab#x}6 z{>lU>_i{6@bS6#m?pDF73ErX_=|OoNkK57Olim)Uj7JIU`^zbl^eJj5iqgsMq2WAw zVZrl8ii2CT)m3BA{u9PurMu}tMdw)@&xG;)^I4jpQ|PRLX8^7==I6811^=+g;Hppt zvB8XP=Xb0g<>Ov-h+a0={c05EPtbHkkV5ZABVB-M3U*YaP>d*7{l&=?gXhSW=4KDP&u&mH;RmdG@zjysZ?C}L!D`kCcFxghm(`Lr%sCkao#V!wZ z)Cb^*onvDFQcgArI+El$k`VGmk8Gc?m*Wgn-dY_1rF7Kif5Wql_Sd8;9*LpojI!Wf z&B>{)mMU8RALqOtOSy&Iq{*k?XPynpa|Vx{wGgI&DhLF~!sz*xm%@-rF*?}kXLg$H zp9mHgu9a8`Cf~pG=0axQ9xcqp^zJvu$@}D&zcHA=+;Vz4)N%Fpv`xD{Jfov z;F6+WU~bUg z!@z++v-I=kshJ|FM_jIL>tKp$P)e&OdFHo^+o;474+bq)ETdb3>jAba4mX3pf5?#G zkwc;}Rrw1f4G1%i&c2U+tzn$$mqe|-4f17z$SQXWbFw0}kA7X-CL$;1VC`*+j!Yu0 z(1h_T|6NqXV-WuLHXBQrWr#qGZG#(N+EuGIWE8SOA}0$VGE{cB2<=Y#1W6^N0-nAU z_baz8=Qr{Sh8`r?ztj@ner4!&f(P_Ozhxd3*Le9f89gWia<}nyg4g)0w@ig^&m_O~ zJstzCkdk|-kjMtP8vsfEN_N#`$tSqHy<>RQlVcHSEyho->V}-6265ODNZ7y2okERv zzZ+S~@4pX9*JLH3zAu+l$y0;+c>^}7eujZjTW0w9FcN^Eu=l#+Y`(Pebe-bf`+H%e z*k_+sfua3HZn=CtG2{@{ov4m~&D`=!r%5Ad_GPx_KfNSh3HNt56}=|(>lC1HC46c_ z0Zt(e_RtM@RPhv{GFRpnpF>m*=YM2gdaU>r^k{QV*vC}V!z{E!`h3$^9#qO6ZM41@ z<|l)9^*M|J6ZHDUwuWD7VUan(PayjS+Y|X=Pduam@NMLJQaZWWo)}w$xUwNUA|8b9 zc;CAl+e)tp>J&ot*F#1)rmGGAF*#1IRfNQht7P1`7^6Nw?!Hs!-8-qu0FY)ht{Fh{xm7J^=^bxMtN&GFm<EdY|pHRt=c+jb`Vc3(Y?CmY$!QoNE7(ru0Q>s9nWA*{6ZL^Cz0LpRnKHk>) zza~){osvCg6Gihm@7d~}u>IpC4Ymfazu-uQmd;k3_F7k-#8G%=s!1ErR18-Or3I@g4E|Rsf{esL$pJZfTnUY}ouzWs~wT-HC6Z%(X z(V8c9%8DBl@FAeDcYnSyZoHb~?mYt`vt;Y|`!6<;d#`QmPP!?a#r0|27SU z_$7xfPPm4kTt$7bPGa2sp`^6|^5;a+8217!oYjVYXfPDeX9#7uoQfCp^q&QVNH#~~ z-7Gv&=)ML}zCzFLzFF#!d;a5iY1q=<$XWO{%;6opu4z(a7+$P4ZR^bV!y|jhRS!Z+ z#lnv{0y_8FvbH9?l?FiD4X(vsSx$e`f+8|`PfSppHecVJ1EezmG;CyPiPC2eTKxS0 zm)OPO(p%sv4cj|aBjgE3LA!zE%d|H=3x!df?gxV6inzAjWq~P`~t!<3I=5=2~o^IR=Po%wd)?mEu;_VcT zdH1V|{&4|&JQO38pBz_=I=@sOB1T|ouix9}d9h7QOvcSBmEon=-QFe`-~`QkqRoAA zT&XYwZ89EPzMd+yN3}r|M<%OUPjr+L(fvm|^Wi7^jE%QCc7q31?M#$`T*y&l{2Rhz zljn7G1Y6t1nx%~Rgl@i7$uzd7N>TCb;YFI(>ZKaM`>K~znf76VE~j9Vo?rXu+au|l zSMO{c*82C--aH^~E0L}CZgO&M*{&2Ln&KyoXXo`l`!a6G%;83EA$5bielezDY;KDE3&B?92k&dx1-bkoTZ9CBo@-pf_d8)Ncas$M`^WNs8eCAv)66e}N0-uR(4Xz_ zEZO5#60uwCD5m8hyry|W;jdC5W?>?WH!(0sPZ;BId-NalS1pli!Fb|cr`jJ|?mn;x z>XpY%yP=gQ^n3rO!WN((4aTA_Mo|`P@=RTR8F7W-pz9S~ivIf(TxP)sru@CKBze%2 z5(e(=KA1PS9-#iPF)+8Z=NMY7*XFm>mwr)tX$U%3b#=iLF-tm1Oecmm$}9t0yOTA^ zNHUMO*UK&ez$`oSIi-t-4a|P4{rT?9;C!?&)PKy*;roFI_ucEM(2dKZ9a5|>DM+=Y zN^1?|74K#eyM1pSf&v4tuMO|=b9BhP1a>~DxuDt%+1P_zcO4MWJfdEiPS!xaUKK}i zc$^{5R_b%J4!-h_=CvWNRz#fbTbpu1`{;XjsCZGdz18tRHr*#%X@$v;^?8LL52PzxGCxWYLYaQkzk7E)7%;L zae!FE4aYd02z$DAJ{~eD*=uFniK1x~a~r#ZLHr7Xrv8jK&Z|j-=o+Z!xwS-6T8ClZ z>4hx^!48^QT889XMYy#NtlbGFy)?)(&^IwYq@TXF+zsPYm%wdXTT$+G?znst@5>(g z-HqVOK^umc`F-A;$hnO2v-1y>2Nh|87yxoq-enWJWdb>J55HpS5|oXKhTFN(~C zPh~_&cPUyKI+9j4C%rU9P}3jT;>EDAgD`x1_&(U^ZLM8%hDSxM^JBVS>@%t2VupzV z^2TYah#txGEv?dNZ9Tm_%~Ifi!JP)Ys00=wxMl4~pdGH{_wuXB{ms3`p&Hw-isCai z$3V@bhl{`M{2Dor?{S#ODqF~xDE%hs-E#i=YGUBYU~WVHY9|6j=q!sc2=(D!wCTsrU)sIik#B7 zmkHH<}=b5x4Z}-c_)+Y`E1ck)iHtonk{p)c3V|H*%aXOghw8Y{p>oA ze_s0$MPkDE1*(`;1Y(_TdO!d|$@Gh9w}^Sw2r2Q~dks?RxaYWGpC^AUH}$WThjArt z*#+q_My(|d@lHt62Kd#)b|h-Vy!r3IpdC>*}D zOfjRoZ!9yVm1@plhRU-Srtx28^Eascb6d6K=*&D0*1QBJ-ZU$e+#`r^;!MFT{2P6Z z=FSiz9qsR_fW<{>G3l*lIyC;`Y`by}OAPgZBg+%A!`oHUV-8_nnH{h2wx2wOe7)Pw zFt#`TX(Z~yPtwk8t=1As1IRUlY2KXBKNrE;wDM+FZbbF5)ET>TOqJ4g&j{OYl&H=j zmY7QVpq<<*`6l?BF9vyME#a%)Y#o#Dg7-g?NR=Yw9A5fhQvleHY}!fNuhfHz9fVwb ztlwcL`)We~@Dm2=4g7W?*3uLyXobb9YcBNK>gToX$?TAMv3lo2c8yihS+q`-_}xw} zgTAb2_1fQ?FhNx19!A*iTXXPlp;zZ1GylWR+MUiG4esZ`u8lmAny9U`eM?06*K_^r z@znWx%kW+agieL9xa@W4*Mm_s62N`M_%SPfBqdT-70#6M<25o3LPoAWG;%CKLvoEl z;Wb9&n59p3C>1J-I_~sGtQiunR1V`ZtVnU~Gdr>${l(ph-EjDziGQpB(h3tv zE%ZD&dU*5mTI(^+t<^?1jVI61Nxf270dGo_sk2H}b5ocJQytD%dn`PA|HU9?JZbB9 zTrqQue!V6Q(!Orgyme)!B+%lXos9zyC~f{z!xDMXJm^NLF|VhY6Rr+ENA0p}l+rZ995m|uT;m3G21W*{jJ8rkPoymJenYTS?fo68yG<1Cuk;3!t z>O}eM;9Sa7<8>}Z<=QWpI||sje(+{C3o$WzzXm1ZZzy-UB!8c89B@?oBC<9Bq6uDeqL@`{oPxxY0c-uR=MT% z^HwG0inY=t2QfJj1@qw+lN0oD#04n>iNxyl`_Xw+RMF88OvKKfUZHJ|%cyOSsAm?`Dq7eT@gN;nt1e;4AW^c$v?3zU2EN7;C!dAXc9EoP&0qALgl4j+iyU(8@QG>USo zogun)X~;r!&h2M>f^+NPKibZ(jtZeaPI*5z{xHiTe_3(K?i>T3<#;{RA9#tO3R)sR z-&3pUT`Llc|9qIaH_Z6; z+5)9lH7D84)7JG-Zm0TZFT*lZ?wQgi?*pB|h#it}UjQkhPGzg@sy@YoVI$uf+YGxx znM|5Ri?2n_H<=vNp-9AnRVAmltnYptp{kSlZVxN)pNlVwD){HfKm@&f_@5WG%4gZ` zgeLrHu#XUK`K;>9*ly=X*;PB8`bS_S$T*thdA}`ELy?aAj0$IXS}{3oJqmUu$|CN;m1auD*mk7XDpqvi4duLP4Ld_4AqH@d;JpEC+*VuI#sAuqT}CkT)}Gjgbq>S z0mefDkYdTMa4fPy^YZ(eaI)r1TAb;nqfdE4E2T|-$&8Y5;6|`m)RuY0S;RPIPlN!c5a~*sAjqEWeLVQ<$v=U+Xlc0zk z8ffVjAv86&ygvpJhcjgV?S$c@6^7_>m>CkwdLt7|rQ@_?8g#Ld`-%03YFxk0+LTyZIR(A? zM##A{^&}t}$ppxggp!(?-0NY6vt(hTH6QDL)SdON&R(@#<{a92uO2`qYwc(_$}C`$ zHR>^m#_W!Jh>A8A4c?L1tRPLxn=Op+eBk2R#WdsU^ReB-G|9o& zhpk-GS0QQ_2w)k~J~z9?RXW|9#kAja@U95uL7#?AMXvS^LY)$-@LQ#9l({h+9Q>>_ z+6PvzK0kIY%%Td7T#{XuG~TbvdfK`uvq}$W1tM)jX(fn=q&lox!>qbDrw%8J%6E3v%rlFJ^~ty|827mwu2^i$@WmPv1S-J>)~a zLxWo4qhp2j{GMCtsPyz?R9=L6B$9P;eLA9!epTc5nuxSP>dP^`;uqvT;!X}kd$Q?4 z{Kx*8V2-aFCkJ=>BE1y0*hIYthAewfv2dG)IVI8bpO-$%FLf3dvo%lmEqq;cxKC|5|@W21NVvin{BUamDG`JVdCDTpJn(rlu55^NlL$E{*ArNNu_*WQoi0*PWF60!A> zZ%7~JmhIMWoEBIA(0fHx;dj*LCVxTi3OAbLNc$7*W`?9e9X+(lKX+FQZ7GM%zJsx& z+cZ}XJCPp!&0bA^MYQ*Pb|Dp!me7fQ=1LUqHV0r`ZiJRvI zuFtK7*SCgAn!1P+jFW$3*C|-csp!2907KA2#oK=6S26pHtDbCIsM zeiIy%x{6NE$OWe7{d{)&B1yD)^6wblii7l?bMxDWK2Oz^O3aNK-Vv+@KJ<3`8s0`0 zaq%Dhn3!vrW>iUZ+wZ5b>CQ=9nUPm+KNn`3F4FYe44bF1?8G^Fy1RR{p#4w(wV=QrY!gEKdTH*+=WAh zppgDNH4g)o6Dq-z;P&`O|rA6kye!2kg#KjYcMxs zVl&9oelxN-Cb%evqMyt|WwCZ2xumll6NDDPh=8zs85m=`YHj3F z$8GSPb*Q_ZHK&>=m5>N&dv zhy+ipl!z{KY4j;hc5+Q`*MBl5rq}GICRf4W!g=C>VMB?MV zjK2j}q}f8UCj4Y=2fMwVlP|rDA87piIA6osFMSVDgp3lnk1xy6B|%(%Lb)IdPDrm* zU+GA_>oa(`I3C?;c9eDI#qB+3Bj#2(@-Z`Bz}}aM*VDvD{Qz8nG{#M1+GX{65jjjO z9+_~OV3u8hWvH9$lfqDE&6Im@4RCL^o7TzRA@au;cW);*sy_)>SY?z7#0&5t)e@Q( zwly;iz_&N^9aA2Yz@}dQCX50=ZVqf-x21+-?kB#7cJ$$LFmyd}R{Q5JL#dkZ%s6zr z8!fF~(`t~)n6ck){rnk)Jf+dQl;1%DlhBJ;q0EoD%XbWhTqIQ;%OG$bd_=QyH(b`3 zTJchJrsR)*4}*XtuEI^WT^M@av`)-~6H)Wo8OiIix03hK^5m=S))C9Vs3to5TZJxI zX;p3(=bd3c0cZ^IWR0+QH)ZMS7AB5vlNw^aK$~B&m8Sk^YyE7uN9d=hdekFs6Wnkm?}+1$supJDmT znjk)-;u{d-{R&FMinHFheCW5X^(}5HNW5r8>SVua1MBq65kOv)b3Wc3%xrr8JU&V5biwQ9t*Bp`iP1{rO*Iy^0toswt>t6_IOAl4i!Eugg`rJ-kF77R z@gaslK{>GghTD1W7NsAV;$4)d4$&u)m{ zQGw=)9uqK?rx`VgHxh1pljXb3JSx&ci2`P^OK^EXxDz-Tov(k1qQ^!iJ#N4kqni zT4)23!AU=+I)`i)9gggmH;ske{79%&^x~iN$EDH`Yz0Jd!P04(zRgf_ICN1EaXre zjE{<@g+ji67|s6tI5{;ly@M%9y};897mYs^kfxUMki64dTuZhBsEK4$J>(tf0cx;$mqv@cMQzrKYO zMe3K%elpT>XLw4%?fkKzwJtjj{1?KKW*T*e%7@azRcvvJWD{gEM6$4B@0J7aVG)bx z3S5F*aU*`C?9IhG&WvKg-ZJz zRUPux>c3_=B8*G0*`!LW(XA+z+^#k*L0|T7-ada zOz#XCyzeS3X_9j06$_R%(fRVwN?s!-yv#JU@|bZb5*k1Y!7W-L=+JWPb7>HaAirs{K^G5(IK_KqXRICt zO$dF;Y@P`&l@*aXV+xC@`#XVDie*_r@>#M-tTjxaOf^S4Y(XB!N7Agdut;VmRJR8~8Pj zFt*)DGr<=xN`E(fk4SG8$aD6_daj)~44l)P`>{ONsP#D6hy*XJY&1_T+p^REJrCI~ z6gU;qv2`-sJNfnrvPnNHP)~S+l(~BEyu5l2@LIQAS<7^rGipx9rtzaT z>9YC+#8?ZWX(8Xh0gwnkFiQhC&bJpvDv=?5u~7eN7lX3;19#T;ymF;1}+vrNpX5tX}5$Y`{~Jekuu1K1qn zbNmDi5dR}0@0AEASG>r7Q~9+S#6m1I?NW_^bHdFA8SQY?(fg+%-0qQJ2y!{_xIBVQ z0Uk~hgdEZrOn%n$66vqqbCsc*{Rd33WkP%u55i# z_neuYr`3ni{>TBKB4Ea>HCavuhu(fg1cCx04y;sTGR(mqk5ge^rM~ zR?pF{6hYbQUAq@Uw={w@XKr>3akD^!Lt|7NgXieZv!y-_TN6GjZc`V&*Du}FKHXV|2#` zSslsG;|pzPXOFy2g9{Ud#})MGVGrd$$B~G2Ne8;+YpN0?<+b7mx#wGpUOb97!C_x2 z;&?|7+QGgx-no+)`ohUPiEyk7U$Kdd|G{=+0G3o{@WV$Vs%(2-y6}4C{*w3dL}$@B zgj!3}KoNwKtsgI`V`O$QrGrRIga-XBd z5K0{TxDwV*ctOnJXmi4bgM&+Zb5CV1bfbJofpy86!jkK15Qcsuhv@>nLTl_UY>_-?_jU*uE}Wt0oh>*IvbmdhuOH@zo*ZM(D*$7#-c zInbB(xKBElO|`g+5&TBEM9R`ZehxbEdRQViQWU!od+CBpHH3s;+w@HeCXU?mS}~<@fs0sM?k`ZmgzCr zgSI`rcjxNRSayij^QNe%%tT6@2IJXq9(Z8%KGDnhzWtdu;f95wC8$QjgLvQw)Q`D6 z6Z)7z-J)mFg5GtE7`;lIn~AK~a!j0Q*h2X!1kMEH9R+VQJAjN8Y%QXpj}I;3yITxH z_jEEi-Z%+Q>FM$ws6V4PRklMHIS>}X2MwIA%Ek?mDlRV0Z>mq6pSPTv znpRPJ7iKm3Xm_@SvAAkw{*9iy&PiQULq$Iq@ftYB!9n?)+;jC^ z>AGW&j(7LCVmJ`{X5dKUKt&X+O6h>E=l=bg_se;P)H z_huU%HeZ24vr$%CW~o*s-rFc&Fp->bMs6pkZWE5dA?D)Z(%iBZy^FoV-qLnwU>aZ% z!A3FgB6l%BA@Tq7_EA260P0RIQS>f26fQ!}$88rfVm%$h7E8iy7v1n+B#$nG1cy{%Yq0#z7QmvC zYqwuPSV<`U#uU0n%M3B0ZozPb$$N8A{`iZ`abf`X1ybs*<%mSnse}XJh*5-%Lcc3> z#tSV%QLr8Sit^l0UJN9{ggK09p;&R*gf8S3u%8HN6J*g?RFgq+Ip*21yKzl$I6cx} zbV>!BFya>6XjVO{KbENg!dG1GW8o;df`DYaSV(a3shUyTr5i#qE>_MMwtB1}#T814 za~oEi+;eD1@kRY*iF6&&AtJ#prW`Vcn@^HAVa%sj**;|uWPLXu3P^c7&x+am85+4R z79}%;TKh&>(r-wm z4rZxNm5pWWggnjt9s5e?)m0cI28i}=iLx{1SK{9hdh#s3N381Od(1^Ck9pu%CW2tW z#?%Ae$R)W&qK{@Pf7I`l=%85ZR4CrarMfGSW3Cs(SSBT8lB_4@W3{zxDBeWiNG8Ei zBuxiXf&;Y}n$UUsDFwrYEJGO}8*(54lS#m_;CS z{O=2U4ms4tsz|g81&FXw3swB?_m-He^qK$ndl~7@i!^-TmZHvq#C@>->O7>&z$kmg zaUd##GufJmry%I;*e<4MiF6 zJ0`<>Xr~y28U}~ZqIhFY!ceCFv{+$h(G)gr_L<91f%N9&uC6aBzD`%oL8nr9rv`yFGMY7$gc42TGJ$lm)w2SmP%)D`KY?_z&eAA zoxiP$)s3^j52}U?dO1zSO2FW@6RgB@%?rQBvFS zy|JSizo~;6_y_We{naa04i45=uhcm>`8oLbIeD4c*#UT&>0Zpk{~7Sf_Pyl?*Z+S2 TPCb(t7yyx#RFtR?GxGf(pe#dC literal 0 HcmV?d00001 diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 319dd28d..99730685 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -450,17 +450,17 @@ User is already logged on. - + Erreur inconnue Unknown error - + Utilisateur ou mot de passe invalide. Invalid username or password. - + La communication avec le serveur n'a pu être établie. Communication with the server couldn't be established. @@ -468,60 +468,70 @@ ConfigWidget - + Utilisateurs Users - + Groupes utilisateurs Users Groups - + Sites Sites - + Appareils Devices - + Type appareils Device Type - + Sous-types appareils Devices Sub-Types - + Types de séances Session Types - + Types évaluations Test types - + Services Services - + Erreur inconnue Unknown error - + Form Form + + + Paramètres + + + + + Journaux + + DanceConfigWidget @@ -1957,6 +1967,97 @@ Please update the software or contribute to the development! Starting... + + LogViewWidget + + + Form + Form + + + + Début + Start + + + + + dd-MM-yyyy + + + + + Fin + End + + + + + Type + Type + + + + Filtrer + Filter + + + + Date + Date + + + + Heure + Time + + + + Message + + + + + + Inconnu + Unknown + + + + Trace + + + + + Debug + + + + + Information + Information + + + + Avertissement + Warning + + + + Critique + + + + + Erreur + Error + + + + Fatal + + + LoginDialog @@ -4127,32 +4228,32 @@ realized sessions Unknown - + Utilisateur User - + Groupe utilisateur User group - + Utilisateurs: Groupe Users: group - + Utilisateurs: Préférences Users: Preferences - + Site Site - + Type de séance Session type @@ -4161,151 +4262,161 @@ realized sessions Test type - - + + Évaluation Test - + Évaluation: projet Test: project - + Évaluation: site Test: site - + Projet Project - + Appareil Device - + Participant Participant - + Groupe participant Participant group - + Accès: site Access: site - + Accès: projet Access: project - + Séance Session - + Appareil: site Device: site - + Appareil: projet Device: project - + Appareil: participant Device: participant - + Appareil: sous-type Device: sub-type - + Appareil: type Device: type - + Type de séance: projet Session type: project - + Type de séance: site Session type: site - + Séance: événement Session: event - + Service Service - + Service: projet Service: project - + Service: site Service: site - + Service: Accès Service: Access - + Service: Configuration Service: Configuration - + Statistiques Statistics - + Appareil: état Device: status - + Participant: état Participant: status - + Utilisateur: état User: status - + Donnée Asset + + + Journal: Connexion + + + + + Journal: Général + + TeraForm - + Choisir la couleur Chose a color diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index 0bf0e628..8b7a7dfa 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -450,17 +450,17 @@ - + Erreur inconnue - + Utilisateur ou mot de passe invalide. - + La communication avec le serveur n'a pu être établie. @@ -468,60 +468,70 @@ ConfigWidget - + Utilisateurs - + Groupes utilisateurs - + Sites - + Appareils - + Type appareils - + Sous-types appareils - + Types de séances - + Types évaluations - + Services - + Erreur inconnue - + Form + + + Paramètres + + + + + Journaux + + DanceConfigWidget @@ -1925,6 +1935,97 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du + + LogViewWidget + + + Form + + + + + Début + + + + + + dd-MM-yyyy + + + + + Fin + + + + + + Type + + + + + Filtrer + + + + + Date + + + + + Heure + + + + + Message + + + + + + Inconnu + + + + + Trace + + + + + Debug + + + + + Information + + + + + Avertissement + + + + + Critique + + + + + Erreur + + + + + Fatal + + + LoginDialog @@ -4060,181 +4161,191 @@ Souhaitez-vous continuer? - + Utilisateur - + Groupe utilisateur - + Utilisateurs: Groupe - + Utilisateurs: Préférences - + Site - + Type de séance - - + + Évaluation - + Évaluation: projet - + Évaluation: site - + Projet - + Appareil - + Participant - + Groupe participant - + Accès: site - + Accès: projet - + Séance - + Appareil: site - + Appareil: projet - + Appareil: participant - + Appareil: sous-type - + Appareil: type - + Type de séance: projet - + Type de séance: site - + Séance: événement - + Service - + Service: projet - + Service: site - + Service: Accès - + Service: Configuration - + Statistiques - + Appareil: état - + Participant: état - + Utilisateur: état - + Donnée + + + Journal: Connexion + + + + + Journal: Général + + TeraForm - + Choisir la couleur diff --git a/client/src/CMakeLists.txt b/client/src/CMakeLists.txt index c6f58706..ff8319a9 100755 --- a/client/src/CMakeLists.txt +++ b/client/src/CMakeLists.txt @@ -102,6 +102,7 @@ set(headers widgets/ResultMessageWidget.h widgets/TestsWidget.h widgets/QRWidget.h + widgets/LogViewWidget.h # Kits kit/KitConfigDialog.h kit/KitConfigManager.h @@ -200,7 +201,8 @@ set(srcs widgets/AssetsWidget.cpp widgets/ResultMessageWidget.cpp widgets/TestsWidget.cpp - widgets/QRWidget.cpp + widgets/QRWidget.cpp + widgets/LogViewWidget.cpp # Kits kit/KitConfigDialog.cpp kit/KitConfigManager.cpp @@ -262,6 +264,7 @@ SET(uis widgets/AssetsWidget.ui widgets/ResultMessageWidget.ui widgets/TestsWidget.ui + widgets/LogViewWidget.ui # Kits kit/KitConfigDialog.ui kit/KitInSessionDialog.ui diff --git a/client/src/managers/ComManager.cpp b/client/src/managers/ComManager.cpp index 62b9f442..2a72a109 100644 --- a/client/src/managers/ComManager.cpp +++ b/client/src/managers/ComManager.cpp @@ -710,9 +710,14 @@ bool ComManager::handleDataReply(const QString& reply_path, const QString &reply case TERADATA_ASSET: emit assetsReceived(items, reply_query); break; + case TERADATA_LOG_LOG: + emit logsLogsReceived(items, reply_query); + break; + case TERADATA_LOG_LOGIN: + emit logsLoginReceived(items, reply_query); + break; /* default: emit getSignalFunctionForDataType(items_type);*/ - } // Always emit generic signal diff --git a/client/src/managers/ComManager.h b/client/src/managers/ComManager.h index ad5dbdd7..8ed42270 100644 --- a/client/src/managers/ComManager.h +++ b/client/src/managers/ComManager.h @@ -148,6 +148,8 @@ class ComManager : public BaseComManager void onlineParticipantsReceived(QList participants_list, QUrlQuery reply_query); void onlineDevicesReceived(QList devices_list, QUrlQuery reply_query); void assetsReceived(QList assets_list, QUrlQuery reply_query); + void logsLoginReceived(QList logins_list, QUrlQuery reply_query); + void logsLogsReceived(QList logs_list, QUrlQuery reply_query); // Generic session void sessionStarted(TeraData session_type, int id_session); diff --git a/client/src/widgets/ConfigWidget.cpp b/client/src/widgets/ConfigWidget.cpp index e9ba1217..eede0a30 100644 --- a/client/src/widgets/ConfigWidget.cpp +++ b/client/src/widgets/ConfigWidget.cpp @@ -22,8 +22,13 @@ ConfigWidget::ConfigWidget(ComManager *comMan, QWidget *parent) : connectSignals(); ui->lstSections->setCurrentRow(0); + ui->tabSectionsWidget->setCurrentIndex(0); ui->lstSections->setItemDelegate(new IconMenuDelegate(ui->lstSections->gridSize().height(), this)); + // Logs + ui->logsWdg->setComManager(comMan); + ui->logsWdg->setViewMode(LogViewWidget::VIEW_LOGS_ALL); + } ConfigWidget::~ConfigWidget() @@ -58,7 +63,6 @@ void ConfigWidget::setupSections() addSection(tr("Services"), QIcon(TeraData::getIconFilenameForDataType(TERADATA_SERVICE)), TERADATA_SERVICE); } - //ui->lstSections->setItemSelected(ui->lstSections->item(0),true); //ui->lstSections->item(0)->setSelected(true); } @@ -106,3 +110,11 @@ void ConfigWidget::connectSignals() //connect(ui->btnClose, &QPushButton::clicked, this, &ConfigWidget::closeRequest); } + +void ConfigWidget::on_tabSectionsWidget_currentChanged(int index) +{ + if (ui->tabSectionsWidget->currentWidget() == ui->tabLogs){ + ui->logsWdg->refreshData(); + } +} + diff --git a/client/src/widgets/ConfigWidget.h b/client/src/widgets/ConfigWidget.h index 5c64b7a2..82000451 100644 --- a/client/src/widgets/ConfigWidget.h +++ b/client/src/widgets/ConfigWidget.h @@ -32,6 +32,8 @@ private slots: void com_Waiting(bool waiting); void com_NetworkError(QNetworkReply::NetworkError error, QString error_str, QNetworkAccessManager::Operation op); + void on_tabSectionsWidget_currentChanged(int index); + private: Ui::ConfigWidget *ui; @@ -39,7 +41,6 @@ private slots: ComManager* m_comManager; DataListWidget* m_dataListEditor; - void connectSignals(); void addSection(const QString& name, const QIcon& icon, const int& id); diff --git a/client/src/widgets/ConfigWidget.ui b/client/src/widgets/ConfigWidget.ui index dd682b39..83dc825e 100644 --- a/client/src/widgets/ConfigWidget.ui +++ b/client/src/widgets/ConfigWidget.ui @@ -115,126 +115,198 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - - - - 0 - 0 - + + + 1 + + + + + :/icons/config.png:/icons/config.png + + + Paramètres + + + + 0 - - - 150 - 16777215 - + + 0 - - QAbstractItemView::NoEditTriggers + + 0 - - false + + 0 - - Qt::IgnoreAction + + 0 - - QAbstractItemView::SelectRows - - - - 48 - 48 - - - - Qt::ElideRight - - - QListView::Static - - - QListView::LeftToRight - - - true - - - QListView::Adjust - - - - 100 - 90 - - - - QListView::IconMode - - - false - - - true - - - true - - - Qt::AlignCenter - - - - - - - true - - - - - 0 - 0 - 555 - 447 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - + + - + - + 0 0 - + + + 150 + 16777215 + + + + QAbstractItemView::NoEditTriggers + + + false + + + Qt::IgnoreAction + + + QAbstractItemView::SelectRows + + + + 48 + 48 + + + + Qt::ElideRight + + + QListView::Static + + + QListView::LeftToRight + + + true + + + QListView::Adjust + + + + 100 + 90 + + + + QListView::IconMode + + + true + + + true + + + true + + + Qt::AlignCenter + + + + + + + true + + + + + 0 + 0 + 551 + 417 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + - - - - + + + + + + + :/icons/log_icon.png:/icons/log_icon.png + + + Journaux + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + - + + + LogViewWidget + QWidget +

widgets/LogViewWidget.h
+ 1 + + + + + diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp new file mode 100644 index 00000000..4decd3b3 --- /dev/null +++ b/client/src/widgets/LogViewWidget.cpp @@ -0,0 +1,235 @@ +#include "LogViewWidget.h" +#include "ui_LogViewWidget.h" + +#include + +LogViewWidget::LogViewWidget(QWidget *parent): + QWidget(parent), + ui(new Ui::LogViewWidget) +{ + ui->setupUi(this); + ui->cmbLevel->setItemDelegate(new QStyledItemDelegate(ui->cmbLevel)); + m_currentMode = ViewMode::VIEW_LOGS_NONE; + m_currentUuid = QString(); + + ui->dateEndDate->setDate(QDate::currentDate()); + ui->dateStartDate->setDate(QDate::currentDate().addMonths(-6)); + + // Init levels combo box + int level_id = LogEvent::LogLevel_MIN; + QStringList names = getLogLevelNames(); + for(const QString &name:qAsConst(names)){ + ui->cmbLevel->addItem(name, level_id++); + if (level_id == LogEvent::LOGLEVEL_INFO){ + ui->cmbLevel->setCurrentIndex(ui->cmbLevel->count()-1); + } + } +} + +LogViewWidget::LogViewWidget(ComManager* comMan, QWidget *parent) : + LogViewWidget::LogViewWidget(parent) +{ + setComManager(comMan); + + +} + +LogViewWidget::~LogViewWidget() +{ + delete ui; +} + +void LogViewWidget::setComManager(ComManager *comMan) +{ + m_comManager = comMan; + connectSignals(); +} + +void LogViewWidget::setViewMode(const ViewMode &mode, const QString &uuid, const bool &autoload) +{ + m_currentMode = mode; + m_currentUuid = uuid; + + if (autoload){ + refreshData(); + } + +} + +void LogViewWidget::refreshData() +{ + switch(m_currentMode){ + case VIEW_LOGINS_ALL: + queryAllLogins(); + break; + case VIEW_LOGINS_DEVICE: + queryLoginsForDevice(); + break; + case VIEW_LOGINS_PARTICIPANT: + queryLoginsForParticipant(); + break; + case VIEW_LOGINS_USER: + queryLoginsForUser(); + break; + case VIEW_LOGS_ALL: + queryAllLogs(); + break; + case VIEW_LOGS_NONE: + LOG_WARNING("No view mode selected!", "LogViewWidget"); + break; + } +} + +void LogViewWidget::queryLoginsForUser() +{ + if (m_currentUuid.isEmpty()){ + LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForUser"); + return; + } + + QUrlQuery args; + args.addQueryItem(WEB_QUERY_UUID_USER, m_currentUuid); + + if (m_comManager){ + m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); + }else{ + LOG_WARNING("No ComManager was set!", "LogViewWidget"); + } +} + +void LogViewWidget::queryLoginsForParticipant() +{ + if (m_currentUuid.isEmpty()){ + LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForParticipant"); + return; + } + QUrlQuery args; + args.addQueryItem(WEB_QUERY_UUID_PARTICIPANT, m_currentUuid); + + if (m_comManager){ + m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); + }else{ + LOG_WARNING("No ComManager was set!", "LogViewWidget"); + } +} + +void LogViewWidget::queryLoginsForDevice() +{ + if (m_currentUuid.isEmpty()){ + LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForDevice"); + return; + } + QUrlQuery args; + args.addQueryItem(WEB_QUERY_UUID_DEVICE, m_currentUuid); + + if (m_comManager){ + m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); + }else{ + LOG_WARNING("No ComManager was set!", "LogViewWidget"); + } +} + +void LogViewWidget::queryAllLogins() +{ + if (m_comManager){ + m_comManager->doGet(WEB_LOGS_LOGINS_PATH); + }else{ + LOG_WARNING("No ComManager was set!", "LogViewWidget"); + } +} + +void LogViewWidget::queryAllLogs() +{ + if (m_comManager){ + m_comManager->doGet(WEB_LOGS_LOGS_PATH); + }else{ + LOG_WARNING("No ComManager was set!", "LogViewWidget"); + } +} + +void LogViewWidget::connectSignals() +{ + if (m_comManager){ + connect(m_comManager, &ComManager::logsLoginReceived, this, &LogViewWidget::processLogsLogins); + connect(m_comManager, &ComManager::logsLogsReceived, this, &LogViewWidget::processLogsLogs); + } +} + +QStringList LogViewWidget::getLogLevelNames() +{ + QStringList names; + for(int i=LogEvent::LogLevel_MIN; i(i))); + } + return names; +} + +QString LogViewWidget::getLogLevelName(const LogEvent::LogLevel &level) +{ + switch(level){ + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_UNKNOWN: + return tr("Inconnu"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_TRACE: + return tr("Trace"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_DEBUG: + return tr("Debug"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_INFO: + return tr("Information"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_WARNING: + return tr("Avertissement"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_CRITICAL: + return tr("Critique"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_ERROR: + return tr("Erreur"); + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_FATAL: + return tr("Fatal"); + break; + default: + return tr("Inconnu"); + } +} + +void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_data) +{ + +} + +void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data) +{ + ui->tableLogs->clearContents(); + ui->tableLogs->setRowCount(logins.count()); + + int row = 0; + for(const TeraData &login:logins){ + QTableWidgetItem* item = new QTableWidgetItem(); + QDateTime log_date = QDateTime::fromMSecsSinceEpoch(login.getFieldValue("timestamp").toULongLong()); + item->setText(log_date.toString("dd-MM-yyyy")); + ui->tableLogs->setItem(row, 0, item); + + item = new QTableWidgetItem(); + item->setText(log_date.toString("hh:mm:ss.zzz")); + ui->tableLogs->setItem(row, 1, item); + + item = new QTableWidgetItem(); + item->setText(getLogLevelName(static_cast(login.getFieldValue("level").toInt()))); + ui->tableLogs->setItem(row, 2, item); + + item = new QTableWidgetItem(); + item->setText(login.getFieldValue("message").toString()); + ui->tableLogs->setItem(row, 3, item); + row++; + } +} + +void LogViewWidget::on_btnFilter_clicked() +{ + refreshData(); +} + diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h new file mode 100644 index 00000000..515553db --- /dev/null +++ b/client/src/widgets/LogViewWidget.h @@ -0,0 +1,59 @@ +#ifndef LOGVIEWWIDGET_H +#define LOGVIEWWIDGET_H + +#include +#include "managers/ComManager.h" + +#include "LogEvent.pb.h" + +namespace Ui { +class LogViewWidget; +} + +class LogViewWidget : public QWidget +{ + Q_OBJECT + +public: + enum ViewMode{ + VIEW_LOGS_NONE = 0, + VIEW_LOGS_ALL, + VIEW_LOGINS_ALL, + VIEW_LOGINS_USER, + VIEW_LOGINS_PARTICIPANT, + VIEW_LOGINS_DEVICE + }; + + explicit LogViewWidget(QWidget *parent = nullptr); + explicit LogViewWidget(ComManager* comMan = nullptr, QWidget *parent = nullptr); + ~LogViewWidget(); + + void setComManager(ComManager* comMan); + void setViewMode(const ViewMode &mode, const QString& uuid = QString(), const bool& autoload=false); + + void refreshData(); + +private: + Ui::LogViewWidget* ui; + ComManager* m_comManager; + ViewMode m_currentMode; + QString m_currentUuid; + + void connectSignals(); + + QStringList getLogLevelNames(); + QString getLogLevelName(const LogEvent::LogLevel &level); + + void queryLoginsForUser(); + void queryLoginsForParticipant(); + void queryLoginsForDevice(); + void queryAllLogins(); + void queryAllLogs(); + +private slots: + void processLogsLogins(QList logins, QUrlQuery reply_data); + void processLogsLogs(QList logins, QUrlQuery reply_data); + void on_btnFilter_clicked(); +}; + +#endif // LOGVIEWWIDGET_H diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui new file mode 100644 index 00000000..cd0c9066 --- /dev/null +++ b/client/src/widgets/LogViewWidget.ui @@ -0,0 +1,231 @@ + + + LogViewWidget + + + + 0 + 0 + 762 + 525 + + + + Form + + + + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + Début + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 100 + 0 + + + + true + + + dd-MM-yyyy + + + true + + + Qt::LocalTime + + + + 2020 + 10 + 27 + + + + + + + + + 0 + 0 + + + + Fin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 100 + 0 + + + + true + + + false + + + + 0 + 0 + 0 + 2020 + 9 + 14 + + + + dd-MM-yyyy + + + true + + + Qt::LocalTime + + + + 2022 + 12 + 31 + + + + + + + + + 0 + 0 + + + + Type + + + + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + + + + + 100 + 32 + + + + PointingHandCursor + + + Filtrer + + + + :/icons/filter.png:/icons/filter.png + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::SelectRows + + + true + + + + Date + + + + + Heure + + + + + Type + + + + + Message + + + + + + + + + + + diff --git a/shared/src/WebAPI.h b/shared/src/WebAPI.h index 2e3e5229..bb2c55ee 100755 --- a/shared/src/WebAPI.h +++ b/shared/src/WebAPI.h @@ -49,6 +49,10 @@ #define WEB_VERSIONSINFO_PATH "/api/user/versions" #define WEB_FORMS_PATH "/api/user/forms" + +#define WEB_LOGS_LOGINS_PATH "/log/api/logging/login_entries" +#define WEB_LOGS_LOGS_PATH "/log/api/logging/log_entries" + #define WEB_FORMS_QUERY_USER "type=user" #define WEB_FORMS_QUERY_SITE "type=site" #define WEB_FORMS_QUERY_DEVICE "type=device" @@ -84,6 +88,8 @@ #define WEB_QUERY_UUID_PARTICIPANT "participant_uuid" #define WEB_QUERY_UUID_ASSET "asset_uuid" +#define WEB_QUERY_UUID_USER "user_uuid" +#define WEB_QUERY_UUID_DEVICE "device_uuid" #define WEB_QUERY_UUID "uuid" #define WEB_QUERY_APPTAG "app_tag" diff --git a/shared/src/data/TeraData.cpp b/shared/src/data/TeraData.cpp index 7a8e69c5..86b7a52c 100644 --- a/shared/src/data/TeraData.cpp +++ b/shared/src/data/TeraData.cpp @@ -301,6 +301,10 @@ QString TeraData::getDataTypeName(const TeraDataTypes &data_type) return "asset"; case TERADATA_TEST: return "test"; + case TERADATA_LOG_LOGIN: + return "log_login"; + case TERADATA_LOG_LOG: + return "log_log"; } return ""; @@ -381,6 +385,10 @@ QString TeraData::getDataTypeNameText(const TeraDataTypes &data_type) return tr("Donnée"); case TERADATA_TEST: return tr("Évaluation"); + case TERADATA_LOG_LOGIN: + return tr("Journal: Connexion"); + case TERADATA_LOG_LOG: + return tr("Journal: Général"); } return ""; @@ -423,6 +431,8 @@ TeraDataTypes TeraData::getDataTypeFromPath(const QString &path) if (path==WEB_TESTTYPEPROJECT_PATH) return TERADATA_TESTTYPEPROJECT; if (path==WEB_TESTTYPESITE_PATH) return TERADATA_TESTTYPESITE; if (path==WEB_TESTINFO_PATH) return TERADATA_TEST; + if (path==WEB_LOGS_LOGINS_PATH) return TERADATA_LOG_LOGIN; + if (path==WEB_LOGS_LOGS_PATH) return TERADATA_LOG_LOG; LOG_ERROR("Unknown data type for path: " + path, "TeraData::getDataTypeFromPath"); @@ -450,6 +460,8 @@ QString TeraData::getPathForDataType(const TeraDataTypes &data_type) if (data_type==TERADATA_ASSET) return WEB_ASSETINFO_PATH; if (data_type==TERADATA_TESTTYPE) return WEB_TESTTYPEINFO_PATH; if (data_type==TERADATA_TEST) return WEB_TESTINFO_PATH; + if (data_type==TERADATA_LOG_LOGIN) return WEB_LOGS_LOGINS_PATH; + if (data_type==TERADATA_LOG_LOG) return WEB_LOGS_LOGS_PATH; LOG_ERROR("Unknown path for data_type: " + getDataTypeName(data_type), "TeraData::getPathForDataType"); @@ -493,6 +505,9 @@ QString TeraData::getIconFilenameForDataType(const TeraDataTypes &data_type) return "://icons/service.png"; case TERADATA_ASSET: return "://icons/data.png"; + case TERADATA_LOG_LOGIN: + case TERADATA_LOG_LOG: + return "://icons/log_icon.png"; default: return "://icons/error.png"; } diff --git a/shared/src/data/TeraData.h b/shared/src/data/TeraData.h index 695276ba..538a5d6c 100644 --- a/shared/src/data/TeraData.h +++ b/shared/src/data/TeraData.h @@ -48,7 +48,9 @@ enum TeraDataTypes { TERADATA_ONLINE_PARTICIPANT, TERADATA_ONLINE_DEVICE, TERADATA_ASSET, - TERADATA_TEST + TERADATA_TEST, + TERADATA_LOG_LOG, + TERADATA_LOG_LOGIN }; Q_DECLARE_METATYPE(TeraDataTypes) From a5f1246b8f38b657e3081b2259bb812b63887ffc Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 23 Nov 2022 14:33:52 -0500 Subject: [PATCH 04/42] Refs #80. Working general logs viewer (in admin section) --- client/src/dialogs/BaseDialog.ui | 3 + client/src/widgets/LogViewWidget.cpp | 178 +++++++++++++++------------ client/src/widgets/LogViewWidget.h | 13 +- client/src/widgets/LogViewWidget.ui | 130 ++++++++++++++++--- shared/src/WebAPI.h | 4 + 5 files changed, 227 insertions(+), 101 deletions(-) diff --git a/client/src/dialogs/BaseDialog.ui b/client/src/dialogs/BaseDialog.ui index 9a889cec..bd1739db 100644 --- a/client/src/dialogs/BaseDialog.ui +++ b/client/src/dialogs/BaseDialog.ui @@ -71,6 +71,9 @@ Fermer + + false + diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 4decd3b3..106e0698 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -11,9 +11,8 @@ LogViewWidget::LogViewWidget(QWidget *parent): ui->cmbLevel->setItemDelegate(new QStyledItemDelegate(ui->cmbLevel)); m_currentMode = ViewMode::VIEW_LOGS_NONE; m_currentUuid = QString(); - - ui->dateEndDate->setDate(QDate::currentDate()); - ui->dateStartDate->setDate(QDate::currentDate().addMonths(-6)); + m_maxCount = 50; // 50 items at a time by default + m_filtering = false; // Init levels combo box int level_id = LogEvent::LogLevel_MIN; @@ -51,100 +50,60 @@ void LogViewWidget::setViewMode(const ViewMode &mode, const QString &uuid, const m_currentUuid = uuid; if (autoload){ - refreshData(); + refreshData(true); } } -void LogViewWidget::refreshData() +void LogViewWidget::refreshData(const bool &stats_only) { + QUrlQuery args; + if (stats_only) + args.addQueryItem(WEB_QUERY_STATS, "1"); + else{ + // Set limiters and offset + int offset = (ui->spinPage->value()-1) * m_maxCount; + args.addQueryItem(WEB_QUERY_OFFSET, QString::number(offset)); + args.addQueryItem(WEB_QUERY_LIMIT, QString::number(m_maxCount)); + } + + if (m_filtering){ + // Set start and end date + args.addQueryItem(WEB_QUERY_START_DATE, ui->dateStartDate->date().toString(Qt::ISODate)); + args.addQueryItem(WEB_QUERY_END_DATE, ui->dateEndDate->date().toString(Qt::ISODate)); + // Log level + args.addQueryItem(WEB_QUERY_LOG_LEVEL, QString::number(ui->cmbLevel->currentIndex())); + } + + // Set paths and UUIDs if required + QString path; switch(m_currentMode){ case VIEW_LOGINS_ALL: - queryAllLogins(); + path = WEB_LOGS_LOGINS_PATH; break; case VIEW_LOGINS_DEVICE: - queryLoginsForDevice(); + path = WEB_LOGS_LOGINS_PATH; + args.addQueryItem(WEB_QUERY_UUID_DEVICE, m_currentUuid); break; case VIEW_LOGINS_PARTICIPANT: - queryLoginsForParticipant(); + path = WEB_LOGS_LOGINS_PATH; + args.addQueryItem(WEB_QUERY_UUID_PARTICIPANT, m_currentUuid); break; case VIEW_LOGINS_USER: - queryLoginsForUser(); + path = WEB_LOGS_LOGINS_PATH; + args.addQueryItem(WEB_QUERY_UUID_USER, m_currentUuid); break; case VIEW_LOGS_ALL: - queryAllLogs(); + path = WEB_LOGS_LOGS_PATH; break; case VIEW_LOGS_NONE: LOG_WARNING("No view mode selected!", "LogViewWidget"); - break; - } -} - -void LogViewWidget::queryLoginsForUser() -{ - if (m_currentUuid.isEmpty()){ - LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForUser"); return; + break; } + setEnabled(false); + m_comManager->doGet(path, args); - QUrlQuery args; - args.addQueryItem(WEB_QUERY_UUID_USER, m_currentUuid); - - if (m_comManager){ - m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); - }else{ - LOG_WARNING("No ComManager was set!", "LogViewWidget"); - } -} - -void LogViewWidget::queryLoginsForParticipant() -{ - if (m_currentUuid.isEmpty()){ - LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForParticipant"); - return; - } - QUrlQuery args; - args.addQueryItem(WEB_QUERY_UUID_PARTICIPANT, m_currentUuid); - - if (m_comManager){ - m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); - }else{ - LOG_WARNING("No ComManager was set!", "LogViewWidget"); - } -} - -void LogViewWidget::queryLoginsForDevice() -{ - if (m_currentUuid.isEmpty()){ - LOG_WARNING("No UUID was set!", "LogViewWidget::queryLoginsForDevice"); - return; - } - QUrlQuery args; - args.addQueryItem(WEB_QUERY_UUID_DEVICE, m_currentUuid); - - if (m_comManager){ - m_comManager->doGet(WEB_LOGS_LOGINS_PATH, args); - }else{ - LOG_WARNING("No ComManager was set!", "LogViewWidget"); - } -} - -void LogViewWidget::queryAllLogins() -{ - if (m_comManager){ - m_comManager->doGet(WEB_LOGS_LOGINS_PATH); - }else{ - LOG_WARNING("No ComManager was set!", "LogViewWidget"); - } -} - -void LogViewWidget::queryAllLogs() -{ - if (m_comManager){ - m_comManager->doGet(WEB_LOGS_LOGS_PATH); - }else{ - LOG_WARNING("No ComManager was set!", "LogViewWidget"); - } } void LogViewWidget::connectSignals() @@ -196,20 +155,51 @@ QString LogViewWidget::getLogLevelName(const LogEvent::LogLevel &level) } } -void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_data) +void LogViewWidget::updateNavButtons() { + ui->btnPrevPage->setEnabled(ui->spinPage->value() > ui->spinPage->minimum()); + ui->btnNextPage->setEnabled(ui->spinPage->value() < ui->spinPage->maximum()); +} +void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_data) +{ + setEnabled(true); } void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data) { + + if (reply_data.hasQueryItem(WEB_QUERY_STATS)){ + // We received stats, update display + if (!logins.empty()){ + int count = logins.first().getFieldValue("count").toInt(); + int page_count = count / m_maxCount + 1; + ui->lblEntriesCount->setText(QString::number(count)); + ui->frameNav->setVisible(count > m_maxCount); + ui->spinPage->setValue(1); + ui->spinPage->setMaximum(page_count); + ui->lblTotalPages->setText("/" + QString::number(page_count)); + ui->btnPrevPage->setDisabled(true); + ui->btnNextPage->setEnabled(page_count>1); + + if (ui->dateStartDate->date().year() <= 2000) + ui->dateStartDate->setDate(logins.first().getFieldValue("min_timestamp").toDate()); + if (ui->dateEndDate->date().year() <= 2000) + ui->dateEndDate->setDate(logins.first().getFieldValue("max_timestamp").toDate()); + refreshData(false); + return; + }else{ + LOG_WARNING("Expected log stats. Received empty reply", "LogViewWidget::processLogsLogs"); + } + } + setEnabled(true); ui->tableLogs->clearContents(); ui->tableLogs->setRowCount(logins.count()); int row = 0; for(const TeraData &login:logins){ QTableWidgetItem* item = new QTableWidgetItem(); - QDateTime log_date = QDateTime::fromMSecsSinceEpoch(login.getFieldValue("timestamp").toULongLong()); + QDateTime log_date = login.getFieldValue("timestamp").toDateTime().toLocalTime(); item->setText(log_date.toString("dd-MM-yyyy")); ui->tableLogs->setItem(row, 0, item); @@ -218,7 +208,7 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data ui->tableLogs->setItem(row, 1, item); item = new QTableWidgetItem(); - item->setText(getLogLevelName(static_cast(login.getFieldValue("level").toInt()))); + item->setText(getLogLevelName(static_cast(login.getFieldValue("log_level").toInt()))); ui->tableLogs->setItem(row, 2, item); item = new QTableWidgetItem(); @@ -230,6 +220,34 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data void LogViewWidget::on_btnFilter_clicked() { - refreshData(); + m_filtering = true; + refreshData(); +} + + +void LogViewWidget::on_btnNextPage_clicked() +{ + if (ui->spinPage->value() + 1 <= ui->spinPage->maximum()){ + ui->spinPage->setValue(ui->spinPage->value() + 1); + updateNavButtons(); + refreshData(false); + } +} + + +void LogViewWidget::on_btnPrevPage_clicked() +{ + if (ui->spinPage->value() - 1 >= ui->spinPage->minimum()){ + ui->spinPage->setValue(ui->spinPage->value() - 1); + updateNavButtons(); + refreshData(false); + } +} + + +void LogViewWidget::on_spinPage_editingFinished() +{ + updateNavButtons(); + refreshData(false); } diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index 515553db..dbd6ba78 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -31,29 +31,30 @@ class LogViewWidget : public QWidget void setComManager(ComManager* comMan); void setViewMode(const ViewMode &mode, const QString& uuid = QString(), const bool& autoload=false); - void refreshData(); + void refreshData(const bool &stats_only = true); private: Ui::LogViewWidget* ui; ComManager* m_comManager; ViewMode m_currentMode; QString m_currentUuid; + int m_maxCount; + bool m_filtering; // Applying selected filters void connectSignals(); QStringList getLogLevelNames(); QString getLogLevelName(const LogEvent::LogLevel &level); - void queryLoginsForUser(); - void queryLoginsForParticipant(); - void queryLoginsForDevice(); - void queryAllLogins(); - void queryAllLogs(); + void updateNavButtons(); private slots: void processLogsLogins(QList logins, QUrlQuery reply_data); void processLogsLogs(QList logins, QUrlQuery reply_data); void on_btnFilter_clicked(); + void on_btnNextPage_clicked(); + void on_btnPrevPage_clicked(); + void on_spinPage_editingFinished(); }; #endif // LOGVIEWWIDGET_H diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui index cd0c9066..0380fa9e 100644 --- a/client/src/widgets/LogViewWidget.ui +++ b/client/src/widgets/LogViewWidget.ui @@ -11,7 +11,7 @@ - Form + Log Viewer @@ -57,9 +57,9 @@ - 2020 - 10 - 27 + 1999 + 12 + 31 @@ -94,16 +94,6 @@ false - - - 0 - 0 - 0 - 2020 - 9 - 14 - - dd-MM-yyyy @@ -115,7 +105,7 @@ - 2022 + 1999 12 31 @@ -222,6 +212,116 @@ + + + + + + + + true + + + + xxx + + + + + + + événements + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + <<< + + + + :/controls/branch_left.png:/controls/branch_left.png + + + + 20 + 20 + + + + + + + + + 50 + 0 + + + + Qt::AlignCenter + + + QAbstractSpinBox::NoButtons + + + true + + + 1 + + + QAbstractSpinBox::DefaultStepType + + + + + + + / 1 + + + + + + + PointingHandCursor + + + >>> + + + + :/controls/branch_closed.png:/controls/branch_closed.png + + + + 20 + 20 + + + + + + + diff --git a/shared/src/WebAPI.h b/shared/src/WebAPI.h index bb2c55ee..dc7c5bb0 100755 --- a/shared/src/WebAPI.h +++ b/shared/src/WebAPI.h @@ -107,6 +107,10 @@ #define WEB_QUERY_DOWNLOAD "download" #define WEB_QUERY_ENABLED "enabled" #define WEB_QUERY_FULL "full" +#define WEB_QUERY_STATS "stats" +#define WEB_QUERY_START_DATE "start_date" +#define WEB_QUERY_END_DATE "end_date" +#define WEB_QUERY_LOG_LEVEL "log_level" #define WEB_QUERY_WITH_USERGROUPS "with_usergroups" #define WEB_QUERY_WITH_SITES "with_sites" From 4f12111c7125287fa695dd082dd354ad186feb6f Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 23 Nov 2022 15:30:33 -0500 Subject: [PATCH 05/42] Refs #80. Added refresh button for logs viewer --- client/src/widgets/LogViewWidget.cpp | 6 ++++++ client/src/widgets/LogViewWidget.h | 1 + client/src/widgets/LogViewWidget.ui | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 106e0698..728692d8 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -251,3 +251,9 @@ void LogViewWidget::on_spinPage_editingFinished() refreshData(false); } + +void LogViewWidget::on_btnRefresh_clicked() +{ + refreshData(false); +} + diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index dbd6ba78..2ba1f45a 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -55,6 +55,7 @@ private slots: void on_btnNextPage_clicked(); void on_btnPrevPage_clicked(); void on_spinPage_editingFinished(); + void on_btnRefresh_clicked(); }; #endif // LOGVIEWWIDGET_H diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui index 0380fa9e..87201146 100644 --- a/client/src/widgets/LogViewWidget.ui +++ b/client/src/widgets/LogViewWidget.ui @@ -180,6 +180,32 @@ + + + + + 0 + 32 + + + + PointingHandCursor + + + Rafraichir + + + + :/icons/refresh.png:/icons/refresh.png + + + + 24 + 24 + + + + From b0fec1059e5e5a5b1983483b2b01c0c6fc3124ce Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 24 Nov 2022 13:01:08 -0500 Subject: [PATCH 06/42] Refs #80. Improved UI behaviour. --- client/src/widgets/ConfigWidget.ui | 2 +- client/src/widgets/LogViewWidget.cpp | 93 ++++++++++++++++++++++++---- client/src/widgets/LogViewWidget.h | 5 ++ client/src/widgets/LogViewWidget.ui | 6 ++ 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/client/src/widgets/ConfigWidget.ui b/client/src/widgets/ConfigWidget.ui index 83dc825e..ee0e2759 100644 --- a/client/src/widgets/ConfigWidget.ui +++ b/client/src/widgets/ConfigWidget.ui @@ -270,7 +270,7 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, :/icons/log_icon.png:/icons/log_icon.png - Journaux + Événements diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 728692d8..3302a9ab 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -19,10 +19,14 @@ LogViewWidget::LogViewWidget(QWidget *parent): QStringList names = getLogLevelNames(); for(const QString &name:qAsConst(names)){ ui->cmbLevel->addItem(name, level_id++); - if (level_id == LogEvent::LOGLEVEL_INFO){ + /*if (level_id == LogEvent::LOGLEVEL_INFO){ ui->cmbLevel->setCurrentIndex(ui->cmbLevel->count()-1); - } + }*/ } + ui->cmbLevel->setProperty("last_value", 0); + ui->dateEndDate->setProperty("last_value", ui->dateEndDate->date()); + ui->dateStartDate->setProperty("last_value", ui->dateStartDate->date()); + ui->cmbLevel->setCurrentIndex(0); } LogViewWidget::LogViewWidget(ComManager* comMan, QWidget *parent) : @@ -69,10 +73,10 @@ void LogViewWidget::refreshData(const bool &stats_only) if (m_filtering){ // Set start and end date - args.addQueryItem(WEB_QUERY_START_DATE, ui->dateStartDate->date().toString(Qt::ISODate)); - args.addQueryItem(WEB_QUERY_END_DATE, ui->dateEndDate->date().toString(Qt::ISODate)); + args.addQueryItem(WEB_QUERY_START_DATE, ui->dateStartDate->property("last_value").toDate().toString(Qt::ISODate)); + args.addQueryItem(WEB_QUERY_END_DATE, ui->dateEndDate->property("last_value").toDateTime().addSecs(23*3600 + 59*60 + 59).addMSecs(999).toString(Qt::ISODate)); // At 23:59:59 // Log level - args.addQueryItem(WEB_QUERY_LOG_LEVEL, QString::number(ui->cmbLevel->currentIndex())); + args.addQueryItem(WEB_QUERY_LOG_LEVEL, QString::number(ui->cmbLevel->itemData(ui->cmbLevel->property("last_value").toInt()).toInt())); } // Set paths and UUIDs if required @@ -127,7 +131,7 @@ QString LogViewWidget::getLogLevelName(const LogEvent::LogLevel &level) { switch(level){ case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_UNKNOWN: - return tr("Inconnu"); + return tr("Tous"); break; case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_TRACE: return tr("Trace"); @@ -155,12 +159,47 @@ QString LogViewWidget::getLogLevelName(const LogEvent::LogLevel &level) } } +QString LogViewWidget::getLogLevelIcon(const LogEvent::LogLevel &level) +{ + switch(level){ + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_UNKNOWN: + return ""; + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_TRACE: + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_DEBUG: + return "://icons/search.png"; + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_INFO: + return "://icons/info.png"; + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_WARNING: + return "://status/warning.png"; + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_FATAL: + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_CRITICAL: + return "://icons/error2.png"; + break; + case opentera::protobuf::LogEvent_LogLevel_LOGLEVEL_ERROR: + return "://icons/error.png"; + break; + default: + return ""; + } +} + void LogViewWidget::updateNavButtons() { ui->btnPrevPage->setEnabled(ui->spinPage->value() > ui->spinPage->minimum()); ui->btnNextPage->setEnabled(ui->spinPage->value() < ui->spinPage->maximum()); } +void LogViewWidget::updateFilterButton() +{ + ui->btnFilter->setEnabled(ui->cmbLevel->property("last_value").toInt() != ui->cmbLevel->currentIndex() + || ui->dateStartDate->property("last_value").toDate() != ui->dateStartDate->date() + || ui->dateEndDate->property("last_value").toDate() != ui->dateEndDate->date()); +} + void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_data) { setEnabled(true); @@ -182,10 +221,15 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data ui->btnPrevPage->setDisabled(true); ui->btnNextPage->setEnabled(page_count>1); - if (ui->dateStartDate->date().year() <= 2000) - ui->dateStartDate->setDate(logins.first().getFieldValue("min_timestamp").toDate()); - if (ui->dateEndDate->date().year() <= 2000) - ui->dateEndDate->setDate(logins.first().getFieldValue("max_timestamp").toDate()); + QDate min_date = logins.first().getFieldValue("min_timestamp").toDate(); + QDate max_date = logins.first().getFieldValue("max_timestamp").toDate(); + + if (ui->dateStartDate->date().year() <= 2000){ + ui->dateStartDate->setProperty("last_value", min_date); + ui->dateStartDate->setDate(min_date); + ui->dateEndDate->setProperty("last_value", max_date); + ui->dateEndDate->setDate(max_date); + } refreshData(false); return; }else{ @@ -208,7 +252,9 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data ui->tableLogs->setItem(row, 1, item); item = new QTableWidgetItem(); - item->setText(getLogLevelName(static_cast(login.getFieldValue("log_level").toInt()))); + LogEvent::LogLevel level = static_cast(login.getFieldValue("log_level").toInt()); + item->setText(getLogLevelName(level)); + item->setIcon(QIcon(getLogLevelIcon(level))); ui->tableLogs->setItem(row, 2, item); item = new QTableWidgetItem(); @@ -221,6 +267,10 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data void LogViewWidget::on_btnFilter_clicked() { m_filtering = true; + ui->cmbLevel->setProperty("last_value", ui->cmbLevel->currentIndex()); + ui->dateEndDate->setProperty("last_value", ui->dateEndDate->date()); + ui->dateStartDate->setProperty("last_value", ui->dateStartDate->date()); + updateFilterButton(); refreshData(); } @@ -244,16 +294,33 @@ void LogViewWidget::on_btnPrevPage_clicked() } } - void LogViewWidget::on_spinPage_editingFinished() { updateNavButtons(); refreshData(false); } - void LogViewWidget::on_btnRefresh_clicked() { refreshData(false); } + +void LogViewWidget::on_cmbLevel_currentIndexChanged(int index) +{ + updateFilterButton(); +} + + +void LogViewWidget::on_dateStartDate_dateChanged(const QDate &date) +{ + ui->dateEndDate->setMinimumDate(date); + updateFilterButton(); +} + + +void LogViewWidget::on_dateEndDate_dateChanged(const QDate &date) +{ + updateFilterButton(); +} + diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index 2ba1f45a..9914b880 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -45,8 +45,10 @@ class LogViewWidget : public QWidget QStringList getLogLevelNames(); QString getLogLevelName(const LogEvent::LogLevel &level); + QString getLogLevelIcon(const LogEvent::LogLevel &level); void updateNavButtons(); + void updateFilterButton(); private slots: void processLogsLogins(QList logins, QUrlQuery reply_data); @@ -56,6 +58,9 @@ private slots: void on_btnPrevPage_clicked(); void on_spinPage_editingFinished(); void on_btnRefresh_clicked(); + void on_cmbLevel_currentIndexChanged(int index); + void on_dateStartDate_dateChanged(const QDate &date); + void on_dateEndDate_dateChanged(const QDate &date); }; #endif // LOGVIEWWIDGET_H diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui index 87201146..eef00d93 100644 --- a/client/src/widgets/LogViewWidget.ui +++ b/client/src/widgets/LogViewWidget.ui @@ -210,6 +210,12 @@ + + QAbstractItemView::NoEditTriggers + + + false + QAbstractItemView::SelectRows From 703e908072ef72b87ceadb656d8e9021ecfd5f0a Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Fri, 25 Nov 2022 11:02:32 -0500 Subject: [PATCH 07/42] Refs #80. Work in progress to display login logs. --- client/resources/TeraClient.qrc | 12 + client/resources/icons.txt | 3 + .../resources/icons/logs/browser_chrome.png | Bin 0 -> 20763 bytes .../resources/icons/logs/browser_firefox.png | Bin 0 -> 121303 bytes .../resources/icons/logs/browser_safari.png | Bin 0 -> 58672 bytes client/resources/icons/logs/certificate.png | Bin 0 -> 31477 bytes client/resources/icons/logs/os_android.png | Bin 0 -> 6064 bytes client/resources/icons/logs/os_ios.png | Bin 0 -> 20413 bytes client/resources/icons/logs/os_linux.png | Bin 0 -> 51038 bytes client/resources/icons/logs/os_mac.png | Bin 0 -> 13046 bytes client/resources/icons/logs/os_windows.png | Bin 0 -> 6043 bytes client/resources/icons/logs/password.png | Bin 0 -> 55208 bytes client/resources/icons/logs/token.png | Bin 0 -> 47232 bytes client/resources/icons/question.png | Bin 0 -> 14032 bytes .../resources/translations/openteraplus_en.ts | 155 +++++++-- .../resources/translations/openteraplus_fr.ts | 151 ++++++-- client/src/widgets/ConfigWidget.cpp | 6 + client/src/widgets/ConfigWidget.ui | 39 ++- client/src/widgets/LogViewWidget.cpp | 323 ++++++++++++++++-- client/src/widgets/LogViewWidget.h | 14 + 20 files changed, 604 insertions(+), 99 deletions(-) create mode 100644 client/resources/icons.txt create mode 100644 client/resources/icons/logs/browser_chrome.png create mode 100644 client/resources/icons/logs/browser_firefox.png create mode 100644 client/resources/icons/logs/browser_safari.png create mode 100644 client/resources/icons/logs/certificate.png create mode 100644 client/resources/icons/logs/os_android.png create mode 100644 client/resources/icons/logs/os_ios.png create mode 100644 client/resources/icons/logs/os_linux.png create mode 100644 client/resources/icons/logs/os_mac.png create mode 100644 client/resources/icons/logs/os_windows.png create mode 100644 client/resources/icons/logs/password.png create mode 100644 client/resources/icons/logs/token.png create mode 100644 client/resources/icons/question.png diff --git a/client/resources/TeraClient.qrc b/client/resources/TeraClient.qrc index 298a3467..5db3c8d3 100755 --- a/client/resources/TeraClient.qrc +++ b/client/resources/TeraClient.qrc @@ -135,5 +135,17 @@ icons/test_type.png icons/qr.png icons/log_icon.png + icons/logs/certificate.png + icons/logs/password.png + icons/logs/token.png + icons/question.png + icons/logs/browser_chrome.png + icons/logs/browser_firefox.png + icons/logs/browser_safari.png + icons/logs/os_android.png + icons/logs/os_ios.png + icons/logs/os_linux.png + icons/logs/os_mac.png + icons/logs/os_windows.png diff --git a/client/resources/icons.txt b/client/resources/icons.txt new file mode 100644 index 00000000..3d8fa63b --- /dev/null +++ b/client/resources/icons.txt @@ -0,0 +1,3 @@ +Logos icons created by Pixel perfect - Flaticon +Logotype icons created by Freepik - Flaticon +Chrome icons created by Pixel perfect - Flaticon \ No newline at end of file diff --git a/client/resources/icons/logs/browser_chrome.png b/client/resources/icons/logs/browser_chrome.png new file mode 100644 index 0000000000000000000000000000000000000000..cb9b58d988bb2935616818dd82c43861f3f669f0 GIT binary patch literal 20763 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 SdhP>(?^{aXu|^+S?*9RnAW{hc literal 0 HcmV?d00001 diff --git a/client/resources/icons/logs/browser_firefox.png b/client/resources/icons/logs/browser_firefox.png new file mode 100644 index 0000000000000000000000000000000000000000..d6763fa8fa4cc7df31018072d1ab51823def597e GIT binary patch literal 121303 zcmYIP1yod9xIQxsFf>C-45;MP>z+d`@B3hJ-rt}#8D&0@Z2mU!@j!Bv8P&X#zzl*1 ztCJ9nL9DjOMoL5=r0VW8`+Sa$AkIyF zw;GKW)$Z`;{HHj|IBqHX8SI`1O?dAlgvs9!Ho3KSbadnyN{(dsLLz_S2Tsv5xdkEn zzCQ0f&R!Z5IS>_{lODCf6h*ab*y*nGJqFrLEU=@-h|GDA1Q;%j?8QzGEeZUv{~0GJ z06)W>!Md7?F#ItbsQx7KnULIdf}0?&aakVtX9$zg=E`PdSv@Hz83`?#TA^)03o#1M z61tl#5#Cx(V7!GQ!@_!sG517HJk%T*g9SCvr`lbPF#&oc+cgzn;r3gSAP&DZ4)E~s z7_=T;eEtBITsDRTFh9f_ab7D*(+dVcL-6Z2m&cx7>H{dTD~V*N!W?oOFlCHm_P2YY z?g}(E?(`p(`sq+R_v{G&`5Y!M|KHr+(QM6Da`m*WEJCi(#zrMPw1fO*9bHp?pC9&; z8vOe}_R*}$<^YprZ-ko4?_wBXsL{bSrPW7fgAWf6Q%Q4t)5#H< z2SJ1bF7O68@ah*cZpDtZc2`2JRxpCt8IzW_DTEI6PuHUyGz z5L#DGIE(6?*XGMV%Lx*Vz(3u_auzI_$(d>pP_Z@p5B{uS_w$Ia4y1cCJioJG1abRq zI4B#4qrjPSpSn=v@&lx{wpG$^1Bxz|xUeOW*2_!t0&B}-G?#q;EXs5IDvMQ7NLLsP z5;a5>OHj>22O^whAn9@{U#r=>dUSfYMg5;9qQSQFKgmMIW0lrad*g0FIfT&YUv|AC zIbrgff6)KYRWxXC=~4O8@tB+R824-OZ-gWSYUTKf%`uhU^$r< z%~Y(um{Kx(x+c}l)h^ki6d*VR`RC+5+$hd3jQpR;P0&`^ObGC(T|(i(H@I{?x#FJq zck`^I{T?PA{w`c-1aarfG8hE+Oap@2?gkd>?cO0cdCK?JPih2jTw5>8>edM(A2A8)|d4 ziYx^lvlTHrXt_s2*S67dXa5!hyh*2Q;2Z8!gdy z-2d9F{1UPz^GFi+jlrkyW4~4I1}MlAsD|VUK zAq|CAP5hSYX|`Z`9&y(?!TYR-?P?C%N`n6+ratZ5PUq3PWs@OXn`2-1+V@`!zxz}~ zOV+c<_xr7(=sU(+64WZnt4cQT(?+~pW+9ZEq^+*67dL+7D|0_J1*$_N03V~IPgAq8 z54Xhs^@S~pa5Y5u4_1sm^uE3K)o7oW&q_MVRpUSS>U#Efacm`gFqjpS5EK1xWCF=~ zl@`pTaW9&2nGa_2)e+UY6C!}{!ua~rQJ&Y|1JAigd&J8HFl}gQ@%{^DUSO}=s+Mkm z#XsjOmw%pttHQRUx9_8PoG((aYR}qrv!RYW`u}Dky!G`}X}7|*KpufZyk+|CTwk8Q zNW{x+L9fev)w4OgxLMG@zA^TC^n*olJK_+)maISP~g8y)|~to`L} z`=w|NN4pM>y+I(DS1jy7i2aMPm%|<2KfT`l*Q*3cE3%$h^l_NqFQ4U=6fX<3z~*3y zYtzB=xmd+MUXyFTDRW2ZKM{3H&~lz)M-6vh60Th+?NWgL>6>yK49Us$#)glYot9Ch z-qyS4?kLsuKc}QYAA60C^m~^mK}eGrbXzmFjqA*xil)0Dtj$THM%hwr>gPFV{jXb& z>4=_<5~y><{R|lOw1qLeR!+5YG#ZPZ zVpT9Y&j;Atw@icqt}>R|p<(?kF9|Y^(>*s=o;EB`|C3u>AXUzH(Dnceb=HrsjW)>S zc_}2i&8i!k?tjO(ZGY7b$I^W=_503p6E$(A>#T|E%EJPzn03Psn>5ra{mK%Hiz75CRd7dE6m;MR_#S$F;n4<59|FyYZ zywjObfjkX8a|~uKYOI~5;bcz<6%a4tyVqwmLe_{Ur!%ri!-5-6ss0rH6TzH7d$WQB z#8p%wIa|?i4T+y)N;@P(YCrfAaxk}o#f5l}!Ud0-U&kB_w7 zC}zr%XaE}8T>D-5O#jT*!&a=*KXaunz4bk~|D_z;pe(_?6m_;tGG9xQG($79?$C)N zS2rq`6T@11K$CrXG|0yPJ7%4?lJh0oKY6wW;qn?ZPy0K{#4&a(-^@z?Qzn^0hszZx zN5%NtOF_K_CY3oa?^6AvRPq0GwYPkP*%EAGta2KV;82k3eIxzdd(DFFYJi=C486Zi zxyrP|p;Z4E)&1W9YCc*M!=Z+6Kzvlgv`i+0d<8Yjq)jznv)3fJQDb{u=6`%|P`~lC z=|BDMq<*??zD`?mlR-vnY2oIF2hPHQafPLBhjq)j`g8dOmm$;Z_J5r%8POuRe#J^U z^(hzpDOJjMVRy@KK@w0T9F)0Nwaj9cla*sn8J+qdJ}!A6`VRW9wk#=Ko`4}MbPUa# zO}>F-AO?f!>2^zGf=-_Qa|CGe@h$TJ3xG?uB>PrNj9ZIN>>c%gx6zggUB#($a%yHz z$RHc5WD!{zVha$Y^Bcy^yWlQFj^35Mm(kGid5q_v=A|tXm86veYHX>cwC95mxB$;v z7=5wL_;lp-r{C-|&{uo{7UvbN-v^fUNjSq;&P~sz(4_`@BZ%Vo|I_%Vj+x6PDIY)B67)q#4HEd?Sz-<^~Rr zy(ML9@)Xgt27!>#;eASDa8=L9ff*vS- zu(Wkl+44-!&Fc#k3WA#U0$APF1(l);8xJBOa<2S^#|-rrLpSwX2|c{Xf8$~=#Or#^ zl#t911UtB?^}dzkHZ%}v4=CD^v>H+kdKa;jh2H|6VdMSoYt7fbHB%aKM&Mt`c<-Sk zatZs#_})J%v|n< zHTA6lXYvByTm-df0l|Nn)$~2x1E!kNJ;G_Sgd1C3pgI!&E1V? zRy&epT@F6I6-oHsa~kk35Iie4J>`J27meu1BA;iV&oH9rOhLLz3Tk;pAhu<=vjd5P6#e=UzWv1zV=s)9cJlF%^~ zq-fj87Wq?ka=eUU_jIVB;5nD$u{7Plug7s#APF!EekH^iG-EXrIpfj6d-_^LxwzqBpTF2c z>G{99A`oyBJK2o9jag&@b=E8%>3f`;G|TOPA)o1enoSXlDNz_Z#@&hj-Lhz&F>gLb zfxwO&3tp2e{7*cU82{5YWUD-iJxAXgq#i)nCw7M&V9?4^yzf1i@L`}fJ*4AJNi4L)O1-6OpdM40o@7V*FNG9kF=PV%q#9bv&ELM6x} zj;rV4$WGtL&^zhm+CW^FawD<8JznpPRQp=Jl9Y?yKaMWb-_jdWE9K+c<+Vv?1IJ0mwg~1k0V=Omu8p4$U(u{5J5TY%Qgw5teq7D^2_txF7ju#^DNMf zvr^g9#9e)Hhd~r>>9Np`VnSFAZI44Pse(Fzkz2c?OyDqdXaIw z#ed7tDh~F&?mcNw>Vw7zvBQINqixczQ@Bj|N`Ni`ErL>lPOw=n@X} zU7wdp9Q?w`?bE|so;M?CT2Nj1V1d?~CGpk}exms9w{U?-U12Wi_$DhEWCQ<}W1;$- zx4bPI#xsF3VlUk5D0BbFJmv?&q_Pab_aTr7j*Ckx%D56F6hy1tKp@G(a^>6{n*gjD z!D**s;k<1e^zewRnd%ONsJ36J{A6`e>WlY$pr&K^!gd6>yj%JXiuD~a;{ z?Qy^<^mKb&-qc}*jFU!S^YZG}iZAu+9Z{TLX9r6Y31|q^O19c()1v_RzuBV!8pF6Y zk7Jg7!GxV1H|OD?C@PJQbR%J#KOi6~oz$2u$hI?X(m+v+sMNG^!p}#)rtmyZiG2<# zFmqb;+(^mj{2)<<$?`FjHoz63#iE4M%Cb7a>MObzoP*9V_pP7y2LL>|8(&L`ezNmN z*4<^`$VJ#2aUwZ!M8_*M3!-P-tb@kCa+Sn2(xO7X(^)UX%eS=QAi09XT{|UKw~p*h zcxaCWD8*^VU>nP0^rIy)@}KoY4e)B`84$8ecLI`st;|%}j=qRRP3|@7eQu9c?R793 z<&}HaUdy-{&(hP_mgLg&Eq(6!?ie@9OcitDxFkL`|J-7ucD^)+wwoQG8^wHAnNmij z)~7WG{&eBN4E`xlHC0Bd<6%xP^-){9(0f>`JiW(HncjWpmO_sA750&b&aB>y&rdK% zzKFc>ZMusV^}^!@>VfOSis_Z2v7Ryyr#d^MKI^SD!^N%P6(QxgeFoOXPLh1H`|7T5 zUZKw;fwU>tOM>`&@uJ4v%TZ6XDFULpXIrm$mCs9Lgl@*5Qosp@f9AgS>IgJ-12yfA zoVX`<3@U=R=9<>BtB+U13W|DpHibI2$0I$JEJmewRHa}z#L`FIlH#Ma#4Yz0F(eK! zfGOKiyu!#UG1?|h_|Xn1LoV^^l#(maKC(dsaaptd4hA(+xspDud|f&?O&@31egNJg z+)8IPnjfcfT-$PHNF&w8BOgo}Xk;OVTO*o)!z~Q_7+)xwtaBWV;bmdXQ90@Lu~3qu zrVqchZ0Gq&-hCzaLBk?emfSv(nx}%r%1&nvMee?WE=SON8efiv!~V_0knmw>s=y&% zz4MUtN)@Jma@gy*eU#0*04{q+9EZE@77mkJ&YuBka@w~vG)}dzJK@1sUcVFW$Q^$RmvOC zliLA28osQo*8_yRhVQ@Y;i_@|R*-mGSFB7R<^^yAP8dxGMVlsFSz%>HPU@eHpLeV1 z2tLr0Mpzb2_)E}ccHP-FSVuPN>7!jBrjIx>L3@Ignv{M8f25S{|2 zwU3gj7!&mcOq3Q&WSrx*w)iQEMVhqv-s*M&EGQ9)m11)Uo;;XRyZ^a)l)?TzS3U(= z=FRqvzKT+7IZohYiw8f>A3)B&wUdK5JmZl8TF8$0sP+;arIIZyXR9LS&$~q3;;$sf zbkMDt0wRKv>u-PD?>uuFS{$1%wQwG8rB*8O%wNzGtH(hJ1@^VP(keH3xKR(}@#3k0 z0!HD0H1%N?G0=k7nimAG|AurrDqdp{e`$dB?m*luI-anq1{!}3A(EgPoN%D(=42G9OE z3}tf(4tg+WXe9t1p(xXbinUXNo2RA@7y8^F9-x)MHnqvgn;c1x`!^hzIX96kE-@#d z*@$nhFUS|GBOD_fGUET4Z8i8SkJhVUYX*>hY2T70x(KiI4)i<2S7nvwmjOI$QEC(T4$gFP8X4 zYLBt&ea~*@tJcQQQBe9xpr3mecyQL0E}1&^O-nz9tgSauJ*c|&`HLchk36frwDDzR zc*D;|1>&+XC-3o3F#NFAO-_qxAKmE2EqsE(16%dKyh6oW_~Ih@#bjRUwXWUR^Q_lX z-#6zWdpFMrva(O)gIOu;W?a@7YuW1kI28)YYZ<@&2n)?!%&`&fR38>SNv;8dqBR#4 z1?c=+1TWMjC`Wq^#Rka`2ZDZl$pg7bqr3rF?V(OzlqWSlfY3$ zQ02bJXH`=`G4ATw20=6R7ZXs9WwA9oZr2Gd>*-ue8ZhV8&z36clj1d_H$q2V@aJOB zfp&@Ert`U7*mG|;!I6oi6n0!t^{DU2ddtw&LaN=)tdVrsizO@WVdo0U6`o--i{H*R z!$MwbDCTdJY3oN%RxGLaLEG&SY<2otb40&2e(=|P3s^XOi;6=$ zhFC>3T-Mo-Cd5SPoZ)Ypijw&JD@+R_d}r_?OGhXI7QpuXS=#SzWLHQR;=Q)xo{>A$L{i#+(Uh&aL0}3#U-Bi6u8~#DdK5kT8W6YV{Dz* z8k8IO1TIv?u5)AU_GQQ$q-~U z8J_#jXVt56(hLfoso?EW@xpIaNZPDw@sFGbzYliU+8p#PvBkM9qa!dz=cnFxmwWq1 z=UR{b=6XEt{Sx5|1AbD$fAMtrcYIx^VbvIsG!!`(yuQY|Fxqa7mma3h#^TM%f>2Bqy5JvgufkmYIs>G%WLRvdb50e z))wsHFe$y%%6!8GYt9UtC}b|nsYtfd*+KWoM&SdD9D&j%D2JPV*m9pGsW8?Q8kpoO z+M7Jqj%SeoBMlDjR<9n;BxX)&#vv&#fmmQ&(%JAeT;5b%hlad#&N*jicvuGNB`bUt zplnl4+ibI+z2ts2YAb1TI)&9;Bd&6OK1aDaN5))M!-lOWBkc3MgzX9^$V`5pOrzJJ ze!{A~_T=T+581a->{{awKdvVDn@)OR2EZh!X}y!f;dYqLq)b;SzJ9FIh@n-;&?*Yh z=H`7&Wqh~FF2AN-AP_8k&@&?*i> z$s9L+F>lZNVFDYP_aE(aZ^qv&oc5C6QzJ=6)2FhcOtY@=F$%E9*j-?IamfeZt{*v~ zWip+gTNmYjtVll$a@$Ly*OD5ZWJ9<{s|+O-vkmyjodfAi7F*MxAIpq8YThX3ehd3G zK_a$DV{ak2f7dMs{YWy#==a84WCUGOVN8YOMb7aJ_LVdDMENGgF0E0)rj*X&^I^h2 z%i~t(Tz&57yELZiO5W{zLAKeuzT;!$uX6D8F6k6MT1ZHkKjbQp!TIA)UofloP9JF2+)qgvUcg zx>92%TtW`+_FY>h1r(kx1$U!BF$xHl5tOX5Dw@Y>B>#DpPNZc}} z_)g5vbTLcpg1%TVc9Ea@f=MsxcG5LNve)a@jX-~62y21wqEzm$$uB9VdPM%x^VqZD zQSZ%Wf$M3U{Ea0O`|SC_$XpF%!r#o(6TFTgiz(p3z(Hi8^6Gotd}c1HS_d~7-?3Al z?f!U_#VSkxzDVO7K@@|1P>sTA%1#-BfOArVA_Q>aH8#HgvMahpY(qAYqSA;K6?#wh z#i&(}2dfqe%#76P9$CBSlP_e~u|hVfvh^8S<@8~#S+8lsqY(K#4G={NA@ntk>+3aQnkdDFN&zo3c|Bj_^TbCz#1&s?+*QpBUMMGJWnOj5T+A2=PqvuPy2r8ri|sva(G9-$ zvGm-if3h=p_l94Hs-@(`6fZIfqio}j<{VW#*nYk7Wd^^0z~xQ-X2~B{5HEH6dpIxv zXFK_W`0sjCe5GX?xXdoUgecxXhjf+e3HSP^9$XYn18n`p>kxdmE6yog>^+|*1I$Rg zaI09hgo7`^kKaNZyt}raq9d}kU4>$GTF6h>$&0$o6E8uEkaz>tTuOu7X^9yIql%5% zhxGvIyq1IAkOr@MR5edr7j2P^-@8X~@RHi-fW0h&j1#Zen_uX8N_69{_pPsT!zK}C zMB}<^oD5R=Tt&bM?jurK5Fc>)7z`TZXiB%!xoIR5b=hoQ4&`2U$PvK}S_VQ7Y3dK! zTShqI(gJn3fvwL`k%I3=(6n9Py0-QgD*Drij5eziypIgx{-*i4hX@48c{4xXwKAxu=HPgm683aF`Jy5mh+w~* z1^)LeK!fCFA=>d7;3gi5XEFkg8u30W5%NqCyLx(tfWJBPVgRPZyrW8R31T`kC7CNQ zop#peJ^4dE-d4`Aql1&i{l|V#ors*|huXyWpaIU-rJrxOJfipY4OJBR=XrL}0eTj3xmfYkjn0`#Jxq5BrQPznur`=}UbUziaHN>)URNs-xchF8mm;C}GF zF^;`w3SHHqJ%-a;p_!cxuDg6Nnl3BRNi!3V2ntY05eT4`K}Kyo|KOV*Y`?k0tKnlg zwWv@Y?r_`uM$|#FcytUv3q2oe15ag(L2D4ArS|WRpx42+mj@-Y zkYaGgN5Zpb<8qip49Mz&8YrB@j{zU)G?QYTwCLY2lqx-9WOrpo1AXG|P0`$+tnuYfxJ^f0DzR%qvox26JBLls^7v&TOLHEVH!hAeW zGNLrzb_y2CXxGd5QKWLktnD!G0kaLaR#>iHH$Ud|w6jztgtpJ$h`ltzrgIM^2qndR zeURfll6}?ydG`azS<<3-I}|<&f3b` zxqmben9ig2NQM7%^zIb_<17Q>aEAm2$I77;|CJOtWVS1Dh|OZmM+6d8{sx7gXCCtw zYmMy9GPL^n5siEx!C;feoSy)J8bWPHj??4tp>0_&Jcrto;y0)9ThbXkb#iH#yd!=q zw1gPnbHafrOqD{aXl%I4|U9zIgXn?Y^kb-h_(^D-SvDOGh|uix#{J>DZ! zwAeInNpTbF?TU3eg|9KqJ`Su+z*$zzzswTYc3w$gMQ>Hu-f>x&OWJ$KIA!noyjzCv z*CrAP^Lk?2^v7_1$VS-Y4Hn>a)=ettG(x~5NFjqqJfp^&Mqu^{`iE%<<(r|Rb~N5t zFhqeb1@$P~)i0@&90{Ep4cu|xN9>#$XUqc~RLfor$gmSHD9v&Wv?%G$KB6{Cqb#3m zte?kk$&QaI0s!{2nrVi0{nK}i3tuoAJ3$HkM%YuLw)D+iuYu<@6j&eDhhiR6!L`~k z>*wb;uY`Csad`?oEbxWEA1<@&@1X!=b;@WExes^BZ2|0E43J~<=8eNztO$>j2b{q# z7Yi3zM?hn#3t^hVZKXCCVgj#;r8c0wtYXjBwWK}0e{~O0@!Uqf)!{AeR3g_0~i5gZKggk19xS}VmFV{Vgggk|mP zV{Ij4IB>rdirlYtK3BNYY~S@uCck3Kr{PLCk%0JTH&rfan0)O+xkYRqmfOMk=8r`$-Kqc zb67r~Hy?pNnWr<*RMdP_lkVfAFO$7PrDkFMnfR(&>XteRgMT8~^}wdB98TT9;g$%He;tT{jSiV$w{nNJaITRM0%Ek(HU0X#tW zC4DOMH;b_|6^aZv)0po(qf1$PX zxPy3!K#hwwnO0;Lq=D@bKw{@#1`fZ&IOV`Iulg7nz?#e7e_Vl1XkGGtd+IXqgy(=!BLhVGpJ)u998xsZ&czs26JDs)a?A$&WK|*V=9ErwCz^!}gH7 z^^=1c6$hdOM95SHRU;Rh-kV4Lil;c;pBV03=`%E9)qcmDNH1CHsQ?23j9#r<#(mz2 zTLIS$t%Q1h*g3WzJBeH~bs#(|H4^}H+B+BsOJukGK2#{Je*DN=&hO)H?sq4_<7IOv zEd2G_yW$G(Z?x3k*yxFbk67o$ZZVo}Rel=H3?*C&Z6t-3-F|$q8FGl$y}VZv2h?Gq zD7^85{O`AQEcY>3@G5~Og`x~gyO|gu`W#ree1-h-fgj?Xpx1GMRZ)8z z4~k$6NrS%`X&9X_lAHbEWl2HNo3%O5hN^unEmE`Rt%GI1Ojr%PptF8LjL7hsFkC51 z{qEV}ltIt#3={$$Z&+Kmlp_6wM_-G}sL75EOxAep`de|AM7xnhP^<~mIn zPz!|?aM{%3ql9w(`@Kx?R)9n%r@wzk8JpPjm$(gd{r0=$c@0#7;&E9B=~5yVXz~_{ z5P?g=5Dtf8AioQ@Fe-mT9`iQWnm}6$5#EXdY2Q_n2RBZ5_n_7n4 zT#%Kj0cNoy)(X$!NkR!;XyLlL0RL7d)l#r*S7|N@q@Q3r=m{7QBZ7OMeAA+n=k@!o zkh;UXn}E&**2Bb*{e>c3L@u*s$q@=5%qzMNkex)jhBEIJD~`i;%%J!-8GywC|C#M0 z|F?vo$jcYRPqRavk_Emfb>7zazUPhnToPh6^5U36q53TfD`MvCE&8<^hzTB3utIT1 zt3=n@U~r^3Z!=nZXuf;sMz-NIPTxa-y-UYk~QohClm0k?Rscgz)u zLESD9oFAkiy*C<=N1X)9*CoR1Q5a&nJ(?bDz=_c6L9p`hvyx!tEiM$Vu3;bz171O9 zWb8NF4rcFXuMTgX#Sl$B%Mklsa`99-y!V*7AL|L2=<4k~1tSU)4@9Qf*cz+P9d9;Z&WxC_Xn?>)s1sB|YnfJs3<3_lz)r^)5~Xg|!!IQ6zIQ zg$z=ki%`uWNh>zmK_i(Febk?L9cBI7waQMN<4k|&UQXbA-t0|3ts7D*`jBmlIueDp z@H4lI?)Yo-;#T9Yfv`Lx{2cyMOB>H~mUkZkM6Q$A23^Jn4-hGvWXmar<4Jd305m>3 zbN)|pOXRIK&bvZKzDHazI6xAlC7{oOKRU1aAe>CH6o}m1`-a9eFcl4rpFSUXa^fPY zTCJKQ(7rtACD(u#K>9UbQwyT@43EWV=b7WkWhw(0$&^+vgT$>pdGRso_cj!v_TA86 zT7)y(c>3lOKaCL%3HdCG&jDvT-!>+x%J48`9?mj$cyjy)csxMCak!uCKPpGoyg}KLSLc?kIz>QRBO$ zX_Uh=>g-omkQP#;dRRSoT>&?pL_c(o!^J6LH$kaQB)O&fjLfLKb-wkQkqw_^?9UH8b;TPjIDlEj*GqfHP>>7OL9L#zJ`B{VZ+`HsLy@^=k zV?_cu&B`P{MthU7Mr4SqZhlse8ti zsy21w3~T9FgCq?M{wy3|8nD8>DV)rtlqB2`YgWDae0B||irau=lU6nj!NDGa$Vpm0 z&P2ZVx=a?LZ3HoH^5X>#Raw0pls#T>2%X@K&C1 z4wfz>09_hpA$DT+Gfzg!ep$-jQ57ouYPNHb*=c-)ljQ%&&aEI=NF*{LqV`=8DcWgG%tN&7>2dG@ z0u9zmbuY{SuiA;f%47!P9>Gt_z15GFjv}n?63<9v)nD?Pu`$5;sJ9sqwgJ_v(jWA! zh2<&VLMBh*ZZ?i*UAc=S>0}z)@@B>Ks;34di_1*N<2iG1y}AM246X zg3h0|J`1>GNF!56c8OcH=^~T+(280YWANn{OME_?~#3BAltVlkHXzbMW6VwQ{Cx>#wnQheG zTn7tn4?Q^F==HRlx(+4C_Z&l^Q1SFPg%wqk=YT}spw)6RftzRadB~>hyIaUC8W!D& z+(iC}ueDJn&0$D8P?p0hHJyDN^iC-pZ`z;A)TI58BS967v18e!{vq&y?}NQWE}E*6 zJ7T)H*a+Xvbg|7?AA9`qN=(|0c#bz074>A2axsDG>beWZhoiv+63qBL>9Y-)t0s_X2 z)S$UbYBfHDs}BSo@OOrqgguO22Cpku9TCi`i4T$tZ~tQq&__y#sbx3=A-QN!_SLCM zLTM&0>X8hNX4klr5|WIJNl$UN`(cY;{hFc5AB$buE85!evah@uXZW54TPS>B#d-R9 zN5gr6NJ!xDhkm=jZvNWq$3~l-16_|Ve4bak zHs-lgo56qn;z_iY&vSiOJRS-MWZR!a0{87;B}k{SjttWJkTM)8PwpedAHB0<0?})s~5~L z6B+6!$-m3Q6r1k?O-gAj(ND)K{U`?f9n;=ynKfVfSe7sP!u^(6DMORZ=$^p{vB_e$ zM6KhSnLY9D;`>)s%uN|LPxrcIzkKjaG-r%0RKbhd8%p=!Lu#T3vxkg0lcPyK^*Z!u zA>G$@PLp_pK4$t>1CEcrYrV#Z+=={^)<;{vzlQg#!PwDU*D4AE$L{%(=OB_+4d4s8G1(&(E(BkE56go_rH^HlzTz3sDX~%)z4;c&9Dhe#K>~Ifg?)koF@< zYCmf}xolnY84^M`1Bv{(>DtWA)5aA^T$(#pj1;keeLTF zj22G$%jc{rg;}pFF%V$ZV1)MQtJlA2k)0y+#SR@3FV1&;m;Fxq3ueMeKt&dDDSh7> z+{VbBx<{M_Y=n&bmcm-Sv4fPcDY`gz(==?A42e9YQ*_NzAR8m?R+mFIt*8J`o{aC4 z3UhLtVCuUL!;{4;sZI!RzqSTn$4W#+Ux3UgvEpN5m_#@ETo}8(_n#4H;bcC-LS)2V z4NhPI7u@&CN2tP$SzV&VcZ1Hz^D;$lgnBSJvsR2K=hh-d?eo4&1+D%%-p_|dl@~oJ zXSUNZMtF;DUvIk7_w_AOTnm=V4V&GXAAP#G{@c02!6KJZoG$9J*8aD%z10jGc)Co< z(@XyYy=Gineym;`Rs|jH3-5~Tleo|3B@&abUZaf`xg;#y3d6g>={9hEE#JjxYT&|k;OER6oh40ekZeR6+Dv3dd8ICIC5#ecAT z@@H}Jk4U6m@6UmJf-3o2&d2Gt!y$myE`0XvR)|G|KPPTA)W+^RQa4TirQs~GTls*aw7W5fV}F% z^4M9Kj#Ry0IRKCb{)^6zYcm&WWM02N>%dxf@?OIbjzYVxu*gw#4#h}l*?_R5=?b32 z$mz2S5kJQ>d9JUty2k~59rE_#jR%(@B>3Mxkz&}C8#I@RR|@h-RkRGB`I(J9hAMfH zZ}`6o1_)!w4#$9I;t=h~{*^t!mEUwGZB_y%hpF=P;H?9RGaXrP2ILB|&nc6(1x3wB zTjdXRMB9WBs8A(0aWdq*4bFaRP}7tVo^5*5=^vrEfB!|P??C(kYcS80NT4DTpyj8H zUcG$6h}yz@d|+TQ2Lv)_Y)$}IA*#MDO*KxC8|2OY3qm7 zkdiOA9`%?yp7Cq8C+d`Z+?l*dhVrcN5`rq+CyJqJuFN4RRpcp#Kq|KMk%+tU`~*OE zb0*QyC|rK}#PmjGM%2-0ohL!@d(`Rb+h~dxUA8bY{Nsk;0Los0PJ}N# ze4@_^Bu+_a{#_o;jJ_J4H z`X#F!iH)6Ti+XcY_bCi$@#`iiTlqQwF!d4k8bb1IVejQ2&@h4wCE|-&!~K*d<@g?N zr$w4n?Rv@}-o)aRR7td*pO|l5su;mUcv+}}8qa1YIDJSE_v<|m12?K6^5HfI)<4h6 z4G%9lB)qme1M(zz>iy3OzIF_}eD<5!Pe+oWxiCVb1CC0I7`eaf(O|<(l!*FN8;@3rS`3Lq}#BH40k)$ z%`2eJ;V6-mkbL*Q&E)jFz@Cr-Mw2$P!+yp64%c{%d9DFbV3GkirED}HGfQ$!m0;Y& z$B*7_=G%{#A*%MilDQ9j2dQxY#!s)`a)33X->bn8Y}Y#^*cHa6V%KkEk;T)U=Llx- z+9nM#ib!ibJJ~W7y{;#qeLL^G#?pPB?a(z7ki-b1>49)F{gCzb&FeYX+8eM*9D%v; zB{r%aze>E^J0CmyOeK96h=Znl@7e_%H7dPWrNu)iT@OV76mLFNk`Hj-;3sEYhJpPK8RA+$n=Z-s`=Tz^X2PmHcUkU8mOsZTM=>aXdA6=RQb)y z<_!Otr{i{}y`|-K$q8MXkFfan*-+zE6L8NG&+m~}pb2TZ%mi3ULCS3QsaQ6Hm4A{y0VAQGJ2VPjwrawPk^^*wt~oG1_r~DkCce!@ZN1*9 zl7fD-0NyP3vW+{<-$%ndZojGCO)X1vEb`TR-#PM85<0zZY;sW5sQ3;#_l6&VW$j*krD?0B{^=a_*uO#iJEGNqRmkEak~mQWFa;!?y|#SI4~HOU*8eW*DY^I~X_lctpAHkJsDzc1tUQE6sIehsiZn$$)w!Uv2)VkiYs zT#;AMpVjrH`|TgYy9yvL0E&!Ak`IJ7xGJ|PGC8&J$!vNTlQg(L&_se>xr>3nPC(ez${MW^FEin|#Q zh#Vdd=s@-j4YlWy|K~=B(qZZ%)!)aNJnb@~q(FY@sl`DEELM1VFpYQI&2B=On7SSI zZv8{33UPCP9zl5a{LE`#DLs##a=g4ne*-*e1|huoUpUk2K&Pq#B*o;5(3vDcU%3y4Au+mxE$!4 zJszoHf$->nY|lNgDvHgTUF9)6-iD%7ahORe#$q;BgeDuYqOVdd3XXkv+~d9v>9X4mNwFZaEX~gBOq!$~ZC`Xj*cM!7guWuL3!A|Dp?F#@V*Y zFGanFkuXIDa`T9U7Nuh_zlV+DwQF6iDX@6LBwGC5{ncMd6p?r%(5Ih%&>>nppmeU& zF5gw8?cAZ@YvCPqPX=%=kvzGzLAf0aq)<1^3icqNur!3sfID8CK8tnu9VNkZ>=q8F z42KfJ{?=G4$zui_!Hi5EAp*J>!#pAnzM3ZMWW*~pE)QqjdHm|o^1V%rxclDh;V^Rj zIw!HfxJ^Mq@}Bu9D4{lkc{C7Fv?#ubpMI)bp*sF^$EDTmeTCEl8*kg(GJxd^#KR{( z({8Bhy2oJfGJ>Vz>6zAi9}`Noqwsm4so`zhBk}X6m48r)UY00a4rI+B7E@m%3lPs? z8N*9G=UZ)cmCG|@<@-!ZC*&~|C5{W?8zZt;5!ziD^Pyc{N(IbKtBqPFxtExaxz!7Q z{@+=E;|jBE*+e1x)XFs52y|{>lC4xWF+qMxv z+Aux8*(VeSU1k#j9J;g@r|`mzo~Jen2?Nx$pzcj@IC(b}CVIIK(L5SxH&xV7&-)-| zi-ETJFE>}(T+6ZZ%|(U_gE|F9j7Xp1qClU-toRP!t-7ntiYkk*N?`$QMsV`18A}U$hO6E6Z!{9qwK{U& zV{BwQ6?*@`$7C@kYW0|H)KP^z3Rb#Re9s9ca!hqx6|a@2*mKSCJiywv75jh6HM%!Cv)6P z^g=^3e%M>Y;rn9ZM7_61U0UobQia2BSMm zBp5r*h=~B0#BbBEaVnre_Hj((5qH1Vi9P;`^jLpdGj8IZv>`_Y6n(3;4v_&hx*=2N zC<5YsJ`(|eJbb|Di-A2fX_$4NZ@HCw&UU=Kxspfuu{*#zbN9$)h zqf3p1!C~=)Sbf8>O?caJ^lV7A9_Y%=?^Ub*`NOBaXkmWol<|HRd(GqrsNM!3;%gbs z8s-rC;lA;Sw>k0%2xpl(52`w9(~;(9ee-fU%zep_FuaG;NDY?B`QEP zLH-g#1Tj%d^5<=|K){}%fC)L-?XY+1L&R;pe0C@n5Ck=Xz&vuoj-%2NvnhnmM|rOQ z>eC5Hl|`uMH!3$7qDR)3Oie~d*Nn&WGDlJ5eQig?(e+`&@_T~N_Xq;}A6ku{{?R9K z-)G*9iJeQJ1TcHe;!4+nK@}*+4=u6CtgMMNc6hvCG5WEuI3serSBZaQem3mM>$q0b zH6`tUrD+zbozTy4Zm{dni46H>vSXnv1O~(JHHh_($_H2dwOFMXq!skQ`M=p>>W-iT zvaUVQMHf|Y;$~WN%lfDTvOuD5GD1ljq z#7G60e}ct9G7iB#T49t|`WrhE=;&s71h8)e7+naEJerlvIv5K_#XIio$YPp@p8x5& zVYuku_U$?Cl4@;GC~{NUDV_YIqG+6|EuC;`3<- zbJ3q{2l*Gz(vx*3o)+M11Nip8=yNOvh}~EAh@~+%DTYMg`w#e{Kqz~9JwS9Vi5Q?l z6)Rux9US}OZ=oU?h*`M1b-`XhmChx&q8UY0pt^#_Ogn zBpW;~0-TMWfcg=k{UgAk#nCV<7XcxF9eb9E4?p${a>PT=|H7Z`+q>~?laq^z+BKnO zA=)K>*^2P50<3z7IfG7&-5Yn|w2px+8CC%&g0IsP3f)oq_#lgc^{cC@ZWf%VtL9v=H0w@3u zmvxvTeqo6RN(txJb)NmyHb*4m+aiEqr57pTR)BoBJB7?d-6i7Z>G#$q#OB*7saf59 z{$9+b`6}af76jU1eY7C3uciR#^2--0jbtoG^ z()X0Vqt}G_{dSqBo7@+?-{qItfP2zf;D8)yYPibL1e1NW|+s|K; zns*n`R{{WY_Wc7Yw>`fn?aGL@A4&)?0yabd zv`8`1x$N-rNr7P1h4eqoZ*C7VZ%XDR5ypiJLSXS8;D{%H!Z?(GDB^`^6Hi|C$-(SIrH_KkP97CM1Z7rrwD*NG@1LNDolE- z^0j0@lwPp!2*Oy~AOMW*T#BDv`$0VL<=;l_a3$G*69I)OpiD;HX%dE+hQBNPy&`7M z`Ru2O1D=KvxVonFG|q6C>M}26ChCmBqb<^p^KlVB$Na+8^!?`zJMWFx-fp&aXA=oupc9_2j5g^9|6@8qf#Qe+U|gi~q zAOR=^ZJh@A$n*c=H+DXC*52KF&fHA$#}ftnUZMciLZ(3sE@tp=DAV5+C z$hwvSf#;>aEjG%)awovFr5;x%1f&9r<30I5&&7>z{w$uo{Yd-_~b@L$X&gAHgEZu!$1z>{7zhV@Ec-4&bqi{$l>9Sn=b1`%d0K2pDk)1guxc z=luglrkZSdm~@YY#ixiW@^?10jOlj&QYj!EKM&x9IlGw_*`(G|J&BQr0j#d{jIk*7SB; zU|);?(B+q(fn__+PJD@JL+?hIFz+|Ck>P$_drjm?fS#)t?rnmAHH^;vv3!#Y43)|k zNDfm8Q<=MZ`LO(xjRf!ghhWnduzY7MAmhkzdo;kd&BLY7oK<+7xItFTGB5=k`Lw{X zhrUCf#sD(nq09g3>fK{!KeKo5hM4>#LesrY9TMfL=nibjU$PyoOa4&gAE%D~XY~4` zJH}AI0x3R7=B<0d_K>+i^il^#oal34#RvP$ZRrt$=-CNke<;2O+ZTtA%C^vR_mbPv zrvr2z!mwe+l{5N)J*2P26Q35)SqODcN-R}DGQAMke$#WX<&B@k?w_44zlu0c4H%r# zVfOp4MhWfqfPF|j`(~Z#_mz_Gu2waU@Ho&|KOl6t3smQx?JqTs8i7A`4UpMpgZ#4g z_+1zf0wr>93oOtVOoayXyHsJ0v}ykE@IhKZTQ7KCMo&9}CgopeKri3470Y(4)!vZl zSzn9Qhy%CH%gbIIyFC!r zUv(WM?`y^|8D_LFif6=&#oM0!5mG=`T~&Bs?+gBO_p@hT5mLOTCno72ZtO{3$GteU z4__!6QN^K{L}l3x_Y>sLJuyj0_JD;I&Nsa2PG#hqo4EM`zy^)Im72%3nm7gG|Bd}2 z<&6}Z)>c0OxPA(_(MSLQ_Cr5 z4uDxsyO*!lEl80Or?Ynh2rIqJC#?=c-W!3t9yN2s*P%OHt#gP22*7Y{7%QtkpubxD z1vv1?6@Pi=?N9&izGrrydqs#sME0_0D}-`QsK4=CaXe+B=Ac#lXgELWSP5OdA|ijj zR?}5vJ>(z#4WLH@Br%G(%#qfK#Q`MQ>%UUCkP5R2J^g=nJpKvHlJZ-L6#bnUzcyec z5F{WEL6W{jWtAFJs50`A0PX>|Z{OE_f6ZRz9KT@vr`bFVkOWVG% z=7(_moe6^d%YdeAH^{f09zXX-q5vQe|KyA}Dh?#dnCmD2me73{UY+h}XVG&3fQ9?N z0Kpih+akcWNA~?DZ6xBeIGIHNGzoJNAd}nnQ$+ytH!-{q)-VX2Ml~1?5x`#rV<$o# zA8bZcjSUMk8Ys2^P?k?ET9p39*8v2Mp89S2>qU3bCy!Q#Ydn0}7yoGc?hAf;@bK~x zhybJp#G`B7&yK#|s0!qo`e>k>guEwMdmE*X2w2`{@+0_5<1a+cqOEd`7z4n@7u=fH>)UrW*^?U58oYLOtRgfKm=Cjx~0ze-5xUO6?g=GKA6=%uxY7hrA~@S6{!SRrJ&?ZJ zha7gG>e&znMp~JkIc$pxmk8h@B{MmjNBOxAT#QH~iH3_;ywxZQSpHFD zYf1GKEIz!G{`{!Z@4e_7KX~fd3qDw_jtI3CkCCz*PSu|w8H{z-gz9YRwuh5Cln7An z0*a*MCn-Eg{fop01=1%;>84098yJ(6b-!99VjHPM@}G>O|LGK*?B!oqb7xFjkkx?j zxqD-m01OeJNZb$sy8fhpqzD^O>oEjd94T}uRDAg$sa`y!Oo{E25u0QUGhWl-Q2+@b zoGRk3&%F+J{`uQcJvIWdOHFz zviI}Gym4j%x8ig}1TZPjB0%teSSp8b0S zWZSY|!dL{zX$kx6e^))rmxT|3Dj{Tv3Z9P=3U;0F#+9Pab7~`W)WmJIIpJj#|F{tE zBg)n4{EYoj)}6c`H*}Ji1w^dTWE@!}p}q!99zwUHOfcAK_^tpVnV$ee0Ej?MQz*aK zwJid;_tHx>hCxv01IX*ZijVk&JiBSPhv4NWz6}u21T4QVT_^GV_gS2hC97hT@tWs{9diU^S9cdiTICKC^m-MLd)*0+5Jk zQ)?Yzkg-X-j0B0;Q~`HCrddK{BPN<4?)eKTMEKVVC@+a3Q4skh*B``$6aXYb@Xc`2 zJG(w2Pm-;(Jyjs>aQd`unltm32#G6I6Sf)8B?9!12_-#B%QZO_pbhiyR@rK46u=~W zm0s%-9DZgoe*Bg{!`{0#0HOvAOA%mHGt>y0dVlO^JcXQ%cUI`X&NhmtBS*L}^EdJU zoFYI-TXmSL6QtiO;+F^nFdY0F@{{iV2P9iIj#M7)e4o-dZYS4n(= zZTSw;bqXUKDlfK1AYc^A^bV=+E@Ktm&IiJHWF)atT#r(qNW!AHY+Tdn z67YX!yDj^{fFyfTTmE=6X$t`!k`w{4Z9@da_Y4sriI{OG@98U&*S&j58rjke_b&0L&ulp4=2jF`F(H4wjW#uh$=8N1@j#`1HR^EWH;(1@h+6z9SZ7H|;x$rU7MG64=M{M-GJtq{|b0ou_tP&O;lbryV2w@8c z<0bJV=gi}JNq+sLj7brriH((oB0<0xiKn%LN)E8^k_!Sr5}m3Yrx37hOHvjP$sd7a z7WIgVOd}1(--=*FDWL8K|IHlQ2;%>uSs3Rq%T-Lrex3;k(LN>$LC&+ z`~T*3AcVkB6^8gt5H)*l<$KKEiIAnS#dO*`%?VV6TkoFofs*fwde_o~L;Rn-Ngr0( z+d*Anl9Bj%-B#!m#=Y_ILy7iVrE>%}Qzjm_5*|T!pSP)r)NP|TBLG9=e}iIGPox5F zqIBLzi)0IvSnJx$5+@J8SeT*EnXi(FZ4m%r9kc5a0gTXI8zLCX z@P@l{h1jaf7iif9FT-IAGfrtO=LfrQLccD5_0EXqLru81Hf9_}1Xd@Is%6nwK$LirB{f?>i z1M;CI)2^M)79FxZvD-(Lu3e_KNIy^Fua{pdHkK9_(0`xta%Jh;~IM*98Eb|NJFby7NLE0CZIZ@c1^NwPK?gzf`{V3no8v5n%hr^N@BO zB7ncxpp?9<2EsWcX^Q}EgO`;x90oH=KP(a8DwDL&*E!fb6qMK?EsE3+FEL}*3%&TP z@8i@L{C;dqZUc<7kxt7s0F2_Ip@02MkY--!)#)ao$*L29-gRJ4!6s)t1fX^UD91o@ zgdkpvKtfmpF?IbrIaDE-d$-({a1dcY+Zr6)?z>k1yzUO>j6yvT#EsDy$Jw2?mQ7UzvBa# zJXisSYEX-Obwxrw1opF<5cqa{n550$M|qEjt9P2T4gT(lI5Pj@bR+BlECBYkW&OffKbSVi|o|GxW4u5i1h^#LBtDAqxA>z z_C%RCBfzK{VseY?omZz||Lm{RB?9<#fVxktJ{3t5%M&0@+Je)(v@DFe(tdNImws8F z*(iWH0TAaQxLs>XLEkdIGa&@_KfM??zVm&U*gvF2fIUnf6%L7KRe#xVuKiKuY`u>v zp39zKwuBXKr7_+cRVqjOX1~ZbIz;-)_d0pDOZ*&5gC+ExO)pTNt9ZaP!h~FweCt8B zH2_c~fdTw|dj_+ks{(+|xoQxLcj&_wnic_8ShF@#03pF}mP8YCtTUd(&<4BPJ35@FD!}<#H1VYGO zD#bq{6vqh!C;%t`ggiEu{S$I;U_fkYBN76#D8{2Cvt>d+6RE7NWbN5PL0+k*l8Jzr zf0ZRZFRxC%6-istFPGR*1kSEh?u^iN18(^suj^y?CT6Lzwq z0AK)Lk{`ob?OzRB3Hy#fXFNxMDx(qsmp9>QTj=fxz`VU5!r(-qWwPbuldA}5)n+Gz zf(#>z6=1#4yk0C3pk8Fb2${^&{0u_1^4Am5W}O>Dm6R;MB4oQ!VU3M3#0f{<)_v)Z zu;SWV73JR$rU+1C1emk#Hk|P*uY#HvXKg5ih)lAP0Z!tonwcn&!}q*>RW-thMNuZV zqc`P4@SO&6#zgUOJre}7NC6P{-b17Pi%64=Lc|I9qZFBsg#bti6o^O+$dLi#XNVOs z+U*w^dD}3^Ue*O8Z_Uf*7j8}k2vxDt1O3(N5dxW?Yb}6|$;Sd>d+I;tmOxu#NPR1< zX!1`AlCiyWu;rb9g2Ou(0z>jy^bXx#i z6J6v)wrz`mW`%+szYX7AaS|_Q{6RGk{AH^n0*v8!p%?MNAp#VkTjf%7^2F@fev-p4 zILac!Y*CStw$l9M{)33fXb}=G8{S!01`t`a166^?J zwV+TITW<|C1O?bSfY?>pla^#Aw*#8Jeqq`|fG{53mj$s!frv9vL?1gL&p1&3SRDB^ zgs~t17XxytK>fg!ZlscP{<`MO$qxNjjkwFW#+*DS`#1x@8U@f>|CA&Usy2*VN&VNJ z(u|db=UyUjg$TuLpULJ?c`h2?$Ia7{YaL`x6{|YAG-nTJJJ62s$7!U0}UO z_Nkl(X}j7GM?TIix?uUm|NcL9ZyxX4Rn-Z8_qoHHhYW9(|NTh#tIRAYY>hiaAj*p^x%qJ=|sX_dCEU9Gm1C^#Sx!Gs~iBm^>tOeA^9 zo9=ygIII8Id+q&QYwvS@=XdW7>i)d@e&_77_F8MNz4lt`?CBWXREG0++Us-QZl5Y5tOWre{SVH?`WDkyEC7&Kyx|QvCV z+}V1^Gm690oC4FrxdXg6omT^IWjGo^fbIauZ~Ot(LOLG=G#xhzE?>^z^Fe^i<9e~- z(mJt0@dk@=R6#&Xj>HqEya=+djOE^^ejZ=**0%uNCQ4=X&AC)Qvm)iEY$BB8s{@eU z;PqPq<$D9k6?Mspn7{$q-tuP^qF|*Ew zqziedpa0@ffFYhnMAHjpggTU9K>18V6=p$W_JPHTr}K@VxkXkbgE3NYB1l=UAau2^L;rPpwsejbEW}57?Z2_ws6g&(I|g*_YC>q zRp$Ue*a#2Q!~zwV!eb)fi~6)jo*cUCvZzFx>MgR^R`>FOY7s2er8@G>wn+|^7 zK*7ULfSL7X41$@3w>2D}9c#+Zjyl+r_Iwba#QceZ?1g&AgW`j@AV7In!@+rsGoTv2 zH+6=S6aDhuXpNYACpSU#B%be#TVP&$0qnW>7F_w36a*v(N$b)8T2UVdA9P5b7ysFL zf8(_EH-J{v$8sWRmjT>D!T?B@-tgH@Bz|%=^i5Oe{?ibC+!-k}3ly0Opt5OuA-XOF z3;Ho+DKs?@=uqM{iLO3`5b3(+D~_BUqFqm4-rD&(GV7-Tatwla+M;#zQ$_(8ho1mQ z!Gc>1sF0#qwA~e+IP}v18XRN}k}x(`m+{v>^aD6{&lAfR1dEViBCDYTd3a8q4-%r# z$n-bww6%;;RBAm#QydQ0r~My*=9}-Vg7!E5;C}`ZC`_`M#ZUz3C=h@45I3xbmOB5eOS7(*VV}^~nho(UtGhw^+1skhrx2 z$ZPKOBSk`W)?lEBKLGLb07Og6l^{Xi-pfcqhXA{+F*;mY+W>X;A?^;KU?4Z8`l29^ zPSVJO(hAx8@(2cR6)&!-9`09H+nx#tgrF=4TK9rduYE9p2E3jL zU>$yTzkz>Kt#pcr%Jl1t_~^TS04t9kut9*W7F?r}UhB2cbt1z3lJRL8Tt}e&yJcTn zWlTqC@Hgho8uXjV>A<$}&7$M*45dSOx%?#-(YG8jH<03PFgb?5dX_R4<)tH~b|ige zMgR_;{5dSG70C<@#?P1Yd=pP*R{|T>c)k0=+i>MKy&m1- znti*^-v=np4m6*Ryg$E#q+xY_Lsdt~r~e7-U}r_Aj#--{4&YGafKZ0{lYU4jy8@tj zxrvf{RQtnv6=>Z80wk@m2qHNUkTl{@^ifIx0}#D9gwAmsQL640m0fia+k>XvDo`9nCpvYb96oH(kS z7~2MCe^hTxKhSIKm(<7jG(o_YV|=;4&~qM*0Nk|_4vvq8C#~w|)0|G9an7CQbo`nQ zb{$SU|5w8cKlTA^I6hnDWuVeyw8R4luuGQY#;gA42^?-YePsfG!wk^`@vPjuLf zV-$W0Obh3{bhijIwk|+Hs}Jy?I68njZfM~2h9MaXv&e@Hfy2eT>7Q}l8UzT@;X0DB z@y3mZHkp=@4_)&iK)G&OUK-m5sZJ&!5bS=^-FW`nUXN~JEjt??#`j%Wgq(*qhoG$N zPtLnK2AGEW+4l2lbvysB)sj3qphNMIzJB!(&}o6pnbOVc0s`6i$4`x;kr5Q9PZY9<{DMGMfI!f(dxTVNT1!B+EigiF>@d>+1-G3B zD2)bNw(0O`s=-7w0jD0`hrjuuAHsUy#cgJ4^nKp~J=1`|^ciWx`#uXU$9=+=Q+&Q( ziao=_^!3~WYW`Tc2h*HR-`97pv_;3yY2U)X1OTjJy?)qe!0|3HlJ6dNc-Leew~!w9 z06<>;_1}pDkL`(f6N3P8d7!+1kRV_x-8o>4)AVI#@YlDlodY!TRL3s`x1W>uPMb#v zgz|g<(5#R1aO{MQ!SiK>>Hz!e>1Z8z=rK<}5PeN15TYO;O;PMV|889Qwy#6CvKF^nhtXk~+$n0r4-+rw21Fpzg&~AIZ6;;eTUi@=gW~I2xh_GMyYy zgswP7kz7_ieBOKkpjZ45+q{)ZD<4v+ZK)S8?h9GoL1^8D@|Aui%g#Dml}mvu!9i;= zpdHFrK^EuUng&pE*5T(yF~LWX0@u6T9l!TXeC$WQADfFCMF+W$R*gmyH2u&y8dkLC zT7p3R%i9+!V`xzDka1JFW^fQ#iCqfxH!*pc%$0V)@oPG(+b#d!^Zo~{;j!x3s6%h$ z1^;X$aM*)$<^I>~o0Hqg>G2-{IP};%0Sr5{Sq@wXW?MnPyn(_5M?5u9Xm!TXG9z`U zaQSQJXhof`dMwqz&{&u!H7-fG@_|8~2dRx$=3i*u9)%Bn8(}$v@ipl9AVi-jZ|v0b zNCp`xuD1pOK<07RdH3LJ-uhZ}izkW`F=CDM_3L{j5(r7m1!qqu{PCcQijOyIk3~;poWwZ1mFtKpOH5X@R4lPec8Znz?-cpacVHO+a#b63Pd9RMa^z z>QE0Hk?qf?AnGWH-g9fKZk5mikP=))kteF+%z4oJB0$L$NKUy-W6c2SX#lMwC|DqF zOnIGZQB}yes&xQm8>0Yt$)EgQ$nK3&qdcsRBGmlHN}_*(eqA}$hwA$Q>ddt- z5iSL+><|bxH?F0_PEFkNtvG$=c}vOJ?rq`HvH~nD{xmk__9m`*@MqgKQb->yAmE{6 zKR*Zdxzghr0LWFZzYu3V_>}n_X&8p_mUI*c=7gE*h#k%$-b|S-1Sw-v0%P=aO28Nn z?iAER{SZFm73fCsm~Vmr=3i<#=tx39eck}SRW$1=R1Y5<5UdGcoFOs+hS)rvwB}O~ z6Tamm_u_^B;^kP{^8j?7KcCiz#$rH#%MDKh_}u`dJPK@p&1(b-zAg+40QY_R zVtnRTz6CgNIzKaS=;jSPDTiMxqkXS$l>-68^@&8w^y5(Q8CwFvAr4*}P-^&0Zw}A6 zZD-wZ{M$($$JyaLtjp&{;iu%)PG-E!VtCsi&26T~cLZSX=D$VP_&OHS{it)E0@HAO z;-1ePC`@t0-oa17I|ocvW<++{ne#9|^BBmPDsEcI<=;BEj?9BOJcv8H4+3Bvc6D-} zt2@)tH30nPZpxBT%VX2N{e4hD<(Vh1FZ!4Kz`~hFapgC^3j5Evfkd9#A-^#P60>Ag zi(uugTD@{!R96I0*y~`LtW=BEA0#JL*p)oD&rP7>VG-n2{Spv#p+Z197of;4q_qt> zK*{`vEFVc*1LJ>PQj4@OlB&nuM&E(+=Z|@zq^Jw0t$YGE3K0L(uL-iF&yVSsafMI< zi7C?nieI2Xg|f=eOWSdPQak)qk<2cY48Uz4d@k<%vzG$<*4sLqP*@wpPMcWga@Beo z(tj?;&OX}*;<442{4`yAuBoOolv?&?t`z8RySN-rp2l(j9^H6uhM5Z=bxcMuhDT+$ zCmfa=Uvgv)ij&jh8vqPgf_nW1{J z0~>ID^{XXv5CAQgc;MlBmJ2{eB{*7K$CPPyB5);Ky)v)Kf%c%WaSC1(&W{GPwgR?> zOBFX8td^W;$3VAl1y{W7Eja7kzsf~*TVNSXCi;m7UK1c9IdZNq{sApUUgH0u`T@OWz65ef_!xQ1~q_!RUyB*03~hNC5=$z)Vq-qz@Dv zWf!R8R!nny|7N6-L5TH zuCIOXoAKyv7sP{tk_@x6@Xk~(yge{|8Qd^2Uq=YC2>6=%GMCH0Eyqf{dHXu`)Ih1T z=1b%&b_8Dwhob=i`?|}qmY`bnsyr6PK%p_d1;By;vhm9!P+LxqTL2(0dDGY6(8Gr- zhdk54Pl1^d1k4dAWSsNT-3H90I*-($g}c=u*$^0GK|z2y>KArfVHL0_{sAMP#(@_T zvVqb(f0h6njNaw5to&#Ik9g#ug9=neRE;5fFnt)zj)Aae1DCz^+wjyYei}fT&*sH) z5(Lue{C`Mqg-{iV|Mr{5D*$=0OC-+4(}6Q#nT(sl}02`MgS6g7e^iP zlthw;AOM}D<`pZpT;IZ*UB;mY5b1}DG!TgL($fmd3uf9~knt4hSjs6VFpjx)^bv*v zh~O3klHA$(+p>}ZlA`m?;eDEJ;O;TD4uJ6~s<`;F!(YGaJ8^nt7wrsCy{A4_M1>2T;I(3^-< z7pUL%>G=?^RX#B#0G8x610}bW25PzeB(HwWpWw;2zrIkALM^0w!+}_CCc(%I_$e@3 zp*ttcw)oou{gD0(F3(>}qTi*C83M}pTe)m#)ATqi-U8)Iq1llEu5c7XT+yiLB)ka2 z^oRJ=G6BdV5dInQaq^3otNJkdZDuoz$WF|GBlCtO#z^*eNz=6xI#jbPj#qt?Pv3Te)EFL(Hu($?jYS^}cg){O%{?_!pm!QxBbq0HD_q5dn>kNM2CcL$cHD zd7&i)z4Vc1>Ep-^0Fgf9L|@9Eaq)wA#lQb|=uR!cM0k4P?SyH(R-;ot%6%QkQOZ!i z;%i?nKbHbf^()V}UW?;xp5)-S>+lEhOyui;03k~$7U+>kFW|wfb=otQjrHxd!6&wux<{*ZQB=ez}!Na5GFVWn5|^V3ZaONo2=OJ+9I-guxJd7)Bt*@q>4?L zPXP9n7y0O65YKrEpqI=@4jk0EOI(Oo4zkpRdCBSL;G+aldfDgmAL8$xBe&yeKk~y^ zJaPHXhxF)%(uEiQ6v1Lx1&$D9UJ+V>pu^8hL`6;Z`D;opSn3TvK?Vw66yi7V7+FCmX;~X*w51T~esl>b1una3~y@A%WYtarHQ zn(xAGH~g=Oo#?sCe({hTx)93jO<|Xv6<7@-x}c6?pgRQEXT(~eXLTgF=UzmSMsm!- zHjmO0S1#uVF<JvWoM5@5P;fX_#i+Xzv8E@UpCMH%qwt)rNsA{ z78#U);v|@FU%%|QDKN=+$+#O@XxE+jR?%mCWcbaCrC{~mi*Wr1e;=n;pHNn(tHTV1 zm|gni*@|}}(V6MY2*3HqCKPKgTTKm@mtkrPIyzOL!}0;fu@{`d;O!H$s#?mk)?Oom zps*6+*#x-qT7hpxsQe||OkjYa4d%Q#IC1AusXbr0$LB)eWKl=H*F zCSL!({{#CD9|P7~j+1z+bX*k>u3Tt4U!Fdzp8I^ZOGMc4LVdaFgN-s!yv|VUq|it7;NjCx3D5oXoumBDk>-@d>?HW{4&U^Lr?L0s zqNVb9IPk4F(5=wjA($=KVNUuU&IAFlMglQLPV0s^l(RXNlmvPsN~U>uh3Oc~uT5a;jdnBNo#NnV*Q`9ZFxq|+4v zgg$v{4Gwzi=;M$6*3$rbo&gYP?!EYCJpBiM5=$2xFIyIT5YPwW3l1hY=RcCPeiDF9 z!RlRyasOXGAD{odugBWS_|2Y6a3F2j89q~mYm+Jd`l;pqUJu>x%DY2%z31js=C zgffryzaO6c$sIcvVCMsXm%QnF@YI`rF=Br_9QJ^d`^Oe2%tgC_YZ85jYm85RbN8Jc zKn?=3<4-tR4%!C+Y9u;mpcppO4a5)Q6*29Nn-eAQyXm`>jQ zY+V0Wzm1L6vxEXu ziB4BSUdJHWL55V9^kxfKUq!?H^;~~0ZWh_aGN6DV1{!@D9f-cBOatVhc4WpN5S;tk zKfsf}^|ulBwk)~8zU@nvedIl4TnH$m3EU_KtqavQ1*>-*!kvHkGTicquR_0`!of5J z(4$94fsO_PB0Z!RJnJo=z~$fm2f)$YuKy@Mq1NNvc%cEG|7+{G=|@zbvEh5BU*YBc z$K_P%8+`EklC7r4)A4gUYZVAfz}n(SXKNrGf_17>o;gO2_m754KY7Ls?k1-lkho3q z+E;%XNA9|!Q23`D1Z)9j%lyqre-!rsTx(IBanRJE&0D^_$Fu_mopyz@VK`dzhz6FPseu68s(nXakoK+z7=s3?d!AfR&zd_P2wn}S z$EOA=G~g58tM^`r8$SG7SULF==-WR#6txLmcAv#@2m}iyG_a_}wJleZs@v+&OrurK zRz;f6^5Q^kwqCdR>7vG1xzLx>1P#wpkOfhv59km;2mlF-*FaVFn;y#v1%Xm?~0L^s{j8FNWwJu>J;Gr8{i<>_2%YZCH04(^G zeK|Pj(b3R<=o`)-b=qn`Fcu8dsweiD`qC^i-R{?;oYAjgjTjM&F6;|}YFHgY>jhF^ zkw*Z;Y$pT)B+a3R1jpwIh*HjL0+6Q`06-Sjap}K#7Y@GY^Du8{E>J7izBOD>lb^Lb zNGreKlNI2;Kf40g{^B=b?c|c(E|{hXE_mS=aMh3gI`GJDtE-jcbSdjFC z8OYab)m5)RzE2R0c1(|}3bGmJh-=y@$3K_TcQg*I0gLMZb^+@PZSu2N@*QqwxdeB7 z^ZszzwL5Y&@D`*toaAd?|3RF0+iMEuop9hYoLtkvZH4X*!pvKTt=40^aQtho$pU7F z?ScUBgaK4Tdt6r@j`yqVm984LY6=0h4B2=a#~&KTal&uYKuW{eGo4mB$%dLIBB+>eQ=l5KlH7BLva6xpM`7Q{ViBuS&W?aag~6G;N?I1J2>*yHvy*>Q2HK< zp69*$)R*{|`$QEG1e>4nc4+S?AjFj|Bi_t&l>X|UXZ}4Mu?Tj`=?~%>OAR^)H-M$n zK(`2-ULI!0%a)lKnj#p&rLb$#ho^nAu?KPfv_%QFlf3cjV>tZKfg1ZPhq%okV9PK& z90ZKgZsD4OyMb#G?ul^T32N#Ff`AMk3O??qgew-l+jeH(?Xl+N#S;68k zG@YkqBgy!a&oXx1^FMgDfQ4JR`(oVqk@w@&iDyRVmELx{R8Amf6d+coZ5X_|u@p?6 z3kb&4B0iUqXUZoKbm5(8=AcCr?dUNjsIxAICB-*)>*z(w0?Bwy+a77nK^)H4Arb+P z67;FgI^?f-brc}axzpCcU56jRSN-GklB1HNic9^n6_Nb8msHFXRAV&%smp`v3gXSX^1MU)l7H)CvOfwRg^JXM%&}{DB~# zISS^yT~&^G^Y$tX&_N?@(QWO7<5wI9Hh^8HBh1Dgs|zhORS?j!^Fw7Cj33{8Te$4n z-ydavLfVli0s!O%Z@(1h-Tr+5+RA70y)t?QY{5@~*$Uk)!pzMdEX-v34oSWr;d2a* zc?}Mhm9*LmO~Ah1K7vOm3+A|}4B+*K1C9J_bOMznxZvpp3vWcwl2yWA)deotu%amS zh3%)*ZcB%b;=Id#3x^N(k9SYO$Ldq42cu=l_{*n7!c8J`IPj1U@005goix@r>jFQ&mNz7TNeS@+@6w|^M> z51z!Au6;Vt2dtl70#*;<+^aqZtS;LA==*m59kC;Nt$kPXT24$A!uKVvqp5E|2}B0^ zp6T}u{+h`t?L28qj^EMoIF_ucy&15YQX9SZ<{ z{Njs$24_5+R{zgZz81`YGRy@tqEv%)C%|lrKaXo7{h9Ka%EJsewi#@sfLd1r0cXR- z1}|7g*ad3{Gu{#7)snl}Ppm-B_ymP@My_RSikB^TeDK3{WPDW+2Wya<^$I{I*nRfh zc+#c6g5~Z3Jo4bHGT*HY7MxL7WuEJRV7tLUaL5K@-+(xZfj}w|1yN1~NFT%DKv0RK zI*`2{AlQy?*Gr7h&VUy*T{5&%`6%xvqtH8F|S1 z&Vq453jSdO*1X}B{vH+s4qtu`F8!vz!1|XD;^8lxhlf6Y1Q)#I2JAYp3T&ve8PK^* zJJ^TJcQZMBtoymXUQ1hbbg<1n4GWVyLZjd1{yJxRJRHBKMU()b3&5sxrBz4PPCihN zX0{(Cp|mMy{=|Fl{pkVjCZ{b(wVmX3uecXy-G8Lk4POay`s0T}8kK&$_v|yw+_Xnv z9vAvM2&d2dzIwPkwj%+^C*0_DUa*%CuP`mF%T z^~p!<0_B;$r6;&v=_Z~eeUEVO1z*5(e(Fci9okHlQy9{PYL`GLx?@ssBLfMa4;D>5 zgA;CqKXK!k_|W%%C-xpbjaR?tXAq9=vpSdc*7ci8_r;adYq&BETcx`1;j?od>aWbh z7;hwp(4mcQg1egA=^NtH@$Bh=y({s$>wvZ04U|5L8761*8#dY-h)n@D!nU^qPLSyA zBv1Jt590-&egsSFp|TP~j&7%%?2bFw9e~*ikE3u?aL)lZ#7CeE@0?W$jpK)N{IH73 zy9$8%gg|z(e&L=@kTZ{LpjyWaw+ve5wPkQzex)Y?m3-L{Ry2U-TiKAog<$iMgZSb{ z-;D?FdlP(`vY+4N8F4k7`@~>iT%}@$#(?=){UjI3Fg9pFf)xQRdQ^c(zOEJkCM1&Q z*wd>296XeKJz*~vmx63(x%FMzIH;%y8wznb-x=- zW<$y>5dX~O`#@v8$F2Y6YAh^vc*-lT0ZuFtxYJ=D$ZBdj_13j7L-<$@2B+)GhE9tI zDP@{`1XM2vzP_BkQjTBKZUOe4D);^FG7XUHHh`?9768hN%#?BhJi75S;qp(tb0Wuc zu`*YZ7r*sK@syi>!eX4@3v`eWNR}~U^-CAv z=4<`~jy-(kuzlEKKrl}*u+>ok0e}$557J`303e-sSBB|}6W0TiBrlKz0`!AA>Zp+x z4~G1vuTDOs3Q3AEa$rypB$_=15Aj~%dc_9-3+s5^yT1>6pY;H3K?FHJvHGq-NERK- za7qy1f~Aa$8aeGmSi64@78g&Udt^_83^4VZ|3zo^!Zx>i5CG7#MC7uIrPjw0_@06 z$UJMEN{#*JngDDr;@W@ni#YmMmlkJ)NV|DZkTS=PFzX08cYn{i(9#oboJ4Q=+!b_Z z2jHAFZs0a8^QV2t>nHQW_!_u+E=T=8;}fcfQ!uahOq0u*`bvS}KFxKVoXCc7@;q%d zh0Sdukmh9#L$X>9PueEYneoP?B;WAjlQ{6`-YSYjx;N1q)vgIY1;*oioMGnX&%sQk z-^4uu4hw)R0$~BrRniNg0f*G7HWJQ__y?)s@^F1{eMjtDxRwo+mEVA+etTv(&f|rC zpgjHradl7+3V%#bC_fN9^Ou~7gc!rAy{074)f~p897MF-wZf@`xW^6 zkN-!UUO7?~ZLt?ou`zFur1AzHrP>uNFnf9+w;zg;hM)|MtFWZV&8S;+tTCI86i#p7 zv8mGAJNc%ldVTpYP*3IcP$AICgLzFrU)aru2T8K6iHmgo%zynK@WgNUn60!AuvK^h zn2~^LDI8P`Dtz@w{A4$ry%o*CQ@ zrdiUPndAj;e-bYK?A=yD{s{*GTjp=8^jmPgp_c(H0*i~lF2n^W`#0ioln2(zu=!AD zH_w8F69A`{fK!X{8{{V!fYSg@Zy+8#y$XcB;l$Lr2f8Lc0zKoh>@eZ;8$p1|qdB)e zU?BM0w8kirT+MJi^TLkVw~NwA`ThVRz)fK^@EQJHos35XWMcu3eEt=<@e}VsztK2e zLsLtI-5W*yys+cphgH69th}GgzAOcGd@7(_2v>&vv$F=Wqb%ek)q#)pO;0jVpWxFM z472l>_(BaS@U$RMdNI$$O+832c;>(UC7k=lkCgV(wF~UXr#Sn{@i(qZ%^W_4C+D{o zfID}`uB!b^=vq^&|NDlXb#$;9sXBTP!n|8wSE1qQl z2T!{EUBKqRpn8*~M#pTHkvd=7;gQYvge$K5-dQXUrN!AGGt$E8Z?`=Xs3Y1yN(-2U z4jCR3U_4H24+bcm3)|#Ram`KN$){fe7WM#p1vtD8961T>Jyw8+I<-%{@U41AMFN2R z>oK5cq1#*p9$AimCj>aX34{|sh@Umh^ZU9;OC6*VGDg!ez|K@<<2$JuCP5hi5XuiI zwACS-&%ctP&MWw(lu>7-c+`QL{qCBle5Z0f^R9C6#!ZNSr$@cm?-b~P`>BFqaT8}< z_90w-$+PkB&0mX~ulspyt{*faVl~Sm2h^2Dm>^ljvq*DcZHADp#fZc-8*k6ci|SGx zL!g8HLe!5CU_Q`8`da7&=-a=3Lb(twPDk-(HAzmq+!w2uSNJ2O871m(#6h1CXNiRaMvzi zmG+`n_|$f}KEa)x#Sn;hu5acC4LLY_qpoachwx?l$^ebzZOT?t#uHq} zNU7`N7R?qLnNJzWA1c$J!&wXc*Nw3}D-DsJoaE)N`y`IsePzT(oEamSp`8Qs@Kazs z&Jma$r#&ZqBVCU><@LLOUAus@)`4?R00&o#a#uJ)-C~2VKotORM;XK8ahc)j5To1& zb^#AB0w*>AISF)|wvJ|)FW5zLeAI6Ro9OTmpqWMpwY-b%C;FN%gwH+W0L8awOCCdS zBTPqSt2}k=WIT^jUQ`2q1fridUh0$*NI47mT>7UsW|N64anVzZh5+^wmTE@t+jhx@a)L$L|v$ z<3j$Nu`R391kd`8_u$;G|5$0m{M}>ci2QX`Z;Sx=78Cc(wx7|hdzOI{3r@#aS6Qcs zN$xZ7#EYRp9x{d(|HO zlwUbZqN4=Z5M(Di;KedNy7tou42z*(SB7%(F>u zCCT%?`3RnN{k{7A?_npp6$knc;NVRI%;K+w%XMb%fD3zpvp0c@9|o2;o#a~`bilz> z9n)QG6;FQN!qcD`LJg}*e{3lZNj`QOz=?R?WNVRMUR37{3Lhk7oVGJAI!aI(h){3` z3~y5uhpRQRU}WR+yz|ze!{hKUgSZ==p4&GY?Iex$4v&2PmALWRU&Q9xA#mEMV8C{d zIuKwHX1YO}I~j0e(9&Nr)B=g0My7drMQnE0^=WQC(Dp&may>#Kn)mrD-Q@5gl7o$S zn-&!+n0|&gZ512}07QB`?}vXJhhBIK*>@KNpwP+EGSi174sF-gkn)hTZ^CF}1mND? zz%l+pos@nwWILR0HEn@Wh5@@``Xtpm0QDM!Mn7-z2f{(u*)Dl|-4fKqG|Ta8Y6Sp5 z9MRQv07Gy5W9)H#e7?~Ef_Z5Dr^53-^CKfz$H{Fq$&0@6UAW+;e>=_5&gnp#I3FjN zE%P^mI{?65;Ori_=wV>bI!52fYm01=R-J4+uv?(%;qwAOZzPg&*SYP-7J<8#f#WF{ zFmL=Q#xJ1WC!gitppnUwg{-Cu2)AK36nNvDL*l78fa=@mf_n-J>Y^)!yn{|$0U^EcO`8E*9Q^Kik5}-=}(y)CILwWb_A$>VY zIDZu&diyfEuCYRXi|;L67dWW%mQ?TZaUscb@Z}7Fo_u&uZ~weYpidtXQv5vxz>EI< z_hRqUA1Xr0PMsW}78}iPT(fbnP6t$aobQ8+0Nl4L_C2qw0WmG z1l!it`i-BIr=@VF|GWNR@OeIbFPM=s>WSr+rCtOG0AUeWqos92=$iR5n>@=*(_?F& z3zvWP@);$kvoSTvOW*LfIC9quD#m=nXKMclf`D!F*TTj07lGy7z*8Rs&NWkG~IV zYiApA6*9xYfUQ;u1S&X_FS&U|^Me4A+P{yHfLWTC0P{N492lthbOpas%uxU!Qbqw1 zo{UeQXMbJ3HmJJ<0@9ZO^V@%)%OSx^E*B39Dgl5_Hv}M@#!G+gJF$4qN|6)my9%lm z1em09cKT>=k{N0t zq!6wYD%}rTj{%q+3KX{M;D=zExb^@~=z*s{04!~m!rOAj1hi#XN7Fo?fyd(-nnPD( zpF18<=E6e_zup12?*Se<4af-;hfDOl0cZvRK+~wGq2x0JfW}ruFE6nR6ghvaOs;#d zhSkfNjg&ZiaOM2gFD~ob#~4C^tOXps^`*G+n)hIR{Y;CV($N0?x=YAFKAf79FwpIBnJOYueQV`x^m32*CO- zmj~OItNcd0HrW~o5Y~DLmt41{L;bc`dqAG?#sm1OyN{vU#B68$6$k=$ls3W4q`wb% z(rMtS4>T0ecMM?E8CS3_W5@9{U_Ea0EHq#fJbxrdmEEuQH}HsW?)NVPclE&faTvp) zy<^HjLQ0ek1a~f;TF7O{I8?@HhM`p8&Xw`TP*6q^no}RdH}h7>&*f*{qz$XUn;jng z;)`&@C*F(o(`OIbkF6a4+v+Yc08l5y3=@8Gyec-ySbADnpFqqTFFS7JfFM4RA`hvZ zKJNt3IsK5~bnm|g5jv{S=V^vAil6~N3>pqU<3>F1-~ANA5>?9xgm?`M6AEul>HGYa z2jMbeL2^J6fIId8t7%PuS)}Lsf7PgHJWsT|PF4%{NDr$1MZbO;A;U0lY_CI&YcvRG z$fN?8GYR5ZEc+S(01(#!^bPz~miI$r@GW0Q>7mV6h39?hF9t9TrmY_Ze0cAh;?^%< z^!Qt(`$;f_f6DQ=G8OMc7`9Ebjt35Vfr|mW>`v?aXQkF+A4_C3Yw+2YU?=b*1NkOf z*pe5+z()sjK5xr9cMZ651Gww}u;(x!i}XdfV2|?Ewr?G`%Xxh%UXK11)a@HAj$EIl z551Rz>XKMGTt=Oa_;SQkPqN`>45RV`%JIm|Yo7qsQ1UUYiey1>_?aKai{Jj$c+QKz z1q(}$RmcBEQA|JnSCRp4c9&3KY#xg#R<6}-fbwC*3e>dnvb??X z6q=zjPnsV6{H3_#KfT%MzUn&#AXO!}05d3Ucy{tW=|acczZh)~T(|-(rQpgm9_gy; z8#fH70S(`*7^L^Xl{aT_Fb=xTJP}P}BM6uFy?&rC)8fs_Ze#)ZNMnw~ypL_^>->Qa z%m)kH^VfnO0I2Np+P@lRXgF;>09ammwd>iIpUTP3fboVh=Qm*H!8@5Y(KlrruCNO@ zvH-l~PTQ0l)%q<|6P__ml=Uctd;GNiV8rVVkgX(gZN~(EbqJS!)Wa=VWNj+&1xP zh7dz^c(*SDUtR-xJ}ZIRig9$Vm*io8DF!#&`~Wk*n4gCdtOPXb+{$-2_&IOutG+0x zbr)Bbkvkr%_EuTAc49rwZBlM!Z4vi>?#;OAQ$GtxSJ1b*3(#CE&{{C4Tl3<}n4k+? z@)dolUvi4^41iR>G@22=_E**ha9M2@Ws=tvNSZbf8d>5kei2s;AIbv2kl-{@1oU8_ z;$$}!orRT<*A2wyJ^HYTSN{5UVCkH-B1moq6`r}K_G{+DI%k7dogC>1_bb(X&fn~S zFYdRrGP+=Nq}m0L;l;vpYT@%rcEH_tue18FQ?IIsciT5SXA zfk1{#%YV5v1V2_r;ZAL?hD$%aXPDs$BWw-%K`jF{cxJv_;<*BMEC44@ z0R6gcFTbMSpC;D!Shfy+<#3+22jwfjJ^=CjyX(%CF=a!6=d%x3jyZ3@@D3S84R|!8 z1F&}q*s}!eUIdmGAR8U-`ipPDt$+Fb;MDA9Kzq?3ccswN+m|3{01)@rAsV5A-SXwAJ3KKAno((+rg&fteV~?__0tpj1kh3>1Oas8`B5?cOwhnn` zAE;mx-ov_QW-Bh$p}FQo>+5Wh;jLx2QTNOItGHD5)Tf7B-P_&fRj7b=<^2LrT`aTw-^HE`Yd7N1IDwHyzv_a=B`v~h)Ks0{4 zd*F#1!1;?nw;xzvif?B;44C=xZ9A0j%BaH>l|?B1*YHOOT5nQbCK;cD<)A`gOq&I3 zKLYXOX_~sGo8<-Iz;58|eZZ0Z!1;&b^}*f1-eq9-BEsGU960M{-0_ib&t*;bsJcze ztVEcbD&fd%M#!=jj)Y0bWA)Qcl4PW{>5;f%lW0Vicy1j4$IZs;@rpx;0Y#~_b1)Dy z>W+~2BS3cz1cd`|`osY&tP0L}?oF_W$T|DAH4Fy}Z_3Y9ve`)hEcU>n0LPb5>s-1p zY|xDfU|gBpXr3)rq^8o^`v91&#Y1#F8eNFo?BgC$K((7@s^eE4c5eU+W4?BQz6&yd zrkOv&NG{WW=>l;38Q1^BuYUQa5vJ;5^8lChgcqAzR+P+JKs~qAf|-JcVF<5mhc;@M z)|~(98nEK_bq=M;N`(COFbRnaZVV;nTpO^}4TA4GjmA7_02B>ToE}2iN{kziiUbh>Php z=$KzPd|VeP<-qs?;TsoWF2{}HryPBn^}LzH{0u!FOaX71m*Um}fR$aZ22dNqDNc4? zXI&}57~V#(Fa%R6_5l5`0bKuN_k3at&7%WbhLwS`1Io0Wz{ArW!(%$VZ#)hH8U~U( zdtf{XphsNL^rSW5t0V>k&pHg8a{$=C=)FL?Ccb*jX9L1C^qveguaGtd%GK4|;x~$3vN$zA82(^+bL!xq`NVbRbj`bd9I^`J z#cj+~F31im{q@uznv3ULne4zCHX!S7qkka)M^42f$y>UsG=md}30T?L6EAQ$d>_j+ zS`MCJWB^8xgK!qU51Mm14k{Y_jlFSap*0#m%m}=6o!AUB={i1)&1)4Jv)_I9+;OJr zw6)Ix%jT8knuMr53_x=Z%fNWWOnI!NbK_0r#$e#+=Q0@P@x&_d*uM7J&%VK$4Jbr| zvjt|HmEp~pn);EUWifw)~J}9uSAi_#6Qsb}~I=$h2J#^j#UU7XW8&0A~US zyW>GaM??Hl;p!@Iq6ao(v5E!=uNt?G+Fsj&byH9dCgSZ+TD=}cf0%b}*Geaqj>;?* z+}*`F`@KuRzVy3mp}=8CPlg(@@NoS4i*WqmOTnKDHtbU082U_bWHooT(&Wi*M*pLu z0b=cKC`0?~tfhfBfs?q)>4I7O0a0JjXYrL<+tUcOkf)#h200}Dyl^fN7${d10I;#L z7q|S!e}QLw@9&@l0b)N5oGTJ6YV8xkf?#-|Mj%<{U_kYO9UkdctZbz5l#{?M2dIy6 zqpE(B9%p@b_=K33uJq}lK4wHowo&u5ZRS2ffd)|QiBZb*yjoOAZBz}*H|%m@q@4t_ z1Axg+y#YVDj7y6+f6fwH3;>okE*W>;Xf$@q!2_kSQ{HwRJi{2;7_VpCeq8ws zeJhX4@(~cw2VK=4p*Yx(jK{d<)SRW9gkTQQ>$5wLx>N80v@|ec4g3scfgx2;&;eo7 z%9d=FS%VkiR}K}lJWH_Y$*muJEuQ?kKf~Tj9!B~;GPNVyAt{H(nC)%s;GBKaH((Ql zDBrAWREGPJR;KHL3r_-f?1ypoxgpe=f0jIqafeisRHsf#Rn`9GM zXF`F=%qJSl9kA5JPFd1Q0;q#{&TAB@@Ue_wh4Gd=>PIbmr84k42Mir|U(snt4I9v`!an0lrz)fcXPdqxK(O~53rda<7ePLG=WX5%B&(U_;od)oRh1YlnP_N8-N^IN$yT$=&dOio~v zbS@hbL|lr05wQ*n1FMOJ<2x$ zbebVb#)}c3Y=L3nn4_4JX_3&Zb`zKf9{5O4! zoGG=7xt2OV(y5wh;6emWc3f>ETblgtC3jA(WT zalIOLqowc0cC=)s^pZeTj+uKOOb|Q# zau?_0_bsHk_{F&3aPLC=+uZ@XL;PLrQb6En3eg481W>&JDsNe4bS=`5nc{UaXL$hF zxNk46`MuvrB)5P3Z%9sE^ZHJ4g!ls9wU6p8bM9$pA(U3pY)18rVOr0+uN!8rPV)x{de5aN5j z!+pO1$M<4p8quE`c1$x`Suy}J15af`c98Bo_4W9&lLF}g87V%?zX}4R4g!p`L|sp5 zJkz{P55++0Jid4Yty{*|kQW=D>Y+3CTM7b(>)Sut;Un++UG$r~tsQIy0`N9*AP`9R zy>awqi(MYf?*e-0w!DIeaTmPBnQMj1fQV!qro0JGB3;8Hizi5}ZtfmH9i);trGY;` zqmczjhZ}zBUpXC@qED)~tXuvlXcOQ8*5MNOLihffon}y6Qm&UafOA(09d_0RH(ztb zJ>Q}99dY@LV+swx(#f=4AkjJ++n4i^owp|(U%!L8b)(E$7UY4|MG>o5n}+ zwSoX&Un;|ne+V}KtN?%aYrlY%ljmC-NyYi2K;AwN1_ERYgH{u!0jIn!%GvW! zoId~vn@_dE^3hw%!M7YS!BkFp3ou^cGMuNqRUQ=%aVp@m7okeNU7GDV(8&K3Mge^O ztolyX=T<>BD(p+~c1QT9Ztj1CIts;zggAKq0agGOvYCa>TIBsB+E+5e1i@hxAJz(R(}K1OPOKk$hhrTC$nw^n6Fftx9;8ry`@ObaCJ6ty#N=Sut9~=){jx;$WfdwhdRJ$ zUGqn+xKHMF4#ABYNWotZwUA=cFIsX|MIFe`O3u%nUr?b<{@4%6sPU+oA+2Ya{S1h_ zE-p+Mo-S#w06^aMwgoJ$pEKLZ4mi(In5lt+SAr1-Z)mrIg(*Dx2sZKX&^mDc8Aaim zNk(r+lZXAsQg)vD`uqwP=X-+!6xj>Cl}j;DW{aIOq^-`!24^NeL)9Rdd4d2F$Pm6j zaPtNM8HW_wJ_zUqcl_}`$L%+MbFpt(t?gkv-WJCE@j#$QK%XY*`%a&yq5m$}FDi$^ zUR;yhY&uJ!|^ z3S>I$vB{G7C}$T0q7d-Ntrz3OjYrT-W;kApi_JGFINgHn6W%%J{vfC-$AM%3u($yn zUPI;lJieB*@7=W-9YE>7tc0V^KDd0wInd8@IjQ;cf312Aje2BZ$@CEbRsKPH($cV@ zWEdHKzPvEk@&5CT0GztxLO{wXBwp~$%cQsQ2~K$Ox`0-&z%X0naga{|Za7>Y6Fj9Y zXE~rD_$o^VKxW{XMOUhe;Z)(8_nvs-;qL4dT@b*AD;p!R^2qui9rgM0&$UFDoeOooQ1~_9WwC}-`tZkPcjdh44e@41p4|wDCLVP z>N#@|n9@TWNl>8(cLq{lYa5!F_y;xd-o-rR>&hsrJfBCsIa43ubOHPKf(%_N6 zjW%pOp7+^8fPDO!FPqI+O!EZ*i}Fm%z$^zp1*U0j8f7Lp_|{u~6W0KbhW->j*MMs- zNLxK7Hsl<-^AA?m7i{8khGYzW9tIS*F@zKZfPS-qY|zbZra0(ffUARZ7TMScjXZ#O zBD|jYP%e$ponfC%y|@gIv9lp+;ou+uz`Y-OB|i1n??zc<7lsGX0`l;CmCr1?K!E&X z5?fmAm*3Xu?H+(62d$rXPzPGp9VFMAw#=D;AjCptSUznr3@WDrXLbd+cILx^%+eBq zGjf0~Dzt$^4xVVypHSdxW3_-|_n(7fe}A5B=hiw97J6n*+m>{x^+s(P%P1kGhVo^4 z63ad5M4{p7aPXbWI)}@;Dx5zd2xyI9Xb@m{@_abOHb)9KYedkoo8#+BxUA4+n`VV0F^A5KvN3!wtf)Gu{L3Y9L z%=QFM(W!rDd?9{KaUNqBW`neolUd`jyC|veudFlMX}$npbNNN3k(hSFSy#g+uF+b~ zdy0dv95<-Z>3)buqcknttzcyapT}2#8!o_L@@+JS{yeT_D_k?R&1J~|h|EvB8Gy8U zK(MgdG?Rh=vGCcrX_-(PP{wBrvKm6lX9EgBK2kWBlTt6H z9d1r-!fMw7Io-Gxf$Jru$x&RmHno^%p();&()xr<$2A%Knt%Ynqjw*{iCfMgLAwF~ zs=zbrDE~U9jh+zB)?saixVR}R#n%JppP)u3B^ev0uj<80=LE&kqs|F6&jnzo4OnNM z@R^Z77XVS9Me8S4MmWe3j)Dcp}|d z+4J5{dN{EY3>mhpYt*5tC_9x2z6@ZtQSmTh?w~DwXm|n*0+?4e$gLnC=clq&u%YF} z;xjyq1OWov{oh}QYyR>V;%#f*UPWQL)c9dP?LdGR+%dB{L|+#4#f5^2pVTfnSOBe^ z4wAv^ujFm`Qr$!Ak#M*{RZn3Dx#8BvzfHO=e8$|&Pb`oL2VEx6D8 z%C}AEJKV;AJ>@Yy%R#K_t`q?hnk?AX%c~GRt+3uKuL1kkT_sc@h`+L!V@SbBSG_Yw z!Icm4X*qioWvraNv&F4NgPAASXAR$6Po|j#19Th2E6KzBQEHt9;1ly=Z)Oy#FC&lP ztO*Dr=u|)ez|#6TR!}3(a}Z{V6Lt;S#H(-~#^IXEV=JI(!l40|(wzagb!esUjQg?l zxRjNF=R?RR-BUfRZRr0eVy)d(f)6VP6K$Ne$Ci8QwHFr8))&=Wt7j&b~aoi2P3 zkPW;vMleSBXa0N;uqL?q_rDFF_~_46U5EGg6ZOW+)wDo>^Cac<0Ho{ER6s0TmeNTH z7(!V%m~o`?rM@bc;HG-ZDtmR|XHT8}|z&-s8=Tp`-*?7a*KrV3cnbF*{7b)sIPx9s}zOoK1BPt{+;rVXZ% zheMBA`HS7|F)ExU2LST&moH**eNWqYw;ZvD!E6|#L>R!^3KoE>k*D%FmCuy4o7uU=ljPyISMeUbTd-L*EVU-y7w8`;E@A1K>&)Zsvw}ZIu$^=(bwcOpY_VX z^u2#fTqD9AR(@XADN$4+i6A#0_0SOzxzLb5}*6b_hpFcrASL}?>K1z3`LxHG~OrW3vfhcc9PrvX7Z!k@197#cj+)8{hU2ZrRlvcXM zzmz|ilJYXXg-zhhm7)knQ37gb4|3Xq>~Ptk@@ND%#-OvO{MN~W+$XDYt|NFcR8zb{ z{f>fx0AP93+8@52@}@tUrrK#=5z5wQOWove{7fGKSnV$l)ZjB~=Kk+{r= zgEzPcX2+)lT5z*?P`FLt@Am+=or@|}2EGOvOtZ?4;*x<|X+9o~TDPL)LuCf?$H4g$ zoE!u|V1fW+AbN)bWUR#I=d4B$z|ZK%1_4qVWS|5A0viNyryZ()D+tK#6VM-9z~B7F z`*8c6ZzjjpIOxZ9wXK}`XIKnKbN{Kle%3iT;~0x2M~~>J`ylxwK#qq3;&efub`I#O z`trSiDCEE=2Bttn^mzd6;yeB;0_+oj=4H`B+Ry?f#bRBK*_=Hz>?Y+}m@RnYLH z&uE(lOPG0vHAgjATfNmYmF^eAfrobmCa z2j-1HYMs{$zs-y5N zB)=F%5$i%g0n+Y(kZ>b(&@lj@1O)omr#beZ*g6d!%87uPW`fhEiA`T{1GLc3tmv0q zzh2v)-^z0tQi1^JUVoT+yTA~D1{HdZv6^8F#L7FP zLEQ)1fwBJeC9g*eKF(aF-3wV*sNT>sf2wSu<}IHps5pGR>6t2AE(n4R@@a}>~2v<;(=y8t|R*NZ10o@Wh!G`0YuG*;%* zH|T)lB)%uXWH_cjBM9*dWpEGRn!68Tl=zvmM&v z9!@GhfqrYK6rj#5?#(Hc(eKI|S}q7M1`^hAa1a2X8h#xL0)+84@G-5iVMMVwkA3D6 z{Nb;C1jkNZWK_56iUHtleOXk^ZZiASKcqlF_qq3(j-Z^lSGMu#!nmHCIS>#!S|pcO z{YziIZV)}cBo&3>LwP(mMxB4c?lX3WhS-Z6QtPk zpZV=?v@7bHhJK`@WS#QAVt2XliF8Iko$i|pDeMb?b55eNk=e0xez4UrL-P&0n@1KL zZnh`l@GEbIHU%P#qh}bYX_)DZhL{y50O;09CSImazMNWz0nP01^W;n8_Vi1JkrQiE zfB*nXYfrLPh3U?6@N>cl!91Q*0xc^z`7M6}TmW$Y2JnfeHaeJ3z9VXOt8vHhv|zV* zKc5zEF>L{t4+1Lvk@EJEvP%%)=0OZ0Q4c+pQn}tM%)lN9^h-fNr;w^ukB$13Ld@K6Un?$V}2!ztEKt z22rQnWx_n1u5ab~=3P$T>kzjeM!r-orOqWd=xKcbj|J!yL&$Uja$u9?LJJb9kCp?D zw7w2+ZWqC+M-F4@xXnBRvGO0dT*aQx&6&S9l z=bII)$+uThaSQW8q2b@qjSuiPK$TgnEzZ1Y@IbaYKr0Y1`P`)_5K!lz-~!R= zN#(Ulemz&{07q}sz)7B2oBx@R(dYB zzhyewCj>ZhyzYZ+Q*+};v3+J}Dn$NT+Xk7IvGJkcY@Ac@cj9H?P6N$1ktn&K1Q#Fi^d4p{VlIZI@GgT3K@189ymtpfU=d4*+5uTz`inx?B#RK!Rv4trI>pY=^UL-afR9_MjaffS*)8@v$`REPzP?fZ)eFEUxW~H&cQD z@2safSH_Dr%#Kb84Di|Fp{0;xa4kOi)4*r;0iU~|ZoFCdg9;%7Wxdgu1;3Rv!qbkH z83JXaSq!f2oZ`xWthqCw`C9CvKtqcxZEkOg2Ef04<)W@F3=GdxIGhlizU@qW=>5Nm zKmY6hgiTo@yVTy>Z|rSWc{eKH;DrK#UR@yB%RF=);;Dam?OAsE5~l>xD{z^MPpK>Y zPrBk_xJ>`PxH%uZbEmhJ+t3s5&*4*$swlfDE9@Yw(; z>k+8!?Lj|ceTQC)&|pr{0S*qZxq#zeJh!dGW{#c=kg-Q35U=khFedxwj}GRcez(@7 zq?B>~F&I5nXFs$(Z*WdtVZ062IkZr2cPFLR4K%FJo^?N(W@uuCTbf|w_cG&o>wY5rA!Ods2FfqC8`)qf zpn*j@2;e<&8g#Xa@moWEG8p;WG}R=Ul(@41Dspe+<9(yVu~pqc1Az<{UVH!C)Xh zvnoF8_}`Em_$JEnzWUmoMHWI{c&_!6Q0D*DajOI7E#LZoJSQN+u+vnTmOv#GM=uJe zqZa(Wu5}2x*@8|JC{8IO1aVy)IsEj$pi5=M;MJyTeTYZ^Ga?TuYaCRr00$vX> z{gsV?wW0H^A^N~K4F}YkLuXy?9Ztr_0fExrdm6P!@V@#}gB!tXV}`X<^!7K+_Hk*t>oy7W>lM za)pJASSZSqD;=D$Mqn~t#YGKWLU4!6DMNMcb!r3yH-*m@4_koKI<@#+TLG>)1333A z;EKDDhmZmxs+4C)n;QAK-k}XL_q_l-L_3Su(R(3X*e-pCZ;a1Alr?3-JUD)YV0y$i zST>wl&r(O{&$H?fr|OwL+UOu_0kXb`er+l9B7K0Y#n^g%Wj`MK^3!m`&EJOmkG{|p z2c%Rb@u@6oirq3k3}R=%^U0;T%O*Hoq$c_EJ%#mbaLcwni03HAdutEsv6MoVB z+~~B#FoYz((w==5VwqCl1}>({3p(k8F;&4(yyY*i#&h2BUlEoSAW5`k+@*0HqCAbx z8C@3ZjB+_=w~WIgdt^>llDH$_fdd6c;LpC1{tJwi^hr(;X`7P!%h(YUF3UEoB&iSqw{qu#~0+0J}~< zs~A>xR1JqXFAVe^75_asswqhFBpCYX=TP3({@ zS6#{FvieJHgV;upoH!WZ^X{YV4k=%-nb2i_5r7V9&wVP@Q@4!wB@hsVwB;$Jb8F%| zLl??$crL2_FVQYy?INt=q(yUa;a_s#sey#r1LzYCq2`N0K%esC83H}|HI*;P6ueMg zVEQ$QIpR%p)tm@ZE_&mq0CIZ<0Gl07-0?&lc=`i`eU83krU!!I)i@t^Vt$8AH6%_B zi%|f;S*L&p_M@I!71!UC7q*4Ox3S^gwu6+>Ic=uJ3Z476I7RnLuRM@5CJ|Zx5P+rA zR-WF5vaMh~lU7;#!9cKivzpu&Gi->>>%t`8J}m%PUO&GO8gQJWPS`n3Rf?H!*h7w0 ztgxzJffNoC`806i+G`50GvG4*#4iUB>np%@dw~OI0$1Dz99Ss`yi)j1R313M38})3 zBJU}*2g~z4Wln;hvo3=?d&5D$NcG^3s@DzK_7CsDsRy5k#~!{Ak39A?eEG4kPwk6(6Jk^SJF1_JSoJ3it)jPJ8jXt5NdP8S?u1_p% z#7_mUE+8K5=oG3}E@y*7T?d}4H~fj^yK>r8u4VVRj)jqOndggOY3osyOFa45 zNbP`Gk%bOpXAuC&ujxwrXt6}>)Br^1*ps8whl04kk7OGp?X{PX-8eFT0)#yNkn%&4 z4cP!4y$F(nhxlCh>OgVM5o;O8E=xNigyPqF_%i{5yViQ7+CaZzr=tzg zpV5d%xj+sSR8EP|P5(c2ZxVA`lAMQqdEZd=YM#2Qn@u*mse#>+Ae*u+%9acfkS7{~ zz?BVdWN0BTwD15M5Ddux?X6+0y|Ar~27~fM38G~Pq-~LuC~zIwTCr{X0 zawv7wGWU2ly)Wr9C~iF#+!G&y{n@^yEm$Y@t`uHNo=dM;9;))!_ty@vfB4-mwSVnP z-#UfvG6UfH<5w=4Wp7;VY&@G9M`4J|rbfX6*VKV9Hy%5~0F6L$zg6j6(Xd17suP}q zi|45ftyiJt2)uC*_y;co&)oz*{te*ew`-$cbtZoD+5^54l6RNqy^aT%Gpmq*wzJ{* z&1dk<&wdPFe(jIqQ?LEoc>T`DL|L_vVLcIqJjB7f6_x%W+qQIpW;psdICwf0Q!|uF z6b}BuF_z(qZ%}PGv_t6Ja-0EWZJ)NY$&esd1{-o*xQNF=gt>X;B7oX_QchXc;ogZ{ zib%O$+lFxlV?AH_KW5?w4xdN|(_U+<{Fmm4nr(#Cnu%I7!aQ7A-e)AS?zH+ZBn8CP#h{1NMe2OfQ`{1Lr zMKKN*a;#5CRB_HT^k?oP4HM>(vb523jbATSzF53xnizwjoM-w1mZanuubor%0AEk~ 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 literal 0 HcmV?d00001 diff --git a/client/resources/icons/logs/browser_safari.png b/client/resources/icons/logs/browser_safari.png new file mode 100644 index 0000000000000000000000000000000000000000..50252ee9f7361f587652a7c68371b8e10b7e8b5f GIT binary patch literal 58672 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 vJp>wqO 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 VgOSl0FopyGC@n+HqFXl4{tIa0^RWN` literal 0 HcmV?d00001 diff --git a/client/resources/icons/logs/os_android.png b/client/resources/icons/logs/os_android.png new file mode 100644 index 0000000000000000000000000000000000000000..df05a94554e304869675f4af6c9d121b564c9854 GIT binary patch literal 6064 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 literal 0 HcmV?d00001 diff --git a/client/resources/icons/logs/os_ios.png b/client/resources/icons/logs/os_ios.png new file mode 100644 index 0000000000000000000000000000000000000000..120582bb343b619ebe236afd61491a06a3483a5e GIT binary patch literal 20413 zcmXtA2RxPk_kS)KA$w)Jl#z^55h1Q9dy~CF*|KLPT@=a67EzIPl~qQ`t|Vn;W@SY7 z=6@dF|L^Je+UtfIIgg?uUEI$+ex4lc zQ62Z*njbfR)&=z6yk$N-P?^|MA^Ky$sL1d}MCGoYcxR)7RI(UF?+V za4O343GHve*p{=c<4cT3c^Uh+@U5J;%^&1DeUm)aW=G#Hv`bR@$LbsND?MKNkB|=eWhQ^nIbPRrC2xsZ&*6ur{ zs-BF;w~RZwxjk@7&5$p`81sc2Q&=Sy ev?kiQ()Fm=Tj28yII#0X0xz+2CFP$?m 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

tg^ej?(d849Wr@I*b{MA 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%0AieOBU*W3JIwpObN%pDUDbizCD|1swb7 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

MQahm&-yLyD7UgNu%mPH3GV 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 GgeCyaXK3yK literal 0 HcmV?d00001 diff --git a/client/resources/icons/logs/token.png b/client/resources/icons/logs/token.png new file mode 100644 index 0000000000000000000000000000000000000000..90b54d9e358c9fb4a10a48488b96aa36302cada7 GIT binary patch literal 47232 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

widgets/LogViewWidget.h
+ 1 + TeraForm QWidget diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index b3a56305..89f5ea9d 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -121,6 +121,11 @@ void LogViewWidget::setViewMode(const ViewMode &mode, const QString &uuid, const } +void LogViewWidget::setUuidName(const QString &uuid, const QString &name) +{ + m_uuidsNames[uuid] = name; +} + void LogViewWidget::refreshData(const bool &stats_only) { QUrlQuery args; @@ -146,6 +151,7 @@ void LogViewWidget::refreshData(const bool &stats_only) switch(m_currentMode){ case VIEW_LOGINS_ALL: path = WEB_LOGS_LOGINS_PATH; + args.addQueryItem(WEB_QUERY_WITH_NAMES, "1"); break; case VIEW_LOGINS_DEVICE: path = WEB_LOGS_LOGINS_PATH; @@ -465,22 +471,66 @@ void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_da item->setIcon(QIcon(getLogLevelIcon(level))); ui->tableLogs->setItem(row, 2, item); - /*if (m_currentMode == VIEW_LOGINS_ALL){ - ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); - ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Qui"))); + // Try to find the name associated to that log + QString login_name; + QString login_icon = ""; + if (login.hasFieldName("login_user_name")){ + QString name = login.getFieldValue("login_user_name").toString(); + if (!name.isEmpty()){ + login_name = /*tr("Utilisateur") + ": " +*/ name; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_USER); + } } - if (m_currentMode == VIEW_LOGINS_DEVICE){ - ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); - ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Appareil"))); + if (login.hasFieldName("login_participant_name") && login_name.isEmpty()){ + QString name = login.getFieldValue("login_participant_name").toString(); + if (!name.isEmpty()){ + login_name = /*tr("Participant") + ": " +*/ name; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_PARTICIPANT); + } } - if (m_currentMode == VIEW_LOGINS_PARTICIPANT){ - ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); - ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Participant"))); + if (login.hasFieldName("login_device_name") && login_name.isEmpty()){ + QString name = login.getFieldValue("login_device_name").toString(); + if (!name.isEmpty()){ + login_name = /*tr("Appareil") + ": " + */name; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_DEVICE); + } } - if (m_currentMode == VIEW_LOGINS_USER){ - ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); - ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Utilisateur"))); - }*/ + if (login.hasFieldName("login_service_name") && login_name.isEmpty()){ + QString name = login.getFieldValue("login_service_name").toString(); + if (!name.isEmpty()){ + login_name = /*tr("Service") + ": " +*/ name; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_SERVICE); + } + } + + if (login_name.isEmpty()){ + QString user_login_uuid = login.getFieldValue("login_user_uuid").toString(); + QString part_login_uuid = login.getFieldValue("login_participant_uuid").toString(); + QString device_login_uuid = login.getFieldValue("login_device_uuid").toString(); + QString service_login_uuid = login.getFieldValue("login_service_uuid").toString(); + + if (m_uuidsNames.contains(user_login_uuid)){ + login_name = m_uuidsNames[user_login_uuid]; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_USER); + } + else if (m_uuidsNames.contains(part_login_uuid)){ + login_name = m_uuidsNames[part_login_uuid]; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_PARTICIPANT); + } + else if (m_uuidsNames.contains(device_login_uuid)){ + login_name = m_uuidsNames[device_login_uuid]; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_DEVICE); + } + else if (m_uuidsNames.contains(service_login_uuid)){ + login_name = m_uuidsNames[service_login_uuid]; + login_icon = TeraData::getIconFilenameForDataType(TeraDataTypes::TERADATA_SERVICE); + } + } + + item = new QTableWidgetItem(); + item->setText(login_name); + item->setIcon(QIcon(login_icon)); + ui->tableLogs->setItem(row, 3, item); item = new QTableWidgetItem(); LoginEvent::LoginType login_type = static_cast(login.getFieldValue("login_type").toInt()); diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index a6fbe5aa..99682d27 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -31,17 +31,19 @@ class LogViewWidget : public QWidget void setComManager(ComManager* comMan); void setViewMode(const ViewMode &mode, const QString& uuid = QString(), const bool& autoload=false); + void setUuidName(const QString& uuid, const QString& name); void refreshData(const bool &stats_only = true); private: - Ui::LogViewWidget* ui; - ComManager* m_comManager; - ViewMode m_currentMode; - QString m_currentUuid; - int m_maxCount; - bool m_filtering; // Applying selected filters - bool m_listening; // Listening for logs + Ui::LogViewWidget* ui; + ComManager* m_comManager; + ViewMode m_currentMode; + QString m_currentUuid; + int m_maxCount; + bool m_filtering; // Applying selected filters + bool m_listening; // Listening for logs + QHash m_uuidsNames; void connectSignals(); void disconnectSignals(); diff --git a/shared/src/WebAPI.h b/shared/src/WebAPI.h index dc7c5bb0..3f2e02ae 100755 --- a/shared/src/WebAPI.h +++ b/shared/src/WebAPI.h @@ -127,6 +127,7 @@ #define WEB_QUERY_WITH_ONLY_TOKEN "with_only_token" #define WEB_QUERY_WITH_SESSIONTYPE "with_session_type" #define WEB_QUERY_WITH_DEVICES "with_devices" +#define WEB_QUERY_WITH_NAMES "with_names" #define WEB_QUERY_ORDERBY_RECENTS "orderby_recents" From c57a91e307a4d33bc956361bbfb9020592f212de Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 29 Nov 2022 10:38:16 -0500 Subject: [PATCH 10/42] UI fixes and improvements. --- client/resources/stylesheet.qss | 29 +- client/src/editors/DataEditorWidget.cpp | 8 +- client/src/editors/DataEditorWidget.h | 8 +- client/src/editors/GroupWidget.ui | 402 ++-- client/src/editors/ParticipantWidget.cpp | 6 +- client/src/editors/ParticipantWidget.ui | 2314 +++++++++++----------- client/src/editors/ProjectWidget.ui | 588 +++--- client/src/editors/SessionWidget.ui | 2 +- client/src/editors/SiteWidget.ui | 647 +++--- client/src/editors/TeraForm.cpp | 73 +- client/src/editors/TeraForm.h | 1 + client/src/editors/TeraForm.ui | 89 +- 12 files changed, 2027 insertions(+), 2140 deletions(-) diff --git a/client/resources/stylesheet.qss b/client/resources/stylesheet.qss index dc40039d..d02e7acf 100644 --- a/client/resources/stylesheet.qss +++ b/client/resources/stylesheet.qss @@ -121,7 +121,7 @@ QGroupBox { border-color: gray; margin-top: 27px; font-size: 12px; - font-weight: bold; + font-weight: bold; border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; } @@ -131,9 +131,11 @@ QGroupBox::title { subcontrol-position: top left; border-top-left-radius: 15px; border-top-right-radius: 15px; - padding: 5px 150px; + padding: 5px; /* 150px;*/ background-color: #FF17365D; color: rgb(255, 255, 255); + text-align: center; + min-width: 250px; } /* @@ -145,10 +147,17 @@ QToolBox{ } QToolBox::tab { - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 rgba(0,5,45,50%), stop: 0.2 rgba(0,64,128,50%), stop:1 rgba(0,5,45,50%)); + /*background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 rgba(0,5,45,50%), stop: 0.2 rgba(0,64,128,50%), stop:1 rgba(0,5,45,50%)); border-radius: 5px; - color: darkgray; + color: darkgray;*/ + border-top-left-radius: 15px; + border-top-right-radius: 15px; + padding: 5px; + background-color: #FF17365D; + color: rgb(255, 255, 255); height: 40px; + font-size: 12px; + text-align: center; } QToolBox::tab:selected { @@ -157,10 +166,20 @@ QToolBox::tab:selected { } QToolBox::tab:!selected { - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 rgba(0,64,128,50%), stop: 0.2 rgba(0,5,45,50%), stop:1 rgba(0,64,128,50%)); + /*background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 rgba(0,64,128,50%), stop: 0.2 rgba(0,5,45,50%), stop:1 rgba(0,64,128,50%));*/ + background-color: rgba(0,64,128,50%); color: white; } +/*QToolBox::tab:only-one { + background-color: transparent; + color: transparent; + height: 0px; + icon-size: 0px; + margin: 0px; + padding: 0px; +}*/ + /* QDialog. */ diff --git a/client/src/editors/DataEditorWidget.cpp b/client/src/editors/DataEditorWidget.cpp index 1c890ad5..2d3d56b6 100644 --- a/client/src/editors/DataEditorWidget.cpp +++ b/client/src/editors/DataEditorWidget.cpp @@ -190,7 +190,7 @@ ComManager *DataEditorWidget::getComManager() return m_comManager; } -void DataEditorWidget::setEditorControls(TeraForm* mainForm, QPushButton *editToggle, QFrame *frameSave, QPushButton *saveButton, QPushButton *cancelButton) +void DataEditorWidget::setEditorControls(TeraForm* mainForm, QAbstractButton *editToggle, QFrame *frameSave, QAbstractButton *saveButton, QAbstractButton *cancelButton) { m_mainForm = mainForm; m_editToggle = editToggle; @@ -207,9 +207,9 @@ void DataEditorWidget::setEditorControls(TeraForm* mainForm, QPushButton *editTo } // Connect signals - if (m_cancelButton) connect(m_cancelButton, &QPushButton::clicked, this, &DataEditorWidget::undoButtonClicked); - if (m_saveButton) connect(m_saveButton, &QPushButton::clicked, this, &DataEditorWidget::saveButtonClicked); - if (m_editToggle) connect(m_editToggle, &QPushButton::clicked, this, &DataEditorWidget::editToggleClicked); + if (m_cancelButton) connect(m_cancelButton, &QAbstractButton::clicked, this, &DataEditorWidget::undoButtonClicked); + if (m_saveButton) connect(m_saveButton, &QAbstractButton::clicked, this, &DataEditorWidget::saveButtonClicked); + if (m_editToggle) connect(m_editToggle, &QAbstractButton::clicked, this, &DataEditorWidget::editToggleClicked); } diff --git a/client/src/editors/DataEditorWidget.h b/client/src/editors/DataEditorWidget.h index c6b05e1a..077c6f35 100644 --- a/client/src/editors/DataEditorWidget.h +++ b/client/src/editors/DataEditorWidget.h @@ -69,10 +69,10 @@ class DataEditorWidget : public QWidget EditorState m_editState; // Common controls to toggle editing state - QPushButton* m_editToggle; + QAbstractButton* m_editToggle; QFrame* m_frameSave; - QPushButton* m_saveButton; - QPushButton* m_cancelButton; + QAbstractButton* m_saveButton; + QAbstractButton* m_cancelButton; TeraForm* m_mainForm; @@ -86,7 +86,7 @@ class DataEditorWidget : public QWidget QComboBox* buildRolesComboBox(const QMap &roles); QString getRoleName(const QString& role); - virtual void setEditorControls(TeraForm* mainForm, QPushButton* editToggle, QFrame* frameSave, QPushButton* saveButton, QPushButton* cancelButton); + virtual void setEditorControls(TeraForm* mainForm, QAbstractButton* editToggle, QFrame* frameSave, QAbstractButton* saveButton, QAbstractButton* cancelButton); signals: void dataWasChanged(); diff --git a/client/src/editors/GroupWidget.ui b/client/src/editors/GroupWidget.ui index fa16c4af..1ce75076 100644 --- a/client/src/editors/GroupWidget.ui +++ b/client/src/editors/GroupWidget.ui @@ -304,20 +304,23 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 1 + 0 + 0 - +

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 literal 0 HcmV?d00001 diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 99730685..03fb0405 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -209,12 +209,12 @@ BaseDialog - + Dialog Dialogue - + Fermer Close @@ -468,68 +468,73 @@ ConfigWidget - + Utilisateurs Users - + Groupes utilisateurs Users Groups - + Sites Sites - + Appareils Devices - + Type appareils Device Type - + Sous-types appareils Devices Sub-Types - + Types de séances Session Types - + Types évaluations Test types - + Services Services - + Erreur inconnue Unknown error - + Form Form - + Paramètres - - Journaux + + Événements + Events + + + + Journal d'accès @@ -1970,90 +1975,166 @@ Please update the software or contribute to the development! LogViewWidget - Form - Form + Form + + + + Log Viewer + - + Début Start - - + + dd-MM-yyyy - + Fin End - - + + xxx + + + + + événements + + + + + <<< + + + + + / 1 + + + + + >>> + + + + + + Type Type - + Filtrer Filter - + + Rafraichir + Refresh + + + Date Date - + Heure Time - + + + Message - - + Inconnu Unknown - + Trace - + + Qui + + + + + Appareil + Device + + + + Participant + Participant + + + + Utilisateur + User + + + + État + State + + + + Système + + + + + Client + + + + + Tous + All + + + Debug - + Information Information - + Avertissement Warning - + Critique - + Erreur Error - + Fatal diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index 8b7a7dfa..a410df72 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -209,12 +209,12 @@ BaseDialog - + Dialog - + Fermer @@ -468,68 +468,73 @@ ConfigWidget - + Utilisateurs - + Groupes utilisateurs - + Sites - + Appareils - + Type appareils - + Sous-types appareils - + Types de séances - + Types évaluations - + Services - + Erreur inconnue - + Form - + Paramètres - - Journaux + + Événements + + + + + Journal d'accès @@ -1938,90 +1943,162 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du LogViewWidget - - Form + + Log Viewer - + Début - - + + dd-MM-yyyy - + Fin - - + + xxx + + + + + événements + + + + + <<< + + + + + / 1 + + + + + >>> + + + + + + Type - + Filtrer - + + Rafraichir + + + + Date - + Heure - + + + Message - - + Inconnu - + Trace - + + Qui + + + + + Appareil + + + + + Participant + + + + + Utilisateur + + + + + État + + + + + Système + + + + + Client + + + + + Tous + + + + Debug - + Information - + Avertissement - + Critique - + Erreur - + Fatal diff --git a/client/src/widgets/ConfigWidget.cpp b/client/src/widgets/ConfigWidget.cpp index eede0a30..7b5eff70 100644 --- a/client/src/widgets/ConfigWidget.cpp +++ b/client/src/widgets/ConfigWidget.cpp @@ -28,6 +28,8 @@ ConfigWidget::ConfigWidget(ComManager *comMan, QWidget *parent) : // Logs ui->logsWdg->setComManager(comMan); ui->logsWdg->setViewMode(LogViewWidget::VIEW_LOGS_ALL); + ui->loginsWdg->setComManager(comMan); + ui->loginsWdg->setViewMode(LogViewWidget::VIEW_LOGINS_ALL); } @@ -116,5 +118,9 @@ void ConfigWidget::on_tabSectionsWidget_currentChanged(int index) if (ui->tabSectionsWidget->currentWidget() == ui->tabLogs){ ui->logsWdg->refreshData(); } + + if (ui->tabSectionsWidget->currentWidget() == ui->tabLogins){ + ui->loginsWdg->refreshData(); + } } diff --git a/client/src/widgets/ConfigWidget.ui b/client/src/widgets/ConfigWidget.ui index ee0e2759..b5506ec7 100644 --- a/client/src/widgets/ConfigWidget.ui +++ b/client/src/widgets/ConfigWidget.ui @@ -117,7 +117,13 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - 1 + 0 + + + + 20 + 20 + @@ -226,7 +232,7 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 0 0 551 - 417 + 413 @@ -293,6 +299,35 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 3302a9ab..107450c3 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -13,6 +13,7 @@ LogViewWidget::LogViewWidget(QWidget *parent): m_currentUuid = QString(); m_maxCount = 50; // 50 items at a time by default m_filtering = false; + m_listening = false; // Init levels combo box int level_id = LogEvent::LogLevel_MIN; @@ -45,7 +46,7 @@ LogViewWidget::~LogViewWidget() void LogViewWidget::setComManager(ComManager *comMan) { m_comManager = comMan; - connectSignals(); + //connectSignals(); } void LogViewWidget::setViewMode(const ViewMode &mode, const QString &uuid, const bool &autoload) @@ -53,6 +54,67 @@ void LogViewWidget::setViewMode(const ViewMode &mode, const QString &uuid, const m_currentMode = mode; m_currentUuid = uuid; + // Adjust columns in display + + // Keep first 3 columns + while(ui->tableLogs->columnCount()>3){ + ui->tableLogs->removeColumn(ui->tableLogs->columnCount()-1); + } + + switch(m_currentMode){ + case VIEW_LOGINS_ALL: + case VIEW_LOGINS_DEVICE: + case VIEW_LOGINS_PARTICIPANT: + case VIEW_LOGINS_USER: + { + if (m_currentMode == VIEW_LOGINS_ALL){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Qui"))); + } + if (m_currentMode == VIEW_LOGINS_DEVICE){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Appareil"))); + } + if (m_currentMode == VIEW_LOGINS_PARTICIPANT){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Participant"))); + } + if (m_currentMode == VIEW_LOGINS_USER){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Utilisateur"))); + } + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Type"))); + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("État"))); + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Système"))); + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Client"))); + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Ressource"))); + + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Message"))); + } + break; + case VIEW_LOGS_ALL: + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Message"))); + break; + default: + LOG_WARNING("No view mode or unmanaged mode selected!", "LogViewWidget::setViewMode"); + return; + break; + } + + //connectSignals(); + if (autoload){ refreshData(true); } @@ -106,18 +168,30 @@ void LogViewWidget::refreshData(const bool &stats_only) break; } setEnabled(false); + connectSignals(); m_comManager->doGet(path, args); + // TODO: Keep args to check if the response we get is really for us! + } void LogViewWidget::connectSignals() { - if (m_comManager){ + if (m_comManager && m_currentMode != VIEW_LOGS_NONE && !m_listening){ connect(m_comManager, &ComManager::logsLoginReceived, this, &LogViewWidget::processLogsLogins); connect(m_comManager, &ComManager::logsLogsReceived, this, &LogViewWidget::processLogsLogs); } } + + +void LogViewWidget::disconnectSignals() +{ + if (m_comManager) + disconnect(m_comManager, nullptr, this, nullptr); + m_listening = false; +} + QStringList LogViewWidget::getLogLevelNames() { QStringList names; @@ -187,6 +261,124 @@ QString LogViewWidget::getLogLevelIcon(const LogEvent::LogLevel &level) } } +QString LogViewWidget::getLoginTypeName(const LoginEvent::LoginType &login_type) +{ + switch(login_type){ + case LoginEvent_LoginType_LOGIN_TYPE_TOKEN: + return tr("Jeton"); + break; + case LoginEvent_LoginType_LOGIN_TYPE_PASSWORD: + return tr("Mot de passe"); + break; + case LoginEvent_LoginType_LOGIN_TYPE_CERTIFICATE: + return tr("Certificat"); + break; + case LoginEvent_LoginType_LOGIN_TYPE_UNKNOWN: + default: + return tr("Inconnu"); + break; + } +} + +QString LogViewWidget::getLoginTypeIcon(const LoginEvent::LoginType &login_type) +{ + switch(login_type){ + case LoginEvent_LoginType_LOGIN_TYPE_TOKEN: + return "://icons/logs/token.png"; + break; + case LoginEvent_LoginType_LOGIN_TYPE_PASSWORD: + return "://icons/logs/password.png"; + break; + case LoginEvent_LoginType_LOGIN_TYPE_CERTIFICATE: + return "://icons/logs/certificate.png"; + break; + case LoginEvent_LoginType_LOGIN_TYPE_UNKNOWN: + default: + return "://icons/question.png"; + break; + } +} + +QString LogViewWidget::getLoginStatusName(const LoginEvent::LoginStatus &status) +{ + switch(status){ + case LoginEvent_LoginStatus_LOGIN_STATUS_SUCCESS: + return tr("Succès"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_WRONG_PASSWORD: + return tr("Mauvais mot de passe"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_WRONG_USERNAME: + return tr("Mauvais code utilisateur"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_MAX_ATTEMPTS_REACHED: + return tr("Nombre maximum d'essais atteint"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_ALREADY_LOGGED_IN: + return tr("Déjà connecté"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_INVALID_TOKEN: + return tr("Jeton invalide"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_DISABLED_ACCOUNT: + return tr("Compte désactivé"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_EXPIRED_TOKEN: + return tr("Jeton expiré"); + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_UNKNOWN: + default: + return tr("Inconnu"); + break; + } +} + +QString LogViewWidget::getLoginStatusIcon(const LoginEvent::LoginStatus &status) +{ + switch(status){ + case LoginEvent_LoginStatus_LOGIN_STATUS_SUCCESS: + return "://status/status_ok.png"; + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_WRONG_PASSWORD: + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_WRONG_USERNAME: + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_INVALID_TOKEN: + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_EXPIRED_TOKEN: + return "://status/status_notok.png"; + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_MAX_ATTEMPTS_REACHED: + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_ALREADY_LOGGED_IN: + case LoginEvent_LoginStatus_LOGIN_STATUS_FAILED_WITH_DISABLED_ACCOUNT: + return "://status/status_incomplete.png"; + break; + case LoginEvent_LoginStatus_LOGIN_STATUS_UNKNOWN: + default: + return "://icons/question.png"; + break; + } +} + +QString LogViewWidget::getOSIcon(const QString &os) +{ + QString compare_os = os.toLower(); + if (compare_os.contains("windows")) + return "://icons/logs/os_windows.png"; + + if (compare_os.contains("apple")) + return "://icons/logs/os_apple.png"; + + if (compare_os.contains("linux") || compare_os.contains("ubuntu")) + return "://icons/logs/os_linux.png"; + + if (compare_os.contains("ios")) + return "://icons/logs/os_ios.png"; + + if (compare_os.contains("android")) + return "://icons/logs/os_android.png"; + + return ""; + +} + void LogViewWidget::updateNavButtons() { ui->btnPrevPage->setEnabled(ui->spinPage->value() > ui->spinPage->minimum()); @@ -200,37 +392,119 @@ void LogViewWidget::updateFilterButton() || ui->dateEndDate->property("last_value").toDate() != ui->dateEndDate->date()); } +void LogViewWidget::processStats(const TeraData &stats) +{ + int count = stats.getFieldValue("count").toInt(); + int page_count = count / m_maxCount + 1; + ui->lblEntriesCount->setText(QString::number(count)); + ui->frameNav->setVisible(count > m_maxCount); + ui->spinPage->setValue(1); + ui->spinPage->setMaximum(page_count); + ui->lblTotalPages->setText("/" + QString::number(page_count)); + ui->btnPrevPage->setDisabled(true); + ui->btnNextPage->setEnabled(page_count>1); + + QDate min_date = stats.getFieldValue("min_timestamp").toDate(); + QDate max_date = stats.getFieldValue("max_timestamp").toDate(); + + if (ui->dateStartDate->date().year() <= 2000){ + ui->dateStartDate->setProperty("last_value", min_date); + ui->dateStartDate->setDate(min_date); + ui->dateEndDate->setProperty("last_value", max_date); + ui->dateEndDate->setDate(max_date); + } + refreshData(false); +} + void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_data) { + if (reply_data.hasQueryItem(WEB_QUERY_STATS)){ + // We received stats, update display + if (!logins.empty()){ + processStats(logins.first()); + return; + }else{ + LOG_WARNING("Expected log stats. Received empty reply", "LogViewWidget::processLogsLogins"); + } + } setEnabled(true); + ui->tableLogs->clearContents(); + ui->tableLogs->setRowCount(logins.count()); + int row = 0; + for(const TeraData &login:logins){ + QTableWidgetItem* item = new QTableWidgetItem(); + QDateTime log_date = login.getFieldValue("login_timestamp").toDateTime().toLocalTime(); + item->setText(log_date.toString("dd-MM-yyyy")); + ui->tableLogs->setItem(row, 0, item); + + item = new QTableWidgetItem(); + item->setText(log_date.toString("hh:mm:ss.zzz")); + ui->tableLogs->setItem(row, 1, item); + + item = new QTableWidgetItem(); + LogEvent::LogLevel level = static_cast(login.getFieldValue("login_log_level").toInt()); + item->setText(getLogLevelName(level)); + item->setIcon(QIcon(getLogLevelIcon(level))); + ui->tableLogs->setItem(row, 2, item); + + /*if (m_currentMode == VIEW_LOGINS_ALL){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Qui"))); + } + if (m_currentMode == VIEW_LOGINS_DEVICE){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Appareil"))); + } + if (m_currentMode == VIEW_LOGINS_PARTICIPANT){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Participant"))); + } + if (m_currentMode == VIEW_LOGINS_USER){ + ui->tableLogs->insertColumn(ui->tableLogs->columnCount()); + ui->tableLogs->setHorizontalHeaderItem(ui->tableLogs->columnCount()-1, new QTableWidgetItem(tr("Utilisateur"))); + }*/ + + item = new QTableWidgetItem(); + LoginEvent::LoginType login_type = static_cast(login.getFieldValue("login_type").toInt()); + item->setText(getLoginTypeName(login_type)); + item->setIcon(QIcon(getLoginTypeIcon(login_type))); + ui->tableLogs->setItem(row, 4, item); + + item = new QTableWidgetItem(); + LoginEvent::LoginStatus login_status = static_cast(login.getFieldValue("login_status").toInt()); + item->setText(getLoginStatusName(login_status)); + item->setIcon(QIcon(getLoginStatusIcon(login_status))); + ui->tableLogs->setItem(row, 5, item); + + item = new QTableWidgetItem(); + item->setText(login.getFieldValue("login_os_name").toString() + " " + login.getFieldValue("login_os_version").toString()); + ui->tableLogs->setItem(row, 6, item); + + item = new QTableWidgetItem(); + item->setText(login.getFieldValue("login_client_name").toString() + " " + login.getFieldValue("login_client_version").toString()); + ui->tableLogs->setItem(row, 7, item); + + item = new QTableWidgetItem(); + item->setText(login.getFieldValue("login_server_endpoint").toString()); + ui->tableLogs->setItem(row, 8, item); + + item = new QTableWidgetItem(); + item->setText(login.getFieldValue("login_message").toString()); + ui->tableLogs->setItem(row, 9, item); + row++; + } + + ui->tableLogs->resizeColumnsToContents(); + disconnectSignals(); + } void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data) { - if (reply_data.hasQueryItem(WEB_QUERY_STATS)){ // We received stats, update display if (!logins.empty()){ - int count = logins.first().getFieldValue("count").toInt(); - int page_count = count / m_maxCount + 1; - ui->lblEntriesCount->setText(QString::number(count)); - ui->frameNav->setVisible(count > m_maxCount); - ui->spinPage->setValue(1); - ui->spinPage->setMaximum(page_count); - ui->lblTotalPages->setText("/" + QString::number(page_count)); - ui->btnPrevPage->setDisabled(true); - ui->btnNextPage->setEnabled(page_count>1); - - QDate min_date = logins.first().getFieldValue("min_timestamp").toDate(); - QDate max_date = logins.first().getFieldValue("max_timestamp").toDate(); - - if (ui->dateStartDate->date().year() <= 2000){ - ui->dateStartDate->setProperty("last_value", min_date); - ui->dateStartDate->setDate(min_date); - ui->dateEndDate->setProperty("last_value", max_date); - ui->dateEndDate->setDate(max_date); - } - refreshData(false); + processStats(logins.first()); return; }else{ LOG_WARNING("Expected log stats. Received empty reply", "LogViewWidget::processLogsLogs"); @@ -262,6 +536,9 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data ui->tableLogs->setItem(row, 3, item); row++; } + + ui->tableLogs->resizeColumnsToContents(); + disconnectSignals(); } void LogViewWidget::on_btnFilter_clicked() diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index 9914b880..a6fbe5aa 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -5,6 +5,7 @@ #include "managers/ComManager.h" #include "LogEvent.pb.h" +#include "LoginEvent.pb.h" namespace Ui { class LogViewWidget; @@ -40,16 +41,29 @@ class LogViewWidget : public QWidget QString m_currentUuid; int m_maxCount; bool m_filtering; // Applying selected filters + bool m_listening; // Listening for logs void connectSignals(); + void disconnectSignals(); QStringList getLogLevelNames(); QString getLogLevelName(const LogEvent::LogLevel &level); QString getLogLevelIcon(const LogEvent::LogLevel &level); + QString getLoginTypeName(const LoginEvent::LoginType &login_type); + QString getLoginTypeIcon(const LoginEvent::LoginType &login_type); + + QString getLoginStatusName(const LoginEvent::LoginStatus &status); + QString getLoginStatusIcon(const LoginEvent::LoginStatus &status); + + QString getOSIcon(const QString &os); + QString getBrowserIcon(const QString &browser); + void updateNavButtons(); void updateFilterButton(); + void processStats(const TeraData& stats); + private slots: void processLogsLogins(QList logins, QUrlQuery reply_data); void processLogsLogs(QList logins, QUrlQuery reply_data); From d1f3a7f07ff171371b2d92123e498665abb9a829 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 28 Nov 2022 09:00:20 -0500 Subject: [PATCH 08/42] Refs #80. Added icons for OS and Browsers in log view. --- client/src/widgets/LogViewWidget.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 107450c3..b3a56305 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -379,6 +379,24 @@ QString LogViewWidget::getOSIcon(const QString &os) } +QString LogViewWidget::getBrowserIcon(const QString &browser) +{ + QString compare_browser = browser.toLower(); + if (compare_browser.contains("chrome") || compare_browser.contains("chromium")) + return "://icons/logs/browser_chrome.png"; + + if (compare_browser.contains("firefox")) + return "://icons/logs/browser_firefox.png"; + + if (compare_browser.contains("safari")) + return "://icons/logs/browser_safari.png"; + + if (compare_browser.contains("openteraplus")) + return "://icons/OpenTeraPlus.ico"; + + return ""; +} + void LogViewWidget::updateNavButtons() { ui->btnPrevPage->setEnabled(ui->spinPage->value() > ui->spinPage->minimum()); @@ -478,10 +496,12 @@ void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_da item = new QTableWidgetItem(); item->setText(login.getFieldValue("login_os_name").toString() + " " + login.getFieldValue("login_os_version").toString()); + item->setIcon(QIcon(getOSIcon(login.getFieldValue("login_os_name").toString()))); ui->tableLogs->setItem(row, 6, item); item = new QTableWidgetItem(); item->setText(login.getFieldValue("login_client_name").toString() + " " + login.getFieldValue("login_client_version").toString()); + item->setIcon(QIcon(getBrowserIcon(login.getFieldValue("login_client_name").toString()))); ui->tableLogs->setItem(row, 7, item); item = new QTableWidgetItem(); From 7d51cef14a46759229b0ef6326dd791e52b6670d Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 28 Nov 2022 13:29:55 -0500 Subject: [PATCH 09/42] Refs #80. Added login informations in ParticipantWidget --- client/src/editors/ParticipantWidget.cpp | 14 ++ client/src/editors/ParticipantWidget.h | 1 + client/src/editors/ParticipantWidget.ui | 279 ++++++++++++++--------- client/src/widgets/LogViewWidget.cpp | 76 ++++-- client/src/widgets/LogViewWidget.h | 16 +- shared/src/WebAPI.h | 1 + 6 files changed, 257 insertions(+), 130 deletions(-) diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 23b9d3f2..4912d34b 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -83,6 +83,7 @@ ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, Q // Default display ui->tabNav->setCurrentIndex(0); ui->tabServicesDetails->setCurrentIndex(0); + ui->tabInfosDetails->setCurrentIndex(0); ui->frameFilterSessionTypes->hide(); } @@ -314,6 +315,11 @@ void ParticipantWidget::initUI() m_resumeIcon = QIcon(":/icons/play.png"); m_testIcon = QIcon(":/icons/test.png"); + // Configure log view + ui->wdgLogins->setComManager(m_comManager); + ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_PARTICIPANT, m_data->getUuid()); + ui->wdgLogins->setUuidName(m_data->getUuid(), m_data->getName()); + } bool ParticipantWidget::canStartNewSession(const int &id_session_type) @@ -1986,3 +1992,11 @@ void ParticipantWidget::on_btnQR_clicked() m_diag_editor->open(); } + +void ParticipantWidget::on_tabInfosDetails_currentChanged(int index) +{ + if (ui->tabInfosDetails->currentWidget() == ui->tabLogins){ + ui->wdgLogins->refreshData(); + } +} + diff --git a/client/src/editors/ParticipantWidget.h b/client/src/editors/ParticipantWidget.h index 6cb79c14..6640128e 100644 --- a/client/src/editors/ParticipantWidget.h +++ b/client/src/editors/ParticipantWidget.h @@ -177,6 +177,7 @@ private slots: void on_lstDevices_itemDoubleClicked(QListWidgetItem *item); void on_btnTestsBrowser_clicked(); void on_btnQR_clicked(); + void on_tabInfosDetails_currentChanged(int index); }; #endif // PARTICIPANTWIDGET_H diff --git a/client/src/editors/ParticipantWidget.ui b/client/src/editors/ParticipantWidget.ui index ba1eed60..deb093e8 100644 --- a/client/src/editors/ParticipantWidget.ui +++ b/client/src/editors/ParticipantWidget.ui @@ -367,7 +367,7 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - 0 + 1 @@ -1754,117 +1754,170 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - - - - - Qt::Horizontal - - - - 40 - 1 - - - - - - - - - 0 - 32 - - - - PointingHandCursor + + + QTabWidget::West + + + 0 + + + + 20 + 20 + + + + + + :/icons/patient.png:/icons/patient.png + + + Participant + + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + 0 + 32 + + + + PointingHandCursor + + + Éditer + + + + :/icons/edit.png:/icons/edit.png + + + + 20 + 20 + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 32 + + + + PointingHandCursor + + + Sauvegarder + + + + :/icons/save.png:/icons/save.png + + + + 24 + 24 + + + + + + + + true + + + + 0 + 32 + + + + PointingHandCursor + + + Annuler + + + + :/icons/undo.png:/icons/undo.png + + + + 24 + 24 + + + + + + + + + + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + 0 - - Éditer + + 0 - - - :/icons/edit.png:/icons/edit.png + + 0 - - - 20 - 20 - + + 0 - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 32 - - - - PointingHandCursor - - - Sauvegarder - - - - :/icons/save.png:/icons/save.png - - - - 24 - 24 - - - - - - - - true - - - - 0 - 32 - - - - PointingHandCursor - - - Annuler - - - - :/icons/undo.png:/icons/undo.png - - - - 24 - 24 - - - - - + + + + + @@ -2063,10 +2116,10 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - :/icons/service.png:/icons/service.png + :/icons/config.png:/icons/config.png - Services + Configuration @@ -2106,6 +2159,12 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } + + LogViewWidget + QWidget +

- - + + 0 @@ -344,16 +347,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - - - 0 - 0 - - - - @@ -413,209 +406,196 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - 0 + + + Résumé - - - - 0 - 0 - 710 - 264 - - - - Résumé - - - - - - 45 - - - 15 - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/icons/session.png - - - true - - - - - - - - 0 - 0 - - - - XXXX Séances - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - XXXX Participants - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/icons/patient.png - - - true - - - - - - - - - - 0 - 0 - - - - - 0 - 150 - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SelectRows - - - true - - - 125 - - - true - - - false - - - false - - - - Participant + + + + + 45 + + + 15 + + + + + + 0 + 0 + - - - - État + + + 48 + 48 + + + + + 48 + 48 + - - - Séances + + + + :/icons/session.png + + + true + + + + + + + + 0 + 0 + - - - Première séance + XXXX Séances + + + Qt::AlignCenter + + + + + + + + 0 + 0 + - - - Dernière séance + XXXX Participants + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + - - - Dernière connexion + + + + :/icons/patient.png + + + true - - - - - + + + + + + + + + 0 + 0 + + + + + 0 + 150 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + true + + + 125 + + + true + + + false + + + false + + + + Participant + + + + + État + + + + + Séances + + + + + Première séance + + + + + Dernière séance + + + + + Dernière connexion + + + + + diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 4912d34b..3bf53069 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -996,7 +996,8 @@ void ParticipantWidget::processStatsReply(TeraData stats, QUrlQuery reply_query) m_totalSessions = stats.getFieldValue("sessions_total_count").toInt(); ui->tableSessions->setRowCount(m_totalSessions); ui->progSessionsLoad->setMaximum(m_totalSessions); - ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); + //ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); + ui->grpSession->setTitle(tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); // Query sessions types QUrlQuery args; @@ -1025,7 +1026,8 @@ void ParticipantWidget::deleteDataReply(QString path, int id) m_currentSessions--; m_totalSessions--; - ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); + //ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); + ui->grpSession->setTitle(tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); } } diff --git a/client/src/editors/ParticipantWidget.ui b/client/src/editors/ParticipantWidget.ui index deb093e8..636888d2 100644 --- a/client/src/editors/ParticipantWidget.ui +++ b/client/src/editors/ParticipantWidget.ui @@ -367,7 +367,7 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - 1 + 0 @@ -391,7 +391,7 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - + 0 @@ -404,390 +404,829 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } 145 - - 0 + + Informations + + + + 2 + + + 2 + + + 2 + + + + + PointingHandCursor + + + Actif + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + + 175 + 0 + + + + PointingHandCursor + + + + + + Accès via lien web + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + + + + QLayout::SetMaximumSize + + + 4 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 250 + 16777215 + + + + + + + + + 32 + 32 + + + + + 32 + 32 + + + + PointingHandCursor + + + Générer code QR + + + + :/icons/qr.png:/icons/qr.png + + + + 28 + 28 + + + + + + + + + 0 + 32 + + + + + 16777215 + 16777215 + + + + PointingHandCursor + + + Copier le lien + + + Copier le lien + + + + :/icons/copy.png:/icons/copy.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 32 + + + + + 16777215 + 16777215 + + + + PointingHandCursor + + + Envoyer par courriel + + + Envoyer le lien par courriel + + + + :/icons/email.png:/icons/email.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 32 + 32 + + + + + 32 + 16777215 + + + + PointingHandCursor + + + Afficher le lien + + + ... + + + + :/icons/view_off.png + :/icons/view_on.png:/icons/view_off.png + + + + 20 + 20 + + + + true + + + + + + + + 0 + 0 + + + + + 0 + 32 + + + + true + + + Aucun lien n'a été généré + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 6 + + + + + + 0 + 0 + + + + + 175 + 0 + + + + PointingHandCursor + + + Accès via identification + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 4 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Code utilisateur + + + + + + + + 0 + 32 + + + + + 125 + 16777215 + + + + Code utilisateur + + + + + + + + 0 + 0 + + + + Mot de passe + + + + + + + PointingHandCursor + + + Générer mot de passe aléatoire + + + ... + + + + :/icons/random.png:/icons/random.png + + + + 24 + 24 + + + + + + + + + 0 + 32 + + + + + 125 + 16777215 + + + + QLineEdit::Password + + + Mot de passe + + + + + + + PointingHandCursor + + + Sauvegarder + + + Sauvegarder + + + + :/icons/save.png:/icons/save.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Séances - - - - 0 - 0 - 978 - 110 - + + + 0 - - - 0 - 0 - + + 0 - - Informations - - - - 2 - - - 2 - - - 2 - - - - - PointingHandCursor - - - Actif - - - - - - - QFrame::StyledPanel - - - QFrame::Raised + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 970 + 440 + - - - 2 - + - 0 + 4 - 0 + 4 - 0 + 4 - 0 + 4 + + + + 24 + + + Qt::AlignCenter + + + Chargement des séances: %p% + + + - - - 6 + + + + 0 + 0 + - - QLayout::SetDefaultConstraint + + + 16777215 + 200 + - - - - - 0 - 0 - - - - - 175 - 0 - - - - PointingHandCursor - - - - - - Accès via lien web - - - - - - - - 0 - 0 - - - - - 0 - 40 - - - - - - - - QLayout::SetMaximumSize + + + 3 + + + + + + 0 + 0 + - - 4 + + PointingHandCursor - - 0 + + ... - - 0 + + + :/controls/branch_left.png:/controls/branch_left.png - - 0 + + + + + + 3 - - + + 0 0 - - - 250 - 16777215 - - - - - - - 32 - 32 - - - - - 32 - 32 + 0 + 0 - - PointingHandCursor - - Générer code QR + Mois 1 - - - :/icons/qr.png:/icons/qr.png + + false - - - 28 - 28 - + + Qt::AlignCenter - + + + true + + + + 0 + 0 + + 0 - 32 + 0 16777215 - 16777215 + 175 PointingHandCursor - Copier le lien - - - Copier le lien + - - - :/icons/copy.png:/icons/copy.png + + + 2013 + 1 + 1 + - - - 24 - 24 - + + + 3000 + 1 + 1 + - - Qt::ToolButtonTextBesideIcon + + true - - - - - - - 0 - 32 - + + QCalendarWidget::SingleSelection - - - 16777215 - 16777215 - + + QCalendarWidget::SingleLetterDayNames - - PointingHandCursor + + QCalendarWidget::NoVerticalHeader - - Envoyer par courriel + + false - - Envoyer le lien par courriel + + false - - - :/icons/email.png:/icons/email.png + + + + + + + + 3 + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + - - - 24 - 24 - + + Mois 2 - - Qt::ToolButtonTextBesideIcon + + Qt::AlignCenter - - - - 32 - 32 - + + + + 0 + 0 + - 32 - 16777215 + 16777215 + 175 PointingHandCursor - - Afficher le lien + + true - - ... + + QCalendarWidget::SingleLetterDayNames - - - :/icons/view_off.png - :/icons/view_on.png:/icons/view_off.png + + QCalendarWidget::NoVerticalHeader - - - 20 - 20 - + + false - - true + + false - - + + + + + + 3 + + + - + 0 0 - - - 0 - 32 - - - - true + + Mois 3 - - Aucun lien n'a été généré + + Qt::AlignCenter - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 6 - - - - - - 0 - 0 - - - - - 175 - 0 - - - - PointingHandCursor - - - Accès via identification - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 4 - - - 0 - - - 0 - - - 0 - - - + 0 @@ -797,949 +1236,478 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } 16777215 - 16777215 + 175 - - Code utilisateur + + PointingHandCursor - - - - - - - 0 - 32 - + + true - - - 125 - 16777215 - + + QCalendarWidget::SingleLetterDayNames + + + QCalendarWidget::NoVerticalHeader + + + false - - Code utilisateur + + false - - + + + + + + + 0 + 0 + + + + PointingHandCursor + + + ... + + + + :/controls/branch_closed.png:/controls/branch_closed.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + - + 0 0 - - Mot de passe - + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 24 + 24 + + + + PointingHandCursor + + + Tout cocher + + + ... + + + + :/controls/check2_on.png:/controls/check2_on.png + + + + 24 + 24 + + + + + + + + + 24 + 24 + + + + PointingHandCursor + + + Tout décocher + + + ... + + + + :/controls/check2_off.png:/controls/check2_off.png + + + + 24 + 24 + + + + + - - - - PointingHandCursor - - - Générer mot de passe aléatoire - - - ... - - - - :/icons/random.png:/icons/random.png - - - - 24 - 24 - + + + + + 0 + 0 + - - - - 0 - 32 + 0 - 125 + 150 16777215 - - QLineEdit::Password + + + 10 + - - Mot de passe + + Qt::ScrollBarAsNeeded - - - - - - PointingHandCursor + + QAbstractScrollArea::AdjustIgnored - - Sauvegarder + + QAbstractItemView::NoEditTriggers - - Sauvegarder + + false - - - :/icons/save.png:/icons/save.png + + QAbstractItemView::NoSelection - - - 24 - 24 - + + QAbstractItemView::SelectRows - - Qt::ToolButtonTextBesideIcon + + QListView::Static - - - - - - Qt::Horizontal + + QListView::TopToBottom - - - 40 - 20 - + + QListView::SinglePass + + + 3 - + + true + + + + + + + 0 + 0 + + + + + 0 + 150 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + 20 + 20 + + + + true + + + 75 + + + 100 + + + false + + + false + + + + Séance + + + + + Date + + + + + Type + + + + + État + + + + + Durée + + + + + Responsable + + + + + Actions + + + + - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - 0 - - - - - 0 - 0 - 978 - 437 - - - - Séances - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - true - - - - - 0 - 0 - 976 - 439 - - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - 24 - - - Qt::AlignCenter - - - Chargement des séances: %p% - - - - - - - - 0 - 0 - - - - - 16777215 - 200 - - - - - 3 - - - - - - 0 - 0 - - - - PointingHandCursor - - - ... - - - - :/controls/branch_left.png:/controls/branch_left.png - - - - - - - 3 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Mois 1 - - - false - - - Qt::AlignCenter - - - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - - - - - 2013 - 1 - 1 - - - - - 3000 - 1 - 1 - - - - true - - - QCalendarWidget::SingleSelection - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - 3 - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - Mois 2 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - true - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - 3 - - - - - - 0 - 0 - - - - Mois 3 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - true - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - ... - - - - :/controls/branch_closed.png:/controls/branch_closed.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + + + + + + + + 125 + 40 + + + + PointingHandCursor + + + Filtrer les séances + + + + :/icons/filter.png:/icons/filter.png + + + + 24 + 24 + + + + true + + + - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 24 - 24 - - - - PointingHandCursor - - - Tout cocher - - - ... - - - - :/controls/check2_on.png:/controls/check2_on.png - - - - 24 - 24 - - - - - - - - - 24 - 24 - - - - PointingHandCursor - - - Tout décocher - - - ... - - - - :/controls/check2_off.png:/controls/check2_off.png - - - - 24 - 24 - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 150 - 16777215 - - - - - 10 - - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::NoSelection - - - QAbstractItemView::SelectRows - - - QListView::Static - - - QListView::TopToBottom - - - QListView::SinglePass - - - 3 - - - true - - - - + + + + 125 + 40 + + + + PointingHandCursor + + + Nouvelle + + + + :/icons/new.png:/icons/new.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + - - - - 0 - 0 - + + + false - 0 - 150 + 125 + 40 - - QAbstractItemView::NoEditTriggers + + PointingHandCursor - - QAbstractItemView::ExtendedSelection + + Supprimer - - QAbstractItemView::SelectRows + + + :/icons/delete_old.png:/icons/delete_old.png - 20 + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + Qt::Horizontal + + + + 40 20 - + + + + + true - - 75 - - - 100 - - - false - - - false - - - - Séance - - - - - Date - - - - - Type - - - - - État - - - - - Durée - - - - - Responsable - - - - - Actions - - + + + 125 + 40 + + + + PointingHandCursor + + + Explorateur de données + + + + :/icons/data.png:/icons/data.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 125 + 40 + + + + PointingHandCursor + + + Explorateur d'évaluations + + + + :/icons/test.png:/icons/test.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + - - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Filtrer les séances - - - - :/icons/filter.png:/icons/filter.png - - - - 24 - 24 - - - - true - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Nouvelle - - - - :/icons/new.png:/icons/new.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - false - - - - 125 - 40 - - - - PointingHandCursor - - - Supprimer - - - - :/icons/delete_old.png:/icons/delete_old.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - 125 - 40 - - - - PointingHandCursor - - - Explorateur de données - - - - :/icons/data.png:/icons/data.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Explorateur d'évaluations - - - - :/icons/test.png:/icons/test.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - + + + - - - + + + diff --git a/client/src/editors/ProjectWidget.ui b/client/src/editors/ProjectWidget.ui index 64551e2f..3258a1af 100644 --- a/client/src/editors/ProjectWidget.ui +++ b/client/src/editors/ProjectWidget.ui @@ -182,20 +182,23 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 1 + 0 + 0 - + - - + + 0 @@ -222,16 +225,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - - - 0 - 0 - - - - @@ -291,298 +284,285 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - 0 + + + Résumé - - - - 0 - 0 - 713 - 469 - - - - Résumé - - - - - - 45 - - - 15 - - - - - - 0 - 0 - - + + + + + 45 + + + 15 + + + + + + 0 + 0 + + + + XXXX Séances + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + :/icons/patient.png + + + true + + + + + + + XXXX Groupes + + + + + + + + 0 + 0 + + + + XXXX Participants + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + :/icons/software_user.png + + + true + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/group.png + + + true + + + + + + + + 0 + 0 + + + + XXXX Utilisateurs + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + :/icons/session.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + 20 + 20 + + + + true + + + 125 + + + true + + + false + + + false + + - XXXX Séances - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - + Participant + + - + État - - :/icons/patient.png - - - true - - - - - + + - XXXX Groupes - - - - - - - - 0 - 0 - + Séances + + - XXXX Participants - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - + Première séance + + - - - - :/icons/software_user.png - - - true - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - + Dernière séance + + - - - - :/icons/group.png - - - true - - - - - - - - 0 - 0 - + Dernière connexion - - XXXX Utilisateurs - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/icons/session.png - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SelectRows - - - - 20 - 20 - - - - true - - - 125 - - - true - - - false - - - false - - - - Participant - - - - - État - - - - - Séances - - - - - Première séance - - - - - Dernière séance - - - - - Dernière connexion - - - - - - - - + + + + + + @@ -1209,6 +1189,12 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + + TeraForm + QWidget +
TeraForm.h
+ 1 +
ClickableLabel QLabel @@ -1218,12 +1204,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, clicked() - - TeraForm - QWidget -
TeraForm.h
- 1 -
diff --git a/client/src/editors/SessionWidget.ui b/client/src/editors/SessionWidget.ui index 480df24e..ebb89dad 100644 --- a/client/src/editors/SessionWidget.ui +++ b/client/src/editors/SessionWidget.ui @@ -198,7 +198,7 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - 4 + 0 diff --git a/client/src/editors/SiteWidget.ui b/client/src/editors/SiteWidget.ui index 221ddcbe..f03ea2ec 100644 --- a/client/src/editors/SiteWidget.ui +++ b/client/src/editors/SiteWidget.ui @@ -191,20 +191,23 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 1 + 0 + 0 - + - - + + 0 @@ -231,16 +234,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - - - - - 0 - 0 - - - - @@ -300,320 +293,310 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - + 0 0 - - - - 0 - 0 - 1034 - 372 - - - - Résumé - - - - - - - 500 - 16777215 - - - - - 10 - - - - - XXX Appareils - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/software_user.png - - - true - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/session.png - - - true - - - - - - - - 0 - 0 - - - - XXXX Séances - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - XXXX Utilisateurs - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/device.png - - - true - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/group.png - - - true - - - - - - - XXXX Groupes - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/project.png - - - true - - - - - - - - 0 - 0 - - - - XXXX Projets - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/icons/patient.png - - - true - - - - - - - - 0 - 0 - - - - XXXX Participants - - - Qt::AlignCenter - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - + + Résumé + + + + + + + 500 + 16777215 + + + + + 10 - - - - + + + + XXX Appareils + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/software_user.png + + + true + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/session.png + + + true + + + + + + + + 0 + 0 + + + + XXXX Séances + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + XXXX Utilisateurs + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/device.png + + + true + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/group.png + + + true + + + + + + + XXXX Groupes + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/project.png + + + true + + + + + + + + 0 + 0 + + + + XXXX Projets + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/icons/patient.png + + + true + + + + + + + + 0 + 0 + + + + XXXX Participants + + + Qt::AlignCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + +
@@ -1128,6 +1111,12 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + + TeraForm + QWidget +
TeraForm.h
+ 1 +
ClickableLabel QLabel @@ -1137,12 +1126,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, clicked() - - TeraForm - QWidget -
TeraForm.h
- 1 -
diff --git a/client/src/editors/TeraForm.cpp b/client/src/editors/TeraForm.cpp index df8a5bbc..b6361542 100644 --- a/client/src/editors/TeraForm.cpp +++ b/client/src/editors/TeraForm.cpp @@ -12,6 +12,7 @@ TeraForm::TeraForm(QWidget *parent, ComManager *com_man) : { ui->setupUi(this); + m_mainWidget = ui->toolboxMain; m_highlightConditionals = true; // TODO: Find out why the global stylesheet isn't correctly used by TeraForm @@ -29,7 +30,10 @@ TeraForm::TeraForm(QWidget *parent, ComManager *com_man) : TeraForm::~TeraForm() { + /*if (m_mainWidget != ui->toolboxMain) + m_mainWidget->deleteLater();*/ delete ui; + } void TeraForm::buildUiFromStructure(const QString &structure) @@ -56,23 +60,40 @@ void TeraForm::buildUiFromStructure(const QString &structure) // Sections if (struct_object.contains("sections")){ QVariantList struct_data =struct_object["sections"].toArray().toVariantList(); - int page_index = 0; - for (const QVariant §ion:qAsConst(struct_data)){ - if (section.canConvert(QMetaType::QVariantHash)){ - QVariantHash section_data = section.toHash(); - //if (page_index>0){ - QWidget* new_page = new QWidget(ui->toolboxMain); - new_page->setObjectName("pageSection" + QString::number(page_index+1)); - new_page->setStyleSheet("QWidget#" + new_page->objectName() + "{border: 1px solid white; border-radius: 5px;}"); - ui->toolboxMain->addItem(new_page,""); - //} - ui->toolboxMain->setItemText(page_index, section_data["label"].toString()); + if (struct_data.count() > 1){ + int page_index = 0; + m_mainWidget = ui->toolboxMain; + for (const QVariant §ion:qAsConst(struct_data)){ + if (section.canConvert(QMetaType::QVariantHash)){ + QVariantHash section_data = section.toHash(); + //if (page_index>0){ + QWidget* new_page = new QWidget(ui->toolboxMain); + new_page->setObjectName("pageSection" + QString::number(page_index+1)); + new_page->setStyleSheet("QWidget#" + new_page->objectName() + "{border: 1px solid white; border-radius: 5px;}"); + ui->toolboxMain->addItem(new_page,""); + //} + ui->toolboxMain->setItemText(page_index, section_data["label"].toString()); + if (section_data.contains("items")){ + if (section_data["items"].canConvert(QMetaType::QVariantList)){ + buildFormFromStructure(ui->toolboxMain->widget(page_index), section_data["items"].toList()); + } + } + page_index++; + } + } + }else{ + if (struct_data.count() == 1){ + // Only one section - hides the header and don't use QToolBox + ui->mainLayout->removeWidget(ui->toolboxMain); + m_mainWidget = new QFrame(this); + ui->mainLayout->addWidget(m_mainWidget); + QVariantHash section_data = struct_data.first().toHash(); if (section_data.contains("items")){ if (section_data["items"].canConvert(QMetaType::QVariantList)){ - buildFormFromStructure(ui->toolboxMain->widget(page_index), section_data["items"].toList()); + buildFormFromStructure(m_mainWidget, section_data["items"].toList()); } } - page_index++; + } } } @@ -430,6 +451,9 @@ void TeraForm::buildFormFromStructure(QWidget *page, const QVariantList &structu QString item_id = item_data["id"].toString(); QWidget* item_widget = nullptr; QLabel* item_label = new QLabel(item_data["label"].toString()); + item_label->setAlignment(Qt::AlignVCenter); + item_label->setStyleSheet("QLabel{min-height: 25px;}"); + /*QFrame* item_frame = new QFrame(); QHBoxLayout* item_frame_layout = new QHBoxLayout(); item_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); @@ -654,6 +678,8 @@ QWidget *TeraForm::createTextWidget(const QVariantHash &structure, bool is_maske item_text->setMaxLength(structure["max_length"].toInt()); } + item_text->setAlignment(Qt::AlignVCenter); + connect(item_text, &QLineEdit::textChanged, this, &TeraForm::widgetValueChanged); return item_text; } @@ -910,7 +936,8 @@ void TeraForm::setWidgetVisibility(QWidget *widget, QWidget *linked_widget, bool if (linked_widget){ form_layout->getWidgetPosition(linked_widget, &parent_row, nullptr); }else { - form_layout->getWidgetPosition(ui->toolboxMain, &parent_row, nullptr); + //form_layout->getWidgetPosition(ui->toolboxMain, &parent_row, nullptr); + form_layout->getWidgetPosition(m_mainWidget, &parent_row, nullptr); } // Ensure the item is at the correct row order @@ -1322,8 +1349,13 @@ void TeraForm::hookReplyReceived(TeraDataTypes data_type, QList datas) void TeraForm::setDisabled(bool disable) { // Disable only the contents of pages, not the toolbox itself - for (int i=0; itoolboxMain->count(); i++){ - ui->toolboxMain->widget(i)->setDisabled(disable); + if (m_mainWidget == ui->toolboxMain){ + for (int i=0; itoolboxMain->count(); i++){ + ui->toolboxMain->widget(i)->setDisabled(disable); + } + }else{ + if (m_mainWidget) + m_mainWidget->setDisabled(disable); } m_disabled = disable; //QWidget::setDisabled(disable); @@ -1334,8 +1366,13 @@ void TeraForm::setDisabled(bool disable) void TeraForm::setEnabled(bool enable) { // Enable only the contents of pages, not the toolbox itself - for (int i=0; itoolboxMain->count(); i++){ - ui->toolboxMain->widget(i)->setEnabled(enable); + if (m_mainWidget == ui->toolboxMain){ + for (int i=0; itoolboxMain->count(); i++){ + ui->toolboxMain->widget(i)->setEnabled(enable); + } + }else{ + if (m_mainWidget) + m_mainWidget->setEnabled(enable); } m_disabled = !enable; //QWidget::setEnabled(enable); diff --git a/client/src/editors/TeraForm.h b/client/src/editors/TeraForm.h index 4955d531..c5b6eb85 100644 --- a/client/src/editors/TeraForm.h +++ b/client/src/editors/TeraForm.h @@ -84,6 +84,7 @@ class TeraForm : public QWidget private: Ui::TeraForm* ui; + QWidget* m_mainWidget; // The main widget that holds the form QMap m_widgets; QHash m_widgetsLabels; QHash m_hidden_rows; diff --git a/client/src/editors/TeraForm.ui b/client/src/editors/TeraForm.ui index 71a3988b..4452a21e 100644 --- a/client/src/editors/TeraForm.ui +++ b/client/src/editors/TeraForm.ui @@ -14,92 +14,9 @@ Form - /* -QWidget{background-color: rgba(0,0,0,0);color:white;border-radius:5px} -QPlainTextEdit,QLineEdit,QListWidget,QComboBox,QTextEdit,QSpinBox,QDateTimeEdit, QTimeEdit{background-color: rgba(255,255,255,50%); color: black;} - -QTableWidget{ -background-color: rgba(0,0,0,50%); -color: white; -border: 1px solid rgba(255,255,255,50%); -} - -QLineEdit:!enabled, QListWidget:!enabled,QComboBox:!enabled,QTextEdit:!enabled,QPlainTextEdit:!enabled, QDateTimeEdit:!enabled, QTimeEdit:!enabled{background-color: rgba(0,0,0,30%);color:white;border:0}QDateEdit::down-arrow:!enabled,QDateTimeEdit::down-arrow:!enabled,QTimeEdit::down-arrow:!enabled,QDateEdit::up-arrow:!enabled,QTimeEdit::up-arrow:!enabled,QComboBox::drop-down:!enabled,QDateTimeEdit::up-arrow:!enabled{background-color:rgba(0,0,0,0%);} - -QCheckBox::indicator:unchecked:!enabled { background-color:rgba(128,0,0,100%);} -QCheckBox::indicator:checked:!enabled { background-color:green;} -QCheckBox::indicator:unchecked{ background-color:red;border-radius:3px;} -QCheckBox::indicator:checked {background-color:rgb(0,255,0);border-radius:3px;} -QCheckBox:checked{color:lightgreen;background-color:rgba(0,0,0,0%);} -QCheckBox:!checked{color:red;background-color:rgba(0,0,0,0%);} - -QPushButton{background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2198c0, stop: 1 #0d5ca6);} -QPushButton:checked{background-color:rgb(128,128,128); border:1px solid rgba(255,255,255,50%);} -QPushButton:hover,QPushButton:checked{background-color: rgba(255,255,255,75%);color:black;} - -QToolButton:hover{background-color: rgba(255,255,255,50);} - -QTabWidget::pane { - background-color: rgba(128,128,140,25%); - border-radius: 5px; - border: 2px solid rgba(128,128,140,50%); - } - - QTabWidget::tab-bar { - left: 5px; - } - - - QTabBar::tab,QTableWidget::tab,QHeaderView::section { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 rgba(128,128,140,50%), stop: 1.0 rgba(128,128,140,25%)); - border: 2px solid rgba(128,128,140,50%); - border-bottom: 0px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - padding: 2px; -min-height:25px; - } - - QTabBar::tab:selected, QTabBar::tab:hover { - -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 rgba(128,128,140,70%), stop: 1.0 rgba(128,128,140,50%)); - } - - QTabBar::tab:selected { - border-color:rgba(128,128,140,60%); - border-bottom-color:rgba(128,128,140,60%); - } - - QTabBar::tab:!selected { - margin-top: 2px; - } - -QToolBox{background-color: rgba(128,150,150,25%);} - -QToolBox::tab { - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 blue, stop: 0.2 darkblue, stop:1 blue); - border-radius: 5px; - color: darkgray; -} - -QToolBox::tab:selected { - font: bold; - color: white; -} - -QToolBox::tab:!selected { - color: white; -} - -QWidget#pageSection1{ - border: 1px solid white; - border-radius: 5px; -} -*/ + - + 0 @@ -132,7 +49,7 @@ QWidget#pageSection1{ 0 0 396 - 267 + 266 From 4741d478bf2d61be9e2ad5303094f7b756f774f7 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 30 Nov 2022 16:10:19 -0500 Subject: [PATCH 11/42] Refs #63. Work in progress --- client/resources/stylesheet.qss | 2 +- client/src/widgets/ProjectNavigator.cpp | 415 ++++++++++++++++++------ client/src/widgets/ProjectNavigator.h | 14 +- client/src/widgets/ProjectNavigator.ui | 34 +- shared/src/data/TeraData.cpp | 2 +- shared/src/data/TeraData.h | 4 +- 6 files changed, 366 insertions(+), 105 deletions(-) diff --git a/client/resources/stylesheet.qss b/client/resources/stylesheet.qss index d02e7acf..293471bb 100644 --- a/client/resources/stylesheet.qss +++ b/client/resources/stylesheet.qss @@ -202,7 +202,7 @@ QLabel { */ QTreeWidget, QTableWidget{ selection-background-color: rgba(255,255,255,50%);/*rgba(0,5,45,50%)*/; - selection-color: lightblue; + selection-color: black/*lightblue*/; } QTreeWidget#treeNavigator { diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 5aa9e48a..6f1c7f51 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -276,27 +276,6 @@ void ProjectNavigator::removeItem(const TeraDataTypes &data_type, const int &id) m_participants_items.remove(id); } } - /*if (removed_item){ - if (removed_item->parent()){ - for (int i=0; iparent()->childCount(); i++){ - if (removed_item->parent()->child(i) == removed_item){ - if (removed_item == ui->treeNavigator->currentItem()){ - // Select parent item if current item was deleted - selectParentItem(); - } - removed_item->parent()->takeChild(i); - if (data_type==TERADATA_GROUP) - m_groups_items.remove(id); - if (data_type==TERADATA_PARTICIPANT){ - //m_participants.remove(m_participants_items.key(item)); - m_participants_items.remove(id); - } - - break; - } - } - } - }*/ } break; default: ; @@ -309,40 +288,14 @@ void ProjectNavigator::removeItem(const TeraDataTypes &data_type, const int &id) if (removed_item){ // Check to remove all childs from appropriate mappings for (int i=0; ichildCount(); i++){ - QTreeWidgetItem* child_item = removed_item->child(i); - TeraDataTypes child_type = getItemType(child_item); - // Items will be deleted when removed from TreeWidget - switch (child_type){ - case TERADATA_PROJECT: - m_projects_items.remove(m_projects_items.key(child_item)); - break; - case TERADATA_GROUP: - m_groups_items.remove(m_groups_items.key(child_item)); - break; - case TERADATA_PARTICIPANT: - m_participants_items.remove(m_participants_items.key(child_item)); - break; - default: - break; - - } + removeChildItemData(removed_item->child(i)); } + if (removed_item->parent()){ if (removed_item == ui->treeNavigator->currentItem()){ // Select parent item if current item was deleted selectParentItem(); } - // Removed the item from the parent - /*for (int i=0; iparent()->childCount(); i++){ - if (removed_item->parent()->child(i) == removed_item){ - if (removed_item == ui->treeNavigator->currentItem()){ - // Select parent item if current item was deleted - selectParentItem(); - } - removed_item->parent()->takeChild(i); - break; - } - }*/ } delete removed_item; } @@ -374,6 +327,7 @@ void ProjectNavigator::connectSignals() connect(m_comManager, &ComManager::projectsReceived, this, &ProjectNavigator::processProjectsReply); connect(m_comManager, &ComManager::groupsReceived, this, &ProjectNavigator::processGroupsReply); connect(m_comManager, &ComManager::participantsReceived, this, &ProjectNavigator::processParticipantsReply); + connect(m_comManager, &ComManager::statsReceived, this, &ProjectNavigator::processStatsReply); //connect(m_comManager, &ComManager::deleteResultsOK, this, &ProjectNavigator::processItemDeletedReply); connect(m_comManager, &ComManager::currentUserUpdated, this, &ProjectNavigator::processCurrentUserUpdated); @@ -406,6 +360,29 @@ void ProjectNavigator::clearData(bool clear_state) ui->treeNavigator->clear(); } +void ProjectNavigator::removeChildItemData(QTreeWidgetItem *item) +{ + for (int i=0; ichildCount(); i++){ + removeChildItemData(item->child(i)); + } + + TeraDataTypes child_type = getItemType(item); + // Items will be deleted when removed from TreeWidget + switch (child_type){ + case TERADATA_PROJECT: + m_projects_items.remove(m_projects_items.key(item)); + break; + case TERADATA_GROUP: + m_groups_items.remove(m_groups_items.key(item)); + break; + case TERADATA_PARTICIPANT: + m_participants_items.remove(m_participants_items.key(item)); + break; + default: + break; + } +} + void ProjectNavigator::updateSite(const TeraData *site) { int index = ui->cmbSites->findData(site->getId()); @@ -443,15 +420,20 @@ void ProjectNavigator::updateProject(const TeraData *project) item = new QTreeWidgetItem(); item->setData(0, Qt::UserRole, id_project); ui->treeNavigator->addTopLevelItem(item); - if (project->hasFieldName("project_participant_group_count")){ - if (project->getFieldValue("project_participant_group_count").toInt() > 0){ - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + item->setData(1, Qt::UserRole, QVariantHash()); // Stats data - empty here. + if (!isAdvancedView()){ + if (project->hasFieldName("project_participant_group_count")){ + if (project->getFieldValue("project_participant_group_count").toInt() > 0){ + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + } } - } - if (project->hasFieldName("project_participant_count")){ - if (project->getFieldValue("project_participant_count").toInt() > 0){ - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + if (project->hasFieldName("project_participant_count")){ + if (project->getFieldValue("project_participant_count").toInt() > 0){ + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + } } + }else{ + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); // Always show indicator in advanced view } m_projects_items[id_project] = item; } @@ -468,6 +450,88 @@ void ProjectNavigator::updateProject(const TeraData *project) //updateAvailableActions(nullptr); } +void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) +{ + if (!isAdvancedView()) + return; // Only in advanced view! + + QVariantHash stats = project_item->data(1, Qt::UserRole).toHash(); + + // Advanced view projects always have 3 childs (some may be hidden): Participants, Users, Devices + // If not, we must add those items! + if (project_item->childCount() != 3){ + qDeleteAll(project_item->takeChildren()); + + QTreeWidgetItem* static_item = new QTreeWidgetItem(); + static_item->setText(0, tr("Participants")); + static_item->setIcon(0, QIcon(TeraData::getIconFilenameForDataType(TERADATA_PARTICIPANT))); + static_item->setForeground(0, QColor(255, 255, 140)); + static_item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + static_item->setData(0, Qt::UserRole, TERADATA_PARTICIPANT); + project_item->addChild(static_item); + + static_item = new QTreeWidgetItem(); + static_item->setText(0, tr("Utilisateurs")); + static_item->setIcon(0, QIcon(TeraData::getIconFilenameForDataType(TERADATA_USER))); + static_item->setForeground(0, QColor(255, 255, 140)); + static_item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + static_item->setData(0, Qt::UserRole, TERADATA_USER); + project_item->addChild(static_item); + + static_item = new QTreeWidgetItem(); + static_item->setText(0, tr("Appareils")); + static_item->setIcon(0, QIcon(TeraData::getIconFilenameForDataType(TERADATA_DEVICE))); + static_item->setForeground(0, QColor(255, 255, 140)); + static_item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + static_item->setData(0, Qt::UserRole, TERADATA_DEVICE); + project_item->addChild(static_item); + } + + // Check what stats we have + // Participants + int count = 0; + if (ui->btnFilterActive->isChecked()){ // We include only enabled participants + if (stats.contains("participants_enabled_count")){ + count = stats["participants_enabled_count"].toInt(); + } + }else{ + if (stats.contains("participants_total_count")){ + count = stats["participants_total_count"].toInt(); + } + } + project_item->child(0)->setText(0, tr("Participants") + " (" + QString::number(count) + ")"); + project_item->child(0)->setHidden(count <= 0); + + // Users + count = 0; + if (ui->btnFilterActive->isChecked()){ // We include only enabled participants + if (stats.contains("users_enabled_count")){ + count = stats["users_enabled_count"].toInt(); + } + }else{ + if (stats.contains("users_total_count")){ + count = stats["users_total_count"].toInt(); + } + } + project_item->child(1)->setText(0, tr("Utilisateurs") + " (" + QString::number(count) + ")"); + project_item->child(1)->setHidden(count <= 0); + + // Devices + count = 0; + if (ui->btnFilterActive->isChecked()){ // We include only enabled participants + if (stats.contains("devices_enabled_count")){ + count = stats["devices_enabled_count"].toInt(); + } + }else{ + if (stats.contains("devices_total_count")){ + count = stats["devices_total_count"].toInt(); + } + } + project_item->child(2)->setText(0, tr("Appareils") + " (" + QString::number(count) + ")"); + project_item->child(2)->setHidden(count <= 0); + +} + void ProjectNavigator::updateGroup(const TeraData *group) { int id_group = group->getId(); @@ -484,13 +548,23 @@ void ProjectNavigator::updateGroup(const TeraData *group) // Maybe that group changed project... if (m_groups_items.contains(id_group)){ if (m_groups_items[id_group] != nullptr){ - int current_group_project = m_projects_items.key(m_groups_items[id_group]->parent()); + QTreeWidgetItem* project_item; + if (!isAdvancedView()) + project_item = m_groups_items[id_group]->parent(); + else + project_item = m_groups_items[id_group]->parent()->parent(); + int current_group_project = m_projects_items.key(project_item); if (current_group_project != id_project){ // It has - change group if available if (m_projects_items.contains(id_project)){ m_groups_items[id_group]->parent()->removeChild(m_groups_items[id_group]); //m_projects_items[id_project]->addChild(m_groups_items[id_group]); - m_projects_items[id_project]->insertChild(0, m_groups_items[id_group]); + getProjectItem(id_project, TERADATA_GROUP)->insertChild(0, m_groups_items[id_group]); + if (isAdvancedView()){ + queryStatsForProject(id_project); + queryStatsForProject(current_group_project); + + } }else{ // Changed site also! Remove it completely from display delete m_groups_items[id_group]; @@ -511,7 +585,7 @@ void ProjectNavigator::updateGroup(const TeraData *group) // New group - add it. item = new QTreeWidgetItem(); item->setData(0, Qt::UserRole, id_group); - QTreeWidgetItem* project_item = m_projects_items[id_project]; + QTreeWidgetItem* project_item = getProjectItem(id_project, TERADATA_GROUP); //project_item->addChild(item); project_item->insertChild(0, item); // Groups always first m_groups_items[id_group] = item; @@ -547,16 +621,37 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) // Check if we need to change its group int id_part_group = m_groups_items.key(item->parent()); if (id_part_group != id_group){ + // Participant changed group if (id_part_group > 0){ - // Participant is not in the correct group, change it + // Was already in a group before QTreeWidgetItem* old_group = m_groups_items[id_part_group]; - if (old_group) + if (old_group){ old_group->removeChild(item); + old_group->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); + } + if(isAdvancedView()){ + // Update counts + int id_part_project = m_projects_items.key(item->parent()->parent()->parent()); + queryStatsForProject(id_part_project); + } }else{ - // Participant was in a project before, find and remove it - QTreeWidgetItem* parent_project = m_projects_items[id_project]; - if (parent_project) + // Participant was not in a group but now is - remove from original project + QTreeWidgetItem* parent_item; + if (!isAdvancedView()){ + parent_item = item->parent(); + }else{ + parent_item = item->parent()->parent(); + } + int id_part_project = m_projects_items.key(parent_item); + //QTreeWidgetItem* parent_project = m_projects_items[id_part_project]; + QTreeWidgetItem* parent_project = getProjectItem(id_part_project, TERADATA_PARTICIPANT); + if (parent_project){ parent_project->removeChild(item); + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_part_project); + } + } } if (id_group > 0){ @@ -569,44 +664,76 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) // Not an expanded group, delete it! m_participants_items.remove(id_participant); delete item; + group_item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); return; } + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_project); + } }else{ // Not in a displayed group, delete it m_participants_items.remove(id_participant); delete item; + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_project); + } return; } }else{ // Participant doesn't have a group, attached to the project itself - QTreeWidgetItem* project_item = m_projects_items[id_project]; + QTreeWidgetItem* project_item = getProjectItem(id_project, TERADATA_PARTICIPANT);//m_projects_items[id_project]; if (project_item){ if (project_item->isExpanded()){ project_item->addChild(item); } + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_project); + } }else{ // Not an expanded project, delete it! m_participants_items.remove(id_participant); delete item; + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_project); + } return; } } }else{ // Check if we need to change its project - int id_part_project = m_projects_items.key(item->parent()); + int id_part_project; + if (!isAdvancedView()) + id_part_project = m_projects_items.key(item->parent()); + else + id_part_project = m_projects_items.key(item->parent()->parent()); + if (id_part_project != id_project && id_part_project>0){ - QTreeWidgetItem* project_item = m_projects_items[id_project]; + QTreeWidgetItem* project_item = getProjectItem(id_project, TERADATA_PARTICIPANT);//m_projects_items[id_project]; if (project_item){ if (item->parent()) item->parent()->removeChild(item); if (project_item->isExpanded()){ project_item->addChild(item); + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_part_project); + queryStatsForProject(id_project); + } + }else{ + // Not an expanded project, delete it! + m_participants_items.remove(id_participant); + delete item; + if(isAdvancedView()){ + // Update counts + queryStatsForProject(id_part_project); + queryStatsForProject(id_project); + } + return; } - }else{ - // Not an expanded project, delete it! - m_participants_items.remove(id_participant); - delete item; - return; } } } @@ -633,6 +760,10 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) if (project_item){ if (!project_item->isExpanded()){ project_item->setExpanded(true); + if (isAdvancedView()){ + // Expand participants in advanced view + getProjectItem(m_currentProjectId, TERADATA_PARTICIPANT)->setExpanded(true); + } } } @@ -645,13 +776,13 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) if (project_item){ if (!project_item->isExpanded()){ project_item->setExpanded(true); - /*project_item->addChild(item); - m_participants_items[id_participant] = item; - }else{ - // No project expanded, add to the list, but also query for other participants in that project - delete item; - return;*/ + if (isAdvancedView()){ + project_item->child(0)->setExpanded(true); + } + } + // Get item to attach to + project_item = getProjectItem(id_project, TERADATA_PARTICIPANT); project_item->addChild(item); m_participants_items[id_participant] = item; /*if (m_currentParticipantUuid == participant->getUuid()) @@ -683,13 +814,35 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) } +void ProjectNavigator::queryParticipantsAndGroupsForProject(const int &id_project) +{ + // Project: load groups for that project + QUrlQuery query; + query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); + query.addQueryItem(WEB_QUERY_LIST, "1"); + m_comManager->doGet(WEB_GROUPINFO_PATH, query); + + // Also loads participant not in a group + query.clear(); + query.addQueryItem(WEB_QUERY_NO_GROUP,"1"); + query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); + //query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); + m_comManager->doGet(WEB_PARTICIPANTINFO_PATH, query); +} + +void ProjectNavigator::queryStatsForProject(const int &id_project) +{ + QUrlQuery query; + query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); + m_comManager->doGet(WEB_STATS_PATH, query); +} + int ProjectNavigator::getParticipantProjectId(QTreeWidgetItem *part_item) { QTreeWidgetItem* current_item = part_item; while (current_item->parent()){ current_item = current_item->parent(); - //if (m_projects_items.values().contains(current_item)) int current_key = m_projects_items.key(current_item, -1); if (current_key>=0) return current_key; @@ -704,15 +857,32 @@ int ProjectNavigator::getParticipantGroupId(QTreeWidgetItem *part_item) while (current_item->parent()){ current_item = current_item->parent(); - //if (m_groups_items.values().contains(current_item)) int current_key = m_groups_items.key(current_item, -1); if (current_key>=0) - //return m_groups_items.key(current_item); return current_key; } return -1; } +QTreeWidgetItem *ProjectNavigator::getProjectItem(const int &id_project, const TeraDataTypes &data_type) +{ + QTreeWidgetItem* project_item = m_projects_items[id_project]; + if (isAdvancedView()){ + if (project_item->childCount()>=3){ + if (data_type == TERADATA_PARTICIPANT || data_type == TERADATA_GROUP){ + project_item = project_item->child(0); + } + if (data_type == TERADATA_USER){ + project_item = project_item->child(1); + } + if (data_type == TERADATA_DEVICE){ + project_item = project_item->child(2); + } + } + } + return project_item; +} + bool ProjectNavigator::isParticipantFiltered(const QString &part_uuid) { // No participant with that uuid! @@ -735,6 +905,11 @@ bool ProjectNavigator::isParticipantFiltered(const QString &part_uuid) return filtered; } +bool ProjectNavigator::isAdvancedView() +{ + return ui->btnAdvanced->isChecked(); +} + void ProjectNavigator::setCurrentItem(QTreeWidgetItem *item) { ui->treeNavigator->setCurrentItem(item); @@ -833,17 +1008,14 @@ void ProjectNavigator::updateAvailableActions(QTreeWidgetItem* current_item) TeraDataTypes ProjectNavigator::getItemType(QTreeWidgetItem *item) { - //if (m_projects_items.values().contains(item)){ if (std::find(m_projects_items.cbegin(), m_projects_items.cend(), item) != m_projects_items.cend()){ return TERADATA_PROJECT; } - //if (m_groups_items.values().contains(item)){ if (std::find(m_groups_items.cbegin(), m_groups_items.cend(), item) != m_groups_items.cend()){ return TERADATA_GROUP; } - //if (m_participants_items.values().contains(item)){ if (std::find(m_participants_items.cbegin(), m_participants_items.cend(), item) != m_participants_items.cend()){ return TERADATA_PARTICIPANT; } @@ -977,7 +1149,7 @@ void ProjectNavigator::processProjectsReply(const QList projects) m_siteJustChanged = false; if (m_projects_items.count() == 1){ // Select the first project since the only one in the list - //navItemClicked(m_projects_items.first()); + //navItemClicked(m_projects_items.first()); // Commented to display dashboard for that site instead } } } @@ -999,6 +1171,28 @@ void ProjectNavigator::processParticipantsReply(const QList participan } } +void ProjectNavigator::processStatsReply(TeraData stats, QUrlQuery reply_query) +{ + // Only process stats in "advanced" view + if (!isAdvancedView()) + return; + + // Projects stats + if (reply_query.hasQueryItem(WEB_QUERY_ID_PROJECT)){ + // Find the associated project + int id_project = reply_query.queryItemValue(WEB_QUERY_ID_PROJECT).toInt(); + if (m_projects_items.contains(id_project)){ + QTreeWidgetItem* item = m_projects_items[id_project]; + + // Save stats in item + item->setData(1, Qt::UserRole, stats.getFieldValues()); + + // Update item stats + updateProjectAdvanced(item); + } + } +} + void ProjectNavigator::ws_participantEvent(ParticipantEvent event) { QString part_uuid = QString::fromStdString(event.participant_uuid()); @@ -1184,20 +1378,13 @@ void ProjectNavigator::navItemExpanded(QTreeWidgetItem *item) { // PROJECT if (std::find(m_projects_items.cbegin(), m_projects_items.cend(), item) != m_projects_items.cend()){ - // Project: load groups for that project int id = m_projects_items.key(item); - - QUrlQuery query; - query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id)); - query.addQueryItem(WEB_QUERY_LIST, "true"); - m_comManager->doGet(WEB_GROUPINFO_PATH, query); - - // Also loads participant not in a group - query.clear(); - query.addQueryItem(WEB_QUERY_NO_GROUP,"true"); - query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id)); - //query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); - m_comManager->doGet(WEB_PARTICIPANTINFO_PATH, query); + if (!isAdvancedView()){ + queryParticipantsAndGroupsForProject(id); + }else{ + // Query stats to display static items + queryStatsForProject(id); + } } @@ -1214,6 +1401,29 @@ void ProjectNavigator::navItemExpanded(QTreeWidgetItem *item) m_comManager->doGet(WEB_PARTICIPANTINFO_PATH, query); } + + if (isAdvancedView()){ + if (item->parent()){ + // Check if we have an aggregrator item, such as all users or participants + TeraDataTypes parent_type = getItemType(item->parent()); + if (parent_type == TERADATA_PROJECT){ + // Parent is project, decide what to do based on the data type + int id = m_projects_items.key(item->parent()); + TeraDataTypes item_type = static_cast(item->data(0, Qt::UserRole).toInt()); + switch (item_type){ + case TERADATA_PARTICIPANT: + queryParticipantsAndGroupsForProject(id); + break; + case TERADATA_USER: + break; + case TERADATA_DEVICE: + break; + default: + break; + } + } + } + } } void ProjectNavigator::btnEditSite_clicked() @@ -1264,7 +1474,7 @@ void ProjectNavigator::on_cmbSites_currentIndexChanged(int index) } -void ProjectNavigator::on_toolButton_clicked() +void ProjectNavigator::on_btnDashboard_clicked() { // Query to display dashboard (default view) emit dataDisplayRequest(TERADATA_NONE, 0); @@ -1292,3 +1502,10 @@ void ProjectNavigator::on_treeNavigator_customContextMenuRequested(const QPoint } + +void ProjectNavigator::on_btnAdvanced_clicked() +{ + // Force a refresh of the data for that site + currentSiteChanged(true); +} + diff --git a/client/src/widgets/ProjectNavigator.h b/client/src/widgets/ProjectNavigator.h index 2632d6c6..aa8c944b 100644 --- a/client/src/widgets/ProjectNavigator.h +++ b/client/src/widgets/ProjectNavigator.h @@ -68,13 +68,20 @@ class ProjectNavigator : public QWidget void updateSite(const TeraData* site); void updateProject(const TeraData* project); + void updateProjectAdvanced(QTreeWidgetItem* project_item); void updateGroup(const TeraData* group); void updateParticipant(const TeraData* participant); + void queryParticipantsAndGroupsForProject(const int& id_project); + void queryStatsForProject(const int& id_project); + int getParticipantProjectId(QTreeWidgetItem *part_item); int getParticipantGroupId(QTreeWidgetItem *part_item); + QTreeWidgetItem* getProjectItem(const int& id_project, const TeraDataTypes& data_type = TERADATA_NONE); + bool isParticipantFiltered(const QString &part_uuid); + bool isAdvancedView(); void setCurrentItem(QTreeWidgetItem* item); void selectItem(QTreeWidgetItem* item); @@ -88,6 +95,8 @@ class ProjectNavigator : public QWidget void clearData(bool clear_state); + void removeChildItemData(QTreeWidgetItem* item); + // Ui items QList m_newItemActions; QMenu* m_newItemMenu; @@ -104,6 +113,7 @@ private slots: void processProjectsReply(const QList projects); void processGroupsReply(const QList groups); void processParticipantsReply(const QList participants, const QUrlQuery reply_args); + void processStatsReply(TeraData stats, QUrlQuery reply_query); void ws_participantEvent(opentera::protobuf::ParticipantEvent event); void processItemDeletedReply(QString path, int id); @@ -121,10 +131,12 @@ private slots: void on_btnSearch_toggled(bool checked); void on_txtNavSearch_textChanged(const QString &search_text); void on_cmbSites_currentIndexChanged(int index); - void on_toolButton_clicked(); + void on_btnDashboard_clicked(); void on_treeNavigator_customContextMenuRequested(const QPoint &pos); + void on_btnAdvanced_clicked(); + signals: void dataDisplayRequest(TeraDataTypes data_type, int data_id); void dataDeleteRequest(TeraDataTypes data_type, int data_id); diff --git a/client/src/widgets/ProjectNavigator.ui b/client/src/widgets/ProjectNavigator.ui index 63b65016..c74b3e9d 100644 --- a/client/src/widgets/ProjectNavigator.ui +++ b/client/src/widgets/ProjectNavigator.ui @@ -93,7 +93,7 @@ - + PointingHandCursor @@ -362,6 +362,38 @@ + + + + + 32 + 20 + + + + PointingHandCursor + + + Vue étendue + + + Avancé + + + + :/icons/navtree.png:/icons/navtree.png + + + + 24 + 24 + + + + true + + + diff --git a/shared/src/data/TeraData.cpp b/shared/src/data/TeraData.cpp index 86b7a52c..3f18cd03 100644 --- a/shared/src/data/TeraData.cpp +++ b/shared/src/data/TeraData.cpp @@ -224,7 +224,7 @@ QList TeraData::getFieldList() const return rval; } -QVariantMap TeraData::getFieldValues() +QVariantHash TeraData::getFieldValues() { return m_fieldsValue; } diff --git a/shared/src/data/TeraData.h b/shared/src/data/TeraData.h index 538a5d6c..0a9f6ea1 100644 --- a/shared/src/data/TeraData.h +++ b/shared/src/data/TeraData.h @@ -110,7 +110,7 @@ class TeraData : public QObject QVariant getFieldValue(const QString &fieldName) const; void setFieldValue(const QString& fieldName, const QVariant& fieldValue); QList getFieldList() const; - QVariantMap getFieldValues(); + QVariantHash getFieldValues(); static QString getDataTypeName(const TeraDataTypes& data_type); static QString getDataTypeNameText(const TeraDataTypes& data_type); @@ -135,7 +135,7 @@ class TeraData : public QObject QString m_busyField; QString m_uuidField; - QVariantMap m_fieldsValue; + QVariantHash m_fieldsValue; //bool hasMetaProperty(const QString& fieldName) const; From 331564b673920fe70a6ff310ba0ecd660e8b0dde Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 6 Dec 2022 13:11:17 -0500 Subject: [PATCH 12/42] Refs #63. Advanced view in project navigator working with participants. --- client/src/widgets/ProjectNavigator.cpp | 118 ++++++++++++++++++------ client/src/widgets/ProjectNavigator.h | 1 + client/src/widgets/ProjectNavigator.ui | 2 +- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 6f1c7f51..49671772 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -274,6 +274,11 @@ void ProjectNavigator::removeItem(const TeraDataTypes &data_type, const int &id) if (m_participants_items.contains(id)){ removed_item = m_participants_items[id]; m_participants_items.remove(id); + + if (isAdvancedView()){ + int id_project = getParticipantProjectId(removed_item); + updateCountsForProject(id_project); + } } } } @@ -561,9 +566,8 @@ void ProjectNavigator::updateGroup(const TeraData *group) //m_projects_items[id_project]->addChild(m_groups_items[id_group]); getProjectItem(id_project, TERADATA_GROUP)->insertChild(0, m_groups_items[id_group]); if (isAdvancedView()){ - queryStatsForProject(id_project); - queryStatsForProject(current_group_project); - + updateCountsForProject(id_project); + updateCountsForProject(current_group_project); } }else{ // Changed site also! Remove it completely from display @@ -632,7 +636,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) if(isAdvancedView()){ // Update counts int id_part_project = m_projects_items.key(item->parent()->parent()->parent()); - queryStatsForProject(id_part_project); + updateCountsForProject(id_part_project); } }else{ // Participant was not in a group but now is - remove from original project @@ -649,7 +653,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) parent_project->removeChild(item); if(isAdvancedView()){ // Update counts - queryStatsForProject(id_part_project); + updateCountsForProject(id_part_project); } } } @@ -669,7 +673,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) } if(isAdvancedView()){ // Update counts - queryStatsForProject(id_project); + updateCountsForProject(id_project); } }else{ // Not in a displayed group, delete it @@ -677,7 +681,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) delete item; if(isAdvancedView()){ // Update counts - queryStatsForProject(id_project); + updateCountsForProject(id_project); } return; } @@ -690,7 +694,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) } if(isAdvancedView()){ // Update counts - queryStatsForProject(id_project); + updateCountsForProject(id_project); } }else{ // Not an expanded project, delete it! @@ -698,7 +702,7 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) delete item; if(isAdvancedView()){ // Update counts - queryStatsForProject(id_project); + updateCountsForProject(id_project); } return; } @@ -720,8 +724,8 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) project_item->addChild(item); if(isAdvancedView()){ // Update counts - queryStatsForProject(id_part_project); - queryStatsForProject(id_project); + updateCountsForProject(id_part_project); + updateCountsForProject(id_project); } }else{ // Not an expanded project, delete it! @@ -729,8 +733,8 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) delete item; if(isAdvancedView()){ // Update counts - queryStatsForProject(id_part_project); - queryStatsForProject(id_project); + updateCountsForProject(id_part_project); + updateCountsForProject(id_project); } return; } @@ -779,11 +783,13 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) if (isAdvancedView()){ project_item->child(0)->setExpanded(true); } - } // Get item to attach to project_item = getProjectItem(id_project, TERADATA_PARTICIPANT); project_item->addChild(item); + if (isAdvancedView()){ + updateCountsForProject(id_project); + } m_participants_items[id_participant] = item; /*if (m_currentParticipantUuid == participant->getUuid()) ui->treeNavigator->setCurrentItem(item);*/ @@ -812,6 +818,9 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) setCurrentItem(item); } + if (isAdvancedView()) + updateCountsForProject(id_project); + } void ProjectNavigator::queryParticipantsAndGroupsForProject(const int &id_project) @@ -837,6 +846,49 @@ void ProjectNavigator::queryStatsForProject(const int &id_project) m_comManager->doGet(WEB_STATS_PATH, query); } +void ProjectNavigator::updateCountsForProject(const int &id_project) +{ + if (isAdvancedView()){ + queryStatsForProject(id_project); // For now... + /*// Count number of sub-items in each categories to update their labels + + // Participants + QTreeWidgetItem* item_project = getProjectItem(id_project, TERADATA_PARTICIPANT); + int count = 0; + for (int i=0; ichildCount(); i++){ + if (item_project->child(i)->childCount() > 0){ + for(int j=0; jchild(i)->childCount(); j++) + if (!item_project->child(i)->child(j)->isHidden()){ + count ++; // Also includes participants in groups ** WILL NOT WORK IF GROUPS ARE NOT EXPANDED! + } + }else{ + if (!item_project->child(i)->isHidden()) + count++; + } + } + item_project->setText(0, tr("Participants") + " (" + QString::number(count) + ")"); + item_project->setHidden(count == 0); + + // Users + item_project = getProjectItem(id_project, TERADATA_USER); + count = 0; + for (int i=0; ichildCount(); i++){ + count++; + } + item_project->setText(0, tr("Utilisateurs") + " (" + QString::number(count) + ")"); + item_project->setHidden(count == 0); + + // Devices + item_project = getProjectItem(id_project, TERADATA_DEVICE); + count = 0; + for (int i=0; ichildCount(); i++){ + count++; + } + item_project->setText(0, tr("Appareils") + " (" + QString::number(count) + ")"); + item_project->setHidden(count == 0);*/ + } +} + int ProjectNavigator::getParticipantProjectId(QTreeWidgetItem *part_item) { QTreeWidgetItem* current_item = part_item; @@ -868,18 +920,19 @@ QTreeWidgetItem *ProjectNavigator::getProjectItem(const int &id_project, const T { QTreeWidgetItem* project_item = m_projects_items[id_project]; if (isAdvancedView()){ - if (project_item->childCount()>=3){ - if (data_type == TERADATA_PARTICIPANT || data_type == TERADATA_GROUP){ - project_item = project_item->child(0); - } - if (data_type == TERADATA_USER){ - project_item = project_item->child(1); - } - if (data_type == TERADATA_DEVICE){ - project_item = project_item->child(2); - } + //if (project_item->childCount()>=3){ + if (data_type == TERADATA_PARTICIPANT || data_type == TERADATA_GROUP){ + project_item = project_item->child(0); + } + if (data_type == TERADATA_USER){ + project_item = project_item->child(1); } + if (data_type == TERADATA_DEVICE){ + project_item = project_item->child(2); + } + //} } + return project_item; } @@ -1340,15 +1393,14 @@ void ProjectNavigator::currentNavItemChanged(QTreeWidgetItem *current, QTreeWidg } // PARTICIPANT GROUP - if (item_type==TERADATA_GROUP){ + if (item_type==TERADATA_GROUP){ // Ensure project id is correctly set - m_currentProjectId = m_participants_items.key(current->parent()); - // We have a participants group + m_currentProjectId = getParticipantProjectId(current); //m_participants_items.key(current->parent()); //navItemExpanded(current); int id = m_groups_items.key(current); m_currentGroupId = id; - id = m_projects_items.key(current->parent()); - m_currentProjectId = id; + /*id = m_projects_items.key(current->parent()); + m_currentProjectId = id;*/ //qDebug() << "Request to display group id: " << m_currentGroupId; emit dataDisplayRequest(TERADATA_GROUP, m_currentGroupId); } @@ -1440,6 +1492,14 @@ void ProjectNavigator::on_btnFilterActive_toggled(bool checked) item->setHidden(filtered); } } + // Update counts for all projects + if (isAdvancedView()){ + for(QTreeWidgetItem* item_project:qAsConst(m_projects_items)){ + if (item_project->isExpanded()){ + updateCountsForProject(m_projects_items.key(item_project)); + } + } + } // Save new setting TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_FILTERINACTIVE, checked); diff --git a/client/src/widgets/ProjectNavigator.h b/client/src/widgets/ProjectNavigator.h index aa8c944b..df5b9966 100644 --- a/client/src/widgets/ProjectNavigator.h +++ b/client/src/widgets/ProjectNavigator.h @@ -74,6 +74,7 @@ class ProjectNavigator : public QWidget void queryParticipantsAndGroupsForProject(const int& id_project); void queryStatsForProject(const int& id_project); + void updateCountsForProject(const int& id_project); int getParticipantProjectId(QTreeWidgetItem *part_item); int getParticipantGroupId(QTreeWidgetItem *part_item); diff --git a/client/src/widgets/ProjectNavigator.ui b/client/src/widgets/ProjectNavigator.ui index c74b3e9d..c66c8b44 100644 --- a/client/src/widgets/ProjectNavigator.ui +++ b/client/src/widgets/ProjectNavigator.ui @@ -381,7 +381,7 @@ - :/icons/navtree.png:/icons/navtree.png + :/icons/details.png:/icons/details.png From 1654d1dc2caf78c5948e3e8b76d296d60e0b7692 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 7 Dec 2022 11:43:09 -0500 Subject: [PATCH 13/42] Refs #63. Added display of users and devices in ProjectNavigator advanced view. --- client/src/main/MainWindow.cpp | 2 +- client/src/widgets/LogViewWidget.cpp | 2 +- client/src/widgets/ProjectNavigator.cpp | 424 +++++++++++++++++++----- client/src/widgets/ProjectNavigator.h | 25 +- shared/src/data/TeraSettings.h | 1 + 5 files changed, 371 insertions(+), 83 deletions(-) diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index 6d1393fc..98b2a4d3 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -475,7 +475,7 @@ void MainWindow::dataDisplayRequested(TeraDataTypes data_type, int data_id) QUrlQuery query; query.addQueryItem(WEB_QUERY_ID, QString::number(data_id)); - if (data_type == TERADATA_USER){ + if (data_type == TERADATA_USER || data_type == TERADATA_DEVICE){ // Also query for status query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); } diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 89f5ea9d..e8392b04 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -369,7 +369,7 @@ QString LogViewWidget::getOSIcon(const QString &os) if (compare_os.contains("windows")) return "://icons/logs/os_windows.png"; - if (compare_os.contains("apple")) + if (compare_os.contains("apple") || compare_os.contains("mac")) return "://icons/logs/os_apple.png"; if (compare_os.contains("linux") || compare_os.contains("ubuntu")) diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 49671772..2d13cdaf 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -65,10 +65,13 @@ void ProjectNavigator::initUi() new_action->setDisabled(true); ui->btnNewItem->setMenu(m_newItemMenu); - // Load setting for filter inactive + // Load settings bool filter = TeraSettings::getUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_FILTERINACTIVE).toBool(); ui->btnFilterActive->setChecked(filter); + bool advanced = TeraSettings::getUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_ADVANCEDVIEW).toBool(); + ui->btnAdvanced->setChecked(advanced); + // Request sites m_comManager->doGet(WEB_SITEINFO_PATH, QUrlQuery(WEB_QUERY_LIST)); @@ -168,6 +171,20 @@ void ProjectNavigator::selectItem(const TeraDataTypes &data_type, const int &id) return; } + if (data_type == TERADATA_USER){ + if (m_users_items.contains(id)){ + setCurrentItem(m_users_items.value(id)); // Get first user! + } + return; + } + + if (data_type == TERADATA_DEVICE){ + if (m_devices_items.contains(id)){ + setCurrentItem(m_devices_items.value(id)); // Get first! + } + return; + } + } @@ -203,6 +220,26 @@ bool ProjectNavigator::selectItemByName(const TeraDataTypes &data_type, const QS } } + if (data_type == TERADATA_USER){ + foreach(QTreeWidgetItem* item, m_users_items){ + if (item->text(0) == name){ + ui->treeNavigator->setCurrentItem(item); + currentNavItemChanged(item, nullptr); + return true; + } + } + } + + if (data_type == TERADATA_DEVICE){ + foreach(QTreeWidgetItem* item, m_devices_items){ + if (item->text(0) == name){ + ui->treeNavigator->setCurrentItem(item); + currentNavItemChanged(item, nullptr); + return true; + } + } + } + return false; } @@ -275,10 +312,10 @@ void ProjectNavigator::removeItem(const TeraDataTypes &data_type, const int &id) removed_item = m_participants_items[id]; m_participants_items.remove(id); - if (isAdvancedView()){ + /*if (isAdvancedView()){ int id_project = getParticipantProjectId(removed_item); updateCountsForProject(id_project); - } + }*/ } } } @@ -332,11 +369,14 @@ void ProjectNavigator::connectSignals() connect(m_comManager, &ComManager::projectsReceived, this, &ProjectNavigator::processProjectsReply); connect(m_comManager, &ComManager::groupsReceived, this, &ProjectNavigator::processGroupsReply); connect(m_comManager, &ComManager::participantsReceived, this, &ProjectNavigator::processParticipantsReply); + connect(m_comManager, &ComManager::usersReceived, this, &ProjectNavigator::processUsersReply); + connect(m_comManager, &ComManager::devicesReceived, this, &ProjectNavigator::processDevicesReply); connect(m_comManager, &ComManager::statsReceived, this, &ProjectNavigator::processStatsReply); //connect(m_comManager, &ComManager::deleteResultsOK, this, &ProjectNavigator::processItemDeletedReply); connect(m_comManager, &ComManager::currentUserUpdated, this, &ProjectNavigator::processCurrentUserUpdated); connect(m_comManager->getWebSocketManager(), &WebSocketManager::participantEventReceived, this, &ProjectNavigator::ws_participantEvent); + connect(m_comManager->getWebSocketManager(), &WebSocketManager::userEventReceived, this, &ProjectNavigator::ws_userEvent); //void (QComboBox::* comboIndexChangedSignal)(int) = &QComboBox::currentIndexChanged; //connect(ui->cmbSites, comboIndexChangedSignal, this, &ProjectNavigator::currentSiteChanged); @@ -355,7 +395,12 @@ void ProjectNavigator::clearData(bool clear_state) m_projects_items.clear(); m_groups_items.clear(); m_participants_items.clear(); + m_users_items.clear(); + m_devices_items.clear(); + + m_users.clear(); m_participants.clear(); + m_devices.clear(); if (clear_state){ m_currentProjectId = -1; m_currentGroupId = -1; @@ -504,7 +549,7 @@ void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) count = stats["participants_total_count"].toInt(); } } - project_item->child(0)->setText(0, tr("Participants") + " (" + QString::number(count) + ")"); + //project_item->child(0)->setText(0, tr("Participants") + " (" + QString::number(count) + ")");*/ project_item->child(0)->setHidden(count <= 0); // Users @@ -518,7 +563,7 @@ void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) count = stats["users_total_count"].toInt(); } } - project_item->child(1)->setText(0, tr("Utilisateurs") + " (" + QString::number(count) + ")"); + //project_item->child(1)->setText(0, tr("Utilisateurs") + " (" + QString::number(count) + ")"); project_item->child(1)->setHidden(count <= 0); // Devices @@ -532,7 +577,7 @@ void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) count = stats["devices_total_count"].toInt(); } } - project_item->child(2)->setText(0, tr("Appareils") + " (" + QString::number(count) + ")"); + //project_item->child(2)->setText(0, tr("Appareils") + " (" + QString::number(count) + ")"); project_item->child(2)->setHidden(count <= 0); } @@ -565,10 +610,10 @@ void ProjectNavigator::updateGroup(const TeraData *group) m_groups_items[id_group]->parent()->removeChild(m_groups_items[id_group]); //m_projects_items[id_project]->addChild(m_groups_items[id_group]); getProjectItem(id_project, TERADATA_GROUP)->insertChild(0, m_groups_items[id_group]); - if (isAdvancedView()){ + /*if (isAdvancedView()){ updateCountsForProject(id_project); updateCountsForProject(current_group_project); - } + }*/ }else{ // Changed site also! Remove it completely from display delete m_groups_items[id_group]; @@ -633,11 +678,11 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) old_group->removeChild(item); old_group->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); } - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts int id_part_project = m_projects_items.key(item->parent()->parent()->parent()); updateCountsForProject(id_part_project); - } + }*/ }else{ // Participant was not in a group but now is - remove from original project QTreeWidgetItem* parent_item; @@ -651,10 +696,10 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) QTreeWidgetItem* parent_project = getProjectItem(id_part_project, TERADATA_PARTICIPANT); if (parent_project){ parent_project->removeChild(item); - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_part_project); - } + }*/ } } @@ -671,18 +716,18 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) group_item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); return; } - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_project); - } + }*/ }else{ // Not in a displayed group, delete it m_participants_items.remove(id_participant); delete item; - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_project); - } + }*/ return; } }else{ @@ -692,18 +737,18 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) if (project_item->isExpanded()){ project_item->addChild(item); } - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_project); - } + }*/ }else{ // Not an expanded project, delete it! m_participants_items.remove(id_participant); delete item; - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_project); - } + }*/ return; } } @@ -722,20 +767,20 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) item->parent()->removeChild(item); if (project_item->isExpanded()){ project_item->addChild(item); - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_part_project); updateCountsForProject(id_project); - } + }*/ }else{ // Not an expanded project, delete it! m_participants_items.remove(id_participant); delete item; - if(isAdvancedView()){ + /*if(isAdvancedView()){ // Update counts updateCountsForProject(id_part_project); updateCountsForProject(id_project); - } + }*/ return; } } @@ -787,10 +832,9 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) // Get item to attach to project_item = getProjectItem(id_project, TERADATA_PARTICIPANT); project_item->addChild(item); - if (isAdvancedView()){ - updateCountsForProject(id_project); - } m_participants_items[id_participant] = item; + /*if (isAdvancedView()) + updateCountsForProject(id_project);*/ /*if (m_currentParticipantUuid == participant->getUuid()) ui->treeNavigator->setCurrentItem(item);*/ }else{ @@ -818,11 +862,119 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) setCurrentItem(item); } - if (isAdvancedView()) - updateCountsForProject(id_project); } +void ProjectNavigator::updateUser(const TeraData *user, const int& id_project) +{ + int id_user = user->getId(); + + QList items = m_users_items.values(id_user); + + if (id_project >=0){ + // Check if already exists for that project + bool exists = false; + for (QTreeWidgetItem* item:qAsConst(items)){ + QTreeWidgetItem* project = getProjectForItem(item); + if (project){ + if (m_projects_items.key(project) == id_project){ + exists = true; + break; + } + } + } + if (!exists){ + // No user item for that project already exists - create it! + // New user - add it. + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setData(0, Qt::UserRole, id_user); + + QTreeWidgetItem* project_item = m_projects_items[id_project]; + if (project_item){ + if (!project_item->isExpanded()){ + project_item->setExpanded(true); + if (isAdvancedView()){ + project_item->child(1)->setExpanded(true); + } + } + // Get item to attach to + project_item = getProjectItem(id_project, TERADATA_USER); + project_item->addChild(item); + m_users_items.insert(id_user, item); + items.append(item); + } + } + }else{ + // No project id specified - check if the user is at least in the list! + if (items.isEmpty()){ + qWarning() << "Trying to add user " << user->getName() << ", but no project for it!"; + return; + } + } + + for (QTreeWidgetItem* item:qAsConst(items)){ + item->setText(0, user->getName()); + item->setIcon(0, QIcon(user->getIconStateFilename())); + } + if (!items.isEmpty()) + m_users[user->getUuid()] = *user; + +} + +void ProjectNavigator::updateDevice(const TeraData *device, const int &id_project) +{ + int id_device = device->getId(); + + QList items = m_devices_items.values(id_device); + + if (id_project >=0){ + // Check if already exists for that project + bool exists = false; + for (QTreeWidgetItem* item:qAsConst(items)){ + QTreeWidgetItem* project = getProjectForItem(item); + if (project){ + if (m_projects_items.key(project) == id_project){ + exists = true; + break; + } + } + } + if (!exists){ + // No item for that project already exists - create it! + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setData(0, Qt::UserRole, id_device); + + QTreeWidgetItem* project_item = m_projects_items[id_project]; + if (project_item){ + if (!project_item->isExpanded()){ + project_item->setExpanded(true); + if (isAdvancedView()){ + project_item->child(2)->setExpanded(true); + } + } + // Get item to attach to + project_item = getProjectItem(id_project, TERADATA_DEVICE); + project_item->addChild(item); + m_devices_items.insert(id_device, item); + items.append(item); + } + } + }else{ + // No project id specified - check if the user is at least in the list! + if (items.isEmpty()){ + qWarning() << "Trying to add device " << device->getName() << ", but no project for it!"; + return; + } + } + + for (QTreeWidgetItem* item:qAsConst(items)){ + item->setText(0, device->getName()); + item->setIcon(0, QIcon(device->getIconStateFilename())); + } + if (!items.isEmpty()) + m_devices[device->getUuid()] = *device; +} + void ProjectNavigator::queryParticipantsAndGroupsForProject(const int &id_project) { // Project: load groups for that project @@ -839,54 +991,31 @@ void ProjectNavigator::queryParticipantsAndGroupsForProject(const int &id_projec m_comManager->doGet(WEB_PARTICIPANTINFO_PATH, query); } -void ProjectNavigator::queryStatsForProject(const int &id_project) +void ProjectNavigator::queryUsersForProject(const int &id_project) { QUrlQuery query; query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); - m_comManager->doGet(WEB_STATS_PATH, query); + query.addQueryItem(WEB_QUERY_LIST, "1"); + query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); + query.addQueryItem(WEB_QUERY_ENABLED, "1"); + m_comManager->doGet(WEB_USERINFO_PATH, query); } -void ProjectNavigator::updateCountsForProject(const int &id_project) +void ProjectNavigator::queryDevicesForProject(const int &id_project) { - if (isAdvancedView()){ - queryStatsForProject(id_project); // For now... - /*// Count number of sub-items in each categories to update their labels - - // Participants - QTreeWidgetItem* item_project = getProjectItem(id_project, TERADATA_PARTICIPANT); - int count = 0; - for (int i=0; ichildCount(); i++){ - if (item_project->child(i)->childCount() > 0){ - for(int j=0; jchild(i)->childCount(); j++) - if (!item_project->child(i)->child(j)->isHidden()){ - count ++; // Also includes participants in groups ** WILL NOT WORK IF GROUPS ARE NOT EXPANDED! - } - }else{ - if (!item_project->child(i)->isHidden()) - count++; - } - } - item_project->setText(0, tr("Participants") + " (" + QString::number(count) + ")"); - item_project->setHidden(count == 0); - - // Users - item_project = getProjectItem(id_project, TERADATA_USER); - count = 0; - for (int i=0; ichildCount(); i++){ - count++; - } - item_project->setText(0, tr("Utilisateurs") + " (" + QString::number(count) + ")"); - item_project->setHidden(count == 0); - - // Devices - item_project = getProjectItem(id_project, TERADATA_DEVICE); - count = 0; - for (int i=0; ichildCount(); i++){ - count++; - } - item_project->setText(0, tr("Appareils") + " (" + QString::number(count) + ")"); - item_project->setHidden(count == 0);*/ - } + QUrlQuery query; + query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); + query.addQueryItem(WEB_QUERY_LIST, "1"); + query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); + query.addQueryItem(WEB_QUERY_ENABLED, "1"); + m_comManager->doGet(WEB_DEVICEINFO_PATH, query); +} + +void ProjectNavigator::queryStatsForProject(const int &id_project) +{ + QUrlQuery query; + query.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(id_project)); + m_comManager->doGet(WEB_STATS_PATH, query); } int ProjectNavigator::getParticipantProjectId(QTreeWidgetItem *part_item) @@ -936,6 +1065,22 @@ QTreeWidgetItem *ProjectNavigator::getProjectItem(const int &id_project, const T return project_item; } +QTreeWidgetItem *ProjectNavigator::getProjectForItem(const QTreeWidgetItem *item) +{ + QTreeWidgetItem* project = nullptr; + QTreeWidgetItem* item_parent = item->parent(); + + while (item_parent){ + if (m_projects_items.key(item_parent, 0) > 0){ + project = item_parent; + break; + } + item_parent = item_parent->parent(); + } + return project; + +} + bool ProjectNavigator::isParticipantFiltered(const QString &part_uuid) { // No participant with that uuid! @@ -1073,6 +1218,14 @@ TeraDataTypes ProjectNavigator::getItemType(QTreeWidgetItem *item) return TERADATA_PARTICIPANT; } + if (std::find(m_users_items.cbegin(), m_users_items.cend(), item) != m_users_items.cend()){ + return TERADATA_USER; + } + + if (std::find(m_devices_items.cbegin(), m_devices_items.cend(), item) != m_devices_items.cend()){ + return TERADATA_DEVICE; + } + return TERADATA_NONE; } @@ -1224,6 +1377,31 @@ void ProjectNavigator::processParticipantsReply(const QList participan } } +void ProjectNavigator::processUsersReply(const QList users, const QUrlQuery reply_args) +{ + /*if (!reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)) + return;*/ + + int id_project = 0; + if (reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)){ + id_project = reply_args.queryItemValue(WEB_QUERY_ID_PROJECT).toInt(); + } + for(const TeraData &user: users){ + updateUser(&user, id_project); + } +} + +void ProjectNavigator::processDevicesReply(const QList devices, const QUrlQuery reply_args) +{ + int id_project = 0; + if (reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)){ + id_project = reply_args.queryItemValue(WEB_QUERY_ID_PROJECT).toInt(); + } + for(const TeraData &device: devices){ + updateDevice(&device, id_project); + } +} + void ProjectNavigator::processStatsReply(TeraData stats, QUrlQuery reply_query) { // Only process stats in "advanced" view @@ -1277,6 +1455,68 @@ void ProjectNavigator::ws_participantEvent(ParticipantEvent event) updateParticipant(part_data); } +void ProjectNavigator::ws_userEvent(UserEvent event) +{ + QString user_uuid = QString::fromStdString(event.user_uuid()); + + if (!m_users.contains(user_uuid)) + return; // User isn't currently displayed, so nothing to do! + + TeraData* user_data = &m_users[user_uuid]; + int user_id = user_data->getId(); + QList user_items = m_users_items.values(user_id); + + if (user_items.isEmpty()){ + LOG_ERROR("Couldn't find the user " + user_uuid + " in the TreeWidgetItem list - mismatch, possible bug...", "ProjectNavigator::ws_userEvent"); + return; // Mismatch between saved data and list of TreeWidgetItems... Shouldn't happen! + } + + if (event.type() == opentera::protobuf::UserEvent_EventType_USER_CONNECTED){ + user_data->setOnline(true); + } + if (event.type() == opentera::protobuf::UserEvent_EventType_USER_DISCONNECTED){ + user_data->setOnline(false); + } + if (event.type() == opentera::protobuf::UserEvent_EventType_USER_JOINED_SESSION){ + user_data->setBusy(true); + } + if (event.type() == opentera::protobuf::UserEvent_EventType_USER_LEFT_SESSION){ + user_data->setBusy(false); + } + updateUser(user_data); +} + +void ProjectNavigator::ws_deviceEvent(DeviceEvent event) +{ + QString device_uuid = QString::fromStdString(event.device_uuid()); + + if (!m_devices.contains(device_uuid)) + return; // Device isn't currently displayed, so nothing to do! + + TeraData* device_data = &m_devices[device_uuid]; + int device_id = device_data->getId(); + QList device_items = m_devices_items.values(device_id); + + if (device_items.isEmpty()){ + LOG_ERROR("Couldn't find the device " + device_uuid + " in the TreeWidgetItem list - mismatch, possible bug...", "ProjectNavigator::ws_deviceEvent"); + return; // Mismatch between saved data and list of TreeWidgetItems... Shouldn't happen! + } + + if (event.type() == opentera::protobuf::DeviceEvent_EventType_DEVICE_CONNECTED){ + device_data->setOnline(true); + } + if (event.type() == opentera::protobuf::DeviceEvent_EventType_DEVICE_DISCONNECTED){ + device_data->setOnline(false); + } + if (event.type() == opentera::protobuf::DeviceEvent_EventType_DEVICE_JOINED_SESSION){ + device_data->setBusy(true); + } + if (event.type() == opentera::protobuf::DeviceEvent_EventType_DEVICE_LEFT_SESSION){ + device_data->setBusy(false); + } + updateDevice(device_data); +} + void ProjectNavigator::processItemDeletedReply(QString path, int id) { if (path == WEB_SITEINFO_PATH){ @@ -1382,6 +1622,16 @@ void ProjectNavigator::currentNavItemChanged(QTreeWidgetItem *current, QTreeWidg current->setExpanded(true); // Will call "navItemExpanded" and expands the item TeraDataTypes item_type = getItemType(current); + if (isAdvancedView() && item_type==TERADATA_NONE && current->parent()){ + // We might have selected a category under a project + int id_project = m_projects_items.key(current->parent(), 0); + if (id_project > 0){ + // We do have a project... + current = current->parent(); + item_type = TERADATA_PROJECT; + } + } + // PROJECT if (item_type==TERADATA_PROJECT){ // We have a project @@ -1415,6 +1665,26 @@ void ProjectNavigator::currentNavItemChanged(QTreeWidgetItem *current, QTreeWidg emit dataDisplayRequest(TERADATA_PARTICIPANT, id); } + // USER + if (item_type==TERADATA_USER){ + QTreeWidgetItem* project = getProjectForItem(current); + if (project){ + m_currentProjectId = m_projects_items.key(project); + } + int id = m_users_items.key(current); + emit dataDisplayRequest(TERADATA_USER, id); + } + + // DEVICE + if (item_type==TERADATA_DEVICE){ + QTreeWidgetItem* project = getProjectForItem(current); + if (project){ + m_currentProjectId = m_projects_items.key(project); + } + int id = m_devices_items.key(current); + emit dataDisplayRequest(TERADATA_DEVICE, id); + } + // Update available actions (new items) updateAvailableActions(current); } @@ -1467,8 +1737,10 @@ void ProjectNavigator::navItemExpanded(QTreeWidgetItem *item) queryParticipantsAndGroupsForProject(id); break; case TERADATA_USER: + queryUsersForProject(id); break; case TERADATA_DEVICE: + queryDevicesForProject(id); break; default: break; @@ -1493,13 +1765,13 @@ void ProjectNavigator::on_btnFilterActive_toggled(bool checked) } } // Update counts for all projects - if (isAdvancedView()){ + /*if (isAdvancedView()){ for(QTreeWidgetItem* item_project:qAsConst(m_projects_items)){ if (item_project->isExpanded()){ updateCountsForProject(m_projects_items.key(item_project)); } } - } + }*/ // Save new setting TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_FILTERINACTIVE, checked); @@ -1565,7 +1837,11 @@ void ProjectNavigator::on_treeNavigator_customContextMenuRequested(const QPoint void ProjectNavigator::on_btnAdvanced_clicked() { + // Save new setting + TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_ADVANCEDVIEW, ui->btnAdvanced->isChecked()); + // Force a refresh of the data for that site - currentSiteChanged(true); + if (m_sitesLoaded) + currentSiteChanged(true); } diff --git a/client/src/widgets/ProjectNavigator.h b/client/src/widgets/ProjectNavigator.h index df5b9966..86d50473 100644 --- a/client/src/widgets/ProjectNavigator.h +++ b/client/src/widgets/ProjectNavigator.h @@ -60,26 +60,35 @@ class ProjectNavigator : public QWidget bool m_siteJustChanged; bool m_sitesLoaded; - QMap m_projects_items; - QMap m_groups_items; - QMap m_participants_items; + QHash m_projects_items; + QHash m_groups_items; + QHash m_participants_items; + QMultiHash m_users_items; + QMultiHash m_devices_items; - QMap m_participants; // UUID - TeraData mapping for participants, required for online status + QHash m_participants; // UUID - TeraData mapping for participants, required for online status + QHash m_users; + QHash m_devices; void updateSite(const TeraData* site); void updateProject(const TeraData* project); void updateProjectAdvanced(QTreeWidgetItem* project_item); void updateGroup(const TeraData* group); void updateParticipant(const TeraData* participant); + void updateUser(const TeraData* user, const int &id_project = 0); + void updateDevice(const TeraData* device, const int &id_project = 0); void queryParticipantsAndGroupsForProject(const int& id_project); + void queryUsersForProject(const int& id_project); + void queryDevicesForProject(const int& id_project); void queryStatsForProject(const int& id_project); - void updateCountsForProject(const int& id_project); + //void updateCountsForProject(const int& id_project); int getParticipantProjectId(QTreeWidgetItem *part_item); int getParticipantGroupId(QTreeWidgetItem *part_item); QTreeWidgetItem* getProjectItem(const int& id_project, const TeraDataTypes& data_type = TERADATA_NONE); + QTreeWidgetItem* getProjectForItem(const QTreeWidgetItem *item); bool isParticipantFiltered(const QString &part_uuid); bool isAdvancedView(); @@ -114,8 +123,12 @@ private slots: void processProjectsReply(const QList projects); void processGroupsReply(const QList groups); void processParticipantsReply(const QList participants, const QUrlQuery reply_args); + void processUsersReply(const QList users, const QUrlQuery reply_args); + void processDevicesReply(const QList devices, const QUrlQuery reply_args); void processStatsReply(TeraData stats, QUrlQuery reply_query); void ws_participantEvent(opentera::protobuf::ParticipantEvent event); + void ws_userEvent(opentera::protobuf::UserEvent event); + void ws_deviceEvent(opentera::protobuf::DeviceEvent event); void processItemDeletedReply(QString path, int id); void moveItemRequested(QTreeWidgetItem* src, QTreeWidgetItem* target); @@ -133,9 +146,7 @@ private slots: void on_txtNavSearch_textChanged(const QString &search_text); void on_cmbSites_currentIndexChanged(int index); void on_btnDashboard_clicked(); - void on_treeNavigator_customContextMenuRequested(const QPoint &pos); - void on_btnAdvanced_clicked(); signals: diff --git a/shared/src/data/TeraSettings.h b/shared/src/data/TeraSettings.h index 3bdd5af1..88c1bade 100644 --- a/shared/src/data/TeraSettings.h +++ b/shared/src/data/TeraSettings.h @@ -3,6 +3,7 @@ #include +#define SETTINGS_UI_ADVANCEDVIEW "ui_advancedView" #define SETTINGS_UI_FILTERINACTIVE "ui_filterInactives" #define SETTINGS_UI_ONLINEFILTERPARTICIPANTS "ui_filterOnlineParticipants" #define SETTINGS_UI_ONLINEFILTERUSERS "ui_filterOnlineUsers" From 1969facbe0ab98a4073c8f89e26120fc820b5801 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 7 Dec 2022 15:10:24 -0500 Subject: [PATCH 14/42] Refs #80. Added logins logs display for users and devices. Added OS information in requests headers. --- client/src/editors/DeviceWidget.cpp | 60 +++++++++++++++-- client/src/editors/DeviceWidget.h | 12 +++- client/src/editors/DeviceWidget.ui | 32 ++++++++++ client/src/editors/UserWidget.cpp | 85 +++++++++++++++++++------ client/src/editors/UserWidget.h | 9 ++- client/src/editors/UserWidget.ui | 36 ++++++++++- client/src/main/MainWindow.cpp | 2 + client/src/managers/BaseComManager.cpp | 12 +++- client/src/managers/BaseComManager.h | 3 + client/src/widgets/ConfigWidget.cpp | 2 +- client/src/widgets/ConfigWidget.ui | 5 +- client/src/widgets/LogViewWidget.cpp | 4 ++ client/src/widgets/LogViewWidget.ui | 10 +++ client/src/widgets/ProjectNavigator.cpp | 5 ++ 14 files changed, 239 insertions(+), 38 deletions(-) diff --git a/client/src/editors/DeviceWidget.cpp b/client/src/editors/DeviceWidget.cpp index 3dcee29a..03d81138 100644 --- a/client/src/editors/DeviceWidget.cpp +++ b/client/src/editors/DeviceWidget.cpp @@ -12,7 +12,8 @@ DeviceWidget::DeviceWidget(ComManager *comMan, const TeraData *data, QWidget *pa setAttribute(Qt::WA_StyledBackground); //Required to set a background image - ui->tabNav->setCurrentIndex(0); + // Initialize User Interface + initUI(); // Use base class to manage editing setEditorControls(ui->wdgDevice, ui->btnEdit, ui->frameButtons, ui->btnSave, ui->btnUndo); @@ -23,8 +24,8 @@ DeviceWidget::DeviceWidget(ComManager *comMan, const TeraData *data, QWidget *pa // Query forms definition queryDataRequest(WEB_FORMS_PATH, QUrlQuery(WEB_FORMS_QUERY_DEVICE)); - ui->wdgDevice->setHighlightConditions(false); - ui->wdgDevice->setComManager(m_comManager); + // Query device access to sites and projects + queryDeviceAccess(); setData(data); @@ -105,6 +106,24 @@ void DeviceWidget::updateControlsState() } queryDataRequest(WEB_SITEINFO_PATH, args); } + }else{ + if (ui->tabNav->indexOf(ui->tabLogins) < 0){ + // Check if current user is project admin in at least a project of that device + bool has_project_admin_access = m_comManager->isCurrentUserSuperAdmin(); + if (!has_project_admin_access){ + // Check if we are admin in a list one site + for(int id_project:qAsConst(m_devicesProjects)){ + if (m_comManager->isCurrentUserProjectAdmin(id_project)){ + has_project_admin_access = true; + break; + } + } + } + + if (has_project_admin_access){ + ui->tabNav->addTab(ui->tabLogins, QIcon(":/icons/password.png"), tr("Journal d'accès")); + } + } } } @@ -176,6 +195,20 @@ void DeviceWidget::connectSignals() connect(ui->treeSites, &QTreeWidget::itemChanged, this, &DeviceWidget::lstSites_itemChanged); } +void DeviceWidget::initUI() +{ + ui->tabNav->setCurrentIndex(0); + ui->wdgDevice->setHighlightConditions(false); + ui->wdgDevice->setComManager(m_comManager); + + // Configure log view + ui->tabNav->removeTab(ui->tabNav->indexOf(ui->tabLogins)); // Remove logs tab for now, will be readded if access is sufficient + + ui->wdgLogins->setComManager(m_comManager); + ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_DEVICE, m_data->getUuid()); + ui->wdgLogins->setUuidName(m_data->getUuid(), m_data->getName()); +} + void DeviceWidget::updateSite(TeraData *site) { int id_site = site->getId(); @@ -246,6 +279,8 @@ void DeviceWidget::updateDeviceProject(TeraData *device_project) int device_id = device_project->getFieldValue("id_device").toInt(); if (device_id > 0){ item->setCheckState(0, Qt::Checked); + if (!m_devicesProjects.contains(id_project)) + m_devicesProjects.append(id_project); }else{ item->setCheckState(0, Qt::Unchecked); } @@ -271,6 +306,10 @@ void DeviceWidget::updateDeviceSite(TeraData *device_site) if (!site_name.isEmpty()) item->setText(0, site_name); + if (device_site->getId() > 0){ + if (!m_deviceSites.contains(id_site)) + m_deviceSites.append(id_site); + } if (m_comManager->isCurrentUserSuperAdmin()){ if (device_site->getId() > 0){ item->setCheckState(0, Qt::Checked); @@ -374,6 +413,14 @@ QJsonArray DeviceWidget::getSelectedSitesAsJsonArray() return sites; } +void DeviceWidget::queryDeviceAccess() +{ + QUrlQuery args; + args.addQueryItem(WEB_QUERY_ID_DEVICE, QString::number(m_data->getId())); + args.addQueryItem(WEB_QUERY_WITH_SITES, "1"); + queryDataRequest(WEB_DEVICESITEINFO_PATH, args); +} + void DeviceWidget::processFormsReply(QString form_type, QString data) { if (form_type == WEB_FORMS_QUERY_DEVICE){ @@ -581,9 +628,7 @@ void DeviceWidget::on_tabNav_currentChanged(int index) if (current_tab == ui->tabSites){ // Sites / Projects if (m_treeSites_items.isEmpty() || m_treeSites_items.isEmpty()){ - args.addQueryItem(WEB_QUERY_ID_DEVICE, QString::number(m_data->getId())); - args.addQueryItem(WEB_QUERY_WITH_SITES, "1"); - queryDataRequest(WEB_DEVICESITEINFO_PATH, args); + queryDeviceAccess(); } } @@ -608,6 +653,9 @@ void DeviceWidget::on_tabNav_currentChanged(int index) } } + if (current_tab == ui->tabLogins){ + ui->wdgLogins->refreshData(); + } } diff --git a/client/src/editors/DeviceWidget.h b/client/src/editors/DeviceWidget.h index 4f3d2fb9..dbef22a6 100644 --- a/client/src/editors/DeviceWidget.h +++ b/client/src/editors/DeviceWidget.h @@ -25,9 +25,12 @@ class DeviceWidget : public DataEditorWidget private: Ui::DeviceWidget *ui; - QMap m_treeSites_items; - QMap m_treeProjects_items; - QMap m_listParticipants_items; + QHash m_treeSites_items; + QHash m_treeProjects_items; + QHash m_listParticipants_items; + + QList m_deviceSites; // Sites that this device is part of + QList m_devicesProjects; // Projects that this device is part of void updateControlsState(); void updateFieldsValue(); @@ -35,6 +38,7 @@ class DeviceWidget : public DataEditorWidget bool validateSitesProjects(); void connectSignals(); + void initUI(); void updateSite(TeraData *site); void updateProject(TeraData * project); @@ -48,6 +52,8 @@ class DeviceWidget : public DataEditorWidget QJsonArray getSelectedProjectsAsJsonArray(); QJsonArray getSelectedSitesAsJsonArray(); + void queryDeviceAccess(); + private slots: void processFormsReply(QString form_type, QString data); void processSitesReply(QList sites); diff --git a/client/src/editors/DeviceWidget.ui b/client/src/editors/DeviceWidget.ui index 1db5b58f..6f3735ba 100644 --- a/client/src/editors/DeviceWidget.ui +++ b/client/src/editors/DeviceWidget.ui @@ -455,6 +455,32 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + @@ -466,6 +492,12 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
TeraForm.h
1 + + LogViewWidget + QWidget +
widgets/LogViewWidget.h
+ 1 +
diff --git a/client/src/editors/UserWidget.cpp b/client/src/editors/UserWidget.cpp index 33dfdce6..7f9a556a 100644 --- a/client/src/editors/UserWidget.cpp +++ b/client/src/editors/UserWidget.cpp @@ -27,7 +27,6 @@ UserWidget::UserWidget(ComManager *comMan, const TeraData *data, QWidget *parent m_passwordJustGenerated = false; setLimited(false); - ui->tabMain->setCurrentIndex(0); // Use base class to manage editing, but manually manage save button setEditorControls(ui->wdgUser, ui->btnEdit, ui->frameButtons, ui->btnSave, ui->btnUndo); @@ -38,9 +37,14 @@ UserWidget::UserWidget(ComManager *comMan, const TeraData *data, QWidget *parent // Query forms definition queryDataRequest(WEB_FORMS_PATH, QUrlQuery(WEB_FORMS_QUERY_USER)); + // Query roles (needed to check what is visible to the current user) + queryUserAccess(); + ui->wdgUser->setComManager(m_comManager); UserWidget::setData(data); + // Init UI + initUI(); } UserWidget::~UserWidget() @@ -150,6 +154,26 @@ void UserWidget::updateControlsState(){ ui->mainWidget->layout()->addWidget(ui->frameButtons); while (ui->tabMain->count() > 1) ui->tabMain->removeTab(1); + }else{ + if (ui->tabMain->indexOf(ui->tabLogins) < 0){ + // Check if current user is site admin in at least a site of that user... or is self! + bool has_site_admin_access = m_comManager->getCurrentUser().getId() == m_data->getId() || m_comManager->isCurrentUserSuperAdmin(); + if (!has_site_admin_access){ + // Check if we are admin in a list one site + QList id_sites = m_userSitesRole.keys(); + for(int id_site:qAsConst(id_sites)){ + if (m_comManager->isCurrentUserSiteAdmin(id_site)){ + has_site_admin_access = true; + break; + } + } + } + + if (has_site_admin_access){ + ui->tabMain->addTab(ui->tabLogins, QIcon(":/icons/password.png"), tr("Journal d'accès")); + } + } + } } @@ -293,6 +317,8 @@ void UserWidget::updateSiteAccess(const TeraData *site_access) ui->tableSitesRoles->setItem(current_row,0,item); item = new QTableWidgetItem(getRoleName(site_access->getFieldValue("site_access_role").toString())); ui->tableSitesRoles->setItem(current_row,1,item); + + m_userSitesRole[site_access->getFieldValue("id_site").toInt()] = site_access->getFieldValue("site_access_role").toString(); } void UserWidget::updateProjectAccess(const TeraData *project_access) @@ -309,6 +335,29 @@ void UserWidget::updateProjectAccess(const TeraData *project_access) ui->tableProjectsRoles->setItem(current_row,2,item); } +void UserWidget::queryUserAccess() +{ + // Roles + m_userSitesRole.clear(); + ui->tableProjectsRoles->clearContents(); // Resets all elements in the tables + ui->tableProjectsRoles->setRowCount(0); + ui->tableProjectsRoles->sortItems(-1); + ui->tableSitesRoles->clearContents(); + ui->tableSitesRoles->setRowCount(0); + ui->tableSitesRoles->sortItems(-1); + + // Query sites and projects roles + if (!m_data->isNew()){ + QUrlQuery args; + args.addQueryItem(WEB_QUERY_ID_USER, QString::number(m_data->getId())); + + queryDataRequest(WEB_SITEACCESS_PATH, args); + args.addQueryItem(WEB_QUERY_WITH_SITES, "1"); + queryDataRequest(WEB_PROJECTACCESS_PATH, args); + + } +} + bool UserWidget::validateUserGroups() { //if (!m_comManager->isCurrentUserSuperAdmin()){ @@ -469,6 +518,18 @@ void UserWidget::connectSignals() } +void UserWidget::initUI() +{ + ui->tabMain->setCurrentIndex(0); + + ui->tabMain->removeTab(ui->tabMain->indexOf(ui->tabLogins)); // Remove logs tab for now, will be readded if access is sufficient + + // Configure log view + ui->wdgLogins->setComManager(m_comManager); + ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_USER, m_data->getUuid()); + ui->wdgLogins->setUuidName(m_data->getUuid(), m_data->getName()); +} + void UserWidget::on_tabMain_currentChanged(int index) { QUrlQuery args; @@ -493,23 +554,7 @@ void UserWidget::on_tabMain_currentChanged(int index) ui->frameGroups->setVisible(!super_admin); } if (current_tab == ui->tabRoles){ - // Roles - ui->tableProjectsRoles->clearContents(); // Resets all elements in the tables - ui->tableProjectsRoles->setRowCount(0); - ui->tableProjectsRoles->sortItems(-1); - ui->tableSitesRoles->clearContents(); - ui->tableSitesRoles->setRowCount(0); - ui->tableSitesRoles->sortItems(-1); - - // Query sites and projects roles - if (!m_data->isNew()){ - args.addQueryItem(WEB_QUERY_ID_USER, QString::number(m_data->getId())); - - queryDataRequest(WEB_SITEACCESS_PATH, args); - args.addQueryItem(WEB_QUERY_WITH_SITES, "1"); - queryDataRequest(WEB_PROJECTACCESS_PATH, args); - - } + queryUserAccess(); } if (current_tab == ui->tabConfig){ @@ -537,6 +582,10 @@ void UserWidget::on_tabMain_currentChanged(int index) queryDataRequest(WEB_USERPREFSINFO_PATH, args); } } + + if (current_tab == ui->tabLogins){ + ui->wdgLogins->refreshData(); + } } void UserWidget::on_btnUpdateGroups_clicked() diff --git a/client/src/editors/UserWidget.h b/client/src/editors/UserWidget.h index 13fd4957..35d66955 100644 --- a/client/src/editors/UserWidget.h +++ b/client/src/editors/UserWidget.h @@ -41,8 +41,11 @@ class UserWidget : public DataEditorWidget private: Ui::UserWidget* ui; - QMap m_listUserGroups_items; - QMap m_listUserUserGroups_items; + QHash m_listUserGroups_items; + QHash m_listUserUserGroups_items; + QHash m_userSitesRole; + + void initUI(); bool m_currentUserPasswordChanged; bool m_passwordJustGenerated; @@ -62,6 +65,8 @@ class UserWidget : public DataEditorWidget void updateSiteAccess(const TeraData* site_access); void updateProjectAccess(const TeraData* project_access); + void queryUserAccess(); + bool validateUserGroups(); public slots: diff --git a/client/src/editors/UserWidget.ui b/client/src/editors/UserWidget.ui index 895daeae..199f7b91 100644 --- a/client/src/editors/UserWidget.ui +++ b/client/src/editors/UserWidget.ui @@ -70,7 +70,7 @@ - 0 + 5 @@ -345,7 +345,7 @@ - :/icons/password.png:/icons/password.png + :/icons/site-icon.png:/icons/site-icon.png Rôles @@ -607,6 +607,32 @@ + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + @@ -618,6 +644,12 @@
TeraForm.h
1 + + LogViewWidget + QWidget +
widgets/LogViewWidget.h
+ 1 +
diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index 98b2a4d3..93bf715e 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -108,6 +108,8 @@ void MainWindow::connectSignals() void MainWindow::initUi() { + ui->btnConfig->hide(); + // Setup messages ui->wdgMessages->hide(); ui->frameCentralBottom->hide(); diff --git a/client/src/managers/BaseComManager.cpp b/client/src/managers/BaseComManager.cpp index e99acdf3..c6176f8a 100644 --- a/client/src/managers/BaseComManager.cpp +++ b/client/src/managers/BaseComManager.cpp @@ -1,4 +1,5 @@ #include "BaseComManager.h" +#include BaseComManager::BaseComManager(QUrl serverUrl, QObject *parent) : QObject{parent}, @@ -8,6 +9,11 @@ BaseComManager::BaseComManager(QUrl serverUrl, QObject *parent) m_settedCredentials = false; m_loggingInProgress = false; + // Get Operating system information to send to server for logging + QOperatingSystemVersion os = QOperatingSystemVersion::current(); + m_osName = os.name(); + m_osVersion = QString::number(os.majorVersion()) + "." + QString::number(os.minorVersion()) + "." + QString::number(os.microVersion()); + // Create correct server url m_serverUrl.setUrl("https://" + serverUrl.host() + ":" + QString::number(serverUrl.port())); @@ -140,8 +146,7 @@ void BaseComManager::doDownload(const QUrl &full_url, const QString &save_path, void BaseComManager::doUpload(const QString &path, const QString &file_name, const QVariantMap extra_headers, const QString &label, const bool &use_token) { - QUrl query = m_serverUrl; - query.setPath(path); + QUrl query = m_serverUrl; query.setPath(path); // Prepare request QNetworkRequest* request = new QNetworkRequest(query); @@ -509,6 +514,9 @@ void BaseComManager::setRequestVersions(QNetworkRequest &request) { request.setRawHeader("X-Client-Name", QByteArray(OPENTERAPLUS_CLIENT_NAME)); request.setRawHeader("X-Client-Version", QByteArray(OPENTERAPLUS_VERSION)); + request.setRawHeader("X-OS-Name", m_osName.toUtf8()); + request.setRawHeader("X-OS-Version", m_osVersion.toUtf8()); + } void BaseComManager::setCredentials(const QString &username, const QString &password){ diff --git a/client/src/managers/BaseComManager.h b/client/src/managers/BaseComManager.h index 251c7aef..4a834521 100644 --- a/client/src/managers/BaseComManager.h +++ b/client/src/managers/BaseComManager.h @@ -58,6 +58,9 @@ class BaseComManager : public QObject QString m_password; QString m_token; + QString m_osName; + QString m_osVersion; + bool m_settedCredentials; diff --git a/client/src/widgets/ConfigWidget.cpp b/client/src/widgets/ConfigWidget.cpp index 7b5eff70..5f696245 100644 --- a/client/src/widgets/ConfigWidget.cpp +++ b/client/src/widgets/ConfigWidget.cpp @@ -46,7 +46,7 @@ void ConfigWidget::addSection(const QString &name, const QIcon &icon, const int tmp->setText(name); tmp->setTextAlignment(Qt::AlignCenter); tmp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); - tmp->setSizeHint(QSize(ui->lstSections->width(), 64)); + //tmp->setSizeHint(QSize(ui->lstSections->width(), 64)); tmp->setToolTip(name); tmp->setData(Qt::UserRole, id); } diff --git a/client/src/widgets/ConfigWidget.ui b/client/src/widgets/ConfigWidget.ui index b5506ec7..da237cff 100644 --- a/client/src/widgets/ConfigWidget.ui +++ b/client/src/widgets/ConfigWidget.ui @@ -195,9 +195,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, true - - QListView::Adjust - 100 @@ -208,7 +205,7 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, QListView::IconMode - true + false true diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index e8392b04..a289c1e3 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -28,6 +28,8 @@ LogViewWidget::LogViewWidget(QWidget *parent): ui->dateEndDate->setProperty("last_value", ui->dateEndDate->date()); ui->dateStartDate->setProperty("last_value", ui->dateStartDate->date()); ui->cmbLevel->setCurrentIndex(0); + ui->lblWarning->hide(); + ui->tableLogs->hide(); } LogViewWidget::LogViewWidget(ComManager* comMan, QWidget *parent) : @@ -422,6 +424,8 @@ void LogViewWidget::processStats(const TeraData &stats) int page_count = count / m_maxCount + 1; ui->lblEntriesCount->setText(QString::number(count)); ui->frameNav->setVisible(count > m_maxCount); + ui->tableLogs->setVisible(count>0); + ui->lblWarning->setVisible(count==0); ui->spinPage->setValue(1); ui->spinPage->setMaximum(page_count); ui->lblTotalPages->setText("/" + QString::number(page_count)); diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui index eef00d93..44294543 100644 --- a/client/src/widgets/LogViewWidget.ui +++ b/client/src/widgets/LogViewWidget.ui @@ -208,6 +208,16 @@ + + + + Aucun résultat n'est disponible. + + + Qt::AlignCenter + + + diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 2d13cdaf..8b5b087e 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -484,8 +484,10 @@ void ProjectNavigator::updateProject(const TeraData *project) } }else{ item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); // Always show indicator in advanced view + updateProjectAdvanced(item); } m_projects_items[id_project] = item; + } item->setText(0, project->getName()); @@ -537,6 +539,9 @@ void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) project_item->addChild(static_item); } + if (stats.isEmpty()) + return; // No stats, do nothing else + // Check what stats we have // Participants int count = 0; From fd722b05ae6e6080f7bd459aa82ff65b38686b78 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 8 Dec 2022 15:51:37 -0500 Subject: [PATCH 15/42] Refs #63. Refactoring of ParticipantWidget to extract the session list and calendar display in a reusable widget. --- client/src/CMakeLists.txt | 3 + client/src/editors/ParticipantWidget.cpp | 873 +-------------------- client/src/editors/ParticipantWidget.h | 60 +- client/src/editors/ParticipantWidget.ui | 777 +----------------- client/src/widgets/SessionsListWidget.cpp | 908 ++++++++++++++++++++++ client/src/widgets/SessionsListWidget.h | 136 ++++ client/src/widgets/SessionsListWidget.ui | 805 +++++++++++++++++++ 7 files changed, 1904 insertions(+), 1658 deletions(-) create mode 100644 client/src/widgets/SessionsListWidget.cpp create mode 100644 client/src/widgets/SessionsListWidget.h create mode 100644 client/src/widgets/SessionsListWidget.ui diff --git a/client/src/CMakeLists.txt b/client/src/CMakeLists.txt index ff8319a9..4cee32b4 100755 --- a/client/src/CMakeLists.txt +++ b/client/src/CMakeLists.txt @@ -103,6 +103,7 @@ set(headers widgets/TestsWidget.h widgets/QRWidget.h widgets/LogViewWidget.h + widgets/SessionsListWidget.h # Kits kit/KitConfigDialog.h kit/KitConfigManager.h @@ -203,6 +204,7 @@ set(srcs widgets/TestsWidget.cpp widgets/QRWidget.cpp widgets/LogViewWidget.cpp + widgets/SessionsListWidget.cpp # Kits kit/KitConfigDialog.cpp kit/KitConfigManager.cpp @@ -265,6 +267,7 @@ SET(uis widgets/ResultMessageWidget.ui widgets/TestsWidget.ui widgets/LogViewWidget.ui + widgets/SessionsListWidget.ui # Kits kit/KitConfigDialog.ui kit/KitInSessionDialog.ui diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 3bf53069..2904d435 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -6,15 +6,11 @@ #include #include -#include "editors/SessionWidget.h" - #include "dialogs/GeneratePasswordDialog.h" #include "dialogs/PasswordStrengthDialog.h" #include "services/DanceService/DanceConfigWidget.h" -#define QUERY_SESSION_LIMIT_PER_QUERY 50 - ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, QWidget *parent) : DataEditorWidget(comMan, data, parent), ui(new Ui::ParticipantWidget) @@ -22,15 +18,8 @@ ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, Q m_diag_editor = nullptr; m_sessionLobby = nullptr; - m_totalSessions = 0; - m_totalAssets = 0; - m_totalTests = 0; - m_currentIdSession = -1; - m_currentSessions = 0; - m_sessionsLoading = false; + m_allowFileTransfers = false; - m_currentSessionShowAssets = false; - m_currentSessionShowTests = false; ui->setupUi(this); @@ -39,7 +28,6 @@ ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, Q setAttribute(Qt::WA_StyledBackground); //Required to set a background image setLimited(false); - setSessionsLoading(false); // Use base class to manage editing setEditorControls(ui->wdgParticipant, ui->btnEdit, ui->frameButtons, ui->btnSave, ui->btnUndo); @@ -75,24 +63,19 @@ ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, Q args.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(m_data->getFieldValue("id_project").toInt())); queryDataRequest(WEB_DEVICEPROJECTINFO_PATH, args); - }else{ - // Set default calendar view for new participants - updateCalendars(QDate::currentDate()); } // Default display ui->tabNav->setCurrentIndex(0); ui->tabServicesDetails->setCurrentIndex(0); ui->tabInfosDetails->setCurrentIndex(0); - ui->frameFilterSessionTypes->hide(); } ParticipantWidget::~ParticipantWidget() { - delete ui; qDeleteAll(m_ids_session_types); - qDeleteAll(m_ids_sessions); + delete ui; if (m_sessionLobby) m_sessionLobby->deleteLater(); @@ -132,7 +115,6 @@ void ParticipantWidget::setData(const TeraData *data) void ParticipantWidget::connectSignals() { connect(m_comManager, &ComManager::formReceived, this, &ParticipantWidget::processFormsReply); - connect(m_comManager, &ComManager::sessionsReceived, this, &ParticipantWidget::processSessionsReply); connect(m_comManager, &ComManager::sessionTypesReceived, this, &ParticipantWidget::processSessionTypesReply); connect(m_comManager, &ComManager::deviceProjectsReceived, this, &ParticipantWidget::processDeviceProjectsReply); connect(m_comManager, &ComManager::deviceParticipantsReceived, this, &ParticipantWidget::processDeviceParticipantsReply); @@ -143,67 +125,13 @@ void ParticipantWidget::connectSignals() connect(m_comManager->getWebSocketManager(), &WebSocketManager::participantEventReceived, this, &ParticipantWidget::ws_participantEvent); - - connect(ui->btnDelSession, &QPushButton::clicked, this, &ParticipantWidget::btnDeleteSession_clicked); connect(ui->btnAddDevice, &QPushButton::clicked, this, &ParticipantWidget::btnAddDevice_clicked); connect(ui->btnDelDevice, &QPushButton::clicked, this, &ParticipantWidget::btnDelDevice_clicked); - connect(ui->tableSessions, &QTableWidget::currentItemChanged, this, &ParticipantWidget::currentSelectedSessionChanged); - - connect(ui->lstFilters, &QListWidget::itemChanged, this, &ParticipantWidget::currentTypeFiltersChanged); - connect(ui->btnNextCal, &QPushButton::clicked, this, &ParticipantWidget::displayNextMonth); - connect(ui->btnPrevCal, &QPushButton::clicked, this, &ParticipantWidget::displayPreviousMonth); - connect(ui->calMonth1, &HistoryCalendarWidget::clicked, this, &ParticipantWidget::currentCalendarDateChanged); - connect(ui->calMonth2, &HistoryCalendarWidget::clicked, this, &ParticipantWidget::currentCalendarDateChanged); - connect(ui->calMonth3, &HistoryCalendarWidget::clicked, this, &ParticipantWidget::currentCalendarDateChanged); - connect(ui->calMonth1, &HistoryCalendarWidget::activated, this, &ParticipantWidget::currentCalendarDateActivated); - connect(ui->calMonth2, &HistoryCalendarWidget::activated, this, &ParticipantWidget::currentCalendarDateActivated); - connect(ui->calMonth3, &HistoryCalendarWidget::activated, this, &ParticipantWidget::currentCalendarDateActivated); - connect(ui->lstAvailDevices, &QListWidget::currentItemChanged, this, &ParticipantWidget::currentAvailDeviceChanged); connect(ui->lstDevices, &QListWidget::currentItemChanged, this, &ParticipantWidget::currentDeviceChanged); -} - -void ParticipantWidget::setSessionsLoading(const bool &loading) -{ - ui->progSessionsLoad->setVisible(loading); - ui->frameCalendar->setVisible(!loading); - ui->tableSessions->setVisible(!loading); - ui->frameSessionsControls->setVisible(!loading); - m_sessionsLoading = loading; -} - -void ParticipantWidget::querySessions() -{ - ui->tableSessions->setSortingEnabled(false); - int sessions_left = m_totalSessions - m_currentSessions; - setSessionsLoading(sessions_left > 0); - - if (sessions_left>0){ - ui->progSessionsLoad->setValue(m_currentSessions); - QUrlQuery query; - query.addQueryItem(WEB_QUERY_ID_PARTICIPANT, QString::number(m_data->getId())); - query.addQueryItem(WEB_QUERY_OFFSET, QString::number(m_currentSessions)); - query.addQueryItem(WEB_QUERY_LIMIT, QString::number(QUERY_SESSION_LIMIT_PER_QUERY)); // Limit number of sessions per query - query.addQueryItem(WEB_QUERY_WITH_SESSIONTYPE, "1"); - query.addQueryItem(WEB_QUERY_LIST, "1"); - //queryDataRequest(WEB_SESSIONINFO_PATH, query); - m_comManager->doGet(WEB_SESSIONINFO_PATH, query); - }else{ - // No more session to query - - // Update calendar view - currentTypeFiltersChanged(nullptr); - updateCalendars(getMaximumSessionDate().addMonths(-2)); - //updateCalendars(getMinimumSessionDate()); - ui->calMonth1->setData(m_ids_sessions.values()); - ui->calMonth2->setData(m_ids_sessions.values()); - ui->calMonth3->setData(m_ids_sessions.values()); - - ui->tableSessions->setSortingEnabled(true); - ui->tableSessions->resizeColumnsToContents(); - } + connect(ui->wdgSessions, &SessionsListWidget::startSessionRequested, this, &ParticipantWidget::showSessionLobby); } void ParticipantWidget::updateControlsState() @@ -220,10 +148,10 @@ void ParticipantWidget::updateControlsState() ui->frameNewSession->hide(); } }else{ - if (!isProjectAdmin()){ - // Not admin in current project - disable deleting - ui->btnDelSession->hide(); - + bool is_admin = isProjectAdmin(); + // Not admin in current project - disable deleting, enable it otherwise + ui->wdgSessions->enableDeletion(is_admin); + if (!is_admin){ // Also disable device assignation ui->frameAssignDevicesButtons->hide(); ui->frameAvailDevices->hide(); @@ -281,13 +209,13 @@ void ParticipantWidget::updateFieldsValue() void ParticipantWidget::initUI() { - ui->btnAssetsBrowser->hide(); - ui->btnTestsBrowser->hide(); + ui->wdgSessions->setComManager(m_comManager); + ui->wdgSessions->setViewMode(SessionsListWidget::VIEW_PARTICIPANT_SESSIONS, m_data->getUuid(), m_data->getFieldValue("id_project").toInt()); + ui->frameParticipantLogin->hide(); ui->frameActive->hide(); ui->frameWeb->hide(); ui->txtWeb->hide(); - ui->btnCheckSessionTypes->hide(); // Disable random password button, handled in the PasswordStrengthDialog now! ui->btnRandomPass->hide(); @@ -302,19 +230,6 @@ void ParticipantWidget::initUI() ui->tabNav->setTabVisible(ui->tabNav->indexOf(ui->tabServices), false); ui->tabServicesDetails->setTabVisible(ui->tabServicesDetails->indexOf(ui->tabServiceParams), false); - // Intercepts some events - ui->tableSessions->installEventFilter(this); - - // Set default session sorting - ui->tableSessions->sortItems(1, Qt::DescendingOrder); - - // Load table icons - m_deleteIcon = QIcon(":/icons/delete_old.png"); - m_viewIcon = QIcon(":/icons/search.png"); - m_downloadIcon = QIcon(":/icons/data.png"); - m_resumeIcon = QIcon(":/icons/play.png"); - m_testIcon = QIcon(":/icons/test.png"); - // Configure log view ui->wdgLogins->setComManager(m_comManager); ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_PARTICIPANT, m_data->getUuid()); @@ -354,7 +269,7 @@ bool ParticipantWidget::canStartNewSession(const int &id_session_type) if (m_ids_session_types.contains(id_session_type)){ TeraData* session_type = m_ids_session_types[id_session_type]; if (session_type->hasFieldName("session_type_service_key")){ - QString service_key = session_type->getFieldValue("service").toString(); + QString service_key = session_type->getFieldValue("session_type_service_key").toString(); can_start = BaseServiceWidget::getHandledServiceKeys().contains(service_key); }else{ ui->lblInfos->setText(tr("Ce type de séance n'est pas supporté dans cette version")); @@ -370,39 +285,6 @@ bool ParticipantWidget::canStartNewSession(const int &id_session_type) return can_start; } -void ParticipantWidget::newSessionRequest(const QDateTime &session_datetime) -{ - if (m_diag_editor){ - m_diag_editor->deleteLater(); - } - m_diag_editor = new BaseDialog(this); - - TeraData* new_session = new TeraData(TERADATA_SESSION); - new_session->setFieldValue("session_name", tr("Nouvelle séance")); - /*QDateTime session_datetime = QDateTime::currentDateTime(); - QTime session_time = QTime::currentTime(); - int minutes = (session_time.minute() / 15) * 15; - session_time.setHMS(session_datetime.time().addSecs(3600).hour(), minutes, 0); - session_datetime.setTime(session_time);*/ - new_session->setFieldValue("session_start_datetime", session_datetime); - new_session->setFieldValue("session_status", TeraSessionStatus::STATUS_NOTSTARTED); - new_session->setFieldValue("id_project", m_data->getFieldValue("id_project")); - new_session->setFieldValue("id_creator_user", m_comManager->getCurrentUser().getId()); - new_session->setFieldValue("participant_uuid", m_data->getUuid()); - - SessionWidget* ses_widget = new SessionWidget(m_comManager, new_session, m_diag_editor); - m_diag_editor->setCentralWidget(ses_widget); - - m_diag_editor->setWindowTitle(tr("Séance")); - m_diag_editor->setMinimumSize(this->width(), this->height()); - - connect(ses_widget, &SessionWidget::closeRequest, m_diag_editor, &QDialog::accept); - connect(ses_widget, &SessionWidget::dataWasChanged, m_diag_editor, &QDialog::accept); - connect(ses_widget, &SessionWidget::dataWasDeleted, m_diag_editor, &QDialog::accept); - - m_diag_editor->open(); -} - bool ParticipantWidget::validateData() { bool valid = false; @@ -412,245 +294,6 @@ bool ParticipantWidget::validateData() return valid; } -void ParticipantWidget::updateSession(const TeraData *session, const bool &auto_position) -{ - int id_session = session->getId(); - - QTableWidgetItem* name_item; - TableDateWidgetItem* date_item; - QTableWidgetItem* type_item; - QTableWidgetItem* duration_item; - QTableWidgetItem* user_item; - QTableWidgetItem* status_item; - QToolButton* btnDownload = nullptr; - QToolButton* btnResume = nullptr; - QToolButton* btnTests = nullptr; - - if (m_listSessions_items.contains(id_session)){ - // Already there, get items - name_item = m_listSessions_items[id_session]; - date_item = dynamic_cast(ui->tableSessions->item(name_item->row(), 1)); - type_item = ui->tableSessions->item(name_item->row(), 2); - status_item = ui->tableSessions->item(name_item->row(), 3); - duration_item = ui->tableSessions->item(name_item->row(), 4); - user_item = ui->tableSessions->item(name_item->row(), 5); - if (ui->tableSessions->cellWidget(name_item->row(), 6)) - if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()){ - if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(2)) - btnDownload = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(2)->widget()); - if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(3)) - btnTests = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(3)->widget()); - if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(4)) - btnResume = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(4)->widget()); - } - - if (m_ids_sessions[id_session]->hasFieldName("session_assets_count")){ - m_totalAssets -= m_ids_sessions[id_session]->getFieldValue("session_assets_count").toInt(); - } - if (m_ids_sessions[id_session]->hasFieldName("session_tests_count")){ - m_totalTests -= m_ids_sessions[id_session]->getFieldValue("session_tests_count").toInt(); - } - delete m_ids_sessions[id_session]; - }else{ - //ui->tableSessions->setSortingEnabled(false); // Disable sorting so we know the correct inserted row - - if (ui->tableSessions->rowCount() < m_currentSessions+1){ - ui->tableSessions->setRowCount(ui->tableSessions->rowCount()+1); - } - int current_row = m_currentSessions; - name_item = new QTableWidgetItem(QIcon(TeraData::getIconFilenameForDataType(TERADATA_SESSION)),""); - ui->tableSessions->setItem(current_row, 0, name_item); - date_item = new TableDateWidgetItem(""); - ui->tableSessions->setItem(current_row, 1, date_item); - type_item = new QTableWidgetItem(""); - ui->tableSessions->setItem(current_row, 2, type_item); - status_item = new QTableWidgetItem(""); - ui->tableSessions->setItem(current_row, 3, status_item); - duration_item = new QTableWidgetItem(""); - duration_item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - ui->tableSessions->setItem(current_row, 4, duration_item); - user_item = new QTableWidgetItem(""); - ui->tableSessions->setItem(current_row, 5, user_item); - - // Create action buttons - QFrame* action_frame = new QFrame(); - QHBoxLayout* layout = new QHBoxLayout(); - layout->setContentsMargins(0,0,0,0); - layout->setSpacing(1); - layout->setAlignment(Qt::AlignLeft); - action_frame->setLayout(layout); - - // View session - QToolButton* btnView = new QToolButton(action_frame); - btnView->setIcon(m_viewIcon); - btnView->setProperty("id_session", session->getId()); - btnView->setCursor(Qt::PointingHandCursor); - btnView->setMaximumWidth(32); - btnView->setToolTip(tr("Ouvrir")); - connect(btnView, &QToolButton::clicked, this, &ParticipantWidget::btnViewSession_clicked); - layout->addWidget(btnView); - - // Delete - QToolButton* btnDelete = new QToolButton(action_frame); - btnDelete->setIcon(m_deleteIcon); - btnDelete->setProperty("id_session", session->getId()); - btnDelete->setCursor(Qt::PointingHandCursor); - btnDelete->setMaximumWidth(32); - btnDelete->setToolTip(tr("Supprimer")); - connect(btnDelete, &QToolButton::clicked, this, &ParticipantWidget::btnDeleteSession_clicked); - if (!isProjectAdmin()) - btnDelete->setVisible(false); - layout->addWidget(btnDelete); - - // Download data - btnDownload = new QToolButton(action_frame); - btnDownload->setIcon(m_downloadIcon); - btnDownload->setProperty("id_session", session->getId()); - btnDownload->setCursor(Qt::PointingHandCursor); - btnDownload->setMaximumWidth(32); - btnDownload->setToolTip(tr("Voir les données")); - connect(btnDownload, &QToolButton::clicked, this, &ParticipantWidget::btnDownloadSession_clicked); - layout->addWidget(btnDownload); - - // Tests display - btnTests = new QToolButton(action_frame); - btnTests->setIcon(m_testIcon); - btnTests->setProperty("id_session", session->getId()); - btnTests->setCursor(Qt::PointingHandCursor); - btnTests->setMaximumWidth(32); - btnTests->setToolTip(tr("Voir les évaluations")); - connect(btnTests, &QToolButton::clicked, this, &ParticipantWidget::btnViewTests_clicked); - layout->addWidget(btnTests); - - // Resume session - btnResume = new QToolButton(action_frame); - btnResume->setIcon(m_resumeIcon); - btnResume->setProperty("id_session", session->getId()); - btnResume->setProperty("id_session_type", session->getFieldValue("id_session_type").toInt()); - btnResume->setCursor(Qt::PointingHandCursor); - btnResume->setMaximumWidth(32); - btnResume->setToolTip(tr("Continuer la séance")); - connect(btnResume, &QToolButton::clicked, this, &ParticipantWidget::btnResumeSession_clicked); - layout->addWidget(btnResume); - - ui->tableSessions->setCellWidget(current_row, 6, action_frame); - - m_listSessions_items[id_session] = name_item; - - m_currentSessions++; - - //ui->tableSessions->setSortingEnabled(true); // Reenable sorting - - } - m_ids_sessions[id_session] = new TeraData(*session); - - // Update values - name_item->setText(session->getName()); - date_item->setDate(session->getFieldValue("session_start_datetime")); - - if (session->hasFieldName("session_type_name")){ - type_item->setText(session->getFieldValue("session_type_name").toString()); - if (session->hasFieldName("session_type_color")) - type_item->setForeground(QColor(session->getFieldValue("session_type_color").toString())); - }else{ - // No session type in that structure - old API - use previous stored values - int session_type = session->getFieldValue("id_session_type").toInt(); - if (m_ids_session_types.contains(session_type)){ - type_item->setText(m_ids_session_types[session_type]->getFieldValue("session_type_name").toString()); - type_item->setForeground(QColor(m_ids_session_types[session_type]->getFieldValue("session_type_color").toString())); - }else{ - type_item->setText("Inconnu"); - } - } - - duration_item->setText(QTime(0,0).addSecs(session->getFieldValue("session_duration").toInt()).toString("hh:mm:ss")); - TeraSessionStatus::SessionStatus session_status = static_cast(session->getFieldValue("session_status").toInt()); - status_item->setText(TeraSessionStatus::getStatusName(session_status)); - // Set color depending on status_item - //status_item->setTextColor(QColor(TeraSessionStatus::getStatusColor(session_status))); - status_item->setForeground(Qt::black); - status_item->setBackground(QColor(TeraSessionStatus::getStatusColor(session_status))); - //QColor back_color = TeraForm::getGradientColor(3, 18, 33, static_cast(session_date.daysTo(QDateTime::currentDateTime()))); - //back_color.setAlphaF(0.5); - //date_item->setBackgroundColor(back_color); - - // Session creator - if (session->hasFieldName("session_creator_user")) - user_item->setText(session->getFieldValue("session_creator_user").toString()); - else if(session->hasFieldName("session_creator_device")) - user_item->setText(tr("Appareil: ") + session->getFieldValue("session_creator_device").toString()); - else if(session->hasFieldName("session_creator_participant")) - user_item->setText(tr("Participant: ") + session->getFieldValue("session_creator_participant").toString()); - else if(session->hasFieldName("session_creator_service")) - user_item->setText(tr("Service: ") + session->getFieldValue("session_creator_service").toString()); - else { - user_item->setText(tr("Inconnu")); - } - - // Download data - if (btnDownload){ - if (session->hasFieldName("session_assets_count")){ - int asset_count = session->getFieldValue("session_assets_count").toInt(); - bool has_assets = asset_count>0; - //btnDownload->setVisible(has_assets); - btnDownload->setEnabled(has_assets); - if (!has_assets){ - btnDownload->setIcon(QIcon()); - }else{ - btnDownload->setIcon(m_downloadIcon); - } - m_totalAssets += asset_count; - ui->btnAssetsBrowser->setVisible(m_totalAssets>0); - }else{ - //btnDownload->hide(); - btnDownload->setEnabled(false); - } - } - - // Show Tests - if (btnTests){ - if (session->hasFieldName("session_tests_count")){ - int test_count = session->getFieldValue("session_tests_count").toInt(); - bool has_tests = test_count>0; - //btnTests->setVisible(has_tests); - btnTests->setEnabled(has_tests); - if (!has_tests){ - btnTests->setIcon(QIcon()); - }else{ - btnTests->setIcon(m_testIcon); - } - m_totalTests += test_count; - ui->btnTestsBrowser->setVisible(m_totalTests>0); - }else{ - //btnTests->hide(); - btnTests->setEnabled(false); - } - } - - // Resume session - if (btnResume){ - if (session->hasFieldName("session_start_datetime")){ - QDateTime session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime(); - btnResume->setVisible(session_date.date() == QDate::currentDate() && canStartNewSession(session->getFieldValue("id_session_type").toInt())); - }else{ - btnResume->hide(); - } - } - - // If only 1 session added, check to put it at the right place in the table - if (m_currentSessions > 1 && auto_position){ - // Check if we have a sorting or not - /*int sort_column = ui->tableSessions->horizontalHeader()->sortIndicatorSection(); - Qt::SortOrder sort_order = ui->tableSessions->horizontalHeader()->sortIndicatorOrder(); - // Reapply sorting - ui->tableSessions->sortByColumn(sort_column, sort_order);*/ - - // Select added item - ui->tableSessions->selectRow(name_item->row()); - ui->tableSessions->scrollToItem(name_item, QAbstractItemView::PositionAtCenter); - } -} - void ParticipantWidget::updateDeviceProject(TeraData *device_project) { int id_device = device_project->getFieldValue("id_device").toInt(); @@ -790,38 +433,6 @@ void ParticipantWidget::processFormsReply(QString form_type, QString data) } } -void ParticipantWidget::processSessionsReply(QList sessions) -{ - - for(TeraData &session:sessions){ - if (session.getId() == m_currentIdSession && sessions.count() == 1){ - // This is a session we requested info on - showSessionEditor(&session); - m_currentIdSession = -1; - return; - } - updateSession(&session, sessions.count()==1); - /*QVariantList session_parts_list = session.getFieldValue("session_participants").toList(); - - for(const QVariant &session_part:qAsConst(session_parts_list)){ - QVariantMap part_info = session_part.toMap(); - - // Is that session for the current participant? - if (part_info["id_participant"].toInt() == m_data->getId()){ - // Add session in table or update it - updateSession(&session, sessions.count()==1); - }else{ - // Session is not for us - ignore it. - continue; - } - }*/ - } - - // Query next sessions if needed - querySessions(); - -} - void ParticipantWidget::processSessionTypesReply(QList session_types) { // ui->cmbSessionType->clear(); @@ -829,13 +440,6 @@ void ParticipantWidget::processSessionTypesReply(QList session_types) for (const TeraData &st:session_types){ if (!m_ids_session_types.contains(st.getId())){ m_ids_session_types[st.getId()] = new TeraData(st); - // Add to session list - QListWidgetItem* s = new QListWidgetItem(st.getName()); - s->setData(Qt::UserRole,st.getId()); - s->setCheckState(Qt::Checked); - s->setForeground(QColor(st.getFieldValue("session_type_color").toString())); - s->setFont(QFont("Arial",10)); - ui->lstFilters->addItem(s); // New session ComboBox QString ses_type_name = st.getName(); @@ -862,15 +466,6 @@ void ParticipantWidget::processSessionTypesReply(QList session_types) }else{ - // Existing, must update values - *m_ids_session_types[st.getId()] = st; - for (int i=0; ilstFilters->count(); i++){ - QListWidgetItem* item = ui->lstFilters->item(i); - if (item->data(Qt::UserRole).toInt() == st.getId()){ - item->setText(st.getName()); - } - } - for (int i=0; icmbSessionType->count(); i++){ if (ui->cmbSessionType->itemData(i).toInt() == st.getId()){ ui->cmbSessionType->setItemText(i, st.getName()); @@ -879,12 +474,8 @@ void ParticipantWidget::processSessionTypesReply(QList session_types) } } - ui->calMonth1->setSessionTypes(m_ids_session_types.values()); - ui->calMonth2->setSessionTypes(m_ids_session_types.values()); - ui->calMonth3->setSessionTypes(m_ids_session_types.values()); - // Query sessions for that participant - if (!m_data->isNew() && m_currentSessions == 0 && !m_sessionsLoading){ + if (!m_data->isNew()){ // Select current session type based on last session type used with that participant int last_session_type_id = TeraSettings::getUserSettingForProject(m_comManager->getCurrentUser().getUuid(), m_data->getFieldValue("id_project").toInt(), @@ -901,7 +492,7 @@ void ParticipantWidget::processSessionTypesReply(QList session_types) } // Query sessions - querySessions(); + ui->wdgSessions->setSessionTypes(session_types); } } @@ -959,6 +550,7 @@ void ParticipantWidget::processServicesReply(QList services, QUrlQuery ui->cmbServices->addItem(service.getName(), service_key); }else{ m_allowFileTransfers = true; // We have a file transfer service with that project - allow uploads! + ui->wdgSessions->enableFileTransfers(true); } if (service.hasFieldName("service_editable_config")){ @@ -993,11 +585,8 @@ void ParticipantWidget::processStatsReply(TeraData stats, QUrlQuery reply_query) return; // Fill stats information - m_totalSessions = stats.getFieldValue("sessions_total_count").toInt(); - ui->tableSessions->setRowCount(m_totalSessions); - ui->progSessionsLoad->setMaximum(m_totalSessions); - //ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); - ui->grpSession->setTitle(tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); + int total_sessions = stats.getFieldValue("sessions_total_count").toInt(); + ui->wdgSessions->setSessionsCount(total_sessions); // Query sessions types QUrlQuery args; @@ -1011,25 +600,7 @@ void ParticipantWidget::deleteDataReply(QString path, int id) return; if (path == WEB_SESSIONINFO_PATH){ - // A session got deleted - check if it affects the current display - if (m_listSessions_items.contains(id)){ - ui->tableSessions->removeRow(m_listSessions_items[id]->row()); - delete m_ids_sessions[id]; - m_ids_sessions.remove(id); - m_listSessions_items.remove(id); - - // Update calendars - ui->calMonth1->setData(m_ids_sessions.values()); - ui->calMonth2->setData(m_ids_sessions.values()); - ui->calMonth3->setData(m_ids_sessions.values()); - - m_currentSessions--; - m_totalSessions--; - - //ui->grpSession->setItemText(0, tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); - ui->grpSession->setTitle(tr("Séances") + " ( " + QString::number(m_totalSessions) + " )"); - - } + // Nothing to do here - all managed in SessionsListWidget } if (path == WEB_DEVICEPARTICIPANTINFO_PATH){ @@ -1074,58 +645,9 @@ void ParticipantWidget::ws_participantEvent(opentera::protobuf::ParticipantEvent updateFieldsValue(); } -void ParticipantWidget::sessionAssetsCountChanged(int id_session, int new_count) +void ParticipantWidget::sessionTotalCountUpdated(int new_count) { - if (m_ids_sessions.contains(id_session)){ - // Check if we need to toggle the "asset download" icon - TeraData session_data(*m_ids_sessions[id_session]); // Local copy to prevent deletion when calling updateSession - session_data.setFieldValue("session_assets_count", new_count); - updateSession(&session_data, false); - } -} - -void ParticipantWidget::sessionTestsCountChanged(int id_session, int new_count) -{ - if (m_ids_sessions.contains(id_session)){ - // Check if we need to toggle the "test" icon - TeraData session_data(*m_ids_sessions[id_session]); // Local copy to prevent deletion when calling updateSession - session_data.setFieldValue("session_tests_count", new_count); - updateSession(&session_data, false); - } -} - -void ParticipantWidget::btnDeleteSession_clicked() -{ - // Check if the sender is a QToolButton (from the action column) - QToolButton* action_btn = dynamic_cast(sender()); - if (action_btn && action_btn != ui->btnDelSession){ - // Select row according to the session id of that button - int id_session = action_btn->property("id_session").toInt(); - QTableWidgetItem* session_item = m_listSessions_items[id_session]; - if (session_item) - ui->tableSessions->selectRow(session_item->row()); - } - - if (ui->tableSessions->selectedItems().count()==0) - return; - - GlobalMessageBox diag; - QMessageBox::StandardButton answer = QMessageBox::No; - if (ui->tableSessions->selectionModel()->selectedRows().count() == 1){ - QTableWidgetItem* base_item = ui->tableSessions->item(ui->tableSessions->currentRow(),0); - answer = diag.showYesNo(tr("Suppression?"), tr("Êtes-vous sûrs de vouloir supprimer """) + base_item->text() + """?"); - }else{ - answer = diag.showYesNo(tr("Suppression?"), tr("Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées?")); - } - - if (answer == QMessageBox::Yes){ - // We must delete! - foreach(QModelIndex selected_row, ui->tableSessions->selectionModel()->selectedRows()){ - QTableWidgetItem* base_item = ui->tableSessions->item(selected_row.row(),0); // Get item at index 0 which is linked to session id - //m_comManager->doDelete(TeraData::getPathForDataType(TERADATA_SESSION), m_listSessions_items.key(base_item)); - m_comManager->doDelete(TeraData::getPathForDataType(TERADATA_SESSION), m_listSessions_items.key(base_item)); - } - } + ui->grpSession->setTitle(tr("Séances") + " ( " + QString::number(new_count) + " )"); } void ParticipantWidget::btnAddDevice_clicked() @@ -1192,161 +714,6 @@ void ParticipantWidget::btnDelDevice_clicked() } } -void ParticipantWidget::btnDownloadSession_clicked() -{ - QToolButton* button = dynamic_cast(sender()); - QTableWidgetItem* session_item = nullptr; - if (button){ - int id_session = button->property("id_session").toInt(); - session_item = m_listSessions_items[id_session]; - if (session_item){ - displaySessionDetails(session_item, true); - } - } -} - -void ParticipantWidget::btnViewSession_clicked() -{ - // Check if the sender is a QToolButton (from the action column) - QToolButton* action_btn = dynamic_cast(sender()); - QTableWidgetItem* session_item = nullptr; - if (action_btn){ - // Select row according to the session id of that button - int id_session = action_btn->property("id_session").toInt(); - session_item = m_listSessions_items[id_session]; - } - - if (session_item){ - displaySessionDetails(session_item); - } - - -} - -void ParticipantWidget::btnResumeSession_clicked() -{ - // Check if the sender is a QToolButton (from the action column) - QToolButton* action_btn = dynamic_cast(sender()); - - if (action_btn){ - // Select row according to the session id of that button - int id_session = action_btn->property("id_session").toInt(); - int id_session_type = action_btn->property("id_session_type").toInt(); - showSessionLobby(id_session_type, id_session); - } - - -} - -void ParticipantWidget::btnViewTests_clicked() -{ - QToolButton* button = dynamic_cast(sender()); - QTableWidgetItem* session_item = nullptr; - if (button){ - int id_session = button->property("id_session").toInt(); - session_item = m_listSessions_items[id_session]; - if (session_item){ - displaySessionDetails(session_item, false, true); - } - } -} - -void ParticipantWidget::currentSelectedSessionChanged(QTableWidgetItem *current, QTableWidgetItem *previous) -{ - Q_UNUSED(previous) - ui->btnDelSession->setEnabled(current); -} - -void ParticipantWidget::currentCalendarDateChanged(QDate current_date) -{ - // Clear current selection - ui->tableSessions->clearSelection(); - - // Temporarly set multi-selection - QAbstractItemView::SelectionMode current_mode = ui->tableSessions->selectionMode(); - ui->tableSessions->setSelectionMode(QAbstractItemView::MultiSelection); - - // Select all the sessions in the list that fits with that date - QTableWidgetItem* first_item = nullptr; - //foreach(TeraData* session, m_ids_sessions){ - for(TeraData* session: qAsConst(m_ids_sessions)){ - if (session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date() == current_date){ - QTableWidgetItem* session_item = m_listSessions_items.value(session->getId()); - if (session_item){ - ui->tableSessions->selectRow(session_item->row()); - if (!first_item) - first_item = session_item; - } - } - } - - // Resets selection mode - ui->tableSessions->setSelectionMode(current_mode); - - // Ensure first session is visible - if (first_item){ - ui->tableSessions->scrollToItem(first_item); - } -} - -void ParticipantWidget::currentCalendarDateActivated(QDate current_date) -{ - // Add a new session - QDateTime session_datetime; - session_datetime.setDate(current_date); - QTime session_time = QTime::currentTime(); - int minutes = (session_time.minute() / 15) * 15; - session_time.setHMS(session_datetime.time().addSecs(3600).hour(), minutes, 0); - session_datetime.setTime(session_time); - - newSessionRequest(session_datetime); -} - -void ParticipantWidget::displaySessionDetails(QTableWidgetItem *session_item, bool show_assets, bool show_tests) -{ - // Query full session information - m_currentIdSession = m_listSessions_items.key(ui->tableSessions->item(session_item->row(),0)); - m_currentSessionShowAssets = show_assets; - m_currentSessionShowTests = show_tests; - - QUrlQuery args; - args.addQueryItem(WEB_QUERY_ID_SESSION, QString::number(m_currentIdSession)); - queryDataRequest(WEB_SESSIONINFO_PATH, args); - -} - -void ParticipantWidget::showSessionEditor(TeraData *session_info) -{ - if (m_diag_editor){ - m_diag_editor->deleteLater(); - } - m_diag_editor = new BaseDialog(this); - - //int id_session = m_listSessions_items.key(ui->tableSessions->item(session_item->row(),0)); - //TeraData* ses_data = m_ids_sessions[id_session]; - //if (ses_data){ - session_info->setFieldValue("id_project", m_data->getFieldValue("id_project")); - SessionWidget* ses_widget = new SessionWidget(m_comManager, session_info, m_diag_editor); - ses_widget->alwaysShowAssets(m_allowFileTransfers); - if (m_currentSessionShowAssets) - ses_widget->showAssets(); - if (m_currentSessionShowTests) - ses_widget->showTests(); - - m_diag_editor->setCentralWidget(ses_widget); - - m_diag_editor->setWindowTitle(tr("Séance")); - //m_diag_editor->setMinimumSize(2*this->width()/3, 5*this->height()/6); - m_diag_editor->setMinimumSize(3*this->width()/4, 3*this->height()/4); - - connect(ses_widget, &SessionWidget::closeRequest, m_diag_editor, &QDialog::accept); - connect(ses_widget, &SessionWidget::assetsCountChanged, this, &ParticipantWidget::sessionAssetsCountChanged); - connect(ses_widget, &SessionWidget::testsCountChanged, this, &ParticipantWidget::sessionTestsCountChanged); - - m_diag_editor->open(); - //} -} - bool ParticipantWidget::isProjectAdmin() { if (!m_comManager) @@ -1355,113 +722,14 @@ bool ParticipantWidget::isProjectAdmin() return m_comManager->getCurrentUserProjectRole(m_data->getFieldValue("id_project").toInt()) == "admin"; } -void ParticipantWidget::currentTypeFiltersChanged(QListWidgetItem *changed) +void ParticipantWidget::showSessionLobby(const int &id_session_type, const int &id_session) { - QList ids; - - for (int i=0; ilstFilters->count(); i++){ - if (ui->lstFilters->item(i)->checkState() == Qt::Checked){ - ids.append(ui->lstFilters->item(i)->data(Qt::UserRole).toInt()); - } - } - - ui->calMonth1->setFilters(ids); - ui->calMonth2->setFilters(ids); - ui->calMonth3->setFilters(ids); - - if (!changed) + if (!canStartNewSession(id_session_type)){ + GlobalMessageBox msg; + msg.showError(tr("Impossible de démarrer cette séance"), tr("Impossible de démarrer cette séance.")); return; - // Update session tables - QString current_ses_type = changed->text(); - for (int i=0; itableSessions->rowCount(); i++){ - if (ui->tableSessions->item(i,2)->text() == current_ses_type){ - ui->tableSessions->setRowHidden(i, changed->checkState()==Qt::Unchecked); - } - } -} - -void ParticipantWidget::updateCalendars(QDate left_date){ - ui->calMonth1->setCurrentPage(left_date.year(),left_date.month()); - ui->lblMonth1->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); - - left_date = left_date.addMonths(1); - ui->calMonth2->setCurrentPage(left_date.year(),left_date.month()); - ui->lblMonth2->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); - - left_date = left_date.addMonths(1); - ui->calMonth3->setCurrentPage(left_date.year(),left_date.month()); - ui->lblMonth3->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); - - // Check if we must enable the previous month button - QDate min_date = getMinimumSessionDate(); - - if (ui->calMonth1->yearShown()<=min_date.year() && ui->calMonth1->monthShown()<=min_date.month()) - ui->btnPrevCal->setEnabled(false); - else - ui->btnPrevCal->setEnabled(true); - -} - -QDate ParticipantWidget::getMinimumSessionDate() -{ - QDate min_date = QDate::currentDate(); - for (TeraData* session:qAsConst(m_ids_sessions)){ - QDate session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date(); - if (session_date < min_date) - min_date = session_date; - } - - return min_date; -} - -QDate ParticipantWidget::getMaximumSessionDate() -{ - QDate max_date = QDate::currentDate(); - for (TeraData* session:qAsConst(m_ids_sessions)){ - QDate session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date(); - if (session_date > max_date) - max_date = session_date; - } - - return max_date; -} - -bool ParticipantWidget::eventFilter(QObject *o, QEvent *e) -{ - if( o == ui->tableSessions && e->type() == QEvent::KeyRelease ) - { - QKeyEvent* key_event = dynamic_cast(e); - if (key_event){ - if (key_event->key() == Qt::Key_Delete){ - btnDeleteSession_clicked(); - } - } } - return QWidget::eventFilter(o,e); -} - - - -void ParticipantWidget::displayNextMonth(){ - QDate new_date; - new_date.setDate(ui->calMonth1->yearShown(), ui->calMonth1->monthShown(), 1); - new_date = new_date.addMonths(1); - - updateCalendars(new_date); -} - -void ParticipantWidget::displayPreviousMonth(){ - QDate new_date; - new_date.setDate(ui->calMonth1->yearShown(), ui->calMonth1->monthShown(), 1); - new_date = new_date.addMonths(-1); - - updateCalendars(new_date); -} - -void ParticipantWidget::showSessionLobby(const int &id_session_type, const int &id_session) -{ - if (m_sessionLobby) m_sessionLobby->deleteLater(); m_sessionLobby = new SessionLobbyDialog(m_comManager, *m_ids_session_types[id_session_type], m_data->getFieldValue("id_project").toInt(), id_session, this); @@ -1740,54 +1008,25 @@ void ParticipantWidget::on_btnNewSession_clicked() int id_session = 0; // Check if we have a recent session (started in the last hour) of the same type we might want to continue - foreach(TeraData* session, m_ids_sessions){ - if (id_session_type == session->getFieldValue("id_session_type").toInt()){ - int session_status = session->getFieldValue("session_status").toInt(); - if (session_status == TeraSessionStatus::STATUS_INPROGRESS || session_status == TeraSessionStatus::STATUS_NOTSTARTED){ - QDateTime session_start_time = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime(); - - if (session_start_time.date() == QDate::currentDate()){ - // Adds duration to have 1h since it was ended - session_start_time = session_start_time.addSecs(session->getFieldValue("session_duration").toInt()); - // Allow for +/- 1h30 around the time of the session, in case of planned session - if(qAbs(session_start_time.secsTo(QDateTime::currentDateTime())) <= 5400){ - GlobalMessageBox msg; - QString status_msg = tr("existe déjà."); - if (session_status == TeraSessionStatus::STATUS_INPROGRESS){ - status_msg = tr("a été réalisée récemment et n'a pas été terminée."); - } - if (session_status == TeraSessionStatus::STATUS_NOTSTARTED){ - status_msg = tr("a été planifiée."); - } - if (msg.showYesNo(tr("Reprendre une séance?"), tr("Un séance de ce type, ") + session->getFieldValue("session_name").toString() + ", " - + status_msg + tr("\n\nSouhaitez-vous continuer cette séance?")) == QMessageBox::Yes){ - id_session = session->getId(); - } - break; - } - } - } + const TeraData* session = ui->wdgSessions->hasResumableSession(id_session_type, QDate::currentDate()); + if (session){ + int session_status = session->getFieldValue("session_status").toInt(); + GlobalMessageBox msg; + QString status_msg = tr("existe déjà."); + if (session_status == TeraSessionStatus::STATUS_INPROGRESS){ + status_msg = tr("a été réalisée récemment et n'a pas été terminée."); + } + if (session_status == TeraSessionStatus::STATUS_NOTSTARTED){ + status_msg = tr("a été planifiée."); + } + if (msg.showYesNo(tr("Reprendre une séance?"), tr("Un séance de ce type, ") + session->getFieldValue("session_name").toString() + ", " + + status_msg + tr("\n\nSouhaitez-vous continuer cette séance?")) == QMessageBox::Yes){ + id_session = session->getId(); // Will resume that session } } - showSessionLobby(id_session_type, id_session); -} - -void ParticipantWidget::on_btnCheckSessionTypes_clicked() -{ - for (int i=0; ilstFilters->count(); i++){ - ui->lstFilters->item(i)->setCheckState(Qt::Checked); - } - ui->btnUnchekSessionTypes->show(); - ui->btnCheckSessionTypes->hide(); -} -void ParticipantWidget::on_btnUnchekSessionTypes_clicked() -{ - for (int i=0; ilstFilters->count(); i++){ - ui->lstFilters->item(i)->setCheckState(Qt::Unchecked); - } - ui->btnUnchekSessionTypes->hide(); - ui->btnCheckSessionTypes->show(); + // If id_session == 0, will start a new session. Otherwise, will resume that session with id_session + showSessionLobby(id_session_type, id_session); } void ParticipantWidget::on_btnViewLink_clicked() @@ -1840,26 +1079,6 @@ void ParticipantWidget::on_cmbSessionType_currentIndexChanged(int index) ui->btnNewSession->setEnabled(can_start); ui->cmbSessionType->setVisible(can_start); - /*if (m_ids_session_types[id_session_type]->getFieldValue("session_type_online").toBool()){ - if (m_data->getFieldValue("participant_login_enabled").toBool() || m_data->getFieldValue("participant_token_enabled").toBool()){ - ui->btnNewSession->setEnabled(true); - ui->cmbSessionType->show(); - ui->btnNewSession->setToolTip(tr("Démarrer une nouvelle séance")); - ui->lblInfos->setText(""); - }else{ - ui->btnNewSession->setEnabled(false); - ui->cmbSessionType->hide(); - ui->btnNewSession->setToolTip(tr("Le participant n'a pas d'accès (web ou identification).")); - ui->lblInfos->setText(tr("Le participant n'a pas d'accès (web ou identification).")); - ui->lblInfos->show(); - } - }else{ - ui->btnNewSession->setEnabled(true); - ui->cmbSessionType->show(); - ui->lblInfos->setText(""); - ui->btnNewSession->setToolTip(tr("Démarrer une nouvelle séance")); - }*/ - // Session type related to a service: select the correct item in the link combo box to generate correct link if (m_ids_session_types[id_session_type]->hasFieldName("session_type_service_key")){ QString service_key = m_ids_session_types[id_session_type]->getFieldValue("session_type_service_key").toString(); @@ -1873,11 +1092,6 @@ void ParticipantWidget::on_cmbSessionType_currentIndexChanged(int index) } -void ParticipantWidget::on_btnFilterSessionsTypes_clicked() -{ - ui->frameFilterSessionTypes->setVisible(ui->btnFilterSessionsTypes->isChecked()); -} - void ParticipantWidget::on_btnAddSession_clicked() { QDateTime session_datetime = QDateTime::currentDateTime(); @@ -1886,17 +1100,10 @@ void ParticipantWidget::on_btnAddSession_clicked() session_time.setHMS(session_datetime.time().addSecs(3600).hour(), minutes, 0); session_datetime.setTime(session_time); - newSessionRequest(session_datetime); + ui->wdgSessions->newSessionRequest(session_datetime); } - -void ParticipantWidget::on_tableSessions_itemDoubleClicked(QTableWidgetItem *item) -{ - displaySessionDetails(item); -} - - void ParticipantWidget::on_btnAssetsBrowser_clicked() { if (m_diag_editor){ diff --git a/client/src/editors/ParticipantWidget.h b/client/src/editors/ParticipantWidget.h index 6640128e..815da842 100644 --- a/client/src/editors/ParticipantWidget.h +++ b/client/src/editors/ParticipantWidget.h @@ -22,7 +22,6 @@ #include "dialogs/DeviceAssignDialog.h" #include "dialogs/BaseDialog.h" -#include "widgets/TableDateWidgetItem.h" #include "widgets/AssetsWidget.h" #include "widgets/TestsWidget.h" #include "widgets/QRWidget.h" @@ -50,11 +49,6 @@ class ParticipantWidget : public DataEditorWidget private: Ui::ParticipantWidget *ui; - QMap m_listSessions_items; // ID Session to QTableWidgetItem* mapping - - QMap m_ids_session_types; - QMap m_ids_sessions; // ID Session to data mapping - QMap m_listAvailDevices_items; // int = device_id QMap m_listDevices_items; // int = device_id @@ -65,38 +59,16 @@ class ParticipantWidget : public DataEditorWidget BaseDialog* m_diag_editor; SessionLobbyDialog* m_sessionLobby; - int m_totalSessions; - int m_totalAssets; - int m_totalTests; - int m_currentSessions; - bool m_sessionsLoading; - - // Infos when querying extra sessions - int m_currentIdSession; - bool m_currentSessionShowAssets; - bool m_currentSessionShowTests; - - // Icons are defined here (speed up load) - QIcon m_deleteIcon; - QIcon m_viewIcon; - QIcon m_downloadIcon; - QIcon m_testIcon; - QIcon m_resumeIcon; - - - void setSessionsLoading(const bool& loading); - void querySessions(); + QHash m_ids_session_types; void updateControlsState() override; void updateFieldsValue() override; void initUI(); bool canStartNewSession(const int& id_session_type=0); - void newSessionRequest(const QDateTime& session_datetime); bool validateData() override; - void updateSession(const TeraData *session, const bool &auto_position); void updateDeviceProject(TeraData* device_project); void updateDeviceParticipant(TeraData* device_participant); @@ -105,20 +77,10 @@ class ParticipantWidget : public DataEditorWidget void refreshWebAccessUrl(); - void updateCalendars(QDate left_date); - QDate getMinimumSessionDate(); - QDate getMaximumSessionDate(); - - bool eventFilter(QObject* o, QEvent* e) override; - void displaySessionDetails(QTableWidgetItem* session_item, bool show_assets = false, bool show_tests = false); - void showSessionEditor(TeraData *session_info); - bool isProjectAdmin(); - private slots: void processFormsReply(QString form_type, QString data); - void processSessionsReply(QList sessions); void processSessionTypesReply(QList session_types); //void processDevicesReply(QList devices); void processDeviceProjectsReply(QList device_projects); @@ -129,23 +91,11 @@ private slots: void deleteDataReply(QString path, int id); void ws_participantEvent(opentera::protobuf::ParticipantEvent event); - void sessionAssetsCountChanged(int id_session, int new_count); - void sessionTestsCountChanged(int id_session, int new_count); - void btnDeleteSession_clicked(); + void sessionTotalCountUpdated(int new_count); + void btnAddDevice_clicked(); void btnDelDevice_clicked(); - void btnDownloadSession_clicked(); - void btnViewSession_clicked(); - void btnResumeSession_clicked(); - void btnViewTests_clicked(); - - void currentSelectedSessionChanged(QTableWidgetItem* current, QTableWidgetItem* previous); - void currentCalendarDateChanged(QDate current_date); - void currentCalendarDateActivated(QDate current_date); - void currentTypeFiltersChanged(QListWidgetItem* changed); - void displayNextMonth(); - void displayPreviousMonth(); void showSessionLobby(const int& id_session_type, const int& id_session); void sessionLobbyStartSessionRequested(); @@ -162,15 +112,11 @@ private slots: void on_txtUsername_textEdited(const QString ¤t); void on_txtPassword_textEdited(const QString ¤t); void on_btnNewSession_clicked(); - void on_btnCheckSessionTypes_clicked(); - void on_btnUnchekSessionTypes_clicked(); void on_btnViewLink_clicked(); void on_cmbServices_currentIndexChanged(int index); void on_btnEmailWeb_clicked(); void on_cmbSessionType_currentIndexChanged(int index); - void on_btnFilterSessionsTypes_clicked(); void on_btnAddSession_clicked(); - void on_tableSessions_itemDoubleClicked(QTableWidgetItem *item); void on_btnAssetsBrowser_clicked(); void on_tabNav_currentChanged(int index); void on_lstAvailDevices_itemDoubleClicked(QListWidgetItem *item); diff --git a/client/src/editors/ParticipantWidget.ui b/client/src/editors/ParticipantWidget.ui index 636888d2..1e8922ad 100644 --- a/client/src/editors/ParticipantWidget.ui +++ b/client/src/editors/ParticipantWidget.ui @@ -945,767 +945,7 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } 0 - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - true - - - - - 0 - 0 - 970 - 440 - - - - - 4 - - - 4 - - - 4 - - - 4 - - - - - 24 - - - Qt::AlignCenter - - - Chargement des séances: %p% - - - - - - - - 0 - 0 - - - - - 16777215 - 200 - - - - - 3 - - - - - - 0 - 0 - - - - PointingHandCursor - - - ... - - - - :/controls/branch_left.png:/controls/branch_left.png - - - - - - - 3 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Mois 1 - - - false - - - Qt::AlignCenter - - - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - - - - - 2013 - 1 - 1 - - - - - 3000 - 1 - 1 - - - - true - - - QCalendarWidget::SingleSelection - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - 3 - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - Mois 2 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - true - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - 3 - - - - - - 0 - 0 - - - - Mois 3 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 16777215 - 175 - - - - PointingHandCursor - - - true - - - QCalendarWidget::SingleLetterDayNames - - - QCalendarWidget::NoVerticalHeader - - - false - - - false - - - - - - - - - - 0 - 0 - - - - PointingHandCursor - - - ... - - - - :/controls/branch_closed.png:/controls/branch_closed.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 24 - 24 - - - - PointingHandCursor - - - Tout cocher - - - ... - - - - :/controls/check2_on.png:/controls/check2_on.png - - - - 24 - 24 - - - - - - - - - 24 - 24 - - - - PointingHandCursor - - - Tout décocher - - - ... - - - - :/controls/check2_off.png:/controls/check2_off.png - - - - 24 - 24 - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 150 - 16777215 - - - - - 10 - - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustIgnored - - - QAbstractItemView::NoEditTriggers - - - false - - - QAbstractItemView::NoSelection - - - QAbstractItemView::SelectRows - - - QListView::Static - - - QListView::TopToBottom - - - QListView::SinglePass - - - 3 - - - true - - - - - - - - - - - 0 - 0 - - - - - 0 - 150 - - - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - - 20 - 20 - - - - true - - - 75 - - - 100 - - - false - - - false - - - - Séance - - - - - Date - - - - - Type - - - - - État - - - - - Durée - - - - - Responsable - - - - - Actions - - - - - - - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Filtrer les séances - - - - :/icons/filter.png:/icons/filter.png - - - - 24 - 24 - - - - true - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Nouvelle - - - - :/icons/new.png:/icons/new.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - false - - - - 125 - 40 - - - - PointingHandCursor - - - Supprimer - - - - :/icons/delete_old.png:/icons/delete_old.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - true - - - - 125 - 40 - - - - PointingHandCursor - - - Explorateur de données - - - - :/icons/data.png:/icons/data.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 125 - 40 - - - - PointingHandCursor - - - Explorateur d'évaluations - - - - :/icons/test.png:/icons/test.png - - - - 24 - 24 - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - - + @@ -2128,21 +1368,22 @@ QCalendarWidget QSpinBox::down-arrow { width:16px; height:16px; } - LogViewWidget + TeraForm QWidget -
widgets/LogViewWidget.h
+
TeraForm.h
1
- TeraForm + LogViewWidget QWidget -
TeraForm.h
+
widgets/LogViewWidget.h
1
- HistoryCalendarWidget - QCalendarWidget -
widgets/HistoryCalendarWidget.h
+ SessionsListWidget + QWidget +
widgets/SessionsListWidget.h
+ 1
diff --git a/client/src/widgets/SessionsListWidget.cpp b/client/src/widgets/SessionsListWidget.cpp new file mode 100644 index 00000000..486054cf --- /dev/null +++ b/client/src/widgets/SessionsListWidget.cpp @@ -0,0 +1,908 @@ +#include "SessionsListWidget.h" +#include "ui_SessionsListWidget.h" + +#include + +#include "GlobalMessageBox.h" +#include "editors/SessionWidget.h" +#include "widgets/TableDateWidgetItem.h" + +SessionsListWidget::SessionsListWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::SessionsListWidget) +{ + ui->setupUi(this); + + // Init local variables + m_totalSessions = 0; + m_totalAssets = 0; + m_totalTests = 0; + m_currentIdSession = -1; + m_currentSessions = 0; + m_sessionsLoading = false; + m_currentSessionShowAssets = false; + m_currentSessionShowTests = false; + m_allowDeletion = false; + m_allowFileTransfers = false; + m_comManager = nullptr; + m_diag_editor = nullptr; + m_viewMode = VIEW_NONE; + + // InitUI + initUI(); + +} + +SessionsListWidget::~SessionsListWidget() +{ + qDeleteAll(m_ids_session_types); + qDeleteAll(m_ids_sessions); + + delete ui; +} + +void SessionsListWidget::setComManager(ComManager *comMan) +{ + if (m_comManager != comMan){ + m_comManager = comMan; + connectSignals(); + } +} + +void SessionsListWidget::setViewMode(const ViewMode &mode, const QString &uuid, const int &id, const int &id_project) +{ + m_viewMode = mode; + m_currentUuid = uuid; + m_currentIdProject = id_project; + m_currentId = id; +} + +void SessionsListWidget::initUI() +{ + ui->frameFilterSessionTypes->hide(); + ui->btnCheckSessionTypes->hide(); + ui->btnAssetsBrowser->hide(); + ui->btnTestsBrowser->hide(); + + setSessionsLoading(false); + + // Set default calendar view based on current date + updateCalendars(QDate::currentDate()); + + // Intercepts some events + ui->tableSessions->installEventFilter(this); + + // Set default session sorting + ui->tableSessions->sortItems(1, Qt::DescendingOrder); + + // Load table icons + m_deleteIcon = QIcon(":/icons/delete_old.png"); + m_viewIcon = QIcon(":/icons/search.png"); + m_downloadIcon = QIcon(":/icons/data.png"); + m_resumeIcon = QIcon(":/icons/play.png"); + m_testIcon = QIcon(":/icons/test.png"); +} + +void SessionsListWidget::connectSignals() +{ + connect(ui->calMonth1, &HistoryCalendarWidget::clicked, this, &SessionsListWidget::currentCalendarDateChanged); + connect(ui->calMonth2, &HistoryCalendarWidget::clicked, this, &SessionsListWidget::currentCalendarDateChanged); + connect(ui->calMonth3, &HistoryCalendarWidget::clicked, this, &SessionsListWidget::currentCalendarDateChanged); + connect(ui->calMonth1, &HistoryCalendarWidget::activated, this, &SessionsListWidget::currentCalendarDateActivated); + connect(ui->calMonth2, &HistoryCalendarWidget::activated, this, &SessionsListWidget::currentCalendarDateActivated); + connect(ui->calMonth3, &HistoryCalendarWidget::activated, this, &SessionsListWidget::currentCalendarDateActivated); + + if (m_comManager){ + connect(m_comManager, &ComManager::sessionsReceived, this, &SessionsListWidget::processSessionsReply); + connect(m_comManager, &ComManager::deleteResultsOK, this, &SessionsListWidget::deleteDataReply); + } +} + +void SessionsListWidget::enableDeletion(const bool &enable) +{ + m_allowDeletion = enable; + ui->btnDelSession->setVisible(enable); +} + +void SessionsListWidget::enableFileTransfers(const bool &enable) +{ + m_allowFileTransfers = enable; +} + +void SessionsListWidget::setSessionsCount(const int &count) +{ + m_totalSessions = count; + ui->tableSessions->setRowCount(m_totalSessions); + ui->progSessionsLoad->setMaximum(m_totalSessions); + + emit sessionsCountUpdated(m_totalSessions); +} + +int SessionsListWidget::getSessionsCount() +{ + return m_totalSessions; +} + +void SessionsListWidget::setSessionTypes(QList session_types) +{ + for (const TeraData &st:session_types){ + if (!m_ids_session_types.contains(st.getId())){ + m_ids_session_types[st.getId()] = new TeraData(st); + // Add to session list + QListWidgetItem* s = new QListWidgetItem(st.getName()); + s->setData(Qt::UserRole,st.getId()); + s->setCheckState(Qt::Checked); + s->setForeground(QColor(st.getFieldValue("session_type_color").toString())); + s->setFont(QFont("Arial",10)); + ui->lstFilters->addItem(s); + }else{ + // Existing, must update values + *m_ids_session_types[st.getId()] = st; + for (int i=0; ilstFilters->count(); i++){ + QListWidgetItem* item = ui->lstFilters->item(i); + if (item->data(Qt::UserRole).toInt() == st.getId()){ + item->setText(st.getName()); + } + } + } + } + + ui->calMonth1->setSessionTypes(m_ids_session_types.values()); + ui->calMonth2->setSessionTypes(m_ids_session_types.values()); + ui->calMonth3->setSessionTypes(m_ids_session_types.values()); + + // Query sessions + if (m_currentSessions == 0 && !m_sessionsLoading){ + querySessions(); + } +} + +const TeraData *SessionsListWidget::hasResumableSession(const int &id_session_type, const QDate &target_date) +{ + TeraData *rval = nullptr; + // Check if we have a session that can be resumed for that date + for(TeraData* session:qAsConst(m_ids_sessions)){ + if (id_session_type == session->getFieldValue("id_session_type").toInt()){ + int session_status = session->getFieldValue("session_status").toInt(); + if (session_status == TeraSessionStatus::STATUS_INPROGRESS || session_status == TeraSessionStatus::STATUS_NOTSTARTED){ + QDateTime session_start_time = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime(); + + if (session_start_time.date() == target_date){ + // Adds duration to have 1h since it was ended + session_start_time = session_start_time.addSecs(session->getFieldValue("session_duration").toInt()); + // Allow for +/- 1h30 around the time of the session, in case of planned session + if(qAbs(session_start_time.secsTo(QDateTime::currentDateTime())) <= 5400){ + rval = session; + break; + } + } + } + } + } + return rval; +} + +void SessionsListWidget::processSessionsReply(QList sessions, QUrlQuery args) +{ + for(TeraData &session:sessions){ + if (session.getId() == m_currentIdSession && sessions.count() == 1){ + // This is a session we requested info on + showSessionEditor(&session); + m_currentIdSession = -1; + return; + } + updateSession(&session, sessions.count()==1); + } + + // Query next sessions if needed + querySessions(); +} + +void SessionsListWidget::deleteDataReply(QString path, int id) +{ + if (id==0) + return; + + if (path == WEB_SESSIONINFO_PATH){ + // A session got deleted - check if it affects the current display + if (m_listSessions_items.contains(id)){ + ui->tableSessions->removeRow(m_listSessions_items[id]->row()); + delete m_ids_sessions[id]; + m_ids_sessions.remove(id); + m_listSessions_items.remove(id); + + // Update calendars + ui->calMonth1->setData(m_ids_sessions.values()); + ui->calMonth2->setData(m_ids_sessions.values()); + ui->calMonth3->setData(m_ids_sessions.values()); + + m_currentSessions--; + m_totalSessions--; + + emit sessionsCountUpdated(m_totalSessions); + } + } + +} + +void SessionsListWidget::btnViewSession_clicked() +{ + // Check if the sender is a QToolButton (from the action column) + QToolButton* action_btn = dynamic_cast(sender()); + QTableWidgetItem* session_item = nullptr; + if (action_btn){ + // Select row according to the session id of that button + int id_session = action_btn->property("id_session").toInt(); + session_item = m_listSessions_items[id_session]; + } + + if (session_item){ + displaySessionDetails(session_item); + } +} + +void SessionsListWidget::btnViewSessionAssets_clicked() +{ + QToolButton* button = dynamic_cast(sender()); + QTableWidgetItem* session_item = nullptr; + if (button){ + int id_session = button->property("id_session").toInt(); + session_item = m_listSessions_items[id_session]; + if (session_item){ + displaySessionDetails(session_item, true); + } + } +} + +void SessionsListWidget::btnViewSessionTests_clicked() +{ + QToolButton* button = dynamic_cast(sender()); + QTableWidgetItem* session_item = nullptr; + if (button){ + int id_session = button->property("id_session").toInt(); + session_item = m_listSessions_items[id_session]; + if (session_item){ + displaySessionDetails(session_item, false, true); + } + } +} + +void SessionsListWidget::btnResumeSession_clicked() +{ + // Check if the sender is a QToolButton (from the action column) + QToolButton* action_btn = dynamic_cast(sender()); + + if (action_btn){ + // Select row according to the session id of that button + int id_session = action_btn->property("id_session").toInt(); + int id_session_type = action_btn->property("id_session_type").toInt(); + emit startSessionRequested(id_session_type, id_session); + } +} + +void SessionsListWidget::sessionAssetsCountChanged(int id_session, int new_count) +{ + if (m_ids_sessions.contains(id_session)){ + // Check if we need to toggle the "asset download" icon + TeraData session_data(*m_ids_sessions[id_session]); // Local copy to prevent deletion when calling updateSession + session_data.setFieldValue("session_assets_count", new_count); + updateSession(&session_data, false); + } +} + +void SessionsListWidget::sessionTestsCountChanged(int id_session, int new_count) +{ + if (m_ids_sessions.contains(id_session)){ + // Check if we need to toggle the "test" icon + TeraData session_data(*m_ids_sessions[id_session]); // Local copy to prevent deletion when calling updateSession + session_data.setFieldValue("session_tests_count", new_count); + updateSession(&session_data, false); + } +} + +void SessionsListWidget::on_btnDelSession_clicked() +{ + if (!m_allowDeletion) + return; + + // Check if the sender is a QToolButton (from the action column) + QToolButton* action_btn = dynamic_cast(sender()); + if (action_btn && action_btn != ui->btnDelSession){ + // Select row according to the session id of that button + int id_session = action_btn->property("id_session").toInt(); + QTableWidgetItem* session_item = m_listSessions_items[id_session]; + if (session_item) + ui->tableSessions->selectRow(session_item->row()); + } + + if (ui->tableSessions->selectedItems().count()==0) + return; + + GlobalMessageBox diag; + QMessageBox::StandardButton answer = QMessageBox::No; + if (ui->tableSessions->selectionModel()->selectedRows().count() == 1){ + QTableWidgetItem* base_item = ui->tableSessions->item(ui->tableSessions->currentRow(),0); + answer = diag.showYesNo(tr("Suppression?"), tr("Êtes-vous sûrs de vouloir supprimer """) + base_item->text() + """?"); + }else{ + answer = diag.showYesNo(tr("Suppression?"), tr("Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées?")); + } + + if (answer == QMessageBox::Yes){ + // We must delete! + foreach(QModelIndex selected_row, ui->tableSessions->selectionModel()->selectedRows()){ + QTableWidgetItem* base_item = ui->tableSessions->item(selected_row.row(),0); // Get item at index 0 which is linked to session id + //m_comManager->doDelete(TeraData::getPathForDataType(TERADATA_SESSION), m_listSessions_items.key(base_item)); + m_comManager->doDelete(TeraData::getPathForDataType(TERADATA_SESSION), m_listSessions_items.key(base_item)); + } + } +} + + +void SessionsListWidget::on_tableSessions_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) +{ + Q_UNUSED(previous) + ui->btnDelSession->setEnabled(current); +} + + +void SessionsListWidget::on_lstFilters_itemChanged(QListWidgetItem *changed) +{ + QList ids; + + for (int i=0; ilstFilters->count(); i++){ + if (ui->lstFilters->item(i)->checkState() == Qt::Checked){ + ids.append(ui->lstFilters->item(i)->data(Qt::UserRole).toInt()); + } + } + + ui->calMonth1->setFilters(ids); + ui->calMonth2->setFilters(ids); + ui->calMonth3->setFilters(ids); + + if (!changed) + return; + // Update session tables + QString current_ses_type = changed->text(); + for (int i=0; itableSessions->rowCount(); i++){ + if (ui->tableSessions->item(i,2)->text() == current_ses_type){ + ui->tableSessions->setRowHidden(i, changed->checkState()==Qt::Unchecked); + } + } +} + + +void SessionsListWidget::on_btnNextCal_clicked() +{ + QDate new_date; + new_date.setDate(ui->calMonth1->yearShown(), ui->calMonth1->monthShown(), 1); + new_date = new_date.addMonths(1); + + updateCalendars(new_date); +} + +void SessionsListWidget::updateCalendars(QDate left_date) +{ + ui->calMonth1->setCurrentPage(left_date.year(),left_date.month()); + ui->lblMonth1->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); + + left_date = left_date.addMonths(1); + ui->calMonth2->setCurrentPage(left_date.year(),left_date.month()); + ui->lblMonth2->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); + + left_date = left_date.addMonths(1); + ui->calMonth3->setCurrentPage(left_date.year(),left_date.month()); + ui->lblMonth3->setText(Utils::toCamelCase(QLocale().monthName(left_date.month())) + " " + QString::number(left_date.year())); + + // Check if we must enable the previous month button + QDate min_date = getMinimumSessionDate(); + + if (ui->calMonth1->yearShown()<=min_date.year() && ui->calMonth1->monthShown()<=min_date.month()) + ui->btnPrevCal->setEnabled(false); + else + ui->btnPrevCal->setEnabled(true); +} + +QDate SessionsListWidget::getMinimumSessionDate() +{ + QDate min_date = QDate::currentDate(); + for (TeraData* session:qAsConst(m_ids_sessions)){ + QDate session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date(); + if (session_date < min_date) + min_date = session_date; + } + + return min_date; +} + +QDate SessionsListWidget::getMaximumSessionDate() +{ + QDate max_date = QDate::currentDate(); + for (TeraData* session:qAsConst(m_ids_sessions)){ + QDate session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date(); + if (session_date > max_date) + max_date = session_date; + } + + return max_date; +} + +void SessionsListWidget::newSessionRequest(const QDateTime &session_datetime) +{ + if (m_diag_editor){ + m_diag_editor->deleteLater(); + } + m_diag_editor = new BaseDialog(this); + + TeraData* new_session = new TeraData(TERADATA_SESSION); + new_session->setFieldValue("session_name", tr("Nouvelle séance")); + new_session->setFieldValue("session_start_datetime", session_datetime); + new_session->setFieldValue("session_status", TeraSessionStatus::STATUS_NOTSTARTED); + new_session->setFieldValue("id_project", m_currentIdProject); + new_session->setFieldValue("id_creator_user", m_comManager->getCurrentUser().getId()); + if (m_viewMode == VIEW_PARTICIPANT_SESSIONS) + new_session->setFieldValue("participant_uuid", m_currentUuid); + if (m_viewMode == VIEW_USER_SESSIONS) + new_session->setFieldValue("user_uuid", m_currentUuid); + if (m_viewMode == VIEW_DEVICE_SESSIONS) + new_session->setFieldValue("device_uuid", m_currentUuid); + + SessionWidget* ses_widget = new SessionWidget(m_comManager, new_session, m_diag_editor); + m_diag_editor->setCentralWidget(ses_widget); + + m_diag_editor->setWindowTitle(tr("Séance")); + m_diag_editor->setMinimumSize(this->width(), this->height()); + + connect(ses_widget, &SessionWidget::closeRequest, m_diag_editor, &QDialog::accept); + connect(ses_widget, &SessionWidget::dataWasChanged, m_diag_editor, &QDialog::accept); + connect(ses_widget, &SessionWidget::dataWasDeleted, m_diag_editor, &QDialog::accept); + + m_diag_editor->open(); +} + +void SessionsListWidget::setSessionsLoading(const bool &loading) +{ + ui->progSessionsLoad->setVisible(loading); + ui->frameCalendar->setVisible(!loading); + ui->tableSessions->setVisible(!loading); + ui->frameSessionsControls->setVisible(!loading); + m_sessionsLoading = loading; +} + +void SessionsListWidget::querySessions() +{ + if (m_totalSessions == 0){ + qWarning() << "SessionsListWidget::querySessions(): No sessions to query - aborting!"; + return; + } + ui->tableSessions->setSortingEnabled(false); + int sessions_left = m_totalSessions - m_currentSessions; + setSessionsLoading(sessions_left > 0); + + if (sessions_left>0){ + ui->progSessionsLoad->setValue(m_currentSessions); + QUrlQuery query; + switch(m_viewMode){ + case VIEW_NONE: + break; + case VIEW_PARTICIPANT_SESSIONS: + query.addQueryItem(WEB_QUERY_ID_PARTICIPANT, QString::number(m_currentId)); + break; + case VIEW_USER_SESSIONS: + query.addQueryItem(WEB_QUERY_ID_USER, QString::number(m_currentId)); + break; + case VIEW_DEVICE_SESSIONS: + query.addQueryItem(WEB_QUERY_ID_DEVICE, QString::number(m_currentId)); + break; + } + + query.addQueryItem(WEB_QUERY_OFFSET, QString::number(m_currentSessions)); + query.addQueryItem(WEB_QUERY_LIMIT, QString::number(QUERY_SESSION_LIMIT_PER_QUERY)); // Limit number of sessions per query + query.addQueryItem(WEB_QUERY_WITH_SESSIONTYPE, "1"); + query.addQueryItem(WEB_QUERY_LIST, "1"); + //queryDataRequest(WEB_SESSIONINFO_PATH, query); + m_comManager->doGet(WEB_SESSIONINFO_PATH, query); + }else{ + // No more session to query + + // Update calendar view + on_lstFilters_itemChanged(nullptr); + updateCalendars(getMaximumSessionDate().addMonths(-2)); + //updateCalendars(getMinimumSessionDate()); + ui->calMonth1->setData(m_ids_sessions.values()); + ui->calMonth2->setData(m_ids_sessions.values()); + ui->calMonth3->setData(m_ids_sessions.values()); + + ui->tableSessions->setSortingEnabled(true); + ui->tableSessions->resizeColumnsToContents(); + } + +} + +bool SessionsListWidget::eventFilter(QObject *o, QEvent *e) +{ + if( o == ui->tableSessions && e->type() == QEvent::KeyRelease ) + { + QKeyEvent* key_event = dynamic_cast(e); + if (key_event){ + if (key_event->key() == Qt::Key_Delete){ + on_btnDelSession_clicked(); + } + } + } + + return QWidget::eventFilter(o,e); +} + +void SessionsListWidget::updateSession(const TeraData *session, const bool &auto_position) +{ + int id_session = session->getId(); + + QTableWidgetItem* name_item; + TableDateWidgetItem* date_item; + QTableWidgetItem* type_item; + QTableWidgetItem* duration_item; + QTableWidgetItem* user_item; + QTableWidgetItem* status_item; + QToolButton* btnDownload = nullptr; + QToolButton* btnResume = nullptr; + QToolButton* btnTests = nullptr; + + if (m_listSessions_items.contains(id_session)){ + // Already there, get items + name_item = m_listSessions_items[id_session]; + date_item = dynamic_cast(ui->tableSessions->item(name_item->row(), 1)); + type_item = ui->tableSessions->item(name_item->row(), 2); + status_item = ui->tableSessions->item(name_item->row(), 3); + duration_item = ui->tableSessions->item(name_item->row(), 4); + user_item = ui->tableSessions->item(name_item->row(), 5); + if (ui->tableSessions->cellWidget(name_item->row(), 6)) + if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()){ + if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(2)) + btnDownload = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(2)->widget()); + if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(3)) + btnTests = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(3)->widget()); + if (ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(4)) + btnResume = dynamic_cast(ui->tableSessions->cellWidget(name_item->row(), 6)->layout()->itemAt(4)->widget()); + } + + if (m_ids_sessions[id_session]->hasFieldName("session_assets_count")){ + m_totalAssets -= m_ids_sessions[id_session]->getFieldValue("session_assets_count").toInt(); + } + if (m_ids_sessions[id_session]->hasFieldName("session_tests_count")){ + m_totalTests -= m_ids_sessions[id_session]->getFieldValue("session_tests_count").toInt(); + } + delete m_ids_sessions[id_session]; + }else{ + //ui->tableSessions->setSortingEnabled(false); // Disable sorting so we know the correct inserted row + + if (ui->tableSessions->rowCount() < m_currentSessions+1){ + ui->tableSessions->setRowCount(ui->tableSessions->rowCount()+1); + } + int current_row = m_currentSessions; + name_item = new QTableWidgetItem(QIcon(TeraData::getIconFilenameForDataType(TERADATA_SESSION)),""); + ui->tableSessions->setItem(current_row, 0, name_item); + date_item = new TableDateWidgetItem(""); + ui->tableSessions->setItem(current_row, 1, date_item); + type_item = new QTableWidgetItem(""); + ui->tableSessions->setItem(current_row, 2, type_item); + status_item = new QTableWidgetItem(""); + ui->tableSessions->setItem(current_row, 3, status_item); + duration_item = new QTableWidgetItem(""); + duration_item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + ui->tableSessions->setItem(current_row, 4, duration_item); + user_item = new QTableWidgetItem(""); + ui->tableSessions->setItem(current_row, 5, user_item); + + // Create action buttons + QFrame* action_frame = new QFrame(); + QHBoxLayout* layout = new QHBoxLayout(); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(1); + layout->setAlignment(Qt::AlignLeft); + action_frame->setLayout(layout); + + // View session + QToolButton* btnView = new QToolButton(action_frame); + btnView->setIcon(m_viewIcon); + btnView->setProperty("id_session", session->getId()); + btnView->setCursor(Qt::PointingHandCursor); + btnView->setMaximumWidth(32); + btnView->setToolTip(tr("Ouvrir")); + connect(btnView, &QToolButton::clicked, this, &SessionsListWidget::btnViewSession_clicked); + layout->addWidget(btnView); + + // Delete + QToolButton* btnDelete = new QToolButton(action_frame); + btnDelete->setIcon(m_deleteIcon); + btnDelete->setProperty("id_session", session->getId()); + btnDelete->setCursor(Qt::PointingHandCursor); + btnDelete->setMaximumWidth(32); + btnDelete->setToolTip(tr("Supprimer")); + if (m_allowDeletion) + connect(btnDelete, &QToolButton::clicked, this, &SessionsListWidget::on_btnDelSession_clicked); + else + btnDelete->setVisible(false); + layout->addWidget(btnDelete); + + // Download data + btnDownload = new QToolButton(action_frame); + btnDownload->setIcon(m_downloadIcon); + btnDownload->setProperty("id_session", session->getId()); + btnDownload->setCursor(Qt::PointingHandCursor); + btnDownload->setMaximumWidth(32); + btnDownload->setToolTip(tr("Voir les données")); + connect(btnDownload, &QToolButton::clicked, this, &SessionsListWidget::btnViewSessionAssets_clicked); + layout->addWidget(btnDownload); + + // Tests display + btnTests = new QToolButton(action_frame); + btnTests->setIcon(m_testIcon); + btnTests->setProperty("id_session", session->getId()); + btnTests->setCursor(Qt::PointingHandCursor); + btnTests->setMaximumWidth(32); + btnTests->setToolTip(tr("Voir les évaluations")); + connect(btnTests, &QToolButton::clicked, this, &SessionsListWidget::btnViewSessionTests_clicked); + layout->addWidget(btnTests); + + // Resume session + btnResume = new QToolButton(action_frame); + btnResume->setIcon(m_resumeIcon); + btnResume->setProperty("id_session", session->getId()); + btnResume->setProperty("id_session_type", session->getFieldValue("id_session_type").toInt()); + btnResume->setCursor(Qt::PointingHandCursor); + btnResume->setMaximumWidth(32); + btnResume->setToolTip(tr("Continuer la séance")); + connect(btnResume, &QToolButton::clicked, this, &SessionsListWidget::btnResumeSession_clicked); + layout->addWidget(btnResume); + + ui->tableSessions->setCellWidget(current_row, 6, action_frame); + + m_listSessions_items[id_session] = name_item; + + m_currentSessions++; + + //ui->tableSessions->setSortingEnabled(true); // Reenable sorting + + } + m_ids_sessions[id_session] = new TeraData(*session); + + // Update values + name_item->setText(session->getName()); + date_item->setDate(session->getFieldValue("session_start_datetime")); + + if (session->hasFieldName("session_type_name")){ + type_item->setText(session->getFieldValue("session_type_name").toString()); + if (session->hasFieldName("session_type_color")) + type_item->setForeground(QColor(session->getFieldValue("session_type_color").toString())); + }else{ + // No session type in that structure - old API - use previous stored values + int session_type = session->getFieldValue("id_session_type").toInt(); + if (m_ids_session_types.contains(session_type)){ + type_item->setText(m_ids_session_types[session_type]->getFieldValue("session_type_name").toString()); + type_item->setForeground(QColor(m_ids_session_types[session_type]->getFieldValue("session_type_color").toString())); + }else{ + type_item->setText("Inconnu"); + } + } + + duration_item->setText(QTime(0,0).addSecs(session->getFieldValue("session_duration").toInt()).toString("hh:mm:ss")); + TeraSessionStatus::SessionStatus session_status = static_cast(session->getFieldValue("session_status").toInt()); + status_item->setText(TeraSessionStatus::getStatusName(session_status)); + // Set color depending on status_item + //status_item->setTextColor(QColor(TeraSessionStatus::getStatusColor(session_status))); + status_item->setForeground(Qt::black); + status_item->setBackground(QColor(TeraSessionStatus::getStatusColor(session_status))); + //QColor back_color = TeraForm::getGradientColor(3, 18, 33, static_cast(session_date.daysTo(QDateTime::currentDateTime()))); + //back_color.setAlphaF(0.5); + //date_item->setBackgroundColor(back_color); + + // Session creator + if (session->hasFieldName("session_creator_user")) + user_item->setText(session->getFieldValue("session_creator_user").toString()); + else if(session->hasFieldName("session_creator_device")) + user_item->setText(tr("Appareil: ") + session->getFieldValue("session_creator_device").toString()); + else if(session->hasFieldName("session_creator_participant")) + user_item->setText(tr("Participant: ") + session->getFieldValue("session_creator_participant").toString()); + else if(session->hasFieldName("session_creator_service")) + user_item->setText(tr("Service: ") + session->getFieldValue("session_creator_service").toString()); + else { + user_item->setText(tr("Inconnu")); + } + + // Download data + if (btnDownload){ + if (session->hasFieldName("session_assets_count")){ + int asset_count = session->getFieldValue("session_assets_count").toInt(); + bool has_assets = asset_count>0; + //btnDownload->setVisible(has_assets); + btnDownload->setEnabled(has_assets); + if (!has_assets){ + btnDownload->setIcon(QIcon()); + }else{ + btnDownload->setIcon(m_downloadIcon); + } + m_totalAssets += asset_count; + ui->btnAssetsBrowser->setVisible(m_totalAssets>0); + }else{ + //btnDownload->hide(); + btnDownload->setEnabled(false); + } + } + + // Show Tests + if (btnTests){ + if (session->hasFieldName("session_tests_count")){ + int test_count = session->getFieldValue("session_tests_count").toInt(); + bool has_tests = test_count>0; + //btnTests->setVisible(has_tests); + btnTests->setEnabled(has_tests); + if (!has_tests){ + btnTests->setIcon(QIcon()); + }else{ + btnTests->setIcon(m_testIcon); + } + m_totalTests += test_count; + ui->btnTestsBrowser->setVisible(m_totalTests>0); + }else{ + //btnTests->hide(); + btnTests->setEnabled(false); + } + } + + // Resume session + if (btnResume){ + if (session->hasFieldName("session_start_datetime")){ + QDateTime session_date = session->getFieldValue("session_start_datetime").toDateTime().toLocalTime(); + btnResume->setVisible(session_date.date() == QDate::currentDate()); + }else{ + btnResume->hide(); + } + } + + // If only 1 session added, check to put it at the right place in the table + if (m_currentSessions > 1 && auto_position){ + // Check if we have a sorting or not + /*int sort_column = ui->tableSessions->horizontalHeader()->sortIndicatorSection(); + Qt::SortOrder sort_order = ui->tableSessions->horizontalHeader()->sortIndicatorOrder(); + // Reapply sorting + ui->tableSessions->sortByColumn(sort_column, sort_order);*/ + + // Select added item + ui->tableSessions->selectRow(name_item->row()); + ui->tableSessions->scrollToItem(name_item, QAbstractItemView::PositionAtCenter); + } +} + +void SessionsListWidget::showSessionEditor(TeraData *session_info) +{ + if (m_diag_editor){ + m_diag_editor->deleteLater(); + } + m_diag_editor = new BaseDialog(this); + + //int id_session = m_listSessions_items.key(ui->tableSessions->item(session_item->row(),0)); + //TeraData* ses_data = m_ids_sessions[id_session]; + //if (ses_data){ + session_info->setFieldValue("id_project", m_currentIdProject); + SessionWidget* ses_widget = new SessionWidget(m_comManager, session_info, m_diag_editor); + ses_widget->alwaysShowAssets(m_allowFileTransfers); + if (m_currentSessionShowAssets) + ses_widget->showAssets(); + if (m_currentSessionShowTests) + ses_widget->showTests(); + + m_diag_editor->setCentralWidget(ses_widget); + + m_diag_editor->setWindowTitle(tr("Séance")); + //m_diag_editor->setMinimumSize(2*this->width()/3, 5*this->height()/6); + QList screens = QGuiApplication::screens(); + QSize base_size = screens.first()->size(); + m_diag_editor->setMinimumSize(3*base_size.width()/4, 3*base_size.height()/4); + + connect(ses_widget, &SessionWidget::closeRequest, m_diag_editor, &QDialog::accept); + connect(ses_widget, &SessionWidget::assetsCountChanged, this, &SessionsListWidget::sessionAssetsCountChanged); + connect(ses_widget, &SessionWidget::testsCountChanged, this, &SessionsListWidget::sessionTestsCountChanged); + + m_diag_editor->open(); +} + +void SessionsListWidget::displaySessionDetails(QTableWidgetItem *session_item, bool show_assets, bool show_tests) +{ + // Query full session information + m_currentIdSession = m_listSessions_items.key(ui->tableSessions->item(session_item->row(),0)); + m_currentSessionShowAssets = show_assets; + m_currentSessionShowTests = show_tests; + + QUrlQuery args; + args.addQueryItem(WEB_QUERY_ID_SESSION, QString::number(m_currentIdSession)); + if (m_comManager) + m_comManager->doGet(WEB_SESSIONINFO_PATH, args); +} + + +void SessionsListWidget::on_btnPrevCal_clicked() +{ + QDate new_date; + new_date.setDate(ui->calMonth1->yearShown(), ui->calMonth1->monthShown(), 1); + new_date = new_date.addMonths(-1); + + updateCalendars(new_date); +} + +void SessionsListWidget::currentCalendarDateChanged(QDate current_date) +{ + // Clear current selection + ui->tableSessions->clearSelection(); + + // Temporarly set multi-selection + QAbstractItemView::SelectionMode current_mode = ui->tableSessions->selectionMode(); + ui->tableSessions->setSelectionMode(QAbstractItemView::MultiSelection); + + // Select all the sessions in the list that fits with that date + QTableWidgetItem* first_item = nullptr; + //foreach(TeraData* session, m_ids_sessions){ + for(TeraData* session: qAsConst(m_ids_sessions)){ + if (session->getFieldValue("session_start_datetime").toDateTime().toLocalTime().date() == current_date){ + QTableWidgetItem* session_item = m_listSessions_items.value(session->getId()); + if (session_item){ + ui->tableSessions->selectRow(session_item->row()); + if (!first_item) + first_item = session_item; + } + } + } + + // Resets selection mode + ui->tableSessions->setSelectionMode(current_mode); + + // Ensure first session is visible + if (first_item){ + ui->tableSessions->scrollToItem(first_item); + } +} + +void SessionsListWidget::currentCalendarDateActivated(QDate current_date) +{ + // Add a new session + QDateTime session_datetime; + session_datetime.setDate(current_date); + QTime session_time = QTime::currentTime(); + int minutes = (session_time.minute() / 15) * 15; + session_time.setHMS(session_datetime.time().addSecs(3600).hour(), minutes, 0); + session_datetime.setTime(session_time); + + newSessionRequest(session_datetime); +} + + +void SessionsListWidget::on_btnCheckSessionTypes_clicked() +{ + for (int i=0; ilstFilters->count(); i++){ + ui->lstFilters->item(i)->setCheckState(Qt::Checked); + } + ui->btnUncheckSessionTypes->show(); + ui->btnCheckSessionTypes->hide(); +} + + +void SessionsListWidget::on_btnUncheckSessionTypes_clicked() +{ + for (int i=0; ilstFilters->count(); i++){ + ui->lstFilters->item(i)->setCheckState(Qt::Unchecked); + } + ui->btnUncheckSessionTypes->hide(); + ui->btnCheckSessionTypes->show(); +} + + +void SessionsListWidget::on_btnFilterSessionsTypes_clicked() +{ + ui->frameFilterSessionTypes->setVisible(ui->btnFilterSessionsTypes->isChecked()); +} + + +void SessionsListWidget::on_tableSessions_itemDoubleClicked(QTableWidgetItem *item) +{ + displaySessionDetails(item); +} + diff --git a/client/src/widgets/SessionsListWidget.h b/client/src/widgets/SessionsListWidget.h new file mode 100644 index 00000000..db906d2d --- /dev/null +++ b/client/src/widgets/SessionsListWidget.h @@ -0,0 +1,136 @@ +#ifndef SESSIONSLISTWIDGET_H +#define SESSIONSLISTWIDGET_H + +#include +#include +#include + +#include "managers/ComManager.h" +#include "dialogs/BaseDialog.h" + +#define QUERY_SESSION_LIMIT_PER_QUERY 50 + +namespace Ui { +class SessionsListWidget; +} + +class SessionsListWidget : public QWidget +{ + Q_OBJECT + +public: + enum ViewMode{ + VIEW_NONE = 0, + VIEW_PARTICIPANT_SESSIONS, + VIEW_USER_SESSIONS, + VIEW_DEVICE_SESSIONS + }; + + explicit SessionsListWidget(QWidget *parent = nullptr); + ~SessionsListWidget(); + + void setComManager(ComManager* comMan); + void setViewMode(const ViewMode& mode, const QString &uuid, const int &id, const int &id_project = -1); + + void initUI(); + void connectSignals(); + + void enableDeletion(const bool& enable); + void enableFileTransfers(const bool& enable); + + void setSessionsCount(const int& count); + int getSessionsCount(); + + void sessionWasDeleted(const int& id_session); + + void setSessionTypes(QList session_types); // This function will trigger a data load / refresh + + const TeraData* hasResumableSession(const int& id_session_type, const QDate& target_date); + void newSessionRequest(const QDateTime& session_datetime); + +private: + Ui::SessionsListWidget *ui; + ComManager* m_comManager; + ViewMode m_viewMode; + + QHash m_listSessions_items; // ID Session to QTableWidgetItem* mapping + + BaseDialog* m_diag_editor; + + QHash m_ids_session_types; + QHash m_ids_sessions; // ID Session to data mapping + + int m_totalSessions; + int m_totalAssets; + int m_totalTests; + int m_currentSessions; + bool m_sessionsLoading; + + bool m_allowDeletion; + bool m_allowFileTransfers; + + QString m_currentUuid; + int m_currentId; + int m_currentIdProject; + + // Icons are defined here (speed up load) + QIcon m_deleteIcon; + QIcon m_viewIcon; + QIcon m_downloadIcon; + QIcon m_testIcon; + QIcon m_resumeIcon; + + // Infos when querying extra sessions + int m_currentIdSession; + bool m_currentSessionShowAssets; + bool m_currentSessionShowTests; + + void updateCalendars(QDate left_date); + QDate getMinimumSessionDate(); + QDate getMaximumSessionDate(); + + void setSessionsLoading(const bool& loading); + void querySessions(); + + bool eventFilter(QObject* o, QEvent* e) override; + + void updateSession(const TeraData *session, const bool &auto_position); + void showSessionEditor(TeraData *session_info); + void displaySessionDetails(QTableWidgetItem* session_item, bool show_assets = false, bool show_tests = false); + + +private slots: + void processSessionsReply(QList sessions, QUrlQuery args); + void deleteDataReply(QString path, int id); + + void btnViewSession_clicked(); + void btnViewSessionAssets_clicked(); + void btnViewSessionTests_clicked(); + void btnResumeSession_clicked(); + + void sessionAssetsCountChanged(int id_session, int new_count); + void sessionTestsCountChanged(int id_session, int new_count); + + void on_btnDelSession_clicked(); + void on_tableSessions_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous); + void on_lstFilters_itemChanged(QListWidgetItem *changed); + + void on_btnNextCal_clicked(); + void on_btnPrevCal_clicked(); + void currentCalendarDateChanged(QDate current_date); + void currentCalendarDateActivated(QDate current_date); + + void on_btnCheckSessionTypes_clicked(); + + void on_btnUncheckSessionTypes_clicked(); + + void on_btnFilterSessionsTypes_clicked(); + + void on_tableSessions_itemDoubleClicked(QTableWidgetItem *item); + +signals: + void startSessionRequested(int id_session_type, int id_session); + void sessionsCountUpdated(int new_total); +}; + +#endif // SESSIONSLISTWIDGET_H diff --git a/client/src/widgets/SessionsListWidget.ui b/client/src/widgets/SessionsListWidget.ui new file mode 100644 index 00000000..0d3bf375 --- /dev/null +++ b/client/src/widgets/SessionsListWidget.ui @@ -0,0 +1,805 @@ + + + SessionsListWidget + + + + 0 + 0 + 852 + 453 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 850 + 451 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + 24 + + + Qt::AlignCenter + + + Chargement des séances: %p% + + + + + + + + 0 + 0 + + + + + 16777215 + 200 + + + + + 3 + + + + + + 0 + 0 + + + + PointingHandCursor + + + ... + + + + :/controls/branch_left.png:/controls/branch_left.png + + + + + + + 3 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Mois 1 + + + false + + + Qt::AlignCenter + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 175 + + + + PointingHandCursor + + + + + + + 2013 + 1 + 1 + + + + + 3000 + 1 + 1 + + + + true + + + QCalendarWidget::SingleSelection + + + QCalendarWidget::SingleLetterDayNames + + + QCalendarWidget::NoVerticalHeader + + + false + + + false + + + + + + + + + 3 + + + QLayout::SetDefaultConstraint + + + + + + 0 + 0 + + + + Mois 2 + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 175 + + + + PointingHandCursor + + + true + + + QCalendarWidget::SingleLetterDayNames + + + QCalendarWidget::NoVerticalHeader + + + false + + + false + + + + + + + + + 3 + + + + + + 0 + 0 + + + + Mois 3 + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 16777215 + 175 + + + + PointingHandCursor + + + true + + + QCalendarWidget::SingleLetterDayNames + + + QCalendarWidget::NoVerticalHeader + + + false + + + false + + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + ... + + + + :/controls/branch_closed.png:/controls/branch_closed.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 24 + 24 + + + + PointingHandCursor + + + Tout cocher + + + ... + + + + :/controls/check2_on.png:/controls/check2_on.png + + + + 24 + 24 + + + + + + + + + 24 + 24 + + + + PointingHandCursor + + + Tout décocher + + + ... + + + + :/controls/check2_off.png:/controls/check2_off.png + + + + 24 + 24 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + 10 + + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + QAbstractItemView::NoEditTriggers + + + false + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + QListView::Static + + + QListView::TopToBottom + + + QListView::SinglePass + + + 3 + + + true + + + + + + + + + + + 0 + 0 + + + + + 0 + 150 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + 20 + 20 + + + + true + + + 75 + + + 100 + + + false + + + false + + + + Séance + + + + + Date + + + + + Type + + + + + État + + + + + Durée + + + + + Responsable + + + + + Actions + + + + + + + + + + + + + + 125 + 40 + + + + PointingHandCursor + + + Filtrer les séances + + + + :/icons/filter.png:/icons/filter.png + + + + 24 + 24 + + + + true + + + + + + + + 125 + 40 + + + + PointingHandCursor + + + Nouvelle + + + + :/icons/new.png:/icons/new.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + false + + + + 125 + 40 + + + + PointingHandCursor + + + Supprimer + + + + :/icons/delete_old.png:/icons/delete_old.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 125 + 40 + + + + PointingHandCursor + + + Explorateur de données + + + + :/icons/data.png:/icons/data.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 125 + 40 + + + + PointingHandCursor + + + Explorateur d'évaluations + + + + :/icons/test.png:/icons/test.png + + + + 24 + 24 + + + + Qt::ToolButtonTextBesideIcon + + + + + + + + + + + + + + + HistoryCalendarWidget + QCalendarWidget +
widgets/HistoryCalendarWidget.h
+
+
+ + + + +
From cb30ef10ebc88644e365d57bf04aaa9f35590e8a Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Fri, 9 Dec 2022 09:37:07 -0500 Subject: [PATCH 16/42] Refs #63. Fixed wrong session list display for participant. --- client/src/editors/ParticipantWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 2904d435..4d89d4ad 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -210,7 +210,7 @@ void ParticipantWidget::updateFieldsValue() void ParticipantWidget::initUI() { ui->wdgSessions->setComManager(m_comManager); - ui->wdgSessions->setViewMode(SessionsListWidget::VIEW_PARTICIPANT_SESSIONS, m_data->getUuid(), m_data->getFieldValue("id_project").toInt()); + ui->wdgSessions->setViewMode(SessionsListWidget::VIEW_PARTICIPANT_SESSIONS, m_data->getUuid(), m_data->getId(), m_data->getFieldValue("id_project").toInt()); ui->frameParticipantLogin->hide(); ui->frameActive->hide(); From d22bdf1524a75d1888e1df73d171822d09ad6fab Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 17 Jan 2023 14:18:48 -0500 Subject: [PATCH 17/42] Refs #80. Improved loading speed. --- client/src/widgets/LogViewWidget.cpp | 27 +++++++++++++++++++-------- client/src/widgets/LogViewWidget.h | 3 +++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index a289c1e3..0c8791c2 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -11,7 +11,7 @@ LogViewWidget::LogViewWidget(QWidget *parent): ui->cmbLevel->setItemDelegate(new QStyledItemDelegate(ui->cmbLevel)); m_currentMode = ViewMode::VIEW_LOGS_NONE; m_currentUuid = QString(); - m_maxCount = 50; // 50 items at a time by default + m_maxCount = 50; // Number of items displayed by default m_filtering = false; m_listening = false; @@ -43,6 +43,7 @@ LogViewWidget::LogViewWidget(ComManager* comMan, QWidget *parent) : LogViewWidget::~LogViewWidget() { delete ui; + qDeleteAll(m_iconsCache); } void LogViewWidget::setComManager(ComManager *comMan) @@ -405,6 +406,16 @@ QString LogViewWidget::getBrowserIcon(const QString &browser) return ""; } +const QIcon *LogViewWidget::getIcon(const QString &path) +{ + if (m_iconsCache.contains(path)) + return m_iconsCache[path]; + + QIcon* icon = new QIcon(path); + m_iconsCache.insert(path, icon); + return icon; +} + void LogViewWidget::updateNavButtons() { ui->btnPrevPage->setEnabled(ui->spinPage->value() > ui->spinPage->minimum()); @@ -472,7 +483,7 @@ void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_da item = new QTableWidgetItem(); LogEvent::LogLevel level = static_cast(login.getFieldValue("login_log_level").toInt()); item->setText(getLogLevelName(level)); - item->setIcon(QIcon(getLogLevelIcon(level))); + item->setIcon(*getIcon((getLogLevelIcon(level)))); ui->tableLogs->setItem(row, 2, item); // Try to find the name associated to that log @@ -533,29 +544,29 @@ void LogViewWidget::processLogsLogins(QList logins, QUrlQuery reply_da item = new QTableWidgetItem(); item->setText(login_name); - item->setIcon(QIcon(login_icon)); + item->setIcon(*getIcon(login_icon)); ui->tableLogs->setItem(row, 3, item); item = new QTableWidgetItem(); LoginEvent::LoginType login_type = static_cast(login.getFieldValue("login_type").toInt()); item->setText(getLoginTypeName(login_type)); - item->setIcon(QIcon(getLoginTypeIcon(login_type))); + item->setIcon(*getIcon(getLoginTypeIcon(login_type))); ui->tableLogs->setItem(row, 4, item); item = new QTableWidgetItem(); LoginEvent::LoginStatus login_status = static_cast(login.getFieldValue("login_status").toInt()); item->setText(getLoginStatusName(login_status)); - item->setIcon(QIcon(getLoginStatusIcon(login_status))); + item->setIcon(*getIcon(getLoginStatusIcon(login_status))); ui->tableLogs->setItem(row, 5, item); item = new QTableWidgetItem(); item->setText(login.getFieldValue("login_os_name").toString() + " " + login.getFieldValue("login_os_version").toString()); - item->setIcon(QIcon(getOSIcon(login.getFieldValue("login_os_name").toString()))); + item->setIcon(*getIcon(getOSIcon(login.getFieldValue("login_os_name").toString()))); ui->tableLogs->setItem(row, 6, item); item = new QTableWidgetItem(); item->setText(login.getFieldValue("login_client_name").toString() + " " + login.getFieldValue("login_client_version").toString()); - item->setIcon(QIcon(getBrowserIcon(login.getFieldValue("login_client_name").toString()))); + item->setIcon(*getIcon(getBrowserIcon(login.getFieldValue("login_client_name").toString()))); ui->tableLogs->setItem(row, 7, item); item = new QTableWidgetItem(); @@ -602,7 +613,7 @@ void LogViewWidget::processLogsLogs(QList logins, QUrlQuery reply_data item = new QTableWidgetItem(); LogEvent::LogLevel level = static_cast(login.getFieldValue("log_level").toInt()); item->setText(getLogLevelName(level)); - item->setIcon(QIcon(getLogLevelIcon(level))); + item->setIcon(*getIcon(getLogLevelIcon(level))); ui->tableLogs->setItem(row, 2, item); item = new QTableWidgetItem(); diff --git a/client/src/widgets/LogViewWidget.h b/client/src/widgets/LogViewWidget.h index 99682d27..18fb0249 100644 --- a/client/src/widgets/LogViewWidget.h +++ b/client/src/widgets/LogViewWidget.h @@ -44,6 +44,7 @@ class LogViewWidget : public QWidget bool m_filtering; // Applying selected filters bool m_listening; // Listening for logs QHash m_uuidsNames; + QHash m_iconsCache; void connectSignals(); void disconnectSignals(); @@ -61,6 +62,8 @@ class LogViewWidget : public QWidget QString getOSIcon(const QString &os); QString getBrowserIcon(const QString &browser); + const QIcon* getIcon(const QString& path); + void updateNavButtons(); void updateFilterButton(); From 30ae42fc8e6450e0bf6ae36ab23fbdb3970dd414 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 23 Jan 2023 11:57:11 -0500 Subject: [PATCH 18/42] Refs #63. Added Sessions View for UserSummaryWidget. --- client/src/dialogs/PasswordStrengthDialog.ui | 9 + client/src/editors/ParticipantWidget.cpp | 39 +-- client/src/editors/ParticipantWidget.h | 2 - client/src/editors/UserSummaryWidget.cpp | 198 +++++++++++- client/src/editors/UserSummaryWidget.h | 17 +- client/src/editors/UserSummaryWidget.ui | 303 ++++++++++--------- client/src/editors/UserWidget.cpp | 10 +- client/src/main/MainWindow.cpp | 8 +- client/src/widgets/ProjectNavigator.cpp | 42 +++ client/src/widgets/ProjectNavigator.h | 2 + client/src/widgets/SessionsListWidget.cpp | 75 ++++- client/src/widgets/SessionsListWidget.h | 4 + client/src/widgets/SessionsListWidget.ui | 8 +- 13 files changed, 500 insertions(+), 217 deletions(-) diff --git a/client/src/dialogs/PasswordStrengthDialog.ui b/client/src/dialogs/PasswordStrengthDialog.ui index 4ccd9a70..3411ad80 100644 --- a/client/src/dialogs/PasswordStrengthDialog.ui +++ b/client/src/dialogs/PasswordStrengthDialog.ui @@ -219,6 +219,15 @@
+ + txtPassword + txtPasswordConf + btnOK + btnCancel + btnViewPassword + btnGeneratePassword + lstValidate + diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 4d89d4ad..9b10dc91 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -1104,24 +1104,6 @@ void ParticipantWidget::on_btnAddSession_clicked() } -void ParticipantWidget::on_btnAssetsBrowser_clicked() -{ - if (m_diag_editor){ - m_diag_editor->deleteLater(); - } - m_diag_editor = new BaseDialog(this); - - AssetsWidget* asset_widget = new AssetsWidget(m_comManager, m_diag_editor); - asset_widget->displayAssetsForParticipant(m_data->getId()); - m_diag_editor->setCentralWidget(asset_widget); - - m_diag_editor->setWindowTitle(tr("Explorateur de données")); - m_diag_editor->setMinimumSize(3*this->width()/4, 3*this->height()/4); - - m_diag_editor->open(); -} - - void ParticipantWidget::on_tabNav_currentChanged(int index) { QUrlQuery query; @@ -1165,25 +1147,6 @@ void ParticipantWidget::on_lstDevices_itemDoubleClicked(QListWidgetItem *item) btnDelDevice_clicked(); } - -void ParticipantWidget::on_btnTestsBrowser_clicked() -{ - if (m_diag_editor){ - m_diag_editor->deleteLater(); - } - m_diag_editor = new BaseDialog(this); - - TestsWidget* test_widget = new TestsWidget(m_comManager, m_diag_editor); - test_widget->displayTestsForParticipant(m_data->getId()); - m_diag_editor->setCentralWidget(test_widget); - - m_diag_editor->setWindowTitle(tr("Explorateur d'évaluations")); - m_diag_editor->setMinimumSize(3*this->width()/4, 3*this->height()/4); - - m_diag_editor->open(); -} - - void ParticipantWidget::on_btnQR_clicked() { if (m_diag_editor){ @@ -1196,7 +1159,7 @@ void ParticipantWidget::on_btnQR_clicked() m_diag_editor->setCentralWidget(qr_widget); m_diag_editor->setWindowTitle(tr("Code QR du lien")); - m_diag_editor->setFixedSize(this->height()/2-40, this->height()/2); + m_diag_editor->setFixedSize(this->height()/2 - 40, this->height()/2); m_diag_editor->open(); } diff --git a/client/src/editors/ParticipantWidget.h b/client/src/editors/ParticipantWidget.h index 815da842..d259b9ae 100644 --- a/client/src/editors/ParticipantWidget.h +++ b/client/src/editors/ParticipantWidget.h @@ -117,11 +117,9 @@ private slots: void on_btnEmailWeb_clicked(); void on_cmbSessionType_currentIndexChanged(int index); void on_btnAddSession_clicked(); - void on_btnAssetsBrowser_clicked(); void on_tabNav_currentChanged(int index); void on_lstAvailDevices_itemDoubleClicked(QListWidgetItem *item); void on_lstDevices_itemDoubleClicked(QListWidgetItem *item); - void on_btnTestsBrowser_clicked(); void on_btnQR_clicked(); void on_tabInfosDetails_currentChanged(int index); }; diff --git a/client/src/editors/UserSummaryWidget.cpp b/client/src/editors/UserSummaryWidget.cpp index 69c10475..cd8fb566 100644 --- a/client/src/editors/UserSummaryWidget.cpp +++ b/client/src/editors/UserSummaryWidget.cpp @@ -1,29 +1,50 @@ #include "UserSummaryWidget.h" #include "ui_UserSummaryWidget.h" +#include "dialogs/PasswordStrengthDialog.h" + #include -UserSummaryWidget::UserSummaryWidget(ComManager *comMan, const TeraData *data, QWidget *parent) : +UserSummaryWidget::UserSummaryWidget(ComManager *comMan, const TeraData *data, const int &id_project, QWidget *parent) : DataEditorWidget(comMan, data, parent), ui(new Ui::UserSummaryWidget) { m_diag_editor = nullptr; m_sessionLobby = nullptr; + m_passwordJustGenerated = false; + m_idProject = id_project; ui->setupUi(this); setAttribute(Qt::WA_StyledBackground); //Required to set a background image setLimited(true); + // Use base class to manage editing + setEditorControls(ui->wdgUser, ui->btnEdit, ui->frameButtons, ui->btnSave, ui->btnUndo); + // Initialize user interface initUI(); // Connect signals and slots connectSignals(); - // Query sessions types - queryDataRequest(WEB_SESSIONTYPE_PATH); + // Query form definition + QUrlQuery args(WEB_FORMS_QUERY_USER); + if (!dataIsNew()) + args.addQueryItem(WEB_QUERY_ID, QString::number(m_data->getId())); + + queryDataRequest(WEB_FORMS_PATH, args); + + // Query sites related to that user (to check for edit access) + args.clear(); + args.addQueryItem(WEB_QUERY_ID_USER, QString::number(m_data->getId())); + queryDataRequest(WEB_SITEACCESS_PATH, args); + + // Query stats + queryDataRequest(WEB_STATS_PATH, args); // args already contains id_user + + ui->wdgUser->setComManager(m_comManager); } UserSummaryWidget::~UserSummaryWidget() @@ -36,28 +57,28 @@ UserSummaryWidget::~UserSummaryWidget() void UserSummaryWidget::connectSignals() { + connect(m_comManager, &ComManager::formReceived, this, &UserSummaryWidget::processFormsReply); connect(m_comManager, &ComManager::sessionTypesReceived, this, &UserSummaryWidget::processSessionTypesReply); + connect(m_comManager, &ComManager::siteAccessReceived, this, &UserSummaryWidget::processSiteAccessReply); + connect(m_comManager, &ComManager::statsReceived, this, &UserSummaryWidget::processStatsReply); connect(m_comManager->getWebSocketManager(), &WebSocketManager::userEventReceived, this, &UserSummaryWidget::ws_userEvent); + connect(ui->wdgUser, &TeraForm::widgetValueHasChanged, this, &UserSummaryWidget::userFormValueChanged); + connect(ui->wdgUser, &TeraForm::widgetValueHasFocus, this, &UserSummaryWidget::userFormValueHasFocus); + } void UserSummaryWidget::updateControlsState() { - ui->btnEditUser->setVisible(!m_limited); + ui->frameEdit->setVisible(!m_limited); + ui->wdgSessions->enableDeletion(!m_limited); } void UserSummaryWidget::updateFieldsValue() { if (m_data){ ui->lblTitle->setText(m_data->getName()); - QString email = m_data->getFieldValue("user_email").toString(); - if (email.isEmpty()) - email = tr("N/D"); - ui->lblEmailValue->setText(email); - ui->lblFullNameValue->setText(m_data->getName()); - ui->lblLastOnlineValue->setText(m_data->getFieldValue("user_lastonline").toDateTime().toLocalTime().toString("dd-MM-yyyy hh:mm:ss")); - ui->chkEnabled->setChecked(m_data->isEnabled()); // Status ui->icoOnline->setVisible(m_data->isEnabled()); @@ -74,27 +95,81 @@ void UserSummaryWidget::updateFieldsValue() if (m_data->hasBusyStateField()) ui->btnNewSession->setEnabled(!m_data->isBusy()); + + if (ui->wdgUser->formHasStructure()) + ui->wdgUser->fillFormFromData(m_data->toJson()); } } void UserSummaryWidget::initUI() { - ui->btnEditUser->hide(); // For now + ui->wdgSessions->setComManager(m_comManager); + ui->wdgSessions->setViewMode(SessionsListWidget::VIEW_USER_SESSIONS, m_data->getUuid(), m_data->getId()); ui->cmbSessionType->setItemDelegate(new QStyledItemDelegate(ui->cmbSessionType)); + ui->wdgUser->hideFields({"user_username", "user_notes", "user_superadmin"}); + + ui->tabNav->setCurrentIndex(0); } bool UserSummaryWidget::validateData() { - bool valid = false; + return ui->wdgUser->validateFormData(); +} - return valid; +void UserSummaryWidget::processFormsReply(QString form_type, QString data) +{ + if (form_type.startsWith(WEB_FORMS_QUERY_USER)){ + if (!ui->wdgUser->formHasStructure()){ + ui->wdgUser->buildUiFromStructure(data); + if (m_data){ + if (m_data->getId() == m_comManager->getCurrentUser().getId()){ + // Editing self - disable "enable" button + ui->wdgUser->hideField("user_enabled"); + } + } + } + return; + } +} + +void UserSummaryWidget::processSiteAccessReply(QList access_list, QUrlQuery reply_query) +{ + // Check if the current logged user is admin in at least one of the user site + bool is_admin = false; + for(const TeraData &access: access_list){ + if(m_comManager->isCurrentUserSiteAdmin(access.getFieldValue("id_site").toInt())){ + is_admin = true; + break; + } + } + + setLimited(!is_admin); } void UserSummaryWidget::saveData(bool signal) { - Q_UNUSED(signal) - return; // Nothing to save here! + QJsonDocument user_data = ui->wdgUser->getFormDataJson(m_data->isNew()); + + if (*m_data == m_comManager->getCurrentUser()){ + m_currentUserPasswordChanged = ui->wdgUser->getFieldDirty("user_password"); + }else{ + m_currentUserPasswordChanged = false; + } + + //qDebug() << user_data.toJson(); + postDataRequest(WEB_USERINFO_PATH, user_data.toJson()); + + + if (signal){ + TeraData* new_data = ui->wdgUser->getFormDataObject(TERADATA_USER); + new_data->setName(new_data->getFieldValue("user_firstname").toString() + " " + new_data->getFieldValue("user_lastname").toString()); + *m_data = *new_data; + delete new_data; + if (!m_currentUserPasswordChanged) + // If current password was changed, we will process it in the user replies + emit dataWasChanged(); + } } void UserSummaryWidget::processSessionTypesReply(QList session_types) @@ -111,6 +186,97 @@ void UserSummaryWidget::processSessionTypesReply(QList session_types) } } + // Query sessions + ui->wdgSessions->setSessionTypes(session_types); + +} + +void UserSummaryWidget::processUsersReply(QList users) +{ + for (int i=0; isetCredentials(m_data->getFieldValue("user_username").toString(), + ui->wdgUser->getFieldValue("user_password").toString()); + m_currentUserPasswordChanged = false; + emit dataWasChanged(); + } + // We found "ourself" in the list - update data. + //*m_data = users.at(i); + m_data->updateFrom(users.at(i)); + updateFieldsValue(); + break; + } + } +} + +void UserSummaryWidget::processStatsReply(TeraData stats, QUrlQuery reply_query) +{ + + // Check if stats are for us + if (!reply_query.hasQueryItem("id_user")) + return; + + if (reply_query.queryItemValue("id_user").toInt() != m_data->getId()) + return; + + // Fill stats information + int total_sessions = stats.getFieldValue("sessions_total_count").toInt(); + ui->wdgSessions->setSessionsCount(total_sessions); + + // Query sessions types + QUrlQuery args; + /* if (m_idProject > 0) + args.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(m_idProject));*/ + queryDataRequest(WEB_SESSIONTYPE_PATH, args); +} + +void UserSummaryWidget::userFormValueChanged(QWidget *widget, QVariant value) +{ + if (widget == ui->wdgUser->getWidgetForField("user_password")){ + QString current_pass = value.toString(); + if (!current_pass.isEmpty() && !m_passwordJustGenerated){ + // Show password dialog + PasswordStrengthDialog dlg(current_pass); + + if (dlg.exec() == QDialog::Accepted){ + m_passwordJustGenerated = true; + ui->wdgUser->setFieldValue("user_password", dlg.getCurrentPassword()); + }else{ + ui->wdgUser->setFieldValue("user_password", ""); + } + + }else{ + if (m_passwordJustGenerated) + m_passwordJustGenerated = false; + } + } +} + +void UserSummaryWidget::userFormValueHasFocus(QWidget *widget) +{ + if (widget == ui->wdgUser->getWidgetForField("user_password")){ + if (!m_passwordJustGenerated){ + // Show password dialog + QString current_pass = ui->wdgUser->getFieldValue("user_password").toString(); + PasswordStrengthDialog dlg(current_pass); + //QLineEdit* wdg_editor = dynamic_cast(ui->wdgUser->getWidgetForField("user_password")); + //dlg.setCursorPosition(wdg_editor->cursorPosition()); + + if (dlg.exec() == QDialog::Accepted){ + m_passwordJustGenerated = true; + ui->wdgUser->setFieldValue("user_password", dlg.getCurrentPassword()); + }else{ + ui->wdgUser->setFieldValue("user_password", ""); + //wdg_editor->undo(); + } + + }else{ + if (m_passwordJustGenerated) + m_passwordJustGenerated = false; + } + } } void UserSummaryWidget::ws_userEvent(UserEvent event) diff --git a/client/src/editors/UserSummaryWidget.h b/client/src/editors/UserSummaryWidget.h index 3d4193d3..6c4b3355 100644 --- a/client/src/editors/UserSummaryWidget.h +++ b/client/src/editors/UserSummaryWidget.h @@ -21,7 +21,7 @@ class UserSummaryWidget : public DataEditorWidget Q_OBJECT public: - explicit UserSummaryWidget(ComManager* comMan, const TeraData* data = nullptr, QWidget *parent = nullptr); + explicit UserSummaryWidget(ComManager* comMan, const TeraData* data = nullptr, const int &id_project = -1, QWidget *parent = nullptr); ~UserSummaryWidget(); void saveData(bool signal=true); @@ -36,6 +36,11 @@ class UserSummaryWidget : public DataEditorWidget BaseDialog* m_diag_editor; SessionLobbyDialog* m_sessionLobby; + int m_idProject; + + bool m_passwordJustGenerated; + bool m_currentUserPasswordChanged; + void updateControlsState(); void updateFieldsValue(); void initUI(); @@ -43,8 +48,16 @@ class UserSummaryWidget : public DataEditorWidget bool validateData(); private slots: + void processFormsReply(QString form_type, QString data); + void processSiteAccessReply(QList access_list, QUrlQuery reply_query); void processSessionTypesReply(QList session_types); - void ws_userEvent(UserEvent event); + void processUsersReply(QList users); + void processStatsReply(TeraData stats, QUrlQuery reply_query); + + void userFormValueChanged(QWidget* widget, QVariant value); + void userFormValueHasFocus(QWidget* widget); + + void ws_userEvent(opentera::protobuf::UserEvent event); void sessionLobbyStartSessionRequested(); void sessionLobbyStartSessionCancelled(); diff --git a/client/src/editors/UserSummaryWidget.ui b/client/src/editors/UserSummaryWidget.ui index 0608b4a7..bef523b0 100644 --- a/client/src/editors/UserSummaryWidget.ui +++ b/client/src/editors/UserSummaryWidget.ui @@ -53,7 +53,6 @@ color:cyan; - 50 false @@ -200,193 +199,207 @@ color:cyan; 0 + + + 20 + 20 + + :/icons/dashboard.png:/icons/dashboard.png + + Résumé + + + + + + + 0 + 0 + + + + Séances + + + + + + + + + + + + + + :/icons/software_user.png:/icons/software_user.png + Informations - + - Général + Informations - - - 10 - - - - - - 10 - 75 - true - - - - (Dernière connexion) - - - - - + + + 0 0 - - - 10 - 75 - true - - - - (Nom complet) - + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + 0 + 32 + + + + PointingHandCursor + + + Éditer + + + + :/icons/edit.png:/icons/edit.png + + + + 20 + 20 + + + + + - - + + - + 0 0 - - Dernière connexion: - - - - - - - - 0 - 0 - - - - Nom: - - - - - false - - - - 75 - true - - - - PointingHandCursor - - - Actif - - - - - - - - - - Contact - - - - 15 - - - + + - + 0 0 - - Courriel: - - - - - - - - 10 - 75 - true - - - - (Courriel) - - - true - + + + + + + 0 + 32 + + + + PointingHandCursor + + + Sauvegarder + + + + :/icons/save.png:/icons/save.png + + + + 24 + 24 + + + + + + + + true + + + + 0 + 32 + + + + PointingHandCursor + + + Annuler + + + + :/icons/undo.png:/icons/undo.png + + + + 24 + 24 + + + + + - - - - - 0 - 32 - - - - PointingHandCursor - - - Éditer - - - - :/icons/edit.png:/icons/edit.png - - - - 20 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + TeraForm + QWidget +
TeraForm.h
+ 1 +
+ + SessionsListWidget + QWidget +
widgets/SessionsListWidget.h
+ 1 +
+
diff --git a/client/src/editors/UserWidget.cpp b/client/src/editors/UserWidget.cpp index 7f9a556a..0f67d8a4 100644 --- a/client/src/editors/UserWidget.cpp +++ b/client/src/editors/UserWidget.cpp @@ -8,7 +8,6 @@ #include -#include "dialogs/GeneratePasswordDialog.h" #include "dialogs/PasswordStrengthDialog.h" @@ -481,11 +480,6 @@ void UserWidget::processFormsReply(QString form_type, QString data) } // Disable super admin field if not super admin itself! - /*QWidget* item = ui->wdgUser->getWidgetForField("user_superadmin"); - if (item){ - item->setVisible(m_comManager->isCurrentUserSuperAdmin()); - - }*/ if (!m_comManager->isCurrentUserSuperAdmin()) ui->wdgUser->hideField("user_superadmin"); } @@ -513,8 +507,8 @@ void UserWidget::connectSignals() connect(m_comManager, &ComManager::userUserGroupsReceived, this, &UserWidget::processUserUsersGroupsReply); connect(m_comManager, &ComManager::userPreferencesReceived, this, &UserWidget::processUserPrefsReply); connect(m_comManager, &ComManager::postResultsOK, this, &UserWidget::postResultReply); - connect(ui->wdgUser, &TeraForm::widgetValueHasChanged, this, &UserWidget::userFormValueChanged); - connect(ui->wdgUser, &TeraForm::widgetValueHasFocus, this, &UserWidget::userFormValueHasFocus); + connect(ui->wdgUser, &TeraForm::widgetValueHasChanged, this, &UserWidget::userFormValueChanged); + connect(ui->wdgUser, &TeraForm::widgetValueHasFocus, this, &UserWidget::userFormValueHasFocus); } diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index 93bf715e..0887ed1c 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -198,12 +198,14 @@ void MainWindow::showDataEditor(const TeraDataTypes &data_type, const TeraData*d m_data_editor->setLimited(limited); */ - - } if (data_type == TERADATA_USER){ - m_data_editor = new UserSummaryWidget(m_comManager, data); + int id_project = -1; + if (ui->projNavigator->getCurrentItemType() == TERADATA_USER && ui->projNavigator->getCurrentItemId() == data->getId()){ + id_project = ui->projNavigator->getCurrentProjectId(); + } + m_data_editor = new UserSummaryWidget(m_comManager, data, id_project); } if (m_data_editor){ diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 8b5b087e..3611c53a 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -348,6 +348,48 @@ QTreeWidgetItem *ProjectNavigator::getCurrentItem() return ui->treeNavigator->currentItem(); } +TeraDataTypes ProjectNavigator::getCurrentItemType() +{ + QTreeWidgetItem* item = getCurrentItem(); + + if (item){ + if (m_devices_items.key(item, -1) >= 0){ + return TERADATA_DEVICE; + } + if (m_groups_items.key(item, -1) >= 0){ + return TERADATA_GROUP; + } + if (m_participants_items.key(item, -1) >= 0){ + return TERADATA_PARTICIPANT; + } + if (m_users_items.key(item, -1) >= 0){ + return TERADATA_USER; + } + } + return TERADATA_NONE; +} + +int ProjectNavigator::getCurrentItemId() +{ + QTreeWidgetItem* item = getCurrentItem(); + + if (item){ + int id = -1; + id = m_devices_items.key(item, -1); + if (id == -1){ + id = m_groups_items.key(item, -1); + } + if (id == -1){ + id = m_participants_items.key(item, -1); + } + if (id == -1){ + id = m_users_items.key(item, -1); + } + return id; + } + return -1; +} + void ProjectNavigator::setOnHold(const bool &hold) { m_selectionHold = hold; diff --git a/client/src/widgets/ProjectNavigator.h b/client/src/widgets/ProjectNavigator.h index 86d50473..e60a1d5c 100644 --- a/client/src/widgets/ProjectNavigator.h +++ b/client/src/widgets/ProjectNavigator.h @@ -42,6 +42,8 @@ class ProjectNavigator : public QWidget void removeItem(const TeraDataTypes& data_type, const int& id); QTreeWidgetItem* getCurrentItem(); + TeraDataTypes getCurrentItemType(); + int getCurrentItemId(); void setOnHold(const bool& hold); void refreshCurrentItem(); diff --git a/client/src/widgets/SessionsListWidget.cpp b/client/src/widgets/SessionsListWidget.cpp index 486054cf..6945dbd0 100644 --- a/client/src/widgets/SessionsListWidget.cpp +++ b/client/src/widgets/SessionsListWidget.cpp @@ -7,6 +7,9 @@ #include "editors/SessionWidget.h" #include "widgets/TableDateWidgetItem.h" +#include "widgets/AssetsWidget.h" +#include "widgets/TestsWidget.h" + SessionsListWidget::SessionsListWidget(QWidget *parent) : QWidget(parent), ui(new Ui::SessionsListWidget) @@ -722,7 +725,7 @@ void SessionsListWidget::updateSession(const TeraData *session, const bool &auto btnDownload->setIcon(m_downloadIcon); } m_totalAssets += asset_count; - ui->btnAssetsBrowser->setVisible(m_totalAssets>0); + ui->btnAssetsBrowser->setVisible(m_totalAssets>0 && m_viewMode == ViewMode::VIEW_PARTICIPANT_SESSIONS); }else{ //btnDownload->hide(); btnDownload->setEnabled(false); @@ -742,7 +745,7 @@ void SessionsListWidget::updateSession(const TeraData *session, const bool &auto btnTests->setIcon(m_testIcon); } m_totalTests += test_count; - ui->btnTestsBrowser->setVisible(m_totalTests>0); + ui->btnTestsBrowser->setVisible(m_totalTests>0 && m_viewMode == ViewMode::VIEW_PARTICIPANT_SESSIONS); }else{ //btnTests->hide(); btnTests->setEnabled(false); @@ -906,3 +909,71 @@ void SessionsListWidget::on_tableSessions_itemDoubleClicked(QTableWidgetItem *it displaySessionDetails(item); } +void SessionsListWidget::on_btnAssetsBrowser_clicked() +{ + if (m_diag_editor){ + m_diag_editor->deleteLater(); + } + m_diag_editor = new BaseDialog(this); + + AssetsWidget* asset_widget = new AssetsWidget(m_comManager, m_diag_editor); + + switch(m_viewMode){ + case VIEW_NONE: + qWarning() << "None view selected - unable to display assets!"; + break; + case VIEW_PARTICIPANT_SESSIONS: + asset_widget->displayAssetsForParticipant(m_currentId); + break; + case VIEW_USER_SESSIONS: + qWarning() << "Assets display for user not implemented yet!"; + // asset_widget->displayAssetsForUser(m_currentId); + break; + case VIEW_DEVICE_SESSIONS: + qWarning() << "Assets display for device not implemented yet!"; + // asset_widget->displayAssetsForDevice(m_currentId); + break; + } + + m_diag_editor->setCentralWidget(asset_widget); + + m_diag_editor->setWindowTitle(tr("Explorateur de données")); + m_diag_editor->setMinimumSize(3*this->width()/4, 3*this->height()/4); + + m_diag_editor->open(); +} + + +void SessionsListWidget::on_btnTestsBrowser_clicked() +{ + if (m_diag_editor){ + m_diag_editor->deleteLater(); + } + m_diag_editor = new BaseDialog(this); + + TestsWidget* test_widget = new TestsWidget(m_comManager, m_diag_editor); + switch(m_viewMode){ + case VIEW_NONE: + qWarning() << "None view selected - unable to display assets!"; + break; + case VIEW_PARTICIPANT_SESSIONS: + test_widget->displayTestsForParticipant(m_currentId); + break; + case VIEW_USER_SESSIONS: + qWarning() << "Tests display for user not implemented yet!"; + // test_widget->displayTestsForUser(m_currentId); + break; + case VIEW_DEVICE_SESSIONS: + qWarning() << "Tests display for device not implemented yet!"; + // test_widget->displayTestsForDevice(m_currentId); + break; + } + + m_diag_editor->setCentralWidget(test_widget); + + m_diag_editor->setWindowTitle(tr("Explorateur d'évaluations")); + m_diag_editor->setMinimumSize(3*this->width()/4, 3*this->height()/4); + + m_diag_editor->open(); +} + diff --git a/client/src/widgets/SessionsListWidget.h b/client/src/widgets/SessionsListWidget.h index db906d2d..091f19e7 100644 --- a/client/src/widgets/SessionsListWidget.h +++ b/client/src/widgets/SessionsListWidget.h @@ -128,6 +128,10 @@ private slots: void on_tableSessions_itemDoubleClicked(QTableWidgetItem *item); + void on_btnAssetsBrowser_clicked(); + + void on_btnTestsBrowser_clicked(); + signals: void startSessionRequested(int id_session_type, int id_session); void sessionsCountUpdated(int new_total); diff --git a/client/src/widgets/SessionsListWidget.ui b/client/src/widgets/SessionsListWidget.ui index 0d3bf375..543251fa 100644 --- a/client/src/widgets/SessionsListWidget.ui +++ b/client/src/widgets/SessionsListWidget.ui @@ -383,6 +383,12 @@ + + + 0 + 0 + + @@ -525,7 +531,7 @@ 3
- true + false From e98c2e2a533e2b4064d07dbb3eb99c6b075dca77 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 24 Jan 2023 08:04:48 -0500 Subject: [PATCH 19/42] Refs #80. Added login logs viewer for Users --- client/src/editors/UserSummaryWidget.cpp | 14 ++++++++++++++ client/src/editors/UserSummaryWidget.h | 1 + client/src/editors/UserSummaryWidget.ui | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/client/src/editors/UserSummaryWidget.cpp b/client/src/editors/UserSummaryWidget.cpp index cd8fb566..554c02be 100644 --- a/client/src/editors/UserSummaryWidget.cpp +++ b/client/src/editors/UserSummaryWidget.cpp @@ -110,6 +110,11 @@ void UserSummaryWidget::initUI() ui->wdgUser->hideFields({"user_username", "user_notes", "user_superadmin"}); ui->tabNav->setCurrentIndex(0); + + // Access log widget init + ui->wdgLogins->setComManager(m_comManager); + ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_USER, m_data->getUuid()); + ui->wdgLogins->setUuidName(m_data->getUuid(), m_data->getName()); } bool UserSummaryWidget::validateData() @@ -349,3 +354,12 @@ void UserSummaryWidget::on_btnNewSession_clicked() // Show Session Lobby m_sessionLobby->exec(); } + +void UserSummaryWidget::on_tabNav_currentChanged(int index) +{ + Q_UNUSED(index) + if (ui->tabNav->currentWidget() == ui->tabLogs){ + ui->wdgLogins->refreshData(); + } +} + diff --git a/client/src/editors/UserSummaryWidget.h b/client/src/editors/UserSummaryWidget.h index 6c4b3355..b188838a 100644 --- a/client/src/editors/UserSummaryWidget.h +++ b/client/src/editors/UserSummaryWidget.h @@ -64,6 +64,7 @@ private slots: void on_btnNewSession_clicked(); + void on_tabNav_currentChanged(int index); }; #endif // USERSUMMARYWIDGET_H diff --git a/client/src/editors/UserSummaryWidget.ui b/client/src/editors/UserSummaryWidget.ui index bef523b0..ca0537e8 100644 --- a/client/src/editors/UserSummaryWidget.ui +++ b/client/src/editors/UserSummaryWidget.ui @@ -382,6 +382,20 @@ color:cyan; + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + + + + @@ -393,6 +407,12 @@ color:cyan;
TeraForm.h
1 + + LogViewWidget + QWidget +
widgets/LogViewWidget.h
+ 1 +
SessionsListWidget QWidget From a1a905299183d8e3531ca4eab6a323a60692a8e6 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 24 Jan 2023 09:22:00 -0500 Subject: [PATCH 20/42] Refs #63. Added DeviceSummaryWidget to allow to start and view sessions with devices. --- client/src/CMakeLists.txt | 56 +-- client/src/editors/DeviceSummaryWidget.cpp | 290 ++++++++++++++ client/src/editors/DeviceSummaryWidget.h | 62 +++ client/src/editors/DeviceSummaryWidget.ui | 437 +++++++++++++++++++++ client/src/main/MainWindow.cpp | 9 + 5 files changed, 828 insertions(+), 26 deletions(-) create mode 100644 client/src/editors/DeviceSummaryWidget.cpp create mode 100644 client/src/editors/DeviceSummaryWidget.h create mode 100644 client/src/editors/DeviceSummaryWidget.ui diff --git a/client/src/CMakeLists.txt b/client/src/CMakeLists.txt index 4cee32b4..928efcad 100755 --- a/client/src/CMakeLists.txt +++ b/client/src/CMakeLists.txt @@ -47,27 +47,28 @@ set(headers dialogs/CleanUpDialog.h dialogs/FileUploaderDialog.h # Editors - editors/DataEditorWidget.h - editors/UserWidget.h - editors/UserSummaryWidget.h - editors/TeraForm.h + editors/DataEditorWidget.h editors/DataListWidget.h - editors/SiteWidget.h + editors/DeviceSubTypeWidget.h + editors/DeviceSummaryWidget.h + editors/DeviceTypeWidget.h editors/DeviceWidget.h editors/ProjectWidget.h editors/GroupWidget.h editors/ParticipantWidget.h editors/SessionTypeWidget.h editors/SessionWidget.h - editors/DeviceSubTypeWidget.h - editors/DeviceTypeWidget.h - editors/UserGroupWidget.h editors/ServiceWidget.h editors/ServiceConfigWidget.h + editors/SiteWidget.h + editors/TeraForm.h editors/TestTypeWidget.h + editors/UserGroupWidget.h + editors/UserSummaryWidget.h + editors/UserWidget.h # Wizards - wizards/UserWizard.h wizards/BaseWizard.h + wizards/UserWizard.h # Services services/BaseServiceWidget.h services/BaseServiceToolsWidget.h @@ -150,23 +151,25 @@ set(srcs dialogs/FileUploaderDialog.cpp # Editors editors/DataEditorWidget.cpp - editors/UserWidget.cpp - editors/UserSummaryWidget.cpp - editors/TeraForm.cpp - editors/DataListWidget.cpp - editors/SiteWidget.cpp + editors/DataListWidget.cpp editors/DeviceWidget.cpp - editors/ProjectWidget.cpp + editors/DeviceSubTypeWidget.cpp + editors/DeviceSummaryWidget.cpp + editors/DeviceTypeWidget.cpp editors/GroupWidget.cpp editors/ParticipantWidget.cpp + editors/ProjectWidget.cpp editors/SessionTypeWidget.cpp editors/SessionWidget.cpp - editors/DeviceSubTypeWidget.cpp - editors/DeviceTypeWidget.cpp - editors/UserGroupWidget.cpp editors/ServiceWidget.cpp editors/ServiceConfigWidget.cpp + editors/SiteWidget.cpp + editors/TeraForm.cpp editors/TestTypeWidget.cpp + editors/UserGroupWidget.cpp + editors/UserSummaryWidget.cpp + editors/UserWidget.cpp + # Wizards wizards/UserWizard.cpp wizards/BaseWizard.cpp @@ -231,23 +234,24 @@ SET(uis dialogs/CleanUpDialog.ui dialogs/FileUploaderDialog.ui # Editors - editors/UserWidget.ui - editors/UserSummaryWidget.ui - editors/TeraForm.ui editors/DataListWidget.ui - editors/SiteWidget.ui + editors/DeviceSubTypeWidget.ui + editors/DeviceSummaryWidget.ui + editors/DeviceTypeWidget.ui editors/DeviceWidget.ui - editors/ProjectWidget.ui editors/GroupWidget.ui editors/ParticipantWidget.ui + editors/ProjectWidget.ui editors/SessionTypeWidget.ui editors/SessionWidget.ui - editors/DeviceSubTypeWidget.ui - editors/DeviceTypeWidget.ui - editors/UserGroupWidget.ui editors/ServiceWidget.ui editors/ServiceConfigWidget.ui + editors/SiteWidget.ui + editors/TeraForm.ui editors/TestTypeWidget.ui + editors/UserGroupWidget.ui + editors/UserSummaryWidget.ui + editors/UserWidget.ui # Services services/VideoRehabService/VideoRehabWidget.ui services/VideoRehabService/VideoRehabSetupWidget.ui diff --git a/client/src/editors/DeviceSummaryWidget.cpp b/client/src/editors/DeviceSummaryWidget.cpp new file mode 100644 index 00000000..1b468e94 --- /dev/null +++ b/client/src/editors/DeviceSummaryWidget.cpp @@ -0,0 +1,290 @@ +#include "DeviceSummaryWidget.h" +#include "ui_DeviceSummaryWidget.h" + +#include + +DeviceSummaryWidget::DeviceSummaryWidget(ComManager *comMan, const TeraData *data, const int &id_project, QWidget *parent) : + DataEditorWidget(comMan, data, parent), + ui(new Ui::DeviceSummaryWidget) +{ + + m_diag_editor = nullptr; + m_sessionLobby = nullptr; + m_idProject = id_project; + + ui->setupUi(this); + + setAttribute(Qt::WA_StyledBackground); //Required to set a background image + setLimited(true); + + // Use base class to manage editing + setEditorControls(ui->wdgDevice, ui->btnEdit, ui->frameButtons, ui->btnSave, ui->btnUndo); + + // Initialize user interface + initUI(); + + // Connect signals and slots + connectSignals(); + + // Query form definition + QUrlQuery args(WEB_FORMS_QUERY_DEVICE); + if (!dataIsNew()) + args.addQueryItem(WEB_QUERY_ID, QString::number(m_data->getId())); + + queryDataRequest(WEB_FORMS_PATH, args); + + // Query sites related to that device (to check for edit access) + args.clear(); + args.addQueryItem(WEB_QUERY_ID_DEVICE, QString::number(m_data->getId())); + queryDataRequest(WEB_DEVICESITEINFO_PATH, args); + + // Query stats + queryDataRequest(WEB_STATS_PATH, args); // args already contains id_device + + ui->wdgDevice->setComManager(m_comManager); +} + +DeviceSummaryWidget::~DeviceSummaryWidget() +{ + delete ui; + + if (m_sessionLobby) + m_sessionLobby->deleteLater(); +} + +void DeviceSummaryWidget::connectSignals() +{ + connect(m_comManager, &ComManager::formReceived, this, &DeviceSummaryWidget::processFormsReply); + connect(m_comManager, &ComManager::sessionTypesReceived, this, &DeviceSummaryWidget::processSessionTypesReply); + connect(m_comManager, &ComManager::deviceSitesReceived, this, &DeviceSummaryWidget::processSiteAccessReply); + connect(m_comManager, &ComManager::statsReceived, this, &DeviceSummaryWidget::processStatsReply); + + connect(m_comManager->getWebSocketManager(), &WebSocketManager::deviceEventReceived, this, &DeviceSummaryWidget::ws_deviceEvent); + +} + +void DeviceSummaryWidget::updateControlsState() +{ + ui->frameEdit->setVisible(!m_limited); + ui->wdgSessions->enableDeletion(!m_limited); +} + +void DeviceSummaryWidget::updateFieldsValue() +{ + if (m_data){ + ui->lblTitle->setText(m_data->getName()); + + // Status + ui->icoOnline->setVisible(m_data->isEnabled()); + + ui->icoTitle->setPixmap(QPixmap(m_data->getIconStateFilename())); + if (m_data->isOnline() || m_data->isBusy()){ + ui->icoOnline->setPixmap(QPixmap("://status/status_online.png")); + }else{ + ui->icoOnline->setPixmap(QPixmap("://status/status_offline.png")); + } + + bool onlineable = m_data->getFieldValue("device_onlineable").toBool(); + ui->frameNewSession->setVisible(m_data->isEnabled() && !m_data->isNew() && onlineable); + if (!onlineable) + ui->lblInfos->show(); + + if (m_data->hasBusyStateField()) + ui->btnNewSession->setEnabled(!m_data->isBusy()); + + if (ui->wdgDevice->formHasStructure()) + ui->wdgDevice->fillFormFromData(m_data->toJson()); + } +} + +void DeviceSummaryWidget::initUI() +{ + ui->wdgSessions->setComManager(m_comManager); + ui->wdgSessions->setViewMode(SessionsListWidget::VIEW_DEVICE_SESSIONS, m_data->getUuid(), m_data->getId()); + + ui->cmbSessionType->setItemDelegate(new QStyledItemDelegate(ui->cmbSessionType)); + ui->wdgDevice->hideFields(QStringList() << "device_token"); + ui->lblInfos->hide(); + + ui->tabNav->setCurrentIndex(0); + + // Access log widget init + ui->wdgLogins->setComManager(m_comManager); + ui->wdgLogins->setViewMode(LogViewWidget::VIEW_LOGINS_DEVICE, m_data->getUuid()); + ui->wdgLogins->setUuidName(m_data->getUuid(), m_data->getName()); +} + +bool DeviceSummaryWidget::validateData() +{ + return ui->wdgDevice->validateFormData(); +} + +void DeviceSummaryWidget::processFormsReply(QString form_type, QString data) +{ + if (form_type.startsWith(WEB_FORMS_QUERY_DEVICE)){ + if (!ui->wdgDevice->formHasStructure()){ + ui->wdgDevice->buildUiFromStructure(data); + } + return; + } +} + +void DeviceSummaryWidget::processSiteAccessReply(QList access_list, QUrlQuery reply_query) +{ + // Check if the current logged user is admin in at least one of the device site + bool is_admin = false; + for(const TeraData &access: access_list){ + if(m_comManager->isCurrentUserSiteAdmin(access.getFieldValue("id_site").toInt())){ + is_admin = true; + break; + } + } + + setLimited(!is_admin); +} + +void DeviceSummaryWidget::saveData(bool signal) +{ + QJsonDocument device_data = ui->wdgDevice->getFormDataJson(m_data->isNew()); + postDataRequest(WEB_DEVICEINFO_PATH, device_data.toJson()); + + if (signal){ + TeraData* new_data = ui->wdgDevice->getFormDataObject(TERADATA_DEVICE); + *m_data = *new_data; + delete new_data; + emit dataWasChanged(); + } +} + +void DeviceSummaryWidget::processSessionTypesReply(QList session_types) +{ + ui->cmbSessionType->clear(); + + for (const TeraData &st:session_types){ + if (!m_ids_session_types.contains(st.getId())){ + m_ids_session_types[st.getId()] = new TeraData(st); + // New session ComboBox + ui->cmbSessionType->addItem(st.getName(), st.getId()); + }else{ + *m_ids_session_types[st.getId()] = st; + } + } + + // Query sessions + ui->wdgSessions->setSessionTypes(session_types); + +} + +void DeviceSummaryWidget::processDevicesReply(QList devices) +{ + for (int i=0; iupdateFrom(devices.at(i)); + updateFieldsValue(); + break; + } + } +} + +void DeviceSummaryWidget::processStatsReply(TeraData stats, QUrlQuery reply_query) +{ + + // Check if stats are for us + if (!reply_query.hasQueryItem("id_device")) + return; + + if (reply_query.queryItemValue("id_device").toInt() != m_data->getId()) + return; + + // Fill stats information + int total_sessions = stats.getFieldValue("sessions_total_count").toInt(); + ui->wdgSessions->setSessionsCount(total_sessions); + + // Query sessions types + QUrlQuery args; + /* if (m_idProject > 0) + args.addQueryItem(WEB_QUERY_ID_PROJECT, QString::number(m_idProject));*/ + queryDataRequest(WEB_SESSIONTYPE_PATH, args); +} + +void DeviceSummaryWidget::ws_deviceEvent(DeviceEvent event) +{ + if (!m_data) + return; + + if (QString::fromStdString(event.device_uuid()) != m_data->getUuid()) + return; // Not for us! + + if (event.type() == DeviceEvent_EventType_DEVICE_CONNECTED){ + m_data->setOnline(true); + } + if (event.type() == DeviceEvent_EventType_DEVICE_DISCONNECTED){ + m_data->setOnline(false); + } + if (event.type() == DeviceEvent_EventType_DEVICE_JOINED_SESSION){ + m_data->setBusy(true); + } + if (event.type() == DeviceEvent_EventType_DEVICE_LEFT_SESSION){ + m_data->setBusy(false); + } + updateFieldsValue(); +} + +void DeviceSummaryWidget::sessionLobbyStartSessionRequested() +{ + int id_session_type = ui->cmbSessionType->currentData().toInt(); + // Start session + m_comManager->startSession(*m_ids_session_types[id_session_type], + m_sessionLobby->getIdSession(), + m_sessionLobby->getSessionParticipantsUuids(), + m_sessionLobby->getSessionUsersUuids(), + m_sessionLobby->getSessionDevicesUuids(), + m_sessionLobby->getSessionConfig()); + + m_sessionLobby->deleteLater(); + m_sessionLobby = nullptr; +} + +void DeviceSummaryWidget::sessionLobbyStartSessionCancelled() +{ + if (m_sessionLobby){ + m_sessionLobby->deleteLater(); + m_sessionLobby = nullptr; + } +} + +void DeviceSummaryWidget::on_btnNewSession_clicked() +{ + if (ui->cmbSessionType->currentIndex() < 0) + return; + + if (!m_data) + return; + + int id_session_type = ui->cmbSessionType->currentData().toInt(); + + if (m_sessionLobby) + m_sessionLobby->deleteLater(); + + m_sessionLobby = new SessionLobbyDialog(m_comManager, *m_ids_session_types[id_session_type], -1, 0, this); + + // Add current devices to session + m_sessionLobby->addUsersToSession(QList() << m_comManager->getCurrentUser(), QList() << m_comManager->getCurrentUser().getId()); + m_sessionLobby->addDevicesToSession(QList() << *m_data, QList() << m_data->getId()); + + connect(m_sessionLobby, &QDialog::accepted, this, &DeviceSummaryWidget::sessionLobbyStartSessionRequested); + connect(m_sessionLobby, &QDialog::rejected, this, &DeviceSummaryWidget::sessionLobbyStartSessionCancelled); + + // Show Session Lobby + m_sessionLobby->exec(); +} + +void DeviceSummaryWidget::on_tabNav_currentChanged(int index) +{ + Q_UNUSED(index) + if (ui->tabNav->currentWidget() == ui->tabLogs){ + ui->wdgLogins->refreshData(); + } +} + diff --git a/client/src/editors/DeviceSummaryWidget.h b/client/src/editors/DeviceSummaryWidget.h new file mode 100644 index 00000000..edb4d751 --- /dev/null +++ b/client/src/editors/DeviceSummaryWidget.h @@ -0,0 +1,62 @@ +#ifndef DEVICESUMMARYWIDGET_H +#define DEVICESUMMARYWIDGET_H + +#include +#include + +#include "DataEditorWidget.h" +#include "GlobalMessageBox.h" +#include "dialogs/BaseDialog.h" +#include "TeraSessionStatus.h" +#include "Utils.h" +#include "ServiceConfigWidget.h" +#include "dialogs/SessionLobbyDialog.h" + +namespace Ui { +class DeviceSummaryWidget; +} + +class DeviceSummaryWidget : public DataEditorWidget +{ + Q_OBJECT + +public: + explicit DeviceSummaryWidget(ComManager* comMan, const TeraData* data = nullptr, const int &id_project = -1, QWidget *parent = nullptr); + ~DeviceSummaryWidget(); + + void saveData(bool signal=true); + void connectSignals(); + +private: + Ui::DeviceSummaryWidget *ui; + + QMap m_ids_session_types; + + BaseDialog* m_diag_editor; + SessionLobbyDialog* m_sessionLobby; + + int m_idProject; + + void updateControlsState(); + void updateFieldsValue(); + void initUI(); + + bool validateData(); + +private slots: + void processFormsReply(QString form_type, QString data); + void processSiteAccessReply(QList access_list, QUrlQuery reply_query); + void processSessionTypesReply(QList session_types); + void processDevicesReply(QList devices); + void processStatsReply(TeraData stats, QUrlQuery reply_query); + + void ws_deviceEvent(opentera::protobuf::DeviceEvent event); + + void sessionLobbyStartSessionRequested(); + void sessionLobbyStartSessionCancelled(); + + void on_btnNewSession_clicked(); + void on_tabNav_currentChanged(int index); +}; + +#endif // DEVICESUMMARYWIDGET_H diff --git a/client/src/editors/DeviceSummaryWidget.ui b/client/src/editors/DeviceSummaryWidget.ui new file mode 100644 index 00000000..a8c569ef --- /dev/null +++ b/client/src/editors/DeviceSummaryWidget.ui @@ -0,0 +1,437 @@ + + + DeviceSummaryWidget + + + + 0 + 0 + 949 + 831 + + + + Form + + + QLabel#lblFullNameValue, QLabel#lblLastOnlineValue, QLabel#lblEmailValue{ +color:cyan; +} + + + + 10 + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + + + + :/icons/device.png + + + true + + + + + + + + false + + + + Appareil + + + + + + + + 0 + 0 + + + + + 32 + 32 + + + + + + + :/status/status_unknown.png + + + true + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + 0 + 0 + + + + + 0 + 40 + + + + + 400 + 16777215 + + + + + + + + true + + + + 0 + 0 + + + + + 125 + 40 + + + + + 16777215 + 16777215 + + + + PointingHandCursor + + + Nouvelle Séance + + + + :/icons/new.png:/icons/new.png + + + + 24 + 24 + + + + false + + + + + + + + + + Cet appareil ne peut être mis en ligne - impossible de réaliser une nouvelle séance avec celui-ci. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + + 20 + 20 + + + + + + :/icons/dashboard.png:/icons/dashboard.png + + + Résumé + + + + + + + 0 + 0 + + + + Séances + + + + + + + + + + + + + + :/icons/device.png:/icons/device.png + + + Informations + + + + + + Informations + + + + + + + 0 + 0 + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + 0 + 32 + + + + PointingHandCursor + + + Éditer + + + + :/icons/edit.png:/icons/edit.png + + + + 20 + 20 + + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + 0 + 32 + + + + PointingHandCursor + + + Sauvegarder + + + + :/icons/save.png:/icons/save.png + + + + 24 + 24 + + + + + + + + true + + + + 0 + 32 + + + + PointingHandCursor + + + Annuler + + + + :/icons/undo.png:/icons/undo.png + + + + 24 + 24 + + + + + + + + + + + + + + + + :/icons/password.png:/icons/password.png + + + Journal d'accès + + + + + + + + + + + + + + TeraForm + QWidget +
TeraForm.h
+ 1 +
+ + LogViewWidget + QWidget +
widgets/LogViewWidget.h
+ 1 +
+ + SessionsListWidget + QWidget +
widgets/SessionsListWidget.h
+ 1 +
+
+ + + + +
diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index 0887ed1c..0f93db15 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -11,6 +11,7 @@ #include "editors/GroupWidget.h" #include "editors/ParticipantWidget.h" #include "editors/UserSummaryWidget.h" +#include "editors/DeviceSummaryWidget.h" MainWindow::MainWindow(ComManager *com_manager, const QString ¤t_server, QWidget *parent) : @@ -208,6 +209,14 @@ void MainWindow::showDataEditor(const TeraDataTypes &data_type, const TeraData*d m_data_editor = new UserSummaryWidget(m_comManager, data, id_project); } + if (data_type == TERADATA_DEVICE){ + int id_project = -1; + if (ui->projNavigator->getCurrentItemType() == TERADATA_DEVICE && ui->projNavigator->getCurrentItemId() == data->getId()){ + id_project = ui->projNavigator->getCurrentProjectId(); + } + m_data_editor = new DeviceSummaryWidget(m_comManager, data, id_project); + } + if (m_data_editor){ ui->wdgMainTop->layout()->addWidget(m_data_editor); connect(m_data_editor, &DataEditorWidget::dataWasDeleted, this, &MainWindow::dataEditorCancelled); From ed23a8634979b7a52ec94a9460f5848b05c03622 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 25 Jan 2023 11:44:31 -0500 Subject: [PATCH 21/42] Refs #82. Added more warnings when recording / downloading recorded file. Added ability to select save folder. --- client/resources/stylesheet.qss | 5 + .../resources/translations/openteraplus_en.ts | 1254 ++++++++++------ .../resources/translations/openteraplus_fr.ts | 1314 ++++++++++------- client/src/main/MainWindow.cpp | 14 + client/src/main/MainWindow.h | 6 +- .../src/services/BaseServiceToolsWidget.cpp | 3 +- client/src/services/BaseServiceToolsWidget.h | 2 +- client/src/services/BaseServiceWidget.cpp | 5 + client/src/services/BaseServiceWidget.h | 2 + .../VideoRehabToolsWidget.cpp | 25 +- .../VideoRehabService/VideoRehabToolsWidget.h | 2 +- .../VideoRehabService/VideoRehabWidget.cpp | 34 +- .../VideoRehabService/VideoRehabWidget.h | 2 + client/src/widgets/InSessionWidget.cpp | 84 +- client/src/widgets/InSessionWidget.h | 11 +- client/src/widgets/InSessionWidget.ui | 106 +- client/src/widgets/SessionsListWidget.cpp | 5 +- shared/src/data/TeraSettings.h | 1 + 18 files changed, 1839 insertions(+), 1036 deletions(-) diff --git a/client/resources/stylesheet.qss b/client/resources/stylesheet.qss index 293471bb..f1ca8d06 100644 --- a/client/resources/stylesheet.qss +++ b/client/resources/stylesheet.qss @@ -815,3 +815,8 @@ QTreeWidget#treeAssets { selection-color: lightblue; } +/* Customizations for InSessionWidget */ +QPushButton#btnDefaultPath, QPushButton#btnBrowseSavePath{ + max-width: 80px; + min-width: 80px; +} diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 03fb0405..710bee15 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -200,8 +200,8 @@ BaseComManager - - + + Impossible de créer la requête Unable to create request @@ -518,22 +518,22 @@ Unknown error - + Form Form - + Paramètres - + Événements Events - + Journal d'accès @@ -1009,6 +1009,65 @@ dissociating Devices + + DeviceSummaryWidget + + + Form + Form + + + + Appareil + Device + + + + Nouvelle Séance + New Session + + + + Cet appareil ne peut être mis en ligne - impossible de réaliser une nouvelle séance avec celui-ci. + + + + + Séances + Sessions + + + + Résumé + Summary + + + + + Informations + + + + + Éditer + Edit + + + + Sauvegarder + Save + + + + Annuler + Cancel + + + + Journal d'accès + + + DeviceTypeWidget @@ -1051,98 +1110,104 @@ dissociating DeviceWidget - + Sites / Projets associés Associated Sites / Projects - + + + Journal d'accès + + + + Attention Warning - + Aucun site / project n'a été spécifié. Vous devez spécifier au moins un site / projet No site / project was selected. At least on site / project must be selected - + Suppression de site/projet associé Associated site/project deletion - + Au moins un site ou un projet a été retiré de cet appareil. S'il y a des projets qui utilisent cet appareil, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? At least one site or project was removed from this device. If some projects are using this device, they won't be able to use it anymore. Are you sure you want to continue? - + Retirer l'appareil Remove Device - + Êtes-vous sûrs de vouloir retirer cet appareil de ce participant? Si l'appareil est présentement déployé, les données ne seront plus collectés et l'appareil ne sera plus utilisable pendant les séances. Are you sure you want to remove this device from the participant? If the device is currently deployed, the data will not be collected anymore and the device will not be usable during sessions. - + Form Form - + Appareil Device - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + Informations Informations - + Mettre à jour les sites / projets associés à cet appareil Update sites / projects associated to this device - + Sites / Projets Sites / Projects - + Retirer cet appareil de ce participant Remove this device from the participant - + Participants Participants - + Configuration Configuration @@ -1409,92 +1474,92 @@ or Completed Are you sure you want to delete all selected participants? - + Form Form - + Groupe participant Participant Group - + Démarrer Séance Start Session - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + XXXX Séances XXXX Sessions - + XXXX Participants XXXX Participants - + Participant Participant - + État State - + Séances Sessions - + Première séance First Session - + Dernière connexion Last connection - + Nouveau participant New participant - + Supprimer Delete - + Informations Informations - + Dernière séance Last session - + Résumé Summary @@ -1502,88 +1567,88 @@ or Completed InSessionWidget - + Démarrage de séance en cours... Session starting... - + Délai expiré Timeout - + L'opération n'a pu être complétée. Veuillez réessayer à nouveau. The operation did not succeed. Please try again. - + Terminer la séance? Terminate session? - + Mettre fin à la séance? Terminate session? - + Arrêt de la séance en cours... Stopping session... - + En attente de démarrage de séance... Waiting for the session to start... - + Déjà en séance Already in session - + vous a invité dans une séance, mais nous avons refusé l'invitation pour vous. You are invited to a session, but we denied the invitation for you. - + n'a pas répondu à did not respond to - + a refusé refused - + est occupé et ne peut répondre à is busy and cannot answer to - + l'invitation. the invitation. - + Raison: Reason: - + Service non-supporté Unsupported service - + Le service " The service " - - + + " n'est pas gérée par cette version du logiciel. Veuillez vérifier si une mise à jour existe ou contribuez au développement du logiciel! @@ -1591,71 +1656,95 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du Please update the software or contribute to the development! - + Catégorie de séance non-supportée Session category is not supported - + La catégorie de séance " The session category " - + Quitter la séance? Quit session? - + Désirez-vous quitter la séance? Do you want to qui the session? - + + Répertoire où les données seront enregistrées + + + + Form Form - + Gestion Admin - + Quitter Quit - + Terminer Close - + Invités Attendees + + + Données + + + + + Répertoire d'enregistrement + + + + + Défaut + + + + + Parcourir... + Browse... + Inivités Attendees - Paramètres - séance - Session - Parameters + Session - Parameters - + Paramètres Parameters - + Séance inconnue Unknown session - + 00:00:00 00:00:00 @@ -1979,165 +2068,232 @@ Please update the software or contribute to the development! Form - + Log Viewer - + Début Start - - + + dd-MM-yyyy - + Fin End - + + Aucun résultat n'est disponible. + + + + xxx - + événements - + <<< - + / 1 - + >>> - - - + + + Type Type - + Filtrer Filter - + Rafraichir Refresh - + Date Date - + Heure Time - - - + + + Message - + + + Inconnu Unknown - + Trace - + Qui - + Appareil Device - + Participant Participant - + Utilisateur User - + État State - + Système - + Client - + + Ressource + + + + Tous All - + Debug - + Information Information - + Avertissement Warning - + Critique - + Erreur Error - + Fatal + + + Jeton + + + + + Mot de passe + Password + + + + Certificat + + + + + Succès + + + + + Mauvais mot de passe + + + + + Mauvais code utilisateur + + + + + Nombre maximum d'essais atteint + + + + + Déjà connecté + + + + + Jeton invalide + + + + + Compte désactivé + + + + + Jeton expiré + + LoginDialog @@ -2338,59 +2494,59 @@ Please update the software or contribute to the development! MainWindow - + Erreur HTTP HTTP Error - + Erreur Error - + Suppression impossible Cannot delete - + Données sauvegardées. Data saved. - + Données supprimées. Data deleted. - + - mise à jour... - updating... - + Récupération de Retreiving - + - suppression... - deleting... - + Erreur de séance Session error - + Une erreur est survenue: The following error occured: - + La séance ne peut pas continuer. @@ -2399,66 +2555,81 @@ La séance ne peut pas continuer. The session cannot continue. - - + + est en ligne. is online. - - + + est hors-ligne. is offline. - + Erreur de serveur. Server error. - - + + Déconnexion Disconnect - + Vous serez déconnecté du logiciel. Toute donnée non enregistrée sera perdue. You'll be logged out. Unsaved data will be lost. - + Souhaitez-vous continuer? Do you want to continue? - + Votre compte You Account - + Configuration Globale Global Configuration - + Détails Details - + + Séance en cours + + + + + La séance en cours empêche la fermeture du logiciel. + + + + + Veuillez la terminer avant de poursuivre. + + + + Changement de langue Change language - + En ligne ( Online ( - + La langue a été modifiée. Souhaitez-vous vous déconnecter pour appliquer les changements? The language has been modified. @@ -2619,81 +2790,66 @@ Would you like to disconnect to apply the changes? ParticipantWidget - Ouvrir - Open + Open - - Supprimer - Delete + Delete - Continuer la séance - Continue session + Continue session - Appareil: - Device: + Device: - Participant: - Participant: + Participant: - Service: - Service: + Service: - Inconnu - Unknown + Unknown - - Suppression? - Delete? + Delete? - Êtes-vous sûrs de vouloir supprimer - Are you sure you want to delete + Are you sure you want to delete - Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? - Are you sure you want to delete all selected sessions ? + Are you sure you want to delete all selected sessions ? - + Déassignation? Unassign? - + Êtes-vous sûrs de vouloir désassigner Are you sure you want to unassign - - - Séance - Session + Session - + Confirmation Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2706,97 +2862,104 @@ If a the link is re-activated, you will need the newly generated URL to the part Do you want to continue? - + Code utilisateur manquant<br/> Username missing<br/> - Nouvelle séance - New session + New session - + Participant désactivé Participant disabled - + Participant en séance Participant already in session - + Le participant n'a pas d'accès (web ou identification) Participant doesn't have any access (web or by identification) - + Aucun type de séance associé au projet No session type associated to project - + Ce type de séance n'est pas supporté dans cette version Unsupported session type in that version - + Type de séance inconnu Unknown session type - Voir les données - View assets + View assets - Voir les évaluations - View tests + View tests + + + + Impossible de démarrer cette séance + + + + + Impossible de démarrer cette séance. + - + Aucun mot de passe spécifié. No specified password. - + Informations manquantes Missing information - + Les informations suivantes sont incorrectes: The following information is missing: - + existe déjà. already exists. - + a été réalisée récemment et n'a pas été terminée. has been realised lately but not finished. - + a été planifiée. has been planned. - + Reprendre une séance? Resume session? - + Un séance de ce type, A session of the type, - + Souhaitez-vous continuer cette séance? @@ -2813,306 +2976,293 @@ Would you like to continue this session? Participant doesn't have any access (web or login). - - Explorateur de données - Assets explorer + Assets explorer - - Explorateur d'évaluations - Tests explorer + Tests explorer - + Code QR du lien QR code for the link - + Form Form - + + Participant Participant - + Actif Active - + Accès via lien web Web link - + Aucun lien n'a été généré No weblink generated - - + + Copier le lien Copy link - - - - - - + + ... ... - + Envoyer par courriel Send via email - + Démarrer Séance Start Session - + Générer code QR Generate QR code - + Envoyer le lien par courriel Send by email - + Afficher le lien Show link - + Accès via identification Username/password login - - + + Code utilisateur Username - - + + Mot de passe Password - + Générer mot de passe aléatoire Generate random password - - - + + + Sauvegarder Save - - + + Configuration + Configuration + + + + Informations Informations - Chargement des séances: %p% - Loading sessions: %p% + Loading sessions: %p% - Mois 1 - Month 1 + Month 1 - Mois 2 - Month 2 + Month 2 - Mois 3 - Month 3 + Month 3 - Tout cocher - Select All + Select All - Tout décocher - Deselect All + Deselect All - Date - Date + Date - Type - Type + Type - État - State + State - Durée - Duration + Duration - Responsable - Owner + Owner - Actions - Actions + Actions - Nouvelle - New + New - + Résumé Summary - + Paramètres Settings - Services - Services + Services - - - + + Séances Sessions - + Éditer Edit - + Annuler Cancel - + + Journal d'accès + + + + Appareil(s) assigné(s) Assigned Device(s) - + <<< Ajouter <<< Add - + Retirer >>> Remove >>> - + Appareils disponibles Available devices - + Appareils Devices - Filtrer les séances - Filter sessions + Filter sessions PasswordStrengthDialog - - + + Mot de passe Password - + Confirmation Confirmation - + Générer mot de passe Generate password - + ... ... - + Longueur minimale de 10 caractères Minimal length: 10 characters - + Au moins une lettre minuscule At least one lowercase letter - + Au moins une lettre majuscule At least one uppercase letter - + Au moins un chiffre At least one digit - + Au moins un caractère spécial At least one special character - + Appliquer Apply - + Annuler Cancel @@ -3135,59 +3285,84 @@ Would you like to continue this session? Participant - + + Participants + Participants + + + + Utilisateurs + Users + + + + Appareils + Devices + + + Suppression? Delete? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete - + Form Form - + Voir le site View site - - - + + + ... ... - + Tableau de bord Dashboard - + Recherche... Find... - + Nouveau New - + Supprimer Delete - + + Vue étendue + + + + + Avancé + + + + Afficher / masquer les participants inactifs Show / hide inactive participants - + Rafraichir Refresh @@ -3307,170 +3482,170 @@ Do you want to continue? Inactive - + Appareils Devices - + Form Form - + Projet Project - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + XXXX Séances XXXX Sessions - + XXXX Groupes XXXX Groups - + XXXX Participants XXXX Participants - + XXXX Utilisateurs XXXX Users - + Participant Participant - + État State - + Séances Sessions - + Première séance First Session - + Dernière séance Last Session - + Nouveau participant New participant - + Nouveau groupe New group - + Supprimer Delete - + Informations Informations - + Gérer les utilisateurs Manage users - + Gérer les groupes utilisateurs Manage Users Groups - + Groupes utilisateurs User groups - + Mettre à jour les types de séances associés Update associated session types - + Types de séances Session types - + Mettre à jour les appareils associés Update associated devices - + Dernière connexion Last connection - + Résumé Summary - + Utilisateur User + - Rôle Role - - + + Utilisateurs Users - + Groupe Utilisateur User Group - + La modification des accès est désactivée pour les groupes utilisateurs dont l'accès au projet provient du site (i.e. administrateurs du site associé au projet) Access modification is disabled for users groups which project access is specified in the site (i.e. project's administrators) - + Mettre à jour les rôles Update roles @@ -3480,13 +3655,13 @@ Do you want to continue? Users Groups - + Mettre à jour les services associés Updated associated services - - + + Services Services @@ -4024,6 +4199,178 @@ You must select at least one site. Events + + SessionsListWidget + + + + Suppression? + + + + + Êtes-vous sûrs de vouloir supprimer + Are you sure you want to delete + + + + Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? + Are you sure you want to delete all selected sessions ? + + + + Nouvelle séance + New session + + + + + + Séance + Session + + + + Ouvrir + Open + + + + + Supprimer + Delete + + + + Voir les données + View assets + + + + Voir les évaluations + View tests + + + + Continuer la séance + Continue session + + + + Appareil: + Device: + + + + Participant: + Participant: + + + + Service: + Service: + + + + Inconnu + Unknown + + + + + Explorateur de données + Assets explorer + + + + + Explorateur d'évaluations + Tests explorer + + + + Form + Form + + + + Chargement des séances: %p% + Loading sessions: %p% + + + + + + + ... + ... + + + + Mois 1 + Month 1 + + + + Mois 2 + Month 2 + + + + Mois 3 + Month 3 + + + + Tout cocher + Select All + + + + Tout décocher + Deselect All + + + + Date + Date + + + + Type + Type + + + + État + State + + + + Durée + Duration + + + + Responsable + Owner + + + + Actions + Actions + + + + Filtrer les séances + Filter sessions + + + + Nouvelle + New + + SiteWidget @@ -4094,7 +4441,7 @@ Do you want to continue? - + Types de séances Session types @@ -4131,149 +4478,149 @@ realized sessions Users Groups - + Form Form - + Site Site - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + XXXX Groupes XXXX Groups - + Gestion des types de séances Session types management - + Mettre à jour les types de séances associés Update associated session types - + Appareil à rechercher Device to search - + Gestion des appareils Devices management - + Mettre à jour les appareils associés Update associated devices - + Mettre à jour les services associés Updated associated services - + Services Services - + XXX Appareils XXXX Devices - + XXXX Projets XXXX Projects - + XXXX Utilisateurs XXXX Users - + XXXX Séances XXXX Sessions - + XXXX Participants XXXX Participants - + Informations Informations - + Gestion des groupes utilisateurs User groups management - + Mettre à jour les rôles des groupes utilisateurs Update user groups access - + Résumé Summary - - + + Utilisateurs Users - + <html><head/><body><p>Il n'est pas possible de spécifier &quot;Aucun rôle&quot; aux groupes utilisateurs qui ont au moins un accès (Administrateur ou Utilisateur) à un projet du groupe.</p></body></html> <html><head/><body><p>You cannot specify &quot;Any Role&quot; to users groups that have (Administrator or User) access to a project.</p></body></html> - + Groupe utilisateur User Group - + Rôle Role - + Hérité? Inherited? - + Groupes utilisateurs Users Groups - + Appareils Devices @@ -4497,17 +4844,17 @@ realized sessions TeraForm - + Choisir la couleur Chose a color - + Form Form - + Ce formulaire ne contient aucune information. This form does not contain any information. @@ -5084,77 +5431,93 @@ You must select at least one site. UserSummaryWidget - N/D - N/A + N/A - + Form Form - + Utilisateur User - + Nouvelle Séance New Session - + + Séances + Sessions + + + + Résumé + Summary + + + + Sauvegarder + Save + + + + Annuler + Cancel + + + + Journal d'accès + + + Général - General + General - (Dernière connexion) - (Last connection) + (Last connection) - (Nom complet) - (Complete name) + (Complete name) - Dernière connexion: - Last connection: + Last connection: - Nom: - Name: + Name: - Actif - Active + Active - Contact - Contact + Contact - Courriel: - Email: + Email: - (Courriel) - (Email) + (Email) - + Éditer Edit - + + Informations Information @@ -5162,132 +5525,138 @@ You must select at least one site. UserWidget - + Groupes utilisateurs User's groups - + + + Journal d'accès + + + + Langue de l'interface Application language - + Français French - + Anglais English - + Sons lors des notifications Notification sounds - + Attention Warning - + Aucun groupe utilisateur n'a été spécifié. Vous devez spécifier au moins un groupe utilisateur You should specify at least one user group - - + + Utilisateur User - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + Informations Informations - + Mettre à jour les groupes de cet utilisateur Update this user groups - + Groupes Groups - + Rôles effectifs Effective roles - + Sites Sites - - + + Site Site - + Projets Projects - + Projet Project - - + + Rôle Role - + <html><head/><body><p>Cet utilisateur est un super administrateur.</p><p>Il est donc impossible de lui assigner des groupes utiilsateurs.</p></body></html> <html><head/><body><p>This user is a super administrator.</p><p>It is impossible to add user groups to a super administrator.</p></body></html> - + Rôles Roles - + Mettre à jour les préférences Update Preferences - - + + Préférences Preferences - + Configuration Configuration @@ -5461,57 +5830,68 @@ Vous devez spécifier au moins un groupe utilisateur Reconnect - - + + + Enregistrement en cours Recording - - + + Un enregistrement de la séance est en cours. A recording of the session is in progress. - - + + Si vous continuez, l'enregistrement pourrait être perdu. If you continue, the recording might be lost. - - + + Êtes-vous sûrs de vouloir continuer? Do you want to continue? - + + Êtes-vous vraiment sûrs de vouloir arrêter l'enregistrement en cours? + + + + Enregistrer Save - + + Sauvegarde du fichier... + + + + Vous vous apprêtez à enregistrer localement une séance vidéo. You are about to locally record a video session. - + En acceptant de poursuivre avec cet enregistrement, vous acceptez la responsabilité professionnelle, légale et éthique en lien avec la conservation, la diffusion, l'utilisation et la confidentialité requise avec ce type de média. If you accept and proceed with this recording, you accept the professional, legal and ethic responsability related to the storage, distribution, use and confidentiality required with that kind of media. - + Souhaitez-vous toujours activer l'enregistrement vidéo? Do you still want to start video recording? - + Confirmation requise Required confirmation - + Arrêter l'enregistrement Stop recording @@ -5603,37 +5983,37 @@ Vous devez spécifier au moins un groupe utilisateur Connecting... - + Fichier disponible File available - + Le fichier File - + est disponible dans le répertoire is available in the folder - + Impossible de charger la page Unable to load page - + Problème vidéo Video Problem - + Problème audio Audio Problem - + Erreur Error diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index a410df72..530a5184 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -200,8 +200,8 @@ BaseComManager - - + + Impossible de créer la requête @@ -518,22 +518,22 @@ - + Form - + Paramètres - + Événements - + Journal d'accès @@ -1002,6 +1002,65 @@ désassocier + + DeviceSummaryWidget + + + Form + + + + + Appareil + + + + + Nouvelle Séance + + + + + Cet appareil ne peut être mis en ligne - impossible de réaliser une nouvelle séance avec celui-ci. + + + + + Séances + + + + + Résumé + + + + + + Informations + + + + + Éditer + + + + + Sauvegarder + + + + + Annuler + + + + + Journal d'accès + + + DeviceTypeWidget @@ -1043,95 +1102,101 @@ désassocier DeviceWidget - + Sites / Projets associés - + + + Journal d'accès + + + + Attention - + Aucun site / project n'a été spécifié. Vous devez spécifier au moins un site / projet - + Suppression de site/projet associé - + Au moins un site ou un projet a été retiré de cet appareil. S'il y a des projets qui utilisent cet appareil, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? - + Retirer l'appareil - + Êtes-vous sûrs de vouloir retirer cet appareil de ce participant? Si l'appareil est présentement déployé, les données ne seront plus collectés et l'appareil ne sera plus utilisable pendant les séances. - + Form - + Appareil - + Éditer - + Sauvegarder - + Annuler - + Informations - + Mettre à jour les sites / projets associés à cet appareil - + Sites / Projets - + Retirer cet appareil de ce participant - + Participants - + Configuration @@ -1383,92 +1448,92 @@ ou réalisées - + Form - + Groupe participant - + Démarrer Séance - + Éditer - + Sauvegarder - + Annuler - + XXXX Séances - + XXXX Participants - + Participant - + État - + Séances - + Première séance - + Dernière connexion - + Nouveau participant - + Supprimer - + Informations - + Dernière séance - + Résumé @@ -1476,155 +1541,175 @@ ou réalisées InSessionWidget - + Démarrage de séance en cours... - + Délai expiré - + L'opération n'a pu être complétée. Veuillez réessayer à nouveau. - + Terminer la séance? - + Mettre fin à la séance? - + Arrêt de la séance en cours... - + En attente de démarrage de séance... - + Déjà en séance - + vous a invité dans une séance, mais nous avons refusé l'invitation pour vous. - + n'a pas répondu à - + a refusé - + est occupé et ne peut répondre à - + l'invitation. - + Raison: - + Service non-supporté - + Le service " - - + + " n'est pas gérée par cette version du logiciel. Veuillez vérifier si une mise à jour existe ou contribuez au développement du logiciel! - + Catégorie de séance non-supportée - + La catégorie de séance " - + Quitter la séance? - + Désirez-vous quitter la séance? - + + Répertoire où les données seront enregistrées + + + + Form - + Gestion - + Quitter - + Terminer - + Invités - - Paramètres - séance + + Données + + + + + Répertoire d'enregistrement + + + + + Défaut + + + + + Parcourir... - + Paramètres - + Séance inconnue - + 00:00:00 @@ -1943,165 +2028,232 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du LogViewWidget - + Log Viewer - + Début - - + + dd-MM-yyyy - + Fin - + + Aucun résultat n'est disponible. + + + + xxx - + événements - + <<< - + / 1 - + >>> - - - + + + Type - + Filtrer - + Rafraichir - + Date - + Heure - - - + + + Message - + + + Inconnu - + Trace - + Qui - + Appareil - + Participant - + Utilisateur - + État - + Système - + Client - + + Ressource + + + + Tous - + Debug - + Information - + Avertissement - + Critique - + Erreur - + Fatal + + + Jeton + + + + + Mot de passe + + + + + Certificat + + + + + Succès + + + + + Mauvais mot de passe + + + + + Mauvais code utilisateur + + + + + Nombre maximum d'essais atteint + + + + + Déjà connecté + + + + + Jeton invalide + + + + + Compte désactivé + + + + + Jeton expiré + + LoginDialog @@ -2302,124 +2454,139 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du MainWindow - + Erreur HTTP - + Erreur - + Suppression impossible - + Données sauvegardées. - + Données supprimées. - + - mise à jour... - + Récupération de - + - suppression... - + Erreur de séance - + Une erreur est survenue: - + La séance ne peut pas continuer. - - + + est en ligne. - - + + est hors-ligne. - + Erreur de serveur. - - + + Déconnexion - + Vous serez déconnecté du logiciel. Toute donnée non enregistrée sera perdue. - + Souhaitez-vous continuer? - + Votre compte - + Configuration Globale - + Détails - + + Séance en cours + + + + + La séance en cours empêche la fermeture du logiciel. + + + + + Veuillez la terminer avant de poursuivre. + + + + Changement de langue - + En ligne ( - + La langue a été modifiée. Souhaitez-vous vous déconnecter pour appliquer les changements? @@ -2579,126 +2746,62 @@ Souhaitez-vous vous déconnecter pour appliquer les changements? ParticipantWidget - - - Supprimer - - - - - - Suppression? - - - - - Êtes-vous sûrs de vouloir supprimer - - - - - Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? - - - - + Déassignation? - + Êtes-vous sûrs de vouloir désassigner - - Nouvelle séance - - - - + Participant désactivé - + Participant en séance - + Le participant n'a pas d'accès (web ou identification) - + Aucun type de séance associé au projet - + Ce type de séance n'est pas supporté dans cette version - + Type de séance inconnu - - - - Séance - - - - - Ouvrir - - - - - Voir les données - - - - - Voir les évaluations - - - - - Continuer la séance - - - - - Appareil: - - - - - Participant: - - - - - Service: + + Impossible de démarrer cette séance - - Inconnu + + Impossible de démarrer cette séance. - + Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2707,358 +2810,277 @@ Souhaitez-vous continuer? - + Code utilisateur manquant<br/> - + Aucun mot de passe spécifié. - + Informations manquantes - + Les informations suivantes sont incorrectes: - + existe déjà. - + a été réalisée récemment et n'a pas été terminée. - + a été planifiée. - + Reprendre une séance? - + Un séance de ce type, - + Souhaitez-vous continuer cette séance? - - - Explorateur de données - - - - - - Explorateur d'évaluations - - - - + Code QR du lien - + Form - + + Participant - + Actif - + Accès via lien web - + Aucun lien n'a été généré - - + + Copier le lien - - - - - - + + ... - + Envoyer par courriel - + Démarrer Séance - + Générer code QR - + Envoyer le lien par courriel - + Afficher le lien - + Accès via identification - - + + Code utilisateur - - + + Mot de passe - + Générer mot de passe aléatoire - - - + + + Sauvegarder - - - Informations + + Configuration - - Chargement des séances: %p% + + + Informations - - Mois 1 + + Résumé - - Mois 2 + + Paramètres - - Mois 3 + + + Séances - - Tout cocher + + Éditer - - Tout décocher + + Annuler - - Date + + Journal d'accès - - Type + + Appareil(s) assigné(s) - - État + + <<< Ajouter - - Durée + + Retirer >>> - - Responsable + + Appareils disponibles - - Actions + + Appareils + + + PasswordStrengthDialog - - Nouvelle + + + Mot de passe - - Résumé + + Confirmation - - Paramètres + + Générer mot de passe - - Services + + ... - - - - Séances - - - - - Éditer - - - - - Annuler - - - - - Appareil(s) assigné(s) - - - - - <<< Ajouter - - - - - Retirer >>> - - - - - Appareils disponibles - - - - - Appareils - - - - - Filtrer les séances - - - - - PasswordStrengthDialog - - - - Mot de passe - - - - - Confirmation - - - - - Générer mot de passe - - - - - ... - - - - + Longueur minimale de 10 caractères - + Au moins une lettre minuscule - + Au moins une lettre majuscule - + Au moins un chiffre - + Au moins un caractère spécial - + Appliquer - + Annuler @@ -3081,59 +3103,84 @@ Souhaitez-vous continuer cette séance? - + + Participants + + + + + Utilisateurs + + + + + Appareils + + + + Suppression? - + Êtes-vous sûrs de vouloir supprimer - + Form - + Voir le site - - - + + + ... - + Tableau de bord - + Recherche... - + Nouveau - + Supprimer - + + Vue étendue + + + + + Avancé + + + + Afficher / masquer les participants inactifs - + Rafraichir @@ -3249,170 +3296,170 @@ Souhaitez-vous continuer? - + Appareils - + Form - + Projet - + Éditer - + Sauvegarder - + Annuler - + XXXX Séances - + XXXX Groupes - + XXXX Participants - + XXXX Utilisateurs - + Participant - + État - + Séances - + Première séance - + Dernière séance - + Dernière connexion - + Résumé - + Nouveau participant - + Nouveau groupe - + Supprimer - + Informations - + Gérer les utilisateurs - + Groupes utilisateurs - + Mettre à jour les types de séances associés - + Types de séances - + Mettre à jour les appareils associés - + Utilisateur + - Rôle - - + + Utilisateurs - + Gérer les groupes utilisateurs - + Groupe Utilisateur - + La modification des accès est désactivée pour les groupes utilisateurs dont l'accès au projet provient du site (i.e. administrateurs du site associé au projet) - + Mettre à jour les rôles @@ -3422,13 +3469,13 @@ Souhaitez-vous continuer? - + Mettre à jour les services associés - - + + Services @@ -3957,6 +4004,178 @@ Vous devez associer au moins un site. + + SessionsListWidget + + + + Suppression? + + + + + Êtes-vous sûrs de vouloir supprimer + + + + + Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? + + + + + Nouvelle séance + + + + + + + Séance + + + + + Ouvrir + + + + + + Supprimer + + + + + Voir les données + + + + + Voir les évaluations + + + + + Continuer la séance + + + + + Appareil: + + + + + Participant: + + + + + Service: + + + + + Inconnu + + + + + + Explorateur de données + + + + + + Explorateur d'évaluations + + + + + Form + + + + + Chargement des séances: %p% + + + + + + + + ... + + + + + Mois 1 + + + + + Mois 2 + + + + + Mois 3 + + + + + Tout cocher + + + + + Tout décocher + + + + + Date + + + + + Type + + + + + État + + + + + Durée + + + + + Responsable + + + + + Actions + + + + + Filtrer les séances + + + + + Nouvelle + + + SiteWidget @@ -4035,7 +4254,7 @@ Souhaitez-vous continuer? - + Types de séances @@ -4060,149 +4279,149 @@ Souhaitez-vous continuer? - + Form - + Site - + Éditer - + Sauvegarder - + Annuler - + XXXX Groupes - + Informations - + Gestion des types de séances - + Mettre à jour les types de séances associés - + Appareil à rechercher - + Gestion des appareils - + Mettre à jour les appareils associés - + Mettre à jour les services associés - + Services - + XXX Appareils - + XXXX Projets - + XXXX Utilisateurs - + XXXX Séances - + XXXX Participants - + Mettre à jour les rôles des groupes utilisateurs - + Résumé - - + + Utilisateurs - + Gestion des groupes utilisateurs - + <html><head/><body><p>Il n'est pas possible de spécifier &quot;Aucun rôle&quot; aux groupes utilisateurs qui ont au moins un accès (Administrateur ou Utilisateur) à un projet du groupe.</p></body></html> - + Groupe utilisateur - + Rôle - + Hérité? - + Groupes utilisateurs - + Appareils @@ -4422,17 +4641,17 @@ Souhaitez-vous continuer? TeraForm - + Choisir la couleur - + Form - + Ce formulaire ne contient aucune information. @@ -5004,77 +5223,53 @@ Vous devez associer au moins un site. UserSummaryWidget - - N/D - - - - + Form - + Utilisateur - + Nouvelle Séance - - Général - - - - - (Dernière connexion) - - - - - (Nom complet) - - - - - Dernière connexion: - - - - - Nom: + + Séances - - Actif + + Résumé - - Contact + + Sauvegarder - - Courriel: + + Annuler - - (Courriel) + + Journal d'accès - + Éditer - + + Informations @@ -5082,132 +5277,138 @@ Vous devez associer au moins un site. UserWidget - + Groupes utilisateurs - + + + Journal d'accès + + + + Langue de l'interface - + Français - + Anglais - + Sons lors des notifications - + Attention - + Aucun groupe utilisateur n'a été spécifié. Vous devez spécifier au moins un groupe utilisateur - - + + Utilisateur - + Éditer - + Sauvegarder - + Annuler - + Informations - + Mettre à jour les groupes de cet utilisateur - + Groupes - + Rôles effectifs - + Sites - - + + Site - + Projets - + Projet - - + + Rôle - + <html><head/><body><p>Cet utilisateur est un super administrateur.</p><p>Il est donc impossible de lui assigner des groupes utiilsateurs.</p></body></html> - + Rôles - + Mettre à jour les préférences - - + + Préférences - + Configuration @@ -5381,57 +5582,68 @@ Vous devez spécifier au moins un groupe utilisateur - - + + + Enregistrement en cours - - + + Un enregistrement de la séance est en cours. - - + + Si vous continuez, l'enregistrement pourrait être perdu. - - + + Êtes-vous sûrs de vouloir continuer? - + + Êtes-vous vraiment sûrs de vouloir arrêter l'enregistrement en cours? + + + + Enregistrer - + + Sauvegarde du fichier... + + + + Vous vous apprêtez à enregistrer localement une séance vidéo. - + En acceptant de poursuivre avec cet enregistrement, vous acceptez la responsabilité professionnelle, légale et éthique en lien avec la conservation, la diffusion, l'utilisation et la confidentialité requise avec ce type de média. - + Souhaitez-vous toujours activer l'enregistrement vidéo? - + Confirmation requise - + Arrêter l'enregistrement @@ -5523,37 +5735,37 @@ Vous devez spécifier au moins un groupe utilisateur - + Fichier disponible - + Le fichier - + est disponible dans le répertoire - + Impossible de charger la page - + Problème vidéo - + Problème audio - + Erreur diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index 0f93db15..b6222dca 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -1005,6 +1005,20 @@ void MainWindow::changeEvent(QEvent* event) } +void MainWindow::closeEvent(QCloseEvent *event) +{ + // About to close... check if we have something in progress that prevents it + if (m_inSessionWidget){ + if (!m_inSessionWidget->sessionCanBeEnded()){ + GlobalMessageBox msg; + msg.showWarning(tr("Séance en cours"), tr("La séance en cours empêche la fermeture du logiciel.") + "\n\n" + tr("Veuillez la terminer avant de poursuivre.")); + event->ignore(); + return; + } + } + event->accept(); +} + void MainWindow::on_lblLogo_clicked() { diff --git a/client/src/main/MainWindow.h b/client/src/main/MainWindow.h index 787826c1..60c26913 100644 --- a/client/src/main/MainWindow.h +++ b/client/src/main/MainWindow.h @@ -118,7 +118,6 @@ private slots: // Events QIcon getGlobalEventIcon(GlobalEvent &global_event); - void changeEvent(QEvent* event); QString m_currentLanguage; Ui::MainWindow *ui; @@ -140,7 +139,10 @@ private slots: // UI items QMovie* m_loadingIcon; - + // QWidget interface +protected: + void changeEvent(QEvent* event) override; + void closeEvent(QCloseEvent *event) override; }; #endif // MAINWINDOW_H diff --git a/client/src/services/BaseServiceToolsWidget.cpp b/client/src/services/BaseServiceToolsWidget.cpp index 60ccbf34..2a6a66ec 100644 --- a/client/src/services/BaseServiceToolsWidget.cpp +++ b/client/src/services/BaseServiceToolsWidget.cpp @@ -8,8 +8,9 @@ BaseServiceToolsWidget::BaseServiceToolsWidget(ComManager *comMan, BaseServiceWi } -bool BaseServiceToolsWidget::sessionCanBeEnded() +bool BaseServiceToolsWidget::sessionCanBeEnded(const bool &displayConfirmation) { + Q_UNUSED(displayConfirmation) // By default, all sessions can be ended return true; } diff --git a/client/src/services/BaseServiceToolsWidget.h b/client/src/services/BaseServiceToolsWidget.h index 98ce2ae4..b380679f 100644 --- a/client/src/services/BaseServiceToolsWidget.h +++ b/client/src/services/BaseServiceToolsWidget.h @@ -12,7 +12,7 @@ class BaseServiceToolsWidget : public QWidget explicit BaseServiceToolsWidget(ComManager* comMan, BaseServiceWidget* baseWidget, QWidget *parent = nullptr); - virtual bool sessionCanBeEnded(); + virtual bool sessionCanBeEnded(const bool& displayConfirmation = true); public slots: virtual void setReadyState(bool ready_state); diff --git a/client/src/services/BaseServiceWidget.cpp b/client/src/services/BaseServiceWidget.cpp index c0dc0a86..98bc9ec7 100644 --- a/client/src/services/BaseServiceWidget.cpp +++ b/client/src/services/BaseServiceWidget.cpp @@ -29,6 +29,11 @@ bool BaseServiceWidget::handleJoinSessionEvent(const JoinSessionEvent &event) return true; // Accept by default } +void BaseServiceWidget::setDataSavePath() +{ + // Don't do anything here by default +} + QStringList BaseServiceWidget::getHandledServiceKeys() { // Get all handled service keys by that software diff --git a/client/src/services/BaseServiceWidget.h b/client/src/services/BaseServiceWidget.h index 659439b9..08f83285 100644 --- a/client/src/services/BaseServiceWidget.h +++ b/client/src/services/BaseServiceWidget.h @@ -15,6 +15,8 @@ class BaseServiceWidget : public QWidget virtual bool handleJoinSessionEvent(const JoinSessionEvent &event); + virtual void setDataSavePath(); + static QStringList getHandledServiceKeys(); static QList getHandledSessionCategories(); diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp index 4cca519c..9671eecb 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp @@ -18,14 +18,21 @@ VideoRehabToolsWidget::~VideoRehabToolsWidget() delete ui; } -bool VideoRehabToolsWidget::sessionCanBeEnded() +bool VideoRehabToolsWidget::sessionCanBeEnded(const bool &displayConfirmation) { - if (m_isRecording){ - GlobalMessageBox msg_box; - if (msg_box.showYesNo(tr("Enregistrement en cours"), tr("Un enregistrement de la séance est en cours.") + "\n\n" + tr("Si vous continuez, l'enregistrement pourrait être perdu.") + "\n\n" + tr("Êtes-vous sûrs de vouloir continuer?")) != GlobalMessageBox::Yes){ + if (m_isRecording || !ui->btnRecord->isEnabled()){ // Recording or downloading file + if (displayConfirmation){ + GlobalMessageBox msg_box; + if (msg_box.showYesNo(tr("Enregistrement en cours"), tr("Un enregistrement de la séance est en cours.") + "\n\n" + tr("Si vous continuez, l'enregistrement pourrait être perdu.") + "\n\n" + tr("Êtes-vous sûrs de vouloir continuer?")) == GlobalMessageBox::Yes){ + if (msg_box.showYesNo(tr("Enregistrement en cours"), tr("Êtes-vous vraiment sûrs de vouloir arrêter l'enregistrement en cours?")) != GlobalMessageBox::Yes) + return false; + }else{ + return false; + } + on_btnRecord_clicked(); + }else{ return false; } - on_btnRecord_clicked(); } return true; } @@ -33,8 +40,12 @@ bool VideoRehabToolsWidget::sessionCanBeEnded() void VideoRehabToolsWidget::setReadyState(bool ready_state) { setEnabled(ready_state); - if (ui->frameRecord->isVisible()) + if (ui->frameRecord->isVisible()){ ui->frameRecord->setEnabled(ready_state); + if (ready_state){ + ui->btnRecord->setText(tr("Enregistrer")); + } + } } void VideoRehabToolsWidget::on_btnReconnect_clicked() @@ -75,7 +86,7 @@ void VideoRehabToolsWidget::on_btnRecord_clicked() if (m_isRecording){ // Toggle button text and icon ui->btnRecord->setIcon(QIcon("://icons/record.png")); - ui->btnRecord->setText(tr("Enregistrer")); + ui->btnRecord->setText(tr("Sauvegarde du fichier...")); // Stop recording m_isRecording = false; diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h index 1db5aa7e..8faafe23 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h @@ -18,7 +18,7 @@ class VideoRehabToolsWidget : public BaseServiceToolsWidget explicit VideoRehabToolsWidget(ComManager* comMan, BaseServiceWidget *baseWidget, QWidget *parent = nullptr); ~VideoRehabToolsWidget(); - bool sessionCanBeEnded() override; + bool sessionCanBeEnded(const bool &displayConfirmation = true) override; public slots: void setReadyState(bool ready_state) override; diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.cpp b/client/src/services/VideoRehabService/VideoRehabWidget.cpp index bf78c018..d50f8fb1 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabWidget.cpp @@ -3,6 +3,8 @@ #include "VideoRehabWidget.h" #include "ui_VideoRehabWidget.h" +#include "TeraSettings.h" + #include "GlobalMessageBox.h" VideoRehabWidget::VideoRehabWidget(ComManager *comMan, QWidget *parent) : @@ -79,16 +81,9 @@ void VideoRehabWidget::initUI() QWebEngineProfile::defaultProfile()->setHttpCacheType(/*QWebEngineProfile::DiskHttpCache*/QWebEngineProfile::NoCache); // Set download path - QStringList documents_path = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); - QString download_path = ""; // TODO: Other default download path? - if (!documents_path.empty()) - download_path = documents_path.first(); - download_path += "/OpenTeraPlus/downloads/"; - QWebEngineProfile::defaultProfile()->setDownloadPath(download_path); - + setDataSavePath(); connect(QWebEngineProfile::defaultProfile(), &QWebEngineProfile::downloadRequested, this, &VideoRehabWidget::webEngineDownloadRequested); - // Create layout for widget if missing if (!ui->wdgWebEngine->layout()){ QHBoxLayout* layout = new QHBoxLayout(); @@ -143,6 +138,28 @@ void VideoRehabWidget::stopRecording() } } +void VideoRehabWidget::setDataSavePath() +{ + QString current_user_uuid = m_comManager->getCurrentUser().getUuid(); + QVariant path_setting = TeraSettings::getUserSetting(current_user_uuid, SETTINGS_DATA_SAVEPATH); + QString download_path = ""; + if (path_setting.isValid()) + download_path = path_setting.toString(); + else{ + QStringList documents_path = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); + + if (!documents_path.empty()) + download_path = documents_path.first(); + download_path += "/OpenTeraPlus/downloads/"; + // Save path as default + TeraSettings::setUserSetting(current_user_uuid, SETTINGS_DATA_SAVEPATH, download_path); + } + + qDebug() << "Setting download path to " << download_path; + QWebEngineProfile::defaultProfile()->setDownloadPath(download_path); + +} + void VideoRehabWidget::on_txtURL_returnPressed() { m_webEngine->setUrl(ui->txtURL->text()); @@ -193,7 +210,6 @@ void VideoRehabWidget::webEngineDownloadRequested(QWebEngineDownloadItem *item) void VideoRehabWidget::webEngineDownloadCompleted() { - // Enable buttons emit widgetIsReady(true); diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.h b/client/src/services/VideoRehabService/VideoRehabWidget.h index 71fa62d0..31962345 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabWidget.h @@ -38,6 +38,8 @@ class VideoRehabWidget : public BaseServiceWidget void startRecording(); void stopRecording(); + void setDataSavePath() override; // Will update download path based on the user settings + private slots: void on_txtURL_returnPressed(); void webEngineURLChanged(QUrl url); diff --git a/client/src/widgets/InSessionWidget.cpp b/client/src/widgets/InSessionWidget.cpp index f5375126..3ec73aed 100644 --- a/client/src/widgets/InSessionWidget.cpp +++ b/client/src/widgets/InSessionWidget.cpp @@ -2,6 +2,9 @@ #include "widgets/SessionInviteWidget.h" #include "ui_InSessionWidget.h" +#include "TeraSettings.h" +#include +#include InSessionWidget::InSessionWidget(ComManager *comMan, const TeraData* session_type, const int id_session, const int id_project, JoinSessionEvent* initial_event, QWidget *parent) : QWidget(parent), @@ -74,6 +77,14 @@ void InSessionWidget::setPendingEvent(JoinSessionEvent *event) } } +bool InSessionWidget::sessionCanBeEnded() +{ + if (m_serviceToolsWidget){ + return m_serviceToolsWidget->sessionCanBeEnded(false); + } + return true; +} + void InSessionWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); @@ -227,14 +238,13 @@ void InSessionWidget::setReadyState(bool state) void InSessionWidget::on_btnEndSession_clicked() { - if (m_serviceToolsWidget){ - if (!m_serviceToolsWidget->sessionCanBeEnded()){ - return; - } - } - GlobalMessageBox msg_box; if (msg_box.showYesNo(tr("Terminer la séance?"), tr("Mettre fin à la séance?")) == QMessageBox::Yes){ + if (m_serviceToolsWidget){ + if (!m_serviceToolsWidget->sessionCanBeEnded()){ + return; + } + } int id_service = 0; if (getSessionTypeCategory() == TeraSessionCategory::SESSION_TYPE_SERVICE){ id_service = m_sessionType.getFieldValue("id_service").toInt(); @@ -454,9 +464,10 @@ void InSessionWidget::initUI() //ui->wdgInvitees->showAvailableInvitees(true); ui->btnInSessionInfos->setChecked(true); - //ui->tabInfos->hide(); + ui->tabInfos->setCurrentIndex(0); ui->btnEndSession->hide(); + ui->grpSavePath->hide(); // Clean up, if needed if (m_serviceWidget){ @@ -477,7 +488,20 @@ void InSessionWidget::initUI() m_serviceWidget = new VideoRehabWidget(m_comManager, this); setMainWidget(m_serviceWidget); m_serviceToolsWidget = new VideoRehabToolsWidget(m_comManager, m_serviceWidget, this); - setToolsWidget(m_serviceToolsWidget); + setToolsWidget(m_serviceToolsWidget); + QString service_config = m_sessionType.getFieldValue("session_type_config").toString(); + QJsonDocument doc = QJsonDocument::fromJson(service_config.toUtf8()); + if (!doc.isNull()){ + if (doc.object().contains("session_recordable")){ + if (doc.object()["session_recordable"].toBool()){ + QString path = TeraSettings::getUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_DATA_SAVEPATH).toString(); + ui->txtSavePath->setText(path); + ui->grpSavePath->show(); + }else{ + ui->grpSavePath->hide(); + } + } + } handled = true; } @@ -491,16 +515,6 @@ void InSessionWidget::initUI() handled = true; } - //SB - Even more lazy programmer reusing VideoRehabService widget! - /*if (service_key == "DanceService") { - // Main widget = QWebEngine - m_serviceWidget = new VideoRehabWidget(m_comManager, this); - setMainWidget(m_serviceWidget); - m_serviceToolsWidget = new VideoRehabToolsWidget(m_comManager, m_serviceWidget, this); - setToolsWidget(m_serviceToolsWidget); - handled = true; - }*/ - if (!handled){ GlobalMessageBox msg_box; msg_box.showWarning(tr("Service non-supporté"), tr("Le service \"") + service_key + tr("\" n'est pas gérée par cette version du logiciel.\n\nVeuillez vérifier si une mise à jour existe ou contribuez au développement du logiciel!")); @@ -610,3 +624,37 @@ void InSessionWidget::on_btnLeaveSession_clicked() } } } + +void InSessionWidget::on_btnDefaultPath_clicked() +{ + QStringList documents_path = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); + QString download_path = ""; + if (!documents_path.empty()) + download_path = documents_path.first(); + download_path += "/OpenTeraPlus/downloads/"; + // Save path as default + TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_DATA_SAVEPATH, download_path); + + ui->txtSavePath->setText(download_path); + + if (m_serviceWidget) + m_serviceWidget->setDataSavePath(); +} + + +void InSessionWidget::on_btnBrowseSavePath_clicked() +{ + QFileDialog diag; + QString full_path = diag.getExistingDirectory(this, tr("Répertoire où les données seront enregistrées")); + + if (!full_path.isEmpty()){ + TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_DATA_SAVEPATH, full_path); + + ui->txtSavePath->setText(full_path); + + if (m_serviceWidget) + m_serviceWidget->setDataSavePath(); + } + +} + diff --git a/client/src/widgets/InSessionWidget.h b/client/src/widgets/InSessionWidget.h index 275c230f..94597ffd 100644 --- a/client/src/widgets/InSessionWidget.h +++ b/client/src/widgets/InSessionWidget.h @@ -32,6 +32,8 @@ class InSessionWidget : public QWidget void setSessionId(int session_id); void setPendingEvent(JoinSessionEvent* event); + bool sessionCanBeEnded(); + private slots: void on_btnEndSession_clicked(); @@ -43,9 +45,9 @@ private slots: void processUsersReply(QList users); void processParticipantsReply(QList participants); - void ws_JoinSessionEvent(JoinSessionEvent event); - void ws_JoinSessionReplyEvent(JoinSessionReplyEvent event); - void ws_StopSessionEvent(StopSessionEvent event); + void ws_JoinSessionEvent(opentera::protobuf::JoinSessionEvent event); + void ws_JoinSessionReplyEvent(opentera::protobuf::JoinSessionReplyEvent event); + void ws_StopSessionEvent(opentera::protobuf::StopSessionEvent event); void showEvent(QShowEvent *event) override; @@ -61,6 +63,9 @@ private slots: void setReadyState(bool state); + void on_btnDefaultPath_clicked(); + void on_btnBrowseSavePath_clicked(); + private: void connectSignals(); void initUI(); diff --git a/client/src/widgets/InSessionWidget.ui b/client/src/widgets/InSessionWidget.ui index 13dad484..9a17eb47 100644 --- a/client/src/widgets/InSessionWidget.ui +++ b/client/src/widgets/InSessionWidget.ui @@ -13,6 +13,9 @@ Form + + + 0 @@ -208,7 +211,7 @@ QTabWidget::West - 0 + 1 @@ -252,18 +255,111 @@ - + - + 0 0 - - Paramètres - séance + + Données + + + + + + 0 + 0 + + + + Répertoire d'enregistrement + + + + + + + true + + + + + + + + + + 0 + 32 + + + + + 16777215 + 16777215 + + + + PointingHandCursor + + + Défaut + + + + :/icons/leave.png:/icons/leave.png + + + + 24 + 24 + + + + + + + + + 0 + 32 + + + + + 16777215 + 16777215 + + + + PointingHandCursor + + + Parcourir... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/client/src/widgets/SessionsListWidget.cpp b/client/src/widgets/SessionsListWidget.cpp index 6945dbd0..d7bdc45d 100644 --- a/client/src/widgets/SessionsListWidget.cpp +++ b/client/src/widgets/SessionsListWidget.cpp @@ -196,6 +196,7 @@ void SessionsListWidget::processSessionsReply(QList sessions, QUrlQuer } updateSession(&session, sessions.count()==1); } + //qDebug() << sessions.count(); // Query next sessions if needed querySessions(); @@ -479,6 +480,7 @@ void SessionsListWidget::querySessions() } ui->tableSessions->setSortingEnabled(false); int sessions_left = m_totalSessions - m_currentSessions; + //qDebug() << m_totalSessions << m_currentSessions << sessions_left << m_currentIdSession; setSessionsLoading(sessions_left > 0); if (sessions_left>0){ @@ -551,7 +553,8 @@ void SessionsListWidget::updateSession(const TeraData *session, const bool &auto QToolButton* btnTests = nullptr; if (m_listSessions_items.contains(id_session)){ - // Already there, get items + // Already there, get items + //qDebug() << "Updating session " << id_session; name_item = m_listSessions_items[id_session]; date_item = dynamic_cast(ui->tableSessions->item(name_item->row(), 1)); type_item = ui->tableSessions->item(name_item->row(), 2); diff --git a/shared/src/data/TeraSettings.h b/shared/src/data/TeraSettings.h index 88c1bade..23672ab0 100644 --- a/shared/src/data/TeraSettings.h +++ b/shared/src/data/TeraSettings.h @@ -11,6 +11,7 @@ #define SETTINGS_LASTSITEID "lastSiteId" #define SETTINGS_LASTSESSIONTYPEID "lastSessionTypeId" #define SETTINGS_LASTLANGUAGE "lastLanguage" +#define SETTINGS_DATA_SAVEPATH "dataSavePath" class TeraSettings { From 50e311823aea0f4bad36416531c7108ba0e55ece Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 26 Jan 2023 10:16:39 -0500 Subject: [PATCH 22/42] Refs #82. Added video recording pause feature. --- client/resources/TeraClient.qrc | 1 + client/resources/icons/pause.png | Bin 0 -> 22787 bytes client/resources/stylesheet.qss | 4 +- .../resources/translations/openteraplus_en.ts | 37 +++++++++------- .../resources/translations/openteraplus_fr.ts | 37 +++++++++------- .../VideoRehabToolsWidget.cpp | 9 ++++ .../VideoRehabService/VideoRehabToolsWidget.h | 2 + .../VideoRehabToolsWidget.ui | 41 ++++++++++++++++++ .../VideoRehabService/VideoRehabWidget.cpp | 7 +++ .../VideoRehabService/VideoRehabWidget.h | 1 + .../WebSocket/SharedObject.cpp | 5 +++ .../WebSocket/SharedObject.h | 2 + 12 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 client/resources/icons/pause.png diff --git a/client/resources/TeraClient.qrc b/client/resources/TeraClient.qrc index 5db3c8d3..eb9767a4 100755 --- a/client/resources/TeraClient.qrc +++ b/client/resources/TeraClient.qrc @@ -147,5 +147,6 @@ icons/logs/os_linux.png icons/logs/os_mac.png icons/logs/os_windows.png + icons/pause.png diff --git a/client/resources/icons/pause.png b/client/resources/icons/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..db3bd3b538959308800f15d53c15ef68f5c76170 GIT binary patch literal 22787 zcmYIvX*iVMANQFBL$>UsjBF7hk$naUEy$KFMD{hZRLtC#_(hhKB}*u~Y-LHwOe7_t zB9dj4kaZ-CWoDkc|MOhe^UAz1bD#U1@A;n3c5Yjmn{aW6aR30g%uG*N0f2x%A^?UJ z{^J~4=>>p&uh}UBo2aqn7`Epw#?6~=k^c<=z6_yc1NZq~CTBp|O_tvYLT|_{2Bk)Z zN$C3#zYT2+*?wb8FT9xjYQtkq%LSIiq*@5Sf(9Q;4#ecSby^VS>CeYUl< z8aN-+QTV(+=ube3F^D?2z5Nq_pE8VZD9!6APx@lm6-h zJM!jqFz9tEj=@rG_xR6wg%5(v{eN#c(!cY)_hmmaV6~0P>cNOGzAZ?6wP2PIfRE~3 zve7NqyS<5Oy)U|nHtQXyRU{6Th7R2MBY-d@v41jLV1tAPb5%XEm0u*ppcEFZMJ-1n znNv)$+SK5y%v zrh^Pqxrb3dKHD6;aE+buq;bIC@Bs?*wZ)59uJWtKVwjXjwBt3zYk3oin7`{vmro~I z;eg0(G!bL_xN^zzl=zJ{Lkazq3IFBrc*A73q~*q4?S~(+n%wmz$Fm`~&|1#<$3r|| z|KZmrc9T8|PC^`{(1gk0R?N%I>x)xmmUV+pjANVEYxOocFO`R?WvQak>7L;PS$JW2 z=M(tN?d#|H_EMV%%Ep#6dC!6j6gnKjr`@R6YenJ}L`0h6URK`Gn$7h%Wa>^nXGjOU zyzGo};~Yx8R7^Q{Nh>BgYjHIAsa5h|h4Wcx5U&zdIggCfQBW;SzkmaMSib1e*@tI5 z1%Iox`L)W9R|Qsyomy%=d*8jsA9;wMF{H74vU|*X@RULlAMg0RHyQe8)bxNjac|A^ zgI-~8$Zs_*zldbso)x935<2liFq=bQP|Dz$qT3Ta9j-ftqMwqER*6S1HTIY*dAaBL zA|SNH!@ZcQf>WMBD{577MTeg|7XDG(WHwLL-u3Lx89Sl!AL5pv>GP@F&l>TQ6`=xI z>+Bdt)|+B|Lp2#79yxy1-zprDmDM&EzK`%k>DRN%PD_!dqzOa$h<54m#+Lg7js&)? zo6Rkvf6-YnJ}$Zi-Id(s4>*7i)VREPMM0{q!wHRx%t~i}B`9E}&QTjI)?IMztF6n+ z7F~<&sj=0xX2DA(T~}?=O0OSRy6tfAoEi%lgdP=1RqoyIMNY44v*2X^+Kx4?W)bq6 ziFU!HmW4S?^qB)&uf~GU{w^{Jw${4S8vxnJL>5f*v}1bvW= zbHxp5i1@$xWgUKjr%qaay9ZGw{(c*s9GU}^T#qz|SMy`M$KM6l8&Sh9UN?Uy^STuv zxAL(f>-{>x4zmw$uDE}7@K*XDOrjZYj!net?=C(2@x=nSd~8KOfXJk8r9@7}v?_8_ ztS%DFC~cb}nlnCZgynY_w6fK=X}H#-nuf#L%@?czzLSWx#g+UqCh5uUeXf3>Kov6B zS}pUhjsAVsbwTJa0>vt@{_&)%1NJYYRGDq0e73s%E)A(d+nr*%Wcz+84@Y&u`}KbN zpR*(3Abzp6yF`@l%cf{n7dyH?U|cci*akbmzAj3a{S_tFu$nmF9uZQVC&3@RGIsdy zxVq147GXu9OV>U|S~{@NjAGDM!a!of@^jKhtr?CZpFs{usXKs`SAr_|-7| zfTH2RC8yViSujt=K#$JCoYIhf97I{QoeQd|`iY>-KTdZ^sjf+(9I1POmXQ9oo(DNC zQdeh-h>RaKu5`b~QTLQAL)`a(XW?8nou}%IXj&cJc<3c#=AOkFY}Bu;i^(G{H*2Hj z3L}TICLPZP7EBcz;GxIwBXrl^@XOh_hon?_{M%b3I$IJ6xfLjRq}<06n~MRc`@*i#jo+_O(3kvE zVo{OGYw9z#Z;mELMt#`NeOoIPDwYak)#d}HH17)ba27689%W*6ZLsZ@wcUp0Xxo6B z`?2OBtL_mJW5a!|TiDz7TWCn`ZisEz$OI~nGXMEVxvE+8-;X)D>jFhF+rM&BaP;36 z-TRc=2R|O*!brO{0K+r~H^ha74_renYO&;=VeiPWs-J*T`8X{vNra%L8l5}!u;!1y zUq|Vf|HznH<>m*aR_C?m`k30kolZ-3M7|u9*WaMlUN&AAC%ytpUQgP35z|>0F2k}a zGJ6LanfH}^NvC6H?};CurNl}_i;{)*@6xmz@_r@=z>aHK`GHRoq;KYqI9Atk>!lQ* zU0&}~vrvtqQrzM3u1;6ACcf`Au#)wyqpI=e(hFG7?i8_<`w1-j6TKf^FV8*hd1h_$ z=IU*bf#ekGx%}?Ch_dLUzOkn@GB)j-`?xAx*Q9fw!rSkOO|@3;mAp`>+EoP^9Qv(q z_|6K{)V4S_zB`<9O3o#%S|Jv3^4q~S+jOa-;Gp4%<;=m*_N+P&x1)MD=^G;{J%?^S ziTYiZa|#Rs15~`_(FPwBD=)r-{1%NIP`AY&uaW|8f9F4Gmd%~C>G;vwlYk2!9tpS+ zbc+cu&u~o){M}=At}z?OuT-#K55j!N8Xz*(J=f$~cg1xEhmO_$5$93o?`5-;ufB`` zDUXK*6NKR7@6(U%xuR7TbQ+HszH|o@?Gnn}mC=fzr}lbzyuI^aR8jPOSqmHBBZv=& zTTj38I@`=(@>=cW>%zf`bEbcdk+(#!KC{;L z{ug-J1)2g(*?D`(?YTKhgn@&zgB*{s{@i@6UUXq^90SNw`eXcY^hmcF$He(z^H;a$@0Wp$? zVI3lF=ujMDM~%N+71h4xaG3{>7YQkjeaGT%}=V7gB?q;yh zx9Cgs(Q$P)Oo6;03K6*+C6PA8CJ}A9gONR-H-y5g;~NqLY9+g>oh2B3@|c*&8we|& zf2ua%gfG%V&G^yfp$LF~O5(Ad+YPMhT_lL}X9#%ynR(xB#G_|Zj!go*YWp?A zK1Mb;-fOtu_$^1Jxb{SB3ldoEUS9V&8k{{=|7}NJqeFR*cHj+Faxbh;)ui6+Fgro> z_V}igYj=+Bme6$T7#*&}WcKR;&eA10e&?kcmKv$CrRUkQUQupO)w8`d^<#R@Pjj||z8svB9|6~Mf<#=p z-d)73ZCwfWlV$iT$hQi*oX8ic*3vbniIXpIc_FUYcfe#{%6#_z?=h@MDgopSq z2nw}}r-yk;q;yv$U3-|3dvAaZ611}~5zz>+SI%-ha7{O!;v^nj71((5n^{{3`LO_T$7`=1 z)#NMImT%9L~Ysdy*r+W|&c?2d!3E>>XfMJu^$169dc3s*oos``p=%A-+ zck*7i+M+U`;+|slcWXI24mGaO0gKwyqvL9%^G~-tB;77HOwisQ2v)RE-@G;Th-(Og z)bt3^L&laLJTZ0xXf8E$%|2du->LKJVwiNB_C=Z&AipgPY@KM)HJN&GlXLcUL0JE{ zS4SJpXZWQxFCNXVU8LhvT?E->-e=V=RFlv=lC0FEYZ`g5Kwbr+mAW1 z$90P9-*MSq!}EYJQNQ@~ZH7m`{>QJ8EGE|^`;rQx&6j!Dc#GDX6Krg!#8~@2{&|Lp zIM^Ayw&(BR+m^N+?Y@r6zF zOOg&HxBfjmrIq));(C;aUjZ7tTZ(U;S|+bwj0zeomWpZwMF2l z)9nLXU_>}Eig=DDlMgqPy7!TN{d(Ndl7TF_Sy3;sZ67 z&k#^Pzq1o3IDw@cM(XoYw&Qv9?H_6jpb-6XmMF|P9yFW}j2E%&`qwXT)%~~6bAKo< zucq5?pB(d%vZc`tjINQt?dFXsvqx|M)2+`_W<|GSj-7V=k#SnS1N?U22DFiJ-A@ml zPQ>zJ40)gLuDmopPtH`YG-l-t^B^K)!=AA@<+G*ItoRruHhQN$zmC44vgNMR{3o5> z&ob+oJN1WMn_)?7$eS zd4d1G2Po$&GjQjzSQ z=KB#`yPC;-ZM@I^=>9c1EmjX^{oO0JyIqauzV~MTo645j%QF*2Av9U{AAVEw!RiFU zbsuH#>}g&|DrVw~xlZj@o9WYle8`4=T*JVskf1tkQb6oE7`fD_X2C}QXgdPYGP>M} z<>Adk{y$e1RoKqOPe{+XJwspvp6y^1cTEg=$=~nPckC>(r;+gsga<^GL ze?EUWL6n^ds>>}M@cH8M5hS5Glh+h-qJ(~QROH)-#)RWkmO#Yx4CD!q~@0B%FX802K|~d;Rvv$ z%ZgSdLix&!xf-MaCpsF0_;vwAYa|vOXP*YD6Z;^NUh%dbIXJ0dzn>iG$4PmPtA3G_Q0Y$>}(RPd+rmp}*yD z^hg5MP0IqQ-ohuPP4-3DoyVfk4~w5_$C63aX`Yur1J2g_EUM`7PFVhp0?K2G3eTGz z_2wzA-uCtW?-N4DHEn6uc66Kb*sovv@4F)t0>5Ut`ajDp#lQKj(M6}#oIvG_iOQlO?$6gBy1-Mqd_A6qOh zFbpe>lc(xuz4k7+db$i{ya=G9m11>VL4w0m!qMLT%Prr7eu)woy#r;QowGUIYo>xX zYN#Vhv~e|a^4Ku0IHn(ou8LcAoddD6Nk3-GU%nNM&vCiot4stZPx_x=sgnkyZM~Xy zdFs-8QTYR_r3THKepc>uH_atNd!~*J-ALAmR@+8zG}5ouWQt8sM_x#ZcqGn`zu~n% zx-Jz3ECaennyik3;xi5@!6+JIISs8XymtG(drW^+`3e9PnBc0c81vJL_d;8 zT|I)5HJ%^v^!5vr?+2%D6+kNQO}^(`N}`^0q~2p|EnhvOjZ`*H(Q^9K;F= ztf~DvtBS6+EB>H_*n1-{<3QJcLMm&0Y6H&dkpa)VG3?B88~WShkbH@-1n4|Rx)kxP zENNmuRZs!9MEKwmU^c(j8k9Ahpsmh%q}g|QHg-vcpFHN``D=hBHaGZ8UQNG52uxSE zdIjzS=w@NN*HaFUSE8}xgIYiBSwO}rDJ`7Rl!cSsbK_njKv3!C%OBAwprS;5o#5afsR_0o~k(DHT6__zu$u4HjqbIDJ(-JndRP zf8#-{FIlmg_|B9KS-JM&*wX5jXb%F|F$RANA8)9pjx@t6S;$NFSEsaOAtBknt2I7| zHf-^Bl_tQy-bO@mJ;<-xVp=ZDEWm>lO&vsQVx^L)wM zsA!Ir)tw^dcF}pOo+H=2?o#XRu6fdLqu!|M3As-LFpx1vntzm1eaJRyw{d6JoTRPG z)PKBaxowSZv%cl>NZ}}PREYoD@&k40cL-?R!52%(x#$|xJg&@p%n@$7oJwxt$k-mM zw=!%XsMPPMZ$os#hUjX7%hRgpms6L1UMc#h@%@f1oqiK1hku$MDh0&9T+oy&IiaHV zjFMzCjK2cZmS?bh}tzt6L)wxTv5gNZ|f%~WRK|2A)x$qkP zpdYPmvP#d8sMwJjAaPI6*=-P}gB8mXkj(=pkZAXjF;+?9p~uUx8KSt3g4M=QR8u8n z@tU562D3}}^**U-*BA4>+syV87%`8oVOMvN1(?WYs}hyUK0n%-^8w))B8gIQp-#h+ z1+AE3TWw@{_OeM3d8o?z<|&p^E+fHau*55V4ohvnwIvT!(yAriuu6@r%}L3&Gor3n z2;joywmTQ=RgXk`)Z6!9OnL8P$?14}9}hUg0@-^N^B|+Pk32#aoX%h1MH|mf46uXK zjTezK1>9Z9J8-|*{Mi#bn{#3>A+F$>b^)mhMYyeS!#x~B5Z>uK49lzzZ8* zbat|GSfUA5@4_V}B@Z^Ehb6EX3QJb=>HdqtjIS5e@Wsbmp;; zM=QDEkkGn&JIt2TBpi+0TU^5?ZGLC)OszyFMF+h2UB2+puaZ9f$weR4Bwb|RncjT@ zvr2?9+-AsR2&udE_xS4U5{^XSL=~}JmVL16Bz|l1jP^q1;~VE3NvqS++`CIV^E*j# z8j;JbZ4pJvw1;Kcj@2X1*G8&j@yUZp>-k|mVU8;YBga3*M48U>3~KD#)*YUGVv^1a zo`@bG9RV3sp7I3;(#J;(|Ce;>?-DgQo14BHCI((^w8K2_Yj~c@?#*Cu^Z%saNM;uy zkv`XXUJ}M!j=R`;@sbfPm^5k7EY(wiJb&Lk#wVkkwVzzj9|ZeM`Oho`v3$ zm_gx3P11PkxMmhI>Mt|*Vz=Z@=^&tT)-bpEbxG-<&ZG)m9R>;*GR(rP^J?TT8*pJt z$Fq6C?)3bFqRthJZXjCp8b~aJxkZzFlq27hgt$60N!Tm-Q^Bx*5@0 zd8_CdfX~aH-h1NC`Ub*(0WeygPCSr2_wZ(a0PCBUePLl?tbwK=r2Yg!0W86~h_JP~ zp@&KZqhj>*k(*16Cf8d?EjvH4mQ$a+_U}0lPX*r_3NryfzRm0laMy<#cnYwPBNqrk z?s)hPgS}UW9j9ctKp1ic1)VUNXb%51uz@P|K}1!MyMq^NtM#JvJe%I__T=${`>Xr+q2f%-q6f(cj&qIzO zxh5Tjh!fm^`pvtj@t!Uk;mVJpOQi8wNlT)^#iGc`>yHM**6-&(6H~EIaQTdM?*Q#K zGw|b2>>`gB#!6U@_5x)8kBW2G942M4>jO40O$!yfD+r>A zCL##xTo0_{PWcngtlQ>26SDk|{gu%E3;ZcijHT`ujyV71A=0{T zRoy5v9jA4DV-#@AxV=<`f|0yQjpc}+_^PI*n(u*PG>(37B{}JEfCL5Om})tht5I@rbQ(fo=+@SQts3on84hqzcpFqHD?;cVk>L@#)uJuT4iA<>|XVhS(Y;s4Oji<_I^HPguY^06~5R>S_YsMd8Tt9796>QG%Xf-+j%K9PQ@91E+p8; zcKst~h~f0kstH2+i`?JD-F4RQjWJJ0nT!~zCMZ`K3d=pLM9cH9`~3_3u(^4yURM%4IX8SNHCF$pp+)8WoyEwgZo!L+8dK3>7icjXIL{H+ zqjp5zAA`p@XZ`i8?A*?=WG@lxAQRYDy=J_>?zHJC4g_=-!_f@bLDy}iT1HU+9SK~= zNU&d`kma!GyIAsa>y(S`#*p=sbEb~az|kNVwXg_5BqaBuXx9P*xXTw#q+b6@EgEtd zWxyVTY86x7aB6rKBbgIW1Bvo-_)zqV>w1%CBu!ugg z1!U3y3p8Ge=hPf-5F^uBgVy+g^l4b92<=DBPa3XEKZ)0p35|kqJ8& zc>A|SQJ_81>5-Q&LdX>oV8^W9y%;B)?B|6y#(@YnZY(d&d%Lfh5A5NpWqmkjmY4#i zPT|A0A5Jn%3%m5-#v^H}KuF$^(9RV}G`*AD5)VMgc*3FJ_dJvuvOcbB>f^UgXZxk+ zKhr*BO*mozG~#*-_G(a?{H7^@CW)5-$6hWSUu!J`(96xaCjWbet!-Wq8dR>amh?yv z$FH!4tiPFie4ID}Q3tk4i)*fP>hPBHEM?1GZ_IVz~lk-9DO=6%JL5{eEvP8zJ940R(VS4WGqi@qA zOcJEhT^@S@$e4EFY}gx%f=*{``rj`vcl~aouh0;0FGWq?x|y|HcMDJm)nA_-Kf;L! z?Oa6xmyKKTxMr&B=!FrjQ>?{Y_QT8<3Ajzp1p55t!*|pWO+I+dyPZ?j2a!M4f75=Z z*BfVqu@`d#JnG%3?McPNc`UrKo3z#T2<~!hRo1(WI(F=*CYRolbTHuQGx?8W*A5o79rjH2v7QG z@9${9*BNGJKoEd=o+XWr*c$%9Z?0ML2y;>DRCqL?8sOUJG9MWd&gy`+HY6b5)>ic^ z6><|K)JXAe1QU0^|F~;%==QM;!A+KSGhi9CdDO{_HdnH6Db+XA`pqN8PqE*m5650U zsb=i@rf1$mdBxUq(F*+qL4rOo+u%|MQ{QHoxS%ZwVOGPKAGhUZ&4SLOK}Iy_I8A5pO=3&(?S$*1ehhR zu{<6I(ADm{c0qh$SZr(wuD-N@jSHx-DFO;3eM7taPlYB*i2x0{#%-fT5}bz-H<1#{mK--A>CB0C&i^9q*tip4z>2;8s>~GXW`E0W zG>?V}1|Ag!r(SN>?i7e(Wl-DnoJR)>RxuvLjp2*=1n&t)Jce6~0QxyY0^#_8*8^Ag zPvE#3GExdW??aHyY*NiHbL1e7d}C_Ty|*SJPu4Or10O$NAv~`X1e70?Ja()ni}qw< zd<8pc;MqNM?W$uA{fF6rIaLc_4wgz{&cx&#KBW)vwk?)AG=y`b58_=2rv>|G@mSHj zkSZFOWGP9Rq_t;P2U_QLmbIQK>}18ZZ>5KU(88439jl~+yl)B;kVcP|#czS)ZW8=z zOkx=Z|9t1=i-Q2jLi`c(;v!xwp)V0_`e&~n?nlEg0GCs?czOq+X;R!|U)-_r)hPuw zH9xnjsh~jJfs;Yw5!~Qmxu4G+tws!fekNq2wPU092dsE_$*>hS?S#pkGAUno&;Qzg z0Rsv?ehA#3eF9iw;>Hr;^y1s0BmKY8Uycod6TFnnN1bb5hM1Np@(1-N_iI?}qY?6s zQ1^aX5^k@AHnB?RN$iv1$ExzF+y-v99877`lDlkK-((VP*by&KcvhZ6<26#MSI%Cd z%h(>jl0eSz(ScctFzZhnt@olLW8{u5t1}GtVH~v{epN9@`R__@$a~>p1T?$1Pu;`^ zd<4MX92!Rd_X}D4@VF<(O}uGp2|2wgS`?`Kcg$r_r27^^T>?KUDb8G;T2uusZSY*hO1ac;V%^nAu>XLd}jfg(<3lWL=V9(Mq>sx zSpU|E@3?@PFZ(M!(V;!FB?Y0q^1p)3*jaWYgN=@aJaZsW<0Y2Ng@0ZUP?5`TTlN2$j@K`)IJ{Pv~6TfPRGy;r)m8g zIo}Vg`h#!)T`SCFbNk4WaTqavpc#e_Q+T2qlkODx@9I+{yk&mK8{_uC* zbxdz%`YqsckO3PAA3tKBl-PxPHZT_XK=_G=3G4GW67P=%sRJwrWY$}|mnY=L0*D<6 zZqS`U0e*7Pm+VCWx#I8*9w|hgC}6%VV(!gVvRg`u`Vax&&X(}MoG=6*Jq>JbKT}T% z9n>mewX~%Vm?c+o0BT&)LVhVr!Zg0egBf}D0kLC!K*j(@D8+CPS#z4F8T9i|bP!tS zVbyGFK@s4k)IrU57qNNMH=K4N$bM~T{ygbwjo0zt|g<)Z=M7)Q&F ze+fC{A*@QHJa5aOe4p-rFeQfq{pBfiYmxmZK=K$xJpbzG|3C^4C zB%+jRNa}nlmpcN-c~CvKS1E|m)6P9O}`$i9F;j8OtHoGeo(5!C>%+0)jd zP=fbA23N|>58psJ;7tGy9uQBO=Cp zD~|KIZ6Jn3S$tT*5{yi`rx=^9Pnn}Dx+BhES;`vS3j$w|hFo|ndY3M61!Hj9a4D0% z3Xw-Jnt(cYyBiGrvHKNFdLCBqC4-fd1fnHg1v2edvNzNG$2`3zStEuYCm|!}&+sv}PV~ znHj?csiDM~6QetED^D4n8jzn90^HB!w1LOZwFcYl%2WKKw*N)oLQZaIv~4xZ!_SKt;VIkdQ}6XHkC#h8_$q{6GLwD z&yI#m;H#-8kF$ZM??EpGtt-rJhh5~&Sk>PZ%~zJlK;DDAl#3x_NOGs|{uR?EI}}Jm zhspAy$Ap6-mlLKUF@E$`3~3#4LJYXS$H`v@p9w$$_8akuzkMqTzkayJ-E4ebhkGtu zrDqBOq78+gz0GxFfd<*2tr3r>n z3>J+O`C(~a(V%D(g*OJ!z4g?SlRof*vtnkVUN4rKFA;Iz+LsEtEItJka|=GdhbYdX zuL=O4Z1!Sp#HB(GP{;U`P8g~vgv{Y%94dB}v)4i3jG<$+IW^ti{P&qP?G~cz{c6+OI0Yhgk604(S7i`^*@Ej zTX`E_H#;P*Brva?$o@HpKXwpTf$Whb9dIUQ6apIM-T52lm{7Ipg@2=!lR>6R;9MrS z?5`@5(E!Th=u?$q0f{^klNHvnX7LIG!L8>AK_=LD7V#Sw$+^64EbZH~`HR`V^{0p_ z6RQjGyPr`|nT#35gXjA|aS-bdk-MRX$Qs`RzEqqIQ(8mdUV{Qbn%ghilDsl6wh_)!2?fP`^~fAVRjO1xkJF z2LGEUSN@#_C1YBx7Zk2Tg`>VMvsylL-ganc4M0y&27BCQFA*0s;Ml=N8Mcss#h^jI z*x7d}-#L~T^~zvc;Rri2fD!>9^F@;E-|2OM>DD}iogCJMI}|?IQ=WFdKX(`upn$19 zpP~>8RYxOWwBhOBVX4>u&Qt-|d9S|yXZIy#PjHYu_6l2=Atb2cjf*ZE@}*mRL^N=8 z$nap%P%aOJvAe3AGR&5ty}D1FT?1ae86yK3K+$fQ&GQ$6{YbMR^!$*K!9QQ9{ZH>v zvwJw*ePsHIiWMJ!-2X;Lv;~8mac~8X_kZI>ufQAa$8?f}mvC<$a1#K81t{`cf=MG)~LEZvA|0w`yxt|xxop7=RrGivA3-shtJ*Q@e^8t_74N z$@YAf=jc7E{y`G>_|K5R*^f`*M2U$jPrvG^KZuY6aT#3C$3s6De}oF0Apz{5Naj8O zt4$X2@agxViZsqqS@pU=j}!4uOX#KfLMdZ}9TZ&8xQD`4F$eT_10Jx5^d^&bF7_ej zZ|G^(VbK2oGl!9T8z3ZQ?E5Fa{wId_J2I$1mH!VmbPeTlPi$Y}bmj-b`&ysGwaxt) zF)JKmI)erp@<|Oz0LiHiB^|m^sl$eU#ymO$&b^q)JZl#Mpp%M0{+_0NJiHMhWm$F# zE#skQBYw>_ffQ6jZ(^~4%735!Nxwf}S=vgDbcLCy5YorZeG43QtFb`CqjpMon7?QX zS98mgZ_K*WabNCP==*XYypZH@H2AIsG;ddbQk{Jhijd}VYwQ*}s`j?@U~Ho)UQIl9 zzgHEKx0)=panT9kfuz(!u(nn>$UHsD)T+VF9hr+jGXCU{6xENW(2E|RR1yK@2pf25 z74CaS9-H&ZvoX*aVI^^6zU0$6va^XBhltO)_Xvo-`CVB7bnN&$AUymUltEQ}PhGxt z88aLOWg`Qwg15&J7J3^)f3U+LcZZ@qi&bW7`+iSgz_BpUz-r4A#`%GrCA3f%7OQWH zN7lmP%d~}_KZy0IqyQ-~%1#6|g3>HzS8r z{l*kDg#7t{u2stl9A;Rz+z7gJxzXGoG{F4$zC*07cx);aWnV92_@9~yTvjT^paIz; z2DD3hG%+J7jE?jne(zg3)s9MQU=$68qqB zMDu-0#0E+nR%i7J+%6wa!cj`-N7*4F{`3(vw{9VD4*RIiCA8MRO4J!;mY^&@px+z< z?PBAP7~W&d;7lry;`dAY5M;VdZI7yn2WR9l*d;)*aVrk6ak4yALxs|T9T9q4U^(xB zrNU19^Bmy<>qSEz%?f1LL+YUOpH~!KFq6`nIrfSko8)n!N(~{m(^M(4>duqSfGiHoj@qX}jKUlJD zc`1a1;w!cF-nQpTS$<$6*OB2(tiOOB+C2#i{URhfXCB9kSA-xmJshh=jaiVvi{}~(rMTY1Guil8#`A-ER&3B^xe}Kq6i~Ji%C^b5oXR^ zDUsb}M*R3DG%>IzhX!oc;f@N=o|{5LzhN1$&i?W(!Aww`EIQ}|hoxoNp;Cr6&vRu^ z%sb)Xr-FuT+E`Pbn=g+sy=oaSKVbzHIQq2*-$d@QC?UxohbatRBoyT8iUCKh)m7h` z`s;&{HCsD}GTKTc$w^*(^qmhrN&*!tm8SW~37lAMy^sD$Y;8iGnGC^@WpbQ94PDy8x(q4>oK zsQNkr6PSoBt|Hk9jy_^F0@Q^{JFAx)r2gl`OOp5$>QAv?Buo(7^n0m4xFk^{`{&Rh?hCHnR zp9s7VjL4rmJnjGY0MQy^GSvy;)_Y&R3@2622s=)R@`3YW$4GmB0&nQ@u-ym6J@T)s z2p~mN<;uGZ_ub`I?^n#RZ4j=BGV-z=MyA1b{|^|t@v>z55w-pXqZ-Z2WH@cJ(;1i)D2S~JTWwE2v`~7klPPcFxNe3=7&%Vd4 z>^x4_S9rywwaaX@#?f&Jfb!r_^c-szp2Ahl>c`oXfNCxP1-wmTel=BK@QAmY=JXTJ znTH4A%K)#?*lgfo8~d+PD04XG^~avhWf8wg#uWOZLJd)HPJ#`j>uuU!M_T$V;Z*ld zW+f1(L-wQrK5oNY%t$M-EEfUKXjCKYUCSaM*`;t_gyc_mCfrBvq4gW0;anmhq>0Q^IJU2%6fE|E@;*D%Whlxc*NejIf$-pg2lfVbutOt!% z%V$rcoDXPZa*C*s(Mb7ipvvQ0SWbo=C}j?)8+C{Snq{9uQy!(<;I#ho8`j$WeVL76 z$22UI*1tYtDSg5a12o+{jX+1(O7LzNGf&>aX;|FeJPhWW9UDLce520DHtJUIoVr&c zXNob(@?~Lc<;n4oF86pY4Y!}|&W#)~X+ji>P7yWwF299tqfM;(MvfCA=HU)gtp&N$ z{sUe7?OG?hTvHPhxBG42M&J98w8Rnlg$^{(>W8hs7?%k+o8Jvz4EYjssJupLl9@0p z5j^j(1n2dlq{MNm?+fu1?rM!sx&G>?=o5eI=kkL){@~foyZsY{C~7sy@|3Bc%Sr<{ zX0#2x)}Mtlkh1UC^WsLnLnZ%|$TE7*;?)OAg3_{} z(nwupCS4iS9+m)3pNIhu@6MKc*7=1!Um3=sL9TfNv*rpzbeLJl4o2hoPk`Kb1#y0% ziGw1?FPurF0JjI8*{|)c><5~HmZ5uQcwX}cXKTpg?`WgPO#rW1kZr8Gg(ip1KlpC9 zDFHGL?@c?W`Xc4y2H0^qT#I#$>Cm|W6;mT>^L(XpI zU)Z*ApsyL)(v+o#aUCDLHEAs+%AzatMk5^YtpdE}ak)Yn z3!I#CC4y*Jc;ZjDCaUjVt9+W(n0_o_yrurEuG| z3LO2tz}83sacsQ6-~b;OKbl&^0lJcSde%u+4h0@h22Q1wr~;}wsz8}EXt>YQ7g2>L zpVwU0>amYp1$d5dKi>veOuvLE?0F6=3AXe|;P%P9O{-e3m4mRbz?{tw7Md#v<6fK@ z0$3wD@j-9qzp$^xEUhS=OC=V#Sm`TVdGdB%1QUX}t%eM-gNB8%k{)TY6g9PBiI86T ziu*`xEhwP-hsXIo4ABji13tD)1ZayOX#{%!-t|5I7To5Z$116NE$7Fc$4XBoHT{^t zQ$TjBe)vmdX{sUt-8PK82CMUVeDTppY5#?wR!A)Vg#qP0KfP}-1>u1oXgU4vO4@JR zCdl^UrRGt;n%Wx(Xoh_@^l|gB>zU%pA3S7P?kAAXx?H@|ek1)5l+caWG5kr-h!bcGktlO4I5l*r5EN7gbSvYd!gmJ2=Bml2^dq6F@bp ze(_34643hgJt%m@QrtC&oGpx;l(r@-{8hE6F1zH`!jk%}g|Kk`^WM`*Cl*(~bOdOH zV;IbTS8Sc>YY$#rB#x|nBA~%scT^w?v|V+hu=hzLq9FmTuH^PneMGTiy>Rt!=%&vF zM9QGkIYdz#9XlxImq-l@l&{9k(eAY~2)nhdlaBuo@oZ@kM+wQIO3@Hd&F@UjetQm= z;$^AVJnr!sk6CEvI_EawWTyLFW1-(AEHeSwgLMbLgvR3C+8yS1V~gb+&_(|D5^(LU zxuEJOG2mG~X@>;Mie)6$pFy_tvf-)vgDhJ((n593X(84#2&k+o&d6W_TdMj^Oz4!= zpv%qW?pb1(EZ)|dtU4&hy~GxKqv}&9EFmP&-K$AgkO{Xhj5gn%DH?BZ?OfnV-*H1i zb4u+Nd9i5Z=z+mk6`B|Kg;d|0;UVi4#75oY&G|nioO?Xe@B7E!n_(jwa*95tM3kH< zVWW^7a+X6mZx0aradTCI{99$@jVCOzVcTZUf-h}5 zo?}{bKfPu0Hd|5pwtX4W=a)R)E$4In4( z!f;2HmPfb06`xhvhnK;#s?1g^>o%V!oJ2wL;wESEt|8z$wl!IrI5A~{w;V4V$se~I zJub2@-gf({D0EurMt}$97}u6A`kYg2h4P*lCs*(UE8);c^Ml@%NT5!yA-585Sym+; zzP#FUi^PdmoV=$u8ZmacgI02*U)}nC!a*@1#sW}y8qRs$I@nLb996Ow1&whiH!)MY zyvdH7LQe`&gAL|6a6?veMS4(S@+4KvYR-E!_M~Y|!`iJ!2TDOily{oEQeZr zY`c&qGdM)K)5vkF#Q&Vfi=#O5#`~w?)EoBSD+S!r#4yI(z<2Q1Yq4g~*Af;Y z<8Wc%zL1x#E?1B@=k}`aZ5338D#ZaPVQzPc2*S1^3)U6*#voKXzzEyNiPeg${iMlv?8vY zOi+V9u5g86o_-G>^vqQa8(yr>FTL#rm)UYVgS2+BC=B|QCla?XUkTgYosU8vyuK_2VxYAzLS0;;&dQ5Ss23B>ivS)@<+;!bXRiCM1q%f`#|Ym-!%#&+zKlI8CgOraUa+Vd>fLVJ8sFbN@vnS9H60e z%6I^mTbt}hwYa2x5r-VzF1P)6N1z+ly~ke@d2WP6)Wp8l3)P;^0bNTMrKg6b%yPM^ zWWQyD1$K=#5NQ=zT^pQZa>d?;=z04ylzR5VVOl3twLNkascQeZ;wE;V^B{8Hhj89e zOOIexLFLNoB`^Grg9ktXmwc^D`q5$~z`p#qmk$Ebiim)9{MxYJi!k@J`JU?YX04%L zHYZC2A?gW4m8r*4Kd~kUE<4F9D1B~|%NHCIzRAljz-~KY=sahH{|ssPgn1`~O3_oO z>6C52{!qudg~UGC&r(apJPuc*aX@fRd2yR;SDfP*7gXzhF|P5fk1CAXuQch}^~w;l z6O$+weDxuw3*#}$h-}&Ve&Ge-1$WpM_Z1vfz+|aYM#`V8JbnGOVh5s^bfOGM-eyd; z-w0*-aojc=sPlbI-GgtL{ZBVJ$t2F>g8ejZXh=5sH#MuAYiZQ4B5ecFN_@(3-L+3w z^R2n6D8j4fJ7LP7>w&I1 z(;s{a)Lf|P1KNc6H@>?1A~HK{^W72$*thz*+kZyiZ@(Zhd%L23CZ}m(Z^K)d2kz@M+6`ob zs)!a^yo~!c!FzL}m+Bpqaa=?t9Gyq8L+DU!o)jx$g}-oUOAJcS)8Yf80a+X;5@fNQBfiyAj^9=j=Vik8Sr^jJKOa-43;V*-TD&uSgg z-#@-M#}Zc%unM;7-|)czi=e_9s!Z7u9r9FphM?(hezg8#6;QjcCh=;ad$k2= zo`NK{JV`+IKFQERr>_avt8v*%69@dpVy*fyYkI~_vm>@tOdB@+^?U~@ zKl(aY%yrE`x$RotsKGHWcPu7lk129F%07b5{dss^BAE^ z?w346#iV)nZr*9=fkD5*9cx-dArB$uPMg^q3`V}I zCSfl{Lm26HmNqbUkv`&OWq4E_Bf9}+TCTybKFKQ~KRnjy?nT&+99g$@-M2>yyVy)Y zSq6`#Rz&h)<0uoy(;Yoj)u8-r|R~m>UeD2L%{$Hb@huM@{_B@GPiz2-634*x` zfd}4;04ihGh4>zrVTJtD04~o4Y#kf&xDEF=QmH8}W8J4WY=J(bey~#og?+Ob9)7^{ z_l3e&d@yQ*fMjpMI|$m3KG!sN#Un;dIym1}T3;=n;7%98wLe{!s9qZA!x5y09@5Pz z?iQbS70YGIXF`=W+YPzD3*qM4ra zQ(GQ=ES;jks@26&`4Gv1J99msK5GiYX%)p>obYQUR`NsDYi{>r5ZB<4!skA{RFv0*;0)KF&Wa%nF+}z2C!Jnd~}-KR|Nd zoBMQ8On6&4XdjN4gM$9rtjcAzop-IUQ{{oz&BVYzN}S3gn1f>~!uI3|#rV+6g40Y` z^_qRBP~4>>HJ5ALTxS!&+$C%Q=p5=!(k=WH=H})l^g=IWdy+U7Tc)2ik06BU6{!h! z%QZc2_47egPGd##6fbG<_`G|t7WLVF*WfDS`tqS1x~Buoe8zVZ zx`M-r{BuL6wHIZ^XiKd^h>SHVbCeeON*~ricHZ#M+M|Lg**8wPzF>9#6%2ml3>CHU2@$!Mq0s{ zAgN3c%NgN=yW|=(>Q|;nGL;MQojq;q-jl8!Glh&NgN!Dtuz$Rb5i31@6~nV zxZz+sy5{xjNlTAb5~>aKBHv>p@@bj;9baWiQ(A%ixoXB#5L13p3qfci^h5pXiL7Ba zS3x8*cAfp1;u3P0p;0B=dF2^iGpsh>H7OA|>CGKU3Q=JGqm zM6&;MsbuF~fBalU?9;vWPw+7mi$m(1kz);q{v~IO@IW{OeAM=uKvqwEZKl~nse{So zM}{2ge!a*Qa|d$`XLRy#>T!|VrA69H7XrJUJw&0?i2V=v`$wWKw{q^{S`VZHur&B=MO{$r}>odYp4maW*@J`YX8h%m@sfY86ehf~rCnIVF8F5mdPA{2Px zn2SP!Qrcw?f#1y@Qv!#kA+?Z-C!k)6_58fdWV8}=I-Ua%I-=|2c>JU8+07@yedcDf zwTveL|3joyG_8%)x@1rlY)0#Tv?33ki1|y;BL7-fi z$$^P!RSTt2l{~suTqaxOSR)b^x!UD>SU?AU5aNinNSpDwwx-rYXN(p-3EsZY@`i@R zm}nGm>4@G$doqt`tG(ZnR>;haIb2caRRua$arcc*rw>^sIWbjF67MryS=?hySzc)d zsV%ah^%1~$H?fZ0deg!#uUl^3N-55}L6IVr>Uy5cGiRl)s7Kwj9(P>)xhReg2h}I~ zPN7&Y821x#^`)7mcCPgTlGtKQ$>HPldePVFe|x~awD>cmbP=bhz|E@t^aN|?poG*7Rjyf zhgzGIbN76-FrMP?;Fy1gZ};trRQ=pq^*7ioUA~=lF=U+lmpf@=@pv5ZWd)@+Fa+bz z1;M3HsRQ=aBl(gknXo~(^7F;%#!PR$tFd=i{(zyP;_ev&-J7<1s{(bs;&@i;`}Bu? zL&I+mqVVPstJAi?ZIz6JW>Ic7QAsI;GEnZI=X)1>=N5VsqD!SErV5;*#DO3mY)IM%Hp2WGhtozl`M@#J)94UI@$4_Mml0#+`OKkxFVID{S-|@8gHkkjSv! zIa*@mh-!&d2({(QisU(d|7VF}N<0F4?RQdLKBIz{o~3?;`$GmY5fRp$WauD zbBrE}{V(GZ5N#A6xCD}49jP7iI*6%$y&HmaIuBRwQE6ltIbXoDSN~&IZR(fhpB0MX zuQU?Q`)=feK;iAumM+NwidtCp_lVI6fm2w;#=&$V#h=SF*MU}ptSi!IxvNT|ovFlw zaVrOQrGa^(@LAS4{H(stEkFDX8Ki{WZy7_(XPj0Ig%;tzgUANRZTa zd!DaNKKy^1wQ;g?+lv_3ulnlF`UU$%Ok;L6d~vyYJh6%QZx0ra;yG<@l1V&#Y$+IY znaj<*@3`*%0Xlqkhj0DXOAK2K&H3UA+R zF0C8}h&!+e65a@N3Xlber*8o{#BsuseIzo3eSW~eybxvic~@0MFBzWqwr>!_#6)eZ z%T0t1x!!O!junQah0woEX}|1!1@fmKa&OmgBkQyetNM=@(?+fdwz2rgIQpBS_{9Ep z_BbP_K}T;Zv-6!CbJo$zOKOMihbB_3DKACC7a7hK+L-gM&rxo!q7!c}%%-)+P7ygA}rMK8gm$P8lK6IaC4p4Skzr>5a`K2^tPX!WVvhpq^2DltW)}5`$h9LM zd@`UY`H@{t{ciAXIe+89Pq{8F0oy#+ytcT=sf{iBw1O)JC{9btqjX=Fol*(P?a6y~ zWgB=qA76lTaSYa%%Ug~odc|k+A&=u_w)Ai1*T0(k!-)Jm@ZF@>V)y@+(?5wH-OVZg z9=TzVqjpZqCX_9vo2X}}=zM=mYK=k+Pw_wLGx6gCrq{Fp?G%H7$6J#3TKeq!rJ>mB z6YvIEQ&i4}JKiazy1@5@52@JliBX?aa!2-E%lm&m8mLt+0uFIv83S+!Pgl3;i# zx-;~)jS1ZHLAwV#|5pBcwJT^AZ=k1%%=0)X84Y_EXe^En29TCBLbdkK%9W{OIv_BhQmEOwBmW(dSzyBhDW+=!ra+wx8lx$H>UBEAq2&eFXCb4I27JACnjFJCT# z>uU7^Bg2V001Z8#Libm{HIM%C)|*|Qd3f&8IPrNM<87UJWy8{2#qy=-VmmC<4+^Kx zZW@bA1lvVbYTULtkXV1uit2WTb*a&JVY48prsXc8ML)fAw>1{hOh)dSc^X(+z`v{c z{3+=q>6$J+_bhcHLa=FE?7R_P1jSA&Uov$s1dv;}ki-!ST4YJE>hoQn*%cAIi775z zeOH#B*jzgJ4F8+Hpzwi7R5nxVL6^gM{HQ8<`26Lhx4qvotR6I^m!Y}CiX2ku4ivL dFu%Ou?4De5b?RBx1U_>JGBdV1Rbk{2|9@BnZ&m;R literal 0 HcmV?d00001 diff --git a/client/resources/stylesheet.qss b/client/resources/stylesheet.qss index f1ca8d06..41cc5939 100644 --- a/client/resources/stylesheet.qss +++ b/client/resources/stylesheet.qss @@ -636,13 +636,13 @@ QToolButton#btnVideo:!checked, QToolButton#btnLog:!checked, QToolButton#btnFilte border-radius: 5px; } -QToolButton#btnLog:!checked, QToolButton#btnInSessionInfos:!checked{ +QToolButton#btnLog:!checked, QToolButton#btnInSessionInfos:!checked, QToolButton#btnPause:!checked{ color:white; background-color: transparent; border: 0px; } -QToolButton#btnVideo:checked, QToolButton#btnLog:checked, QToolButton#btnInSessionInfos:checked, QToolButton#btnSearch:checked, QToolButton#btnFilterActive:checked{ +QToolButton#btnVideo:checked, QToolButton#btnLog:checked, QToolButton#btnInSessionInfos:checked, QToolButton#btnSearch:checked, QToolButton#btnFilterActive:checked, QToolButton#btnPause:checked{ color:black; background-color: gray; /*border: 2px solid white;*/ diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 710bee15..d1cc2ea1 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -5820,15 +5820,20 @@ Vous devez spécifier au moins un groupe utilisateur VideoRehabToolsWidget - + Form Form - + Reconnecter Reconnect + + + Pause + + @@ -5861,37 +5866,37 @@ Vous devez spécifier au moins un groupe utilisateur - + Enregistrer Save - + Sauvegarde du fichier... - + Vous vous apprêtez à enregistrer localement une séance vidéo. You are about to locally record a video session. - + En acceptant de poursuivre avec cet enregistrement, vous acceptez la responsabilité professionnelle, légale et éthique en lien avec la conservation, la diffusion, l'utilisation et la confidentialité requise avec ce type de média. If you accept and proceed with this recording, you accept the professional, legal and ethic responsability related to the storage, distribution, use and confidentiality required with that kind of media. - + Souhaitez-vous toujours activer l'enregistrement vidéo? Do you still want to start video recording? - + Confirmation requise Required confirmation - + Arrêter l'enregistrement Stop recording @@ -5983,37 +5988,37 @@ Vous devez spécifier au moins un groupe utilisateur Connecting... - + Fichier disponible File available - + Le fichier File - + est disponible dans le répertoire is available in the folder - + Impossible de charger la page Unable to load page - + Problème vidéo Video Problem - + Problème audio Audio Problem - + Erreur Error diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index 530a5184..49634259 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -5572,15 +5572,20 @@ Vous devez spécifier au moins un groupe utilisateur VideoRehabToolsWidget - + Form - + Reconnecter + + + Pause + + @@ -5613,37 +5618,37 @@ Vous devez spécifier au moins un groupe utilisateur - + Enregistrer - + Sauvegarde du fichier... - + Vous vous apprêtez à enregistrer localement une séance vidéo. - + En acceptant de poursuivre avec cet enregistrement, vous acceptez la responsabilité professionnelle, légale et éthique en lien avec la conservation, la diffusion, l'utilisation et la confidentialité requise avec ce type de média. - + Souhaitez-vous toujours activer l'enregistrement vidéo? - + Confirmation requise - + Arrêter l'enregistrement @@ -5735,37 +5740,37 @@ Vous devez spécifier au moins un groupe utilisateur - + Fichier disponible - + Le fichier - + est disponible dans le répertoire - + Impossible de charger la page - + Problème vidéo - + Problème audio - + Erreur diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp index 9671eecb..a250bd20 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp @@ -65,6 +65,7 @@ void VideoRehabToolsWidget::setupTools() { // Recording features ui->frameRecord->hide(); + ui->btnPause->hide(); if (m_comManager->getCurrentSessionType()){ QString session_type_config = m_comManager->getCurrentSessionType()->getFieldValue("session_type_config").toString(); if (!session_type_config.isEmpty()){ @@ -87,6 +88,7 @@ void VideoRehabToolsWidget::on_btnRecord_clicked() // Toggle button text and icon ui->btnRecord->setIcon(QIcon("://icons/record.png")); ui->btnRecord->setText(tr("Sauvegarde du fichier...")); + ui->btnPause->hide(); // Stop recording m_isRecording = false; @@ -111,9 +113,16 @@ void VideoRehabToolsWidget::on_btnRecord_clicked() // Toggle button text and icon ui->btnRecord->setIcon(QIcon("://icons/record_stop.png")); ui->btnRecord->setText(tr("Arrêter l'enregistrement")); + ui->btnPause->show(); // Start recording m_isRecording = true; dynamic_cast(m_baseWidget)->startRecording(); } } + +void VideoRehabToolsWidget::on_btnPause_clicked() +{ + dynamic_cast(m_baseWidget)->pauseRecording(); +} + diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h index 8faafe23..56a301d0 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h @@ -28,6 +28,8 @@ private slots: void on_btnRecord_clicked(); + void on_btnPause_clicked(); + private: void setupTools(); diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.ui b/client/src/services/VideoRehabService/VideoRehabToolsWidget.ui index dcb056a4..4d9157e8 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.ui +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.ui @@ -131,6 +131,47 @@ + + + + + 48 + 48 + + + + + 128 + 60 + + + + PointingHandCursor + + + Pause + + + + :/icons/pause.png:/icons/pause.png + + + + 40 + 40 + + + + true + + + false + + + Qt::ToolButtonTextUnderIcon + + + diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.cpp b/client/src/services/VideoRehabService/VideoRehabWidget.cpp index d50f8fb1..aa6c285b 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabWidget.cpp @@ -138,6 +138,13 @@ void VideoRehabWidget::stopRecording() } } +void VideoRehabWidget::pauseRecording() +{ + if (m_webPage){ + m_webPage->getSharedObject()->pauseRecording(); + } +} + void VideoRehabWidget::setDataSavePath() { QString current_user_uuid = m_comManager->getCurrentUser().getUuid(); diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.h b/client/src/services/VideoRehabService/VideoRehabWidget.h index 31962345..fe29759e 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabWidget.h @@ -37,6 +37,7 @@ class VideoRehabWidget : public BaseServiceWidget void startRecording(); void stopRecording(); + void pauseRecording(); void setDataSavePath() override; // Will update download path based on the user settings diff --git a/client/src/services/VideoRehabService/WebSocket/SharedObject.cpp b/client/src/services/VideoRehabService/WebSocket/SharedObject.cpp index a62bde47..b6f7a98e 100644 --- a/client/src/services/VideoRehabService/WebSocket/SharedObject.cpp +++ b/client/src/services/VideoRehabService/WebSocket/SharedObject.cpp @@ -457,6 +457,11 @@ void SharedObject::stopRecording() emit stopRecordingRequested(); } +void SharedObject::pauseRecording() +{ + emit pauseRecordingRequested(); +} + bool SharedObject::getLocalMirror(){ return m_localMirror; //emit setLocalMirrorSignal(m_localMirror); diff --git a/client/src/services/VideoRehabService/WebSocket/SharedObject.h b/client/src/services/VideoRehabService/WebSocket/SharedObject.h index e42d8516..f074c54e 100644 --- a/client/src/services/VideoRehabService/WebSocket/SharedObject.h +++ b/client/src/services/VideoRehabService/WebSocket/SharedObject.h @@ -54,6 +54,7 @@ class SharedObject : public QObject void startRecording(); void stopRecording(); + void pauseRecording(); public slots: @@ -129,6 +130,7 @@ public slots: void startRecordingRequested(); void stopRecordingRequested(); + void pauseRecordingRequested(); private slots: void camImageSettingsChanged(); From 231ac8caa358f8ad4d7a1e32916378957aec71c4 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 26 Jan 2023 11:22:43 -0500 Subject: [PATCH 23/42] Refs #84. Added disconnect menu to disconnect online users, participants and devices. --- client/src/main/MainWindow.cpp | 2 +- client/src/managers/ComManager.cpp | 3 + client/src/widgets/OnlineManagerWidget.cpp | 82 +++++++++++++++++++++- client/src/widgets/OnlineManagerWidget.h | 16 +++-- client/src/widgets/OnlineManagerWidget.ui | 4 -- shared/src/WebAPI.h | 1 + 6 files changed, 94 insertions(+), 14 deletions(-) diff --git a/client/src/main/MainWindow.cpp b/client/src/main/MainWindow.cpp index b6222dca..ff39ca4e 100644 --- a/client/src/main/MainWindow.cpp +++ b/client/src/main/MainWindow.cpp @@ -669,7 +669,7 @@ void MainWindow::com_posting(QString path, QString data) void MainWindow::com_querying(QString path) { - if (path != WEB_FORMS_PATH && path != WEB_LOGOUT_PATH && path != WEB_REFRESH_TOKEN_PATH){ + if (path != WEB_FORMS_PATH && path != WEB_LOGOUT_PATH && path != WEB_REFRESH_TOKEN_PATH && path != WEB_DISCONNECT_PATH){ QString data_type = TeraData::getDataTypeNameText(TeraData::getDataTypeFromPath(path)); if (!data_type.isEmpty()){ GlobalEvent event(EVENT_DATA_QUERY, tr("Récupération de ") + data_type + "..."); diff --git a/client/src/managers/ComManager.cpp b/client/src/managers/ComManager.cpp index 2a72a109..9f6df7be 100644 --- a/client/src/managers/ComManager.cpp +++ b/client/src/managers/ComManager.cpp @@ -94,6 +94,9 @@ bool ComManager::processNetworkReply(QNetworkReply *reply) if (handled) emit queryResultsOK(reply_path, reply_query); } + if (reply_path == WEB_DISCONNECT_PATH) + handled = true; + if (!handled){ // General case handled=handleDataReply(reply_path, reply_data, reply_query); diff --git a/client/src/widgets/OnlineManagerWidget.cpp b/client/src/widgets/OnlineManagerWidget.cpp index a0f024b3..1a7dc3b6 100644 --- a/client/src/widgets/OnlineManagerWidget.cpp +++ b/client/src/widgets/OnlineManagerWidget.cpp @@ -1,6 +1,7 @@ #include "OnlineManagerWidget.h" #include "ui_OnlineManagerWidget.h" #include "TeraSettings.h" +#include "GlobalMessageBox.h" // Must be reset each time a site is changed. // 1. Query online * using APIs @@ -16,6 +17,7 @@ OnlineManagerWidget::OnlineManagerWidget(QWidget *parent) : m_baseDevices = nullptr; m_baseUsers = nullptr; m_baseParticipants = nullptr; + m_actionsMenu = nullptr; ui->setupUi(this); @@ -55,8 +57,6 @@ void OnlineManagerWidget::setComManager(ComManager *comMan) on_btnFilterParticipants_clicked(); on_btnFilterUsers_clicked(); on_btnFilterDevices_clicked(); - - } void OnlineManagerWidget::setCurrentSite(const QString &site_name, const int &site_id) @@ -70,6 +70,7 @@ void OnlineManagerWidget::setCurrentSite(const QString &site_name, const int &si void OnlineManagerWidget::initUi() { ui->treeOnline->clear(); + ui->treeOnline->setContextMenuPolicy(Qt::CustomContextMenu); m_baseParticipants = new QTreeWidgetItem(); m_baseParticipants->setText(0, tr("Participants")); @@ -102,6 +103,8 @@ void OnlineManagerWidget::connectSignals() connect(m_comManager, &ComManager::onlineDevicesReceived, this, &OnlineManagerWidget::processOnlineDevices); connect(m_comManager, &ComManager::onlineParticipantsReceived, this, &OnlineManagerWidget::processOnlineParticipants); connect(m_comManager, &ComManager::onlineUsersReceived, this, &OnlineManagerWidget::processOnlineUsers); + + connect(m_comManager, &ComManager::currentUserUpdated, this, &OnlineManagerWidget::currentUserWasUpdated); } void OnlineManagerWidget::refreshOnlines() @@ -417,10 +420,25 @@ void OnlineManagerWidget::processOnlineDevices(QList devices) updateCounts(); } +void OnlineManagerWidget::currentUserWasUpdated() +{ + if (m_comManager->isCurrentUserSuperAdmin()){ + if (!m_actionsMenu){ + m_actionsMenu = new QMenu(this); + + QAction* disconnectAction = new QAction(QIcon(":/icons/delete_old.png"), tr("Déconnecter"), m_actionsMenu); + connect(disconnectAction, &QAction::triggered, this, &OnlineManagerWidget::disconnectItemRequested); + m_actionsMenu->addAction(disconnectAction); + + } + } +} + void OnlineManagerWidget::on_treeOnline_itemClicked(QTreeWidgetItem *item, int column) { - if (!item || item == m_baseDevices || item == m_baseParticipants || item == m_baseUsers) + if (!item || item == m_baseDevices || item == m_baseParticipants || item == m_baseUsers){ return; + } item->setSelected(false); QString uuid; @@ -471,3 +489,61 @@ void OnlineManagerWidget::on_btnFilterDevices_clicked() TeraSettings::setUserSetting(m_comManager->getCurrentUser().getUuid(), SETTINGS_UI_ONLINEFILTERDEVICES, ui->btnFilterDevices->isChecked()); } +void OnlineManagerWidget::on_treeOnline_customContextMenuRequested(const QPoint &pos) +{ + if (!m_actionsMenu) + return; + + QTreeWidgetItem* pointed_item = ui->treeOnline->itemAt(pos); + if (!pointed_item){ + return; + } + + if (!pointed_item->parent()) + return; // Top level item = category = no action here. + + // Get item uuid + QString uuid; + if (!m_onlineDevices.key(pointed_item).isEmpty()){ + uuid = m_onlineDevices.key(pointed_item); + } + if (!m_onlineUsers.key(pointed_item).isEmpty()){ + uuid = m_onlineUsers.key(pointed_item); + } + if (!m_onlineParticipants.key(pointed_item).isEmpty()){ + uuid = m_onlineParticipants.key(pointed_item); + } + + if (uuid == m_comManager->getCurrentUser().getUuid()) + return; // Ourselves - can't disconnect!! + + m_actionsMenu->setProperty("uuid", uuid); + + m_actionsMenu->exec(ui->treeOnline->mapToGlobal(pos)); +} + +void OnlineManagerWidget::disconnectItemRequested() +{ + QString uuid = m_actionsMenu->property("uuid").toString(); + QUrlQuery args; + QString name; + + if (m_onlineDevices.contains(uuid)){ + name = m_onlineDevices[uuid]->text(0); + args.addQueryItem(WEB_QUERY_UUID_DEVICE, uuid); + } + if (m_onlineParticipants.contains(uuid)){ + name = m_onlineParticipants[uuid]->text(0); + args.addQueryItem(WEB_QUERY_UUID_PARTICIPANT, uuid); + } + if (m_onlineUsers.contains(uuid)){ + name = m_onlineUsers[uuid]->text(0); + args.addQueryItem(WEB_QUERY_UUID_USER, uuid); + } + + GlobalMessageBox msg; + if (msg.showYesNo(tr("Déconnecter?"), tr("Êtes-vous sûrs de vouloir déconnecter") + " " + name + "?") == GlobalMessageBox::Yes){ + m_comManager->doGet(WEB_DISCONNECT_PATH, args); + } +} + diff --git a/client/src/widgets/OnlineManagerWidget.h b/client/src/widgets/OnlineManagerWidget.h index 47cbfbbd..2f18aa11 100644 --- a/client/src/widgets/OnlineManagerWidget.h +++ b/client/src/widgets/OnlineManagerWidget.h @@ -3,6 +3,7 @@ #include #include +#include #include "managers/ComManager.h" @@ -27,6 +28,7 @@ class OnlineManagerWidget : public QWidget ComManager *m_comManager; QString m_siteName; int m_siteId; + QMenu* m_actionsMenu; // Base tree items QTreeWidgetItem* m_baseUsers; @@ -56,22 +58,24 @@ class OnlineManagerWidget : public QWidget void createOnlineDevice(const QString& uuid, const QString& name); private slots: - void ws_userEvent(UserEvent event); - void ws_participantEvent(ParticipantEvent event); - void ws_deviceEvent(DeviceEvent event); + void ws_userEvent(opentera::protobuf::UserEvent event); + void ws_participantEvent(opentera::protobuf::ParticipantEvent event); + void ws_deviceEvent(opentera::protobuf::DeviceEvent event); void processOnlineUsers(QList users); void processOnlineParticipants(QList participants); void processOnlineDevices(QList devices); - void on_treeOnline_itemClicked(QTreeWidgetItem *item, int column); + void currentUserWasUpdated(); + void on_treeOnline_itemClicked(QTreeWidgetItem *item, int column); void on_btnFilterParticipants_clicked(); - void on_btnFilterUsers_clicked(); - void on_btnFilterDevices_clicked(); + void on_treeOnline_customContextMenuRequested(const QPoint &pos); + void disconnectItemRequested(); + signals: void dataDisplayRequest(TeraDataTypes data_type, QString data_uuid); void totalCountUpdated(int count); diff --git a/client/src/widgets/OnlineManagerWidget.ui b/client/src/widgets/OnlineManagerWidget.ui index 9c6bc09f..deb78aa2 100644 --- a/client/src/widgets/OnlineManagerWidget.ui +++ b/client/src/widgets/OnlineManagerWidget.ui @@ -30,7 +30,6 @@ 9 - 75 true @@ -173,7 +172,6 @@ 9 - 75 true @@ -193,7 +191,6 @@ 9 - 75 true @@ -255,7 +252,6 @@ - diff --git a/shared/src/WebAPI.h b/shared/src/WebAPI.h index 3f2e02ae..42181339 100755 --- a/shared/src/WebAPI.h +++ b/shared/src/WebAPI.h @@ -39,6 +39,7 @@ #define WEB_TESTTYPESITE_PATH "/api/user/testtypes/sites" #define WEB_TESTTYPEPROJECT_PATH "/api/user/testtypes/projects" #define WEB_TESTINFO_PATH "/api/user/tests" +#define WEB_DISCONNECT_PATH "/api/user/disconnect" #define WEB_SESSIONMANAGER_PATH "/api/user/sessions/manager" From 0e88ad22004e4787d4fd042a7036e495be0f5c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20Le=CC=81tourneau?= Date: Wed, 15 Feb 2023 08:43:18 -0500 Subject: [PATCH 24/42] Added missing authors --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fea510fa..e2c1f00f 100755 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ OpenTeraPlus is a client that works with [OpenTera Server](https://github.com/in * Simon Brière, ing. M.Sc.A., Research Center on Aging, CIUSSS de l'Estrie-CHUS (@sbriere) * Dominic Létourneau, ing. M.Sc.A., IntRoLab, Université de Sherbrooke (@doumdi) +* François Michaud, ing. Ph.D., IntRoLab, Université de Sherbrooke +* Michel Tousignant, pht, Ph.D., CDRV, Université de Sherbrooke ## Publication(s) From 39feb2b28a8ed1f14e650eaaf67f98280bb70599 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 20 Feb 2023 10:05:52 -0500 Subject: [PATCH 25/42] Fixed display bugs. --- .../resources/translations/openteraplus_en.ts | 81 +++++++++++-------- .../resources/translations/openteraplus_fr.ts | 81 +++++++++++-------- client/src/editors/DataEditorWidget.cpp | 3 + client/src/services/BaseServiceWidget.h | 2 - .../VideoRehabToolsWidget.cpp | 8 +- .../VideoRehabService/VideoRehabToolsWidget.h | 6 +- .../VideoRehabService/VideoRehabWidget.cpp | 4 +- .../VideoRehabService/VideoRehabWidget.h | 6 +- client/src/widgets/InSessionWidget.cpp | 3 + client/src/widgets/ProjectNavigator.cpp | 9 ++- 10 files changed, 123 insertions(+), 80 deletions(-) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index d1cc2ea1..9524201d 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -445,22 +445,22 @@ ComManager - + L'utilisateur est déjà connecté. User is already logged on. - + Erreur inconnue Unknown error - + Utilisateur ou mot de passe invalide. Invalid username or password. - + La communication avec le serveur n'a pu être établie. Communication with the server couldn't be established. @@ -842,27 +842,27 @@ DataEditorWidget - + Administrateur Administrator - + Utilisateur User - + Aucun rôle No Role - + Les champs suivants doivent être complétés: The following fields must be entered: - + Champs invalides Invalid fields @@ -1637,18 +1637,18 @@ or Completed Reason: - + Service non-supporté Unsupported service - + Le service " The service " - - + + " n'est pas gérée par cette version du logiciel. Veuillez vérifier si une mise à jour existe ou contribuez au développement du logiciel! @@ -1656,27 +1656,27 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du Please update the software or contribute to the development! - + Catégorie de séance non-supportée Session category is not supported - + La catégorie de séance " The session category " - + Quitter la séance? Quit session? - + Désirez-vous quitter la séance? Do you want to qui the session? - + Répertoire où les données seront enregistrées @@ -2733,59 +2733,74 @@ Would you like to disconnect to apply the changes? OnlineManagerWidget - + Form Form - + + - 0 0 - + Filtrer appareils Filter devices - - - + + + ... ... - + Filtrer utilisateurs Filter users - + Filtrer participants Filter Participants - + Items Items - + Participants Participants - + Utilisateurs Users - + Appareils Devices + + + Déconnecter + + + + + Déconnecter? + + + + + Êtes-vous sûrs de vouloir déconnecter + + ParticipantWidget @@ -3300,12 +3315,12 @@ Would you like to continue this session? Devices - + Suppression? Delete? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index 49634259..e5c5e840 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -445,22 +445,22 @@ ComManager - + L'utilisateur est déjà connecté. - + Erreur inconnue - + Utilisateur ou mot de passe invalide. - + La communication avec le serveur n'a pu être établie. @@ -838,27 +838,27 @@ DataEditorWidget - + Administrateur - + Utilisateur - + Aucun rôle - + Les champs suivants doivent être complétés: - + Champs invalides @@ -1611,45 +1611,45 @@ ou réalisées - + Service non-supporté - + Le service " - - + + " n'est pas gérée par cette version du logiciel. Veuillez vérifier si une mise à jour existe ou contribuez au développement du logiciel! - + Catégorie de séance non-supportée - + La catégorie de séance " - + Quitter la séance? - + Désirez-vous quitter la séance? - + Répertoire où les données seront enregistrées @@ -2689,59 +2689,74 @@ Souhaitez-vous vous déconnecter pour appliquer les changements? OnlineManagerWidget - + Form - + + - 0 - + Filtrer appareils - - - + + + ... - + Filtrer utilisateurs - + Filtrer participants - + Items - + Participants - + Utilisateurs - + Appareils + + + Déconnecter + + + + + Déconnecter? + + + + + Êtes-vous sûrs de vouloir déconnecter + + ParticipantWidget @@ -3118,12 +3133,12 @@ Souhaitez-vous continuer cette séance? - + Suppression? - + Êtes-vous sûrs de vouloir supprimer diff --git a/client/src/editors/DataEditorWidget.cpp b/client/src/editors/DataEditorWidget.cpp index 2d3d56b6..b1d34631 100644 --- a/client/src/editors/DataEditorWidget.cpp +++ b/client/src/editors/DataEditorWidget.cpp @@ -1,5 +1,7 @@ #include "DataEditorWidget.h" #include +#include + #include "GlobalMessageBox.h" DataEditorWidget::DataEditorWidget(ComManager *comMan, const TeraData *data, QWidget *parent) : @@ -229,6 +231,7 @@ QComboBox *DataEditorWidget::buildRolesComboBox() item_roles->addItem(getRoleName("admin"), "admin"); item_roles->addItem(getRoleName("user"), "user"); item_roles->setCurrentIndex(0); + item_roles->setItemDelegate(new QStyledItemDelegate(item_roles)); return item_roles; diff --git a/client/src/services/BaseServiceWidget.h b/client/src/services/BaseServiceWidget.h index 08f83285..5835af0d 100644 --- a/client/src/services/BaseServiceWidget.h +++ b/client/src/services/BaseServiceWidget.h @@ -22,11 +22,9 @@ class BaseServiceWidget : public QWidget protected: ComManager* m_comManager; - TeraData* m_session; signals: - void widgetIsReady(bool ready_state); }; diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp index a250bd20..e5420acb 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.cpp @@ -20,7 +20,7 @@ VideoRehabToolsWidget::~VideoRehabToolsWidget() bool VideoRehabToolsWidget::sessionCanBeEnded(const bool &displayConfirmation) { - if (m_isRecording || !ui->btnRecord->isEnabled()){ // Recording or downloading file + if (m_isRecording || m_isDownloading/*!ui->btnRecord->isEnabled()*/){ // Recording or downloading file if (displayConfirmation){ GlobalMessageBox msg_box; if (msg_box.showYesNo(tr("Enregistrement en cours"), tr("Un enregistrement de la séance est en cours.") + "\n\n" + tr("Si vous continuez, l'enregistrement pourrait être perdu.") + "\n\n" + tr("Êtes-vous sûrs de vouloir continuer?")) == GlobalMessageBox::Yes){ @@ -126,3 +126,9 @@ void VideoRehabToolsWidget::on_btnPause_clicked() dynamic_cast(m_baseWidget)->pauseRecording(); } +void VideoRehabToolsWidget::setFileDownloading(bool downloading) +{ + m_isDownloading = downloading; + setReadyState(!downloading); +} + diff --git a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h index 56a301d0..e73f3d59 100644 --- a/client/src/services/VideoRehabService/VideoRehabToolsWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabToolsWidget.h @@ -25,11 +25,12 @@ public slots: private slots: void on_btnReconnect_clicked(); - void on_btnRecord_clicked(); - void on_btnPause_clicked(); +public slots: + void setFileDownloading(bool downloading); + private: void setupTools(); @@ -37,6 +38,7 @@ private slots: bool m_isRecording; bool m_recordWarningShown; + bool m_isDownloading = false; }; diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.cpp b/client/src/services/VideoRehabService/VideoRehabWidget.cpp index aa6c285b..537532e5 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.cpp +++ b/client/src/services/VideoRehabService/VideoRehabWidget.cpp @@ -180,7 +180,7 @@ void VideoRehabWidget::webEngineURLChanged(QUrl url) void VideoRehabWidget::webEngineDownloadRequested(QWebEngineDownloadItem *item) { //qDebug() << "WebEngine: about to download " << item->suggestedFileName(); - emit widgetIsReady(false); + emit fileDownloading(true); // Rework filename QString file_name = item->suggestedFileName(); @@ -218,7 +218,7 @@ void VideoRehabWidget::webEngineDownloadRequested(QWebEngineDownloadItem *item) void VideoRehabWidget::webEngineDownloadCompleted() { // Enable buttons - emit widgetIsReady(true); + emit fileDownloading(false); QWebEngineDownloadItem* item = dynamic_cast(sender()); if (item){ diff --git a/client/src/services/VideoRehabService/VideoRehabWidget.h b/client/src/services/VideoRehabService/VideoRehabWidget.h index fe29759e..18fd83fe 100644 --- a/client/src/services/VideoRehabService/VideoRehabWidget.h +++ b/client/src/services/VideoRehabService/VideoRehabWidget.h @@ -11,12 +11,9 @@ #include "VideoRehabWebPage.h" #include "Utils.h" - #include "VirtualCameraThread.h" - #include "JoinSessionEvent.pb.h" - namespace Ui { class VideoRehabWidget; } @@ -75,6 +72,9 @@ private slots: VideoRehabWebPage* m_webPage; QMovie* m_loadingIcon; VirtualCameraThread* m_virtualCamThread; + + signals: + void fileDownloading(bool downloading); }; #endif // VIDEOREHABWIDGET_H diff --git a/client/src/widgets/InSessionWidget.cpp b/client/src/widgets/InSessionWidget.cpp index 3ec73aed..485bca32 100644 --- a/client/src/widgets/InSessionWidget.cpp +++ b/client/src/widgets/InSessionWidget.cpp @@ -489,6 +489,9 @@ void InSessionWidget::initUI() setMainWidget(m_serviceWidget); m_serviceToolsWidget = new VideoRehabToolsWidget(m_comManager, m_serviceWidget, this); setToolsWidget(m_serviceToolsWidget); + connect(dynamic_cast(m_serviceWidget), &VideoRehabWidget::fileDownloading, + dynamic_cast(m_serviceToolsWidget), &VideoRehabToolsWidget::setFileDownloading); + QString service_config = m_sessionType.getFieldValue("session_type_config").toString(); QJsonDocument doc = QJsonDocument::fromJson(service_config.toUtf8()); if (!doc.isNull()){ diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 3611c53a..0b5a72e9 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -908,8 +908,6 @@ void ProjectNavigator::updateParticipant(const TeraData *participant) // Select current participant setCurrentItem(item); } - - } void ProjectNavigator::updateUser(const TeraData *user, const int& id_project) @@ -1426,8 +1424,8 @@ void ProjectNavigator::processParticipantsReply(const QList participan void ProjectNavigator::processUsersReply(const QList users, const QUrlQuery reply_args) { - /*if (!reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)) - return;*/ + if (!isAdvancedView()) + return; int id_project = 0; if (reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)){ @@ -1440,6 +1438,9 @@ void ProjectNavigator::processUsersReply(const QList users, const QUrl void ProjectNavigator::processDevicesReply(const QList devices, const QUrlQuery reply_args) { + if (!isAdvancedView()) + return; + int id_project = 0; if (reply_args.hasQueryItem(WEB_QUERY_ID_PROJECT)){ id_project = reply_args.queryItemValue(WEB_QUERY_ID_PROJECT).toInt(); From 93412bdcea63d829d6da8b6c7471c7471fa07c9b Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 20 Feb 2023 11:08:57 -0500 Subject: [PATCH 26/42] Updated translations. --- .../resources/translations/openteraplus_en.ts | 516 +++++------------- .../resources/translations/openteraplus_fr.ts | 86 +-- client/src/editors/DeviceSummaryWidget.ui | 2 +- client/src/editors/UserSummaryWidget.ui | 2 +- client/src/widgets/LogViewWidget.ui | 12 +- client/src/widgets/SessionsListWidget.ui | 10 +- 6 files changed, 178 insertions(+), 450 deletions(-) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 9524201d..418d9ab2 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -525,25 +525,21 @@ Paramètres - + Settings Événements - Events + Events Journal d'accès - + Access Log DanceConfigWidget - - Vidéos (*.mp4 *.webm *.mkv *.avi) - Videos (*.mp4 *.webm *.mkv *.avi) - Vidéos (*.mp4 *.m4v *.mpg *.webm *.mkv *.avi) @@ -1013,59 +1009,59 @@ dissociating DeviceSummaryWidget - Form - Form + Résumé appareil + Device Summary Appareil - Device + Device Nouvelle Séance - New Session + New Session Cet appareil ne peut être mis en ligne - impossible de réaliser une nouvelle séance avec celui-ci. - + This device isn't onlineable - can't start a new session. Séances - Sessions + Sessions Résumé - Summary + Summary Informations - + Informations Éditer - Edit + Edit Sauvegarder - Save + Save Annuler - Cancel + Cancel Journal d'accès - + Access Log @@ -1118,7 +1114,7 @@ dissociating Journal d'accès - + Access Log @@ -1678,7 +1674,7 @@ Please update the software or contribute to the development! Répertoire où les données seront enregistrées - + Folder to save data @@ -1708,30 +1704,22 @@ Please update the software or contribute to the development! Données - + Data Répertoire d'enregistrement - + Data recording folder Défaut - + Default Parcourir... - Browse... - - - Inivités - Attendees - - - Paramètres - séance - Session - Parameters + Browse... @@ -2064,235 +2052,205 @@ Please update the software or contribute to the development! LogViewWidget - Form - Form - - - + Log Viewer - + Log Viewer - + Début - Start - - - - - dd-MM-yyyy - + Start - + Fin - End + End - + Aucun résultat n'est disponible. - - - - - xxx - - - - - événements - - - - - <<< - - - - - / 1 - + No result available. - >>> - + événements + events - - + + Type - Type + Type - + Filtrer - Filter + Filter - + Rafraichir - Refresh + Refresh - + Date - Date + Date - + Heure - Time + Time - + Message - + Message Inconnu - Unknown + Unknown Trace - + Qui - + Who Appareil - Device + Device Participant - Participant + Participant Utilisateur - User + User État - State + State Système - + System Client - + Client Ressource - + Resource Tous - All + All Debug - + Debug Information - Information + Information Avertissement - Warning + Warning Critique - + Critical Erreur - Error + Error Fatal - + Fatal Jeton - + Token Mot de passe - Password + Password Certificat - + Certificate Succès - + Success Mauvais mot de passe - + Wrong password Mauvais code utilisateur - + Wrong username Nombre maximum d'essais atteint - + Maximum number of trials reached Déjà connecté - + Already connected Jeton invalide - + Invalid token Compte désactivé - + Disabled account Jeton expiré - + Expired token @@ -2606,17 +2564,17 @@ The session cannot continue. Séance en cours - + Current session La séance en cours empêche la fermeture du logiciel. - + Current session is preventing software exit. Veuillez la terminer avant de poursuivre. - + Please end the session before continuing. @@ -2789,61 +2747,21 @@ Would you like to disconnect to apply the changes? Déconnecter - + Disconnect Déconnecter? - + Disconnect? Êtes-vous sûrs de vouloir déconnecter - + Are you sure you want to disconnect ParticipantWidget - - Ouvrir - Open - - - Supprimer - Delete - - - Continuer la séance - Continue session - - - Appareil: - Device: - - - Participant: - Participant: - - - Service: - Service: - - - Inconnu - Unknown - - - Suppression? - Delete? - - - Êtes-vous sûrs de vouloir supprimer - Are you sure you want to delete - - - Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? - Are you sure you want to delete all selected sessions ? - Déassignation? @@ -2854,10 +2772,6 @@ Would you like to disconnect to apply the changes? Êtes-vous sûrs de vouloir désassigner Are you sure you want to unassign - - Séance - Session - Confirmation @@ -2881,10 +2795,6 @@ Do you want to continue? Code utilisateur manquant<br/> Username missing<br/> - - Nouvelle séance - New session - Participant désactivé @@ -2915,23 +2825,15 @@ Do you want to continue? Type de séance inconnu Unknown session type - - Voir les données - View assets - - - Voir les évaluations - View tests - Impossible de démarrer cette séance - + Unable to start this session Impossible de démarrer cette séance. - + Unable to start this session. @@ -2982,22 +2884,6 @@ Souhaitez-vous continuer cette séance? Would you like to continue this session? - - Démarrer une nouvelle séance - Start a new session - - - Le participant n'a pas d'accès (web ou identification). - Participant doesn't have any access (web or login). - - - Explorateur de données - Assets explorer - - - Explorateur d'évaluations - Tests explorer - Code QR du lien @@ -3098,7 +2984,7 @@ Would you like to continue this session? Configuration - Configuration + Configuration @@ -3106,58 +2992,6 @@ Would you like to continue this session? Informations Informations - - Chargement des séances: %p% - Loading sessions: %p% - - - Mois 1 - Month 1 - - - Mois 2 - Month 2 - - - Mois 3 - Month 3 - - - Tout cocher - Select All - - - Tout décocher - Deselect All - - - Date - Date - - - Type - Type - - - État - State - - - Durée - Duration - - - Responsable - Owner - - - Actions - Actions - - - Nouvelle - New - Résumé @@ -3168,10 +3002,6 @@ Would you like to continue this session? Paramètres Settings - - Services - Services - @@ -3191,7 +3021,7 @@ Would you like to continue this session? Journal d'accès - + Access Log @@ -3218,10 +3048,6 @@ Would you like to continue this session? Appareils Devices - - Filtrer les séances - Filter sessions - PasswordStrengthDialog @@ -3302,17 +3128,17 @@ Would you like to continue this session? Participants - Participants + Participants Utilisateurs - Users + Users Appareils - Devices + Devices @@ -3364,12 +3190,12 @@ Would you like to continue this session? Vue étendue - + Advanced view Avancé - + Advanced @@ -3927,14 +3753,6 @@ Do you want to continue? Retirer de la séance Remove from session - - Nombre d'invités atteint - Maximum attendees reached - - - Impossible d'ajouter ces invités à la séance: le nombre maximal de participants (5) serait dépassé - Cannot add more than five (5) participants - Nombre maximal d'invités atteint @@ -4220,170 +4038,162 @@ You must select at least one site. Suppression? - + Delete? Êtes-vous sûrs de vouloir supprimer - Are you sure you want to delete + Are you sure you want to delete Êtes-vous sûrs de vouloir supprimer toutes les séances sélectionnées? - Are you sure you want to delete all selected sessions ? + Are you sure you want to delete all selected sessions ? Nouvelle séance - New session + New session Séance - Session + Session Ouvrir - Open + Open Supprimer - Delete + Delete Voir les données - View assets + View assets Voir les évaluations - View tests + View tests Continuer la séance - Continue session + Continue session Appareil: - Device: + Device: Participant: - Participant: + Participant: Service: - Service: + Service: Inconnu - Unknown + Unknown Explorateur de données - Assets explorer + Assets explorer Explorateur d'évaluations - Tests explorer + Tests explorer - - Form - Form + + Liste des séances + Sessions list - + Chargement des séances: %p% - Loading sessions: %p% - - - - - - - ... - ... + Loading sessions: %p% - + Mois 1 - Month 1 + Month 1 - + Mois 2 - Month 2 + Month 2 - + Mois 3 - Month 3 + Month 3 - + Tout cocher - Select All + Select All - + Tout décocher - Deselect All + Deselect All Date - Date + Date Type - Type + Type État - State + State Durée - Duration + Duration Responsable - Owner + Owner Actions - Actions + Actions Filtrer les séances - Filter sessions + Filter sessions Nouvelle - New + New @@ -4700,10 +4510,6 @@ realized sessions Type de séance Session type - - Type d'évaluation - Test type - @@ -4848,12 +4654,12 @@ realized sessions Journal: Connexion - + Log: Access Journal: Général - + Log: General @@ -5255,10 +5061,6 @@ You must select at least one site. Exporter Export - - Tout télécharger - Download all - TransferProgressDialog @@ -5445,14 +5247,10 @@ You must select at least one site. UserSummaryWidget - - N/D - N/A - - Form - Form + Résumé Utilisateur + User Summary @@ -5467,63 +5265,27 @@ You must select at least one site. Séances - Sessions + Sessions Résumé - Summary + Summary Sauvegarder - Save + Save Annuler - Cancel + Cancel Journal d'accès - - - - Général - General - - - (Dernière connexion) - (Last connection) - - - (Nom complet) - (Complete name) - - - Dernière connexion: - Last connection: - - - Nom: - Name: - - - Actif - Active - - - Contact - Contact - - - Courriel: - Email: - - - (Courriel) - (Email) + Access Log @@ -5548,7 +5310,7 @@ You must select at least one site. Journal d'accès - + Access Log @@ -5847,7 +5609,7 @@ Vous devez spécifier au moins un groupe utilisateur Pause - + Pause @@ -5877,7 +5639,7 @@ Vous devez spécifier au moins un groupe utilisateur Êtes-vous vraiment sûrs de vouloir arrêter l'enregistrement en cours? - + Are you sure you want to stop the current recording? @@ -5888,7 +5650,7 @@ Vous devez spécifier au moins un groupe utilisateur Sauvegarde du fichier... - + Saving file... diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index e5c5e840..d97f84eb 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -1006,7 +1006,7 @@ désassocier DeviceSummaryWidget - Form + Résumé appareil @@ -2028,87 +2028,61 @@ Veuillez vérifier si une mise à jour existe ou contribuez au développement du LogViewWidget - + Log Viewer - + Début - - - dd-MM-yyyy - - - - + Fin - + Aucun résultat n'est disponible. - - - xxx - - - - - événements - - - - - <<< - - - - - / 1 - - - >>> + événements - - + + Type - + Filtrer - + Rafraichir - + Date - + Heure - + Message @@ -4108,45 +4082,37 @@ Vous devez associer au moins un site. - - Form + + Liste des séances - + Chargement des séances: %p% - - - - - ... - - - - + Mois 1 - + Mois 2 - + Mois 3 - + Tout cocher - + Tout décocher @@ -5237,11 +5203,6 @@ Vous devez associer au moins un site. UserSummaryWidget - - - Form - - Utilisateur @@ -5282,6 +5243,11 @@ Vous devez associer au moins un site. Éditer + + + Résumé Utilisateur + + diff --git a/client/src/editors/DeviceSummaryWidget.ui b/client/src/editors/DeviceSummaryWidget.ui index a8c569ef..e7859fa7 100644 --- a/client/src/editors/DeviceSummaryWidget.ui +++ b/client/src/editors/DeviceSummaryWidget.ui @@ -11,7 +11,7 @@ - Form + Résumé appareil QLabel#lblFullNameValue, QLabel#lblLastOnlineValue, QLabel#lblEmailValue{ diff --git a/client/src/editors/UserSummaryWidget.ui b/client/src/editors/UserSummaryWidget.ui index ca0537e8..0c134cb5 100644 --- a/client/src/editors/UserSummaryWidget.ui +++ b/client/src/editors/UserSummaryWidget.ui @@ -11,7 +11,7 @@ - Form + Résumé Utilisateur QLabel#lblFullNameValue, QLabel#lblLastOnlineValue, QLabel#lblEmailValue{ diff --git a/client/src/widgets/LogViewWidget.ui b/client/src/widgets/LogViewWidget.ui index 44294543..b9bcd48b 100644 --- a/client/src/widgets/LogViewWidget.ui +++ b/client/src/widgets/LogViewWidget.ui @@ -47,7 +47,7 @@ true - dd-MM-yyyy + dd-MM-yyyy true @@ -95,7 +95,7 @@ false - dd-MM-yyyy + dd-MM-yyyy true @@ -265,7 +265,7 @@ - xxx + xxx @@ -295,7 +295,7 @@ PointingHandCursor - <<< + <<< @@ -337,7 +337,7 @@ - / 1 + / 1 @@ -347,7 +347,7 @@ PointingHandCursor - >>> + >>> diff --git a/client/src/widgets/SessionsListWidget.ui b/client/src/widgets/SessionsListWidget.ui index 543251fa..9ec204ea 100644 --- a/client/src/widgets/SessionsListWidget.ui +++ b/client/src/widgets/SessionsListWidget.ui @@ -11,7 +11,7 @@ - Form + Liste des séances @@ -123,7 +123,7 @@ PointingHandCursor - ... + ... @@ -355,7 +355,7 @@ PointingHandCursor - ... + ... @@ -429,7 +429,7 @@ Tout cocher - ... + ... @@ -458,7 +458,7 @@ Tout décocher - ... + ... From 393e2731c69ca0b06eba27393367337d9c1a4d48 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Mon, 20 Feb 2023 14:35:54 -0500 Subject: [PATCH 27/42] Refs #63. Readded session count that was removed. --- .../resources/translations/openteraplus_en.ts | 50 ++++++++++--------- .../resources/translations/openteraplus_fr.ts | 50 ++++++++++--------- client/src/editors/DeviceSummaryWidget.cpp | 7 +++ client/src/editors/DeviceSummaryWidget.h | 2 + client/src/editors/ParticipantWidget.cpp | 1 + client/src/editors/UserSummaryWidget.cpp | 7 +++ client/src/editors/UserSummaryWidget.h | 2 + 7 files changed, 71 insertions(+), 48 deletions(-) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 418d9ab2..1396e0de 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -1028,6 +1028,7 @@ dissociating This device isn't onlineable - can't start a new session. + Séances Sessions @@ -2763,22 +2764,22 @@ Would you like to disconnect to apply the changes? ParticipantWidget - + Déassignation? Unassign? - + Êtes-vous sûrs de vouloir désassigner Are you sure you want to unassign - + Confirmation Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2791,92 +2792,92 @@ If a the link is re-activated, you will need the newly generated URL to the part Do you want to continue? - + Code utilisateur manquant<br/> Username missing<br/> - + Participant désactivé Participant disabled - + Participant en séance Participant already in session - + Le participant n'a pas d'accès (web ou identification) Participant doesn't have any access (web or by identification) - + Aucun type de séance associé au projet No session type associated to project - + Ce type de séance n'est pas supporté dans cette version Unsupported session type in that version - + Type de séance inconnu Unknown session type - + Impossible de démarrer cette séance Unable to start this session - + Impossible de démarrer cette séance. Unable to start this session. - + Aucun mot de passe spécifié. No specified password. - + Informations manquantes Missing information - + Les informations suivantes sont incorrectes: The following information is missing: - + existe déjà. already exists. - + a été réalisée récemment et n'a pas été terminée. has been realised lately but not finished. - + a été planifiée. has been planned. - + Reprendre une séance? Resume session? - + Un séance de ce type, A session of the type, - + Souhaitez-vous continuer cette séance? @@ -2885,7 +2886,7 @@ Souhaitez-vous continuer cette séance? Would you like to continue this session? - + Code QR du lien QR code for the link @@ -3003,7 +3004,7 @@ Would you like to continue this session? Settings - + Séances Sessions @@ -5263,6 +5264,7 @@ You must select at least one site. New Session + Séances Sessions diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index d97f84eb..a19768df 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -1025,6 +1025,7 @@ désassocier + Séances @@ -2735,62 +2736,62 @@ Souhaitez-vous vous déconnecter pour appliquer les changements? ParticipantWidget - + Déassignation? - + Êtes-vous sûrs de vouloir désassigner - + Participant désactivé - + Participant en séance - + Le participant n'a pas d'accès (web ou identification) - + Aucun type de séance associé au projet - + Ce type de séance n'est pas supporté dans cette version - + Type de séance inconnu - + Impossible de démarrer cette séance - + Impossible de démarrer cette séance. - + Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2799,59 +2800,59 @@ Souhaitez-vous continuer? - + Code utilisateur manquant<br/> - + Aucun mot de passe spécifié. - + Informations manquantes - + Les informations suivantes sont incorrectes: - + existe déjà. - + a été réalisée récemment et n'a pas été terminée. - + a été planifiée. - + Reprendre une séance? - + Un séance de ce type, - + Souhaitez-vous continuer cette séance? - + Code QR du lien @@ -2969,7 +2970,7 @@ Souhaitez-vous continuer cette séance? - + Séances @@ -5214,6 +5215,7 @@ Vous devez associer au moins un site. + Séances diff --git a/client/src/editors/DeviceSummaryWidget.cpp b/client/src/editors/DeviceSummaryWidget.cpp index 1b468e94..f577826a 100644 --- a/client/src/editors/DeviceSummaryWidget.cpp +++ b/client/src/editors/DeviceSummaryWidget.cpp @@ -61,6 +61,8 @@ void DeviceSummaryWidget::connectSignals() connect(m_comManager->getWebSocketManager(), &WebSocketManager::deviceEventReceived, this, &DeviceSummaryWidget::ws_deviceEvent); + connect(ui->wdgSessions, &SessionsListWidget::sessionsCountUpdated, this, &DeviceSummaryWidget::sessionTotalCountUpdated); + } void DeviceSummaryWidget::updateControlsState() @@ -288,3 +290,8 @@ void DeviceSummaryWidget::on_tabNav_currentChanged(int index) } } +void DeviceSummaryWidget::sessionTotalCountUpdated(int new_count) +{ + ui->grpSessions->setTitle(tr("Séances") + " ( " + QString::number(new_count) + " )"); +} + diff --git a/client/src/editors/DeviceSummaryWidget.h b/client/src/editors/DeviceSummaryWidget.h index edb4d751..8dec8686 100644 --- a/client/src/editors/DeviceSummaryWidget.h +++ b/client/src/editors/DeviceSummaryWidget.h @@ -57,6 +57,8 @@ private slots: void on_btnNewSession_clicked(); void on_tabNav_currentChanged(int index); + + void sessionTotalCountUpdated(int new_count); }; #endif // DEVICESUMMARYWIDGET_H diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index 9b10dc91..ea645d78 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -132,6 +132,7 @@ void ParticipantWidget::connectSignals() connect(ui->lstDevices, &QListWidget::currentItemChanged, this, &ParticipantWidget::currentDeviceChanged); connect(ui->wdgSessions, &SessionsListWidget::startSessionRequested, this, &ParticipantWidget::showSessionLobby); + connect(ui->wdgSessions, &SessionsListWidget::sessionsCountUpdated, this, &ParticipantWidget::sessionTotalCountUpdated); } void ParticipantWidget::updateControlsState() diff --git a/client/src/editors/UserSummaryWidget.cpp b/client/src/editors/UserSummaryWidget.cpp index 554c02be..98b857a0 100644 --- a/client/src/editors/UserSummaryWidget.cpp +++ b/client/src/editors/UserSummaryWidget.cpp @@ -67,6 +67,8 @@ void UserSummaryWidget::connectSignals() connect(ui->wdgUser, &TeraForm::widgetValueHasChanged, this, &UserSummaryWidget::userFormValueChanged); connect(ui->wdgUser, &TeraForm::widgetValueHasFocus, this, &UserSummaryWidget::userFormValueHasFocus); + connect(ui->wdgSessions, &SessionsListWidget::sessionsCountUpdated, this, &UserSummaryWidget::sessionTotalCountUpdated); + } void UserSummaryWidget::updateControlsState() @@ -363,3 +365,8 @@ void UserSummaryWidget::on_tabNav_currentChanged(int index) } } +void UserSummaryWidget::sessionTotalCountUpdated(int new_count) +{ + ui->grpSessions->setTitle(tr("Séances") + " ( " + QString::number(new_count) + " )"); +} + diff --git a/client/src/editors/UserSummaryWidget.h b/client/src/editors/UserSummaryWidget.h index b188838a..3799621c 100644 --- a/client/src/editors/UserSummaryWidget.h +++ b/client/src/editors/UserSummaryWidget.h @@ -65,6 +65,8 @@ private slots: void on_btnNewSession_clicked(); void on_tabNav_currentChanged(int index); + + void sessionTotalCountUpdated(int new_count); }; #endif // USERSUMMARYWIDGET_H From fe3804501ff8357ba59f23dec7c8d0097257ea53 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 08:51:41 -0500 Subject: [PATCH 28/42] Refs #63. Fixed issue in advanced view when no participant but only a participant group. --- client/src/widgets/ProjectNavigator.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 0b5a72e9..ab020a09 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -596,6 +596,10 @@ void ProjectNavigator::updateProjectAdvanced(QTreeWidgetItem *project_item) count = stats["participants_total_count"].toInt(); } } + if (stats.contains("participants_groups_count")){ + count += stats["participants_groups_count"].toInt(); + } + //project_item->child(0)->setText(0, tr("Participants") + " (" + QString::number(count) + ")");*/ project_item->child(0)->setHidden(count <= 0); @@ -656,11 +660,8 @@ void ProjectNavigator::updateGroup(const TeraData *group) if (m_projects_items.contains(id_project)){ m_groups_items[id_group]->parent()->removeChild(m_groups_items[id_group]); //m_projects_items[id_project]->addChild(m_groups_items[id_group]); - getProjectItem(id_project, TERADATA_GROUP)->insertChild(0, m_groups_items[id_group]); - /*if (isAdvancedView()){ - updateCountsForProject(id_project); - updateCountsForProject(current_group_project); - }*/ + QTreeWidgetItem* parent_item = getProjectItem(id_project, TERADATA_GROUP); + parent_item->insertChild(0, m_groups_items[id_group]); }else{ // Changed site also! Remove it completely from display delete m_groups_items[id_group]; @@ -701,6 +702,11 @@ void ProjectNavigator::updateGroup(const TeraData *group) setCurrentItem(item); item->setExpanded(true); } + QTreeWidgetItem* parent_item = getProjectItem(id_project, TERADATA_GROUP); + parent_item->insertChild(0, m_groups_items[id_group]); + if (parent_item->isHidden()) + parent_item->setHidden(false); // This can happens on new group without any participants in the project yet + } void ProjectNavigator::updateParticipant(const TeraData *participant) From b2b70813391738c93952c02cdfb46ce274fa5eb5 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 09:47:26 -0500 Subject: [PATCH 29/42] Adjusted contextual menus in project navigator. --- .../resources/translations/openteraplus_en.ts | 4 +- .../resources/translations/openteraplus_fr.ts | 4 +- client/src/widgets/ProjectNavigator.cpp | 67 ++++++++++++++++--- client/src/widgets/ProjectNavigator.h | 1 + 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index 1396e0de..aa5609fd 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -3142,12 +3142,12 @@ Would you like to continue this session? Devices - + Suppression? Delete? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index a19768df..f0fb17dc 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -3108,12 +3108,12 @@ Souhaitez-vous continuer cette séance? - + Suppression? - + Êtes-vous sûrs de vouloir supprimer diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index ab020a09..48b852ce 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -61,6 +61,7 @@ void ProjectNavigator::initUi() new_action->setDisabled(true); new_action = addNewItemAction(TERADATA_GROUP, tr("Groupe")); new_action->setDisabled(true); + new_action = addNewItemAction(TERADATA_PARTICIPANT, tr("Participant")); new_action->setDisabled(true); ui->btnNewItem->setMenu(m_newItemMenu); @@ -1147,6 +1148,7 @@ bool ProjectNavigator::isParticipantFiltered(const QString &part_uuid) } // Check for text filtering + if (ui->btnSearch->isChecked() && !filtered){ filtered = !m_participants[part_uuid].getName().contains(ui->txtNavSearch->text(), Qt::CaseInsensitive); } @@ -1202,15 +1204,14 @@ void ProjectNavigator::updateAvailableActions(QTreeWidgetItem* current_item) bool is_project_admin = m_comManager->isCurrentUserProjectAdmin(project_id); bool at_least_one_enabled = false; - // New project //ui->btnEditSite->setVisible(is_site_admin); QAction* new_project = getActionForDataType(TERADATA_PROJECT); if (new_project){ new_project->setEnabled(is_site_admin); - //if (new_project->isEnabled()) at_least_one_enabled = true; new_project->setVisible(is_site_admin); - if (new_project->isVisible()) at_least_one_enabled = true; + if (new_project->isVisible()) + at_least_one_enabled = true; } // New group @@ -1225,7 +1226,7 @@ void ProjectNavigator::updateAvailableActions(QTreeWidgetItem* current_item) // New participant QAction* new_part = getActionForDataType(TERADATA_PARTICIPANT); if (new_part){ - bool allowed = /*is_project_admin &&*/project_id > 0 && (item_type == TERADATA_GROUP || item_type == TERADATA_PARTICIPANT || item_type == TERADATA_PROJECT); + bool allowed = /*is_project_admin &&*/project_id > 0 && (item_type == TERADATA_GROUP || item_type == TERADATA_PARTICIPANT || item_type == TERADATA_PROJECT || item_type == TERADATA_ONLINE_PARTICIPANT); new_part->setEnabled(allowed); //if (new_part->isEnabled()) at_least_one_enabled = true; new_part->setVisible(allowed); @@ -1277,6 +1278,15 @@ TeraDataTypes ProjectNavigator::getItemType(QTreeWidgetItem *item) return TERADATA_DEVICE; } + if (isAdvancedView() && item){ + if (item->data(0, Qt::UserRole).toInt() == TERADATA_PARTICIPANT) + return TERADATA_ONLINE_PARTICIPANT; + if (item->data(0, Qt::UserRole).toInt() == TERADATA_DEVICE) + return TERADATA_ONLINE_DEVICE; + if (item->data(0, Qt::UserRole).toInt() == TERADATA_USER) + return TERADATA_ONLINE_USER; + } + return TERADATA_NONE; } @@ -1325,6 +1335,22 @@ QAction *ProjectNavigator::getActionForDataType(const TeraDataTypes &data_type) return action; } +QAction *ProjectNavigator::getMenuActionForDataType(const TeraDataTypes &data_type) +{ + QAction* action = nullptr; + + if (m_newItemMenu){ + for (int i=0; iactions().count(); i++){ + if (static_cast(m_newItemMenu->actions().at(i)->data().toInt()) == data_type){ + action = m_newItemMenu->actions().at(i); + break; + } + } + } + + return action; +} + void ProjectNavigator::newItemRequested() { QAction* action = dynamic_cast(QObject::sender()); @@ -1761,7 +1787,6 @@ void ProjectNavigator::navItemExpanded(QTreeWidgetItem *item) // Query stats to display static items queryStatsForProject(id); } - } // PARTICIPANT GROUP @@ -1775,7 +1800,6 @@ void ProjectNavigator::navItemExpanded(QTreeWidgetItem *item) query.addQueryItem(WEB_QUERY_LIST, "true"); //query.addQueryItem(WEB_QUERY_WITH_STATUS, "1"); m_comManager->doGet(WEB_PARTICIPANTINFO_PATH, query); - } if (isAdvancedView()){ @@ -1870,6 +1894,8 @@ void ProjectNavigator::on_btnDashboard_clicked() void ProjectNavigator::on_treeNavigator_customContextMenuRequested(const QPoint &pos) { QTreeWidgetItem* pointed_item = ui->treeNavigator->itemAt(pos); + TeraDataTypes pointed_item_type = getItemType(pointed_item); + qDebug() << pointed_item_type; if (!pointed_item){ // No item clicked - remove everything except projects @@ -1879,13 +1905,32 @@ void ProjectNavigator::on_treeNavigator_customContextMenuRequested(const QPoint selectItem(pointed_item); } - if (m_newItemMenu) - m_newItemMenu->exec(ui->treeNavigator->mapToGlobal(pos)); + //getActionForDataType(TERADATA_GROUP)->setVisible(true); + //getActionForDataType(TERADATA_PARTICIPANT)->setVisible(true); - getActionForDataType(TERADATA_GROUP)->setVisible(true); - getActionForDataType(TERADATA_PARTICIPANT)->setVisible(true); - updateAvailableActions(ui->treeNavigator->currentItem()); + //updateAvailableActions(ui->treeNavigator->currentItem()); + + if (m_newItemMenu){ + bool at_least_one = false; + QAction* project_action = getMenuActionForDataType(TERADATA_PROJECT); + if (project_action){ + project_action->setVisible(pointed_item_type == TERADATA_NONE); + if (!at_least_one && project_action->isVisible()) at_least_one = true; + } + QAction* participant_action = getMenuActionForDataType(TERADATA_PARTICIPANT); + if (participant_action){ + participant_action->setVisible(pointed_item_type == TERADATA_PROJECT || pointed_item_type == TERADATA_GROUP || pointed_item_type == TERADATA_ONLINE_PARTICIPANT); + if (!at_least_one && participant_action->isVisible()) at_least_one = true; + } + QAction* group_action = getMenuActionForDataType(TERADATA_GROUP); + if (group_action){ + group_action->setVisible(pointed_item_type == TERADATA_PROJECT || pointed_item_type == TERADATA_ONLINE_PARTICIPANT); + if (!at_least_one && group_action->isVisible()) at_least_one = true; + } + if (at_least_one) + m_newItemMenu->exec(ui->treeNavigator->mapToGlobal(pos)); + } } diff --git a/client/src/widgets/ProjectNavigator.h b/client/src/widgets/ProjectNavigator.h index e60a1d5c..d25a9399 100644 --- a/client/src/widgets/ProjectNavigator.h +++ b/client/src/widgets/ProjectNavigator.h @@ -115,6 +115,7 @@ class ProjectNavigator : public QWidget QAction* addNewItemAction(const TeraDataTypes &data_type, const QString& label); QAction* getActionForDataType(const TeraDataTypes &data_type); + QAction* getMenuActionForDataType(const TeraDataTypes &data_type); private slots: void newItemRequested(); From 310312556242c612aaae9a75f0f100141819e2c1 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 10:25:48 -0500 Subject: [PATCH 30/42] Fixed issue with TeraForm with readonly fields and datetime fields when checking if they are dirty or not. --- client/src/editors/TeraForm.cpp | 20 +++++++++++++++++++- client/src/widgets/ProjectNavigator.cpp | 1 - 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/client/src/editors/TeraForm.cpp b/client/src/editors/TeraForm.cpp index b6361542..8d299745 100644 --- a/client/src/editors/TeraForm.cpp +++ b/client/src/editors/TeraForm.cpp @@ -124,6 +124,7 @@ void TeraForm::fillFormFromData(const QJsonObject &data) m_initialValues.insert(field, value); } } + // qDebug() << m_initialValues; validateFormData(false); emit formIsNowDirty(false); @@ -209,8 +210,16 @@ bool TeraForm::getFieldDirty(const QString &field) bool TeraForm::getFieldDirty(QWidget *widget) { - //if (m_widgets.values().contains(widget)){ + // Read only fields are never dirty + if (widget->property("readonly").isValid()){ + if (widget->property("readonly").toBool()){ + //qDebug() << "Readonly widget never dirty!"; + return false; + } + } + if (std::find(m_widgets.cbegin(), m_widgets.cend(), widget) != m_widgets.cend()){ + QString widget_id = m_widgets.key(widget); if (dynamic_cast(widget)){ return false; // QLabel are never dirty @@ -220,8 +229,17 @@ bool TeraForm::getFieldDirty(QWidget *widget) if (!id.isNull()) value = id; + if (dynamic_cast(widget)){ + // Datetime must be checked as string as they are stored as such... + value = value.toDateTime().toString(Qt::DateFormat::ISODateWithMs); + if (m_initialValues.contains(widget_id)){ + return !m_initialValues[m_widgets.key(widget)].toString().startsWith(value.toString()); + } + } + if (m_initialValues.contains(widget_id)) return m_initialValues[m_widgets.key(widget)] != value; + // No initial value. So dirty if not empty! return !value.toString().isEmpty(); } diff --git a/client/src/widgets/ProjectNavigator.cpp b/client/src/widgets/ProjectNavigator.cpp index 48b852ce..d2ccec0c 100644 --- a/client/src/widgets/ProjectNavigator.cpp +++ b/client/src/widgets/ProjectNavigator.cpp @@ -1895,7 +1895,6 @@ void ProjectNavigator::on_treeNavigator_customContextMenuRequested(const QPoint { QTreeWidgetItem* pointed_item = ui->treeNavigator->itemAt(pos); TeraDataTypes pointed_item_type = getItemType(pointed_item); - qDebug() << pointed_item_type; if (!pointed_item){ // No item clicked - remove everything except projects From 0d5a20f31dcac98dac7104742778161924e0544d Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 10:39:29 -0500 Subject: [PATCH 31/42] Fix removal of an user in a usergroup in the UserGroupWidget causing issue if removing other users afterwards. --- client/src/editors/UserGroupWidget.cpp | 10 ++++++++++ client/src/editors/UserGroupWidget.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/editors/UserGroupWidget.cpp b/client/src/editors/UserGroupWidget.cpp index fb017b05..2a3309c0 100644 --- a/client/src/editors/UserGroupWidget.cpp +++ b/client/src/editors/UserGroupWidget.cpp @@ -111,6 +111,7 @@ void UserGroupWidget::connectSignals() connect(m_comManager, &ComManager::projectsReceived, this, &UserGroupWidget::processProjectsReply); connect(m_comManager, &ComManager::userUserGroupsReceived, this, &UserGroupWidget::processUserUserGroupsReply); connect(m_comManager, &ComManager::postResultsOK, this, &UserGroupWidget::processPostOKReply); + connect(m_comManager, &ComManager::deleteResultsOK, this, &UserGroupWidget::processDeleteReply); connect(ui->btnUpdateSitesRoles, &QPushButton::clicked, this, &UserGroupWidget::btnUpdateSiteAccess_clicked); connect(ui->btnUpdateProjectsRoles, &QPushButton::clicked, this, &UserGroupWidget::btnUpdateProjectAccess_clicked); @@ -407,6 +408,15 @@ void UserGroupWidget::processPostOKReply(QString path) } } +void UserGroupWidget::processDeleteReply(QString path, int id) +{ + if(path == WEB_USERUSERGROUPINFO_PATH){ + if (m_listUsersUserGroups_items.contains(id)){ + m_listUsersUserGroups_items.remove(id); + } + } +} + void UserGroupWidget::btnUpdateSiteAccess_clicked() { diff --git a/client/src/editors/UserGroupWidget.h b/client/src/editors/UserGroupWidget.h index 647128dd..eb556814 100644 --- a/client/src/editors/UserGroupWidget.h +++ b/client/src/editors/UserGroupWidget.h @@ -32,6 +32,7 @@ private slots: void processProjectsReply(QList projects); void processUserUserGroupsReply(QList users_user_groups); void processPostOKReply(QString path); + void processDeleteReply(QString path, int id); void btnUpdateSiteAccess_clicked(); void btnUpdateProjectAccess_clicked(); @@ -56,7 +57,6 @@ private slots: void updateProjectAccess(const TeraData* access); void updateUserUserGroup(const TeraData* uug); - void updateControlsState(); void updateFieldsValue(); bool validateData(); From e2a965d69b483bc35004708305335fd139403701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20Le=CC=81tourneau?= Date: Thu, 2 Mar 2023 11:13:15 -0500 Subject: [PATCH 32/42] fixed menu cor current user in online widget --- client/src/widgets/OnlineManagerWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/widgets/OnlineManagerWidget.cpp b/client/src/widgets/OnlineManagerWidget.cpp index 1a7dc3b6..eb7d2896 100644 --- a/client/src/widgets/OnlineManagerWidget.cpp +++ b/client/src/widgets/OnlineManagerWidget.cpp @@ -57,6 +57,7 @@ void OnlineManagerWidget::setComManager(ComManager *comMan) on_btnFilterParticipants_clicked(); on_btnFilterUsers_clicked(); on_btnFilterDevices_clicked(); + currentUserWasUpdated(); } void OnlineManagerWidget::setCurrentSite(const QString &site_name, const int &site_id) From 42f7ae52be5a346889c9b5cbaad227625c4528ba Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 13:46:28 -0500 Subject: [PATCH 33/42] Bug fixes. --- client/src/editors/DeviceWidget.cpp | 2 +- client/src/editors/ProjectWidget.cpp | 4 ++++ client/src/editors/ProjectWidget.ui | 3 --- client/src/editors/SiteWidget.cpp | 4 ++++ client/src/editors/UserGroupWidget.cpp | 1 - client/src/editors/UserWidget.cpp | 8 ++++---- client/src/widgets/LogViewWidget.cpp | 2 +- 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/src/editors/DeviceWidget.cpp b/client/src/editors/DeviceWidget.cpp index 03d81138..58dbfdd2 100644 --- a/client/src/editors/DeviceWidget.cpp +++ b/client/src/editors/DeviceWidget.cpp @@ -120,7 +120,7 @@ void DeviceWidget::updateControlsState() } } - if (has_project_admin_access){ + if (has_project_admin_access && !dataIsNew()){ ui->tabNav->addTab(ui->tabLogins, QIcon(":/icons/password.png"), tr("Journal d'accès")); } } diff --git a/client/src/editors/ProjectWidget.cpp b/client/src/editors/ProjectWidget.cpp index c340968a..a860d581 100644 --- a/client/src/editors/ProjectWidget.cpp +++ b/client/src/editors/ProjectWidget.cpp @@ -766,6 +766,10 @@ void ProjectWidget::processPostOKReply(QString path) // Update current user access list for the newly created project m_comManager->doUpdateCurrentUser(); } + if (path == WEB_SESSIONTYPEPROJECT_PATH || path == WEB_TESTTYPEPROJECT_PATH){ + // Also update associated services + queryServicesProject(); + } } void ProjectWidget::deleteDataReply(QString path, int del_id) diff --git a/client/src/editors/ProjectWidget.ui b/client/src/editors/ProjectWidget.ui index 3258a1af..12af75a5 100644 --- a/client/src/editors/ProjectWidget.ui +++ b/client/src/editors/ProjectWidget.ui @@ -1069,9 +1069,6 @@ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, 20 - - 40 - false diff --git a/client/src/editors/SiteWidget.cpp b/client/src/editors/SiteWidget.cpp index 76275afe..06706f4a 100644 --- a/client/src/editors/SiteWidget.cpp +++ b/client/src/editors/SiteWidget.cpp @@ -466,6 +466,10 @@ void SiteWidget::processPostOKReply(QString path) // Refresh roles queryUserGroupsSiteAccess(); } + if (path == WEB_SESSIONTYPEPROJECT_PATH || path == WEB_TESTTYPEPROJECT_PATH){ + // Update associated services + queryServiceSiteAccess(); + } } void SiteWidget::btnUpdateAccess_clicked() diff --git a/client/src/editors/UserGroupWidget.cpp b/client/src/editors/UserGroupWidget.cpp index 2a3309c0..bc80b3e5 100644 --- a/client/src/editors/UserGroupWidget.cpp +++ b/client/src/editors/UserGroupWidget.cpp @@ -99,7 +99,6 @@ void UserGroupWidget::saveData(bool signal) void UserGroupWidget::setData(const TeraData *data) { DataEditorWidget::setData(data); - } void UserGroupWidget::connectSignals() diff --git a/client/src/editors/UserWidget.cpp b/client/src/editors/UserWidget.cpp index 0f67d8a4..49af9c35 100644 --- a/client/src/editors/UserWidget.cpp +++ b/client/src/editors/UserWidget.cpp @@ -178,11 +178,11 @@ void UserWidget::updateControlsState(){ void UserWidget::updateFieldsValue(){ if (m_data && !hasPendingDataRequests()){ - if (!ui->wdgUser->formHasData()) + //if (!ui->wdgUser->formHasData()) ui->wdgUser->fillFormFromData(m_data->toJson()); - else { + /*else { ui->wdgUser->resetFormValues(); - } + }*/ ui->lblTitle->setText(m_data->getName()); @@ -392,7 +392,7 @@ void UserWidget::processUsersReply(QList users) // We found "ourself" in the list - update data. //*m_data = users.at(i); m_data->updateFrom(users.at(i)); - updateFieldsValue(); + //updateFieldsValue(); break; } } diff --git a/client/src/widgets/LogViewWidget.cpp b/client/src/widgets/LogViewWidget.cpp index 0c8791c2..c74fc14f 100644 --- a/client/src/widgets/LogViewWidget.cpp +++ b/client/src/widgets/LogViewWidget.cpp @@ -373,7 +373,7 @@ QString LogViewWidget::getOSIcon(const QString &os) return "://icons/logs/os_windows.png"; if (compare_os.contains("apple") || compare_os.contains("mac")) - return "://icons/logs/os_apple.png"; + return "://icons/logs/os_mac.png"; if (compare_os.contains("linux") || compare_os.contains("ubuntu")) return "://icons/logs/os_linux.png"; From ca98c97756bc3764990897e4a2d853427b2f4535 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Thu, 2 Mar 2023 15:37:46 -0500 Subject: [PATCH 34/42] Refs #71. Added QR code copy and export to file. --- client/src/CMakeLists.txt | 3 + client/src/dialogs/BaseDialog.cpp | 2 - client/src/dialogs/QRCodeDialog.cpp | 59 ++++++++++ client/src/dialogs/QRCodeDialog.h | 32 +++++ client/src/dialogs/QRCodeDialog.ui | 141 +++++++++++++++++++++++ client/src/editors/ParticipantWidget.cpp | 16 ++- client/src/editors/ParticipantWidget.h | 5 +- 7 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 client/src/dialogs/QRCodeDialog.cpp create mode 100644 client/src/dialogs/QRCodeDialog.h create mode 100644 client/src/dialogs/QRCodeDialog.ui diff --git a/client/src/CMakeLists.txt b/client/src/CMakeLists.txt index 928efcad..16d32106 100755 --- a/client/src/CMakeLists.txt +++ b/client/src/CMakeLists.txt @@ -46,6 +46,7 @@ set(headers dialogs/DeviceAssignDialog.h dialogs/CleanUpDialog.h dialogs/FileUploaderDialog.h + dialogs/QRCodeDialog.h # Editors editors/DataEditorWidget.h editors/DataListWidget.h @@ -149,6 +150,7 @@ set(srcs dialogs/PasswordStrengthDialog.cpp dialogs/CleanUpDialog.cpp dialogs/FileUploaderDialog.cpp + dialogs/QRCodeDialog.cpp # Editors editors/DataEditorWidget.cpp editors/DataListWidget.cpp @@ -233,6 +235,7 @@ SET(uis dialogs/PasswordStrengthDialog.ui dialogs/CleanUpDialog.ui dialogs/FileUploaderDialog.ui + dialogs/QRCodeDialog.ui # Editors editors/DataListWidget.ui editors/DeviceSubTypeWidget.ui diff --git a/client/src/dialogs/BaseDialog.cpp b/client/src/dialogs/BaseDialog.cpp index 7d852797..bab5b60f 100644 --- a/client/src/dialogs/BaseDialog.cpp +++ b/client/src/dialogs/BaseDialog.cpp @@ -33,5 +33,3 @@ void BaseDialog::showEvent(QShowEvent * event) move(parentWidget()->width()/2 - width()/2, parentWidget()->height()/2 - height()/2); }*/ } - - diff --git a/client/src/dialogs/QRCodeDialog.cpp b/client/src/dialogs/QRCodeDialog.cpp new file mode 100644 index 00000000..f7f12e4e --- /dev/null +++ b/client/src/dialogs/QRCodeDialog.cpp @@ -0,0 +1,59 @@ +#include "QRCodeDialog.h" +#include "ui_QRCodeDialog.h" + +#include +#include +#include + +QRCodeDialog::QRCodeDialog(QString text, QWidget *parent) : + QDialog(parent), + ui(new Ui::QRCodeDialog) +{ + ui->setupUi(this); + setFixedSize(size()); + + ui->wdgCode->setFixedSize(this->height() - 60, this->height() - 60); + + connect(ui->btnClose, &QPushButton::clicked, this, &QDialog::accept); + + setText(text); + +} + + +QRCodeDialog::~QRCodeDialog() +{ + delete ui; +} + +void QRCodeDialog::setText(const QString &text) +{ + if (!text.isEmpty()){ + ui->wdgCode->setText(text); + } +} + +void QRCodeDialog::setContext(const QString &context) +{ + m_context = context; +} + +void QRCodeDialog::on_btnCopy_clicked() +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setPixmap(ui->wdgCode->grab()); +} + + +void QRCodeDialog::on_btnSave_clicked() +{ + QStringList suggested_paths = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + QString base_path; + if (!suggested_paths.isEmpty()) + base_path = suggested_paths.first(); + QString save_file = QFileDialog::getSaveFileName(this, tr("Enregistrer le code QR"), base_path + "/" + m_context, tr("Images") + " (*.png)"); + if (!save_file.isEmpty()){ + ui->wdgCode->grab().save(save_file, "PNG"); + } +} + diff --git a/client/src/dialogs/QRCodeDialog.h b/client/src/dialogs/QRCodeDialog.h new file mode 100644 index 00000000..b68e6f80 --- /dev/null +++ b/client/src/dialogs/QRCodeDialog.h @@ -0,0 +1,32 @@ +#ifndef QRCODEDIALOG_H +#define QRCODEDIALOG_H + +#include + +namespace Ui { +class QRCodeDialog; +} + +class QRCodeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit QRCodeDialog(QString text = QString(), QWidget *parent = nullptr); + ~QRCodeDialog(); + + void setText(const QString& text); + void setContext(const QString& context); + +private slots: + void on_btnCopy_clicked(); + + void on_btnSave_clicked(); + +private: + Ui::QRCodeDialog *ui; + + QString m_context = "code"; +}; + +#endif // QRCODEDIALOG_H diff --git a/client/src/dialogs/QRCodeDialog.ui b/client/src/dialogs/QRCodeDialog.ui new file mode 100644 index 00000000..82645d25 --- /dev/null +++ b/client/src/dialogs/QRCodeDialog.ui @@ -0,0 +1,141 @@ + + + QRCodeDialog + + + + 0 + 0 + 554 + 403 + + + + Code QR + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + PointingHandCursor + + + Copier + + + + :/icons/copy.png:/icons/copy.png + + + + 20 + 20 + + + + + + + + PointingHandCursor + + + Enregistrer + + + + :/icons/save.png:/icons/save.png + + + + 20 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PointingHandCursor + + + Fermer + + + false + + + + + + + + + + QRWidget + QWidget +
widgets/QRWidget.h
+ 1 +
+
+ + + + +
diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index ea645d78..b98ba1ed 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -16,7 +16,6 @@ ParticipantWidget::ParticipantWidget(ComManager *comMan, const TeraData *data, Q ui(new Ui::ParticipantWidget) { - m_diag_editor = nullptr; m_sessionLobby = nullptr; m_allowFileTransfers = false; @@ -79,6 +78,9 @@ ParticipantWidget::~ParticipantWidget() if (m_sessionLobby) m_sessionLobby->deleteLater(); + + if (m_diag_qr) + m_diag_qr->deleteLater(); } @@ -1150,7 +1152,15 @@ void ParticipantWidget::on_lstDevices_itemDoubleClicked(QListWidgetItem *item) void ParticipantWidget::on_btnQR_clicked() { - if (m_diag_editor){ + if (m_diag_qr){ + m_diag_qr->deleteLater(); + } + + m_diag_qr = new QRCodeDialog(ui->txtWeb->text(), this); + m_diag_qr->setContext(m_data->getName()); + m_diag_qr->open(); + + /*if (m_diag_editor){ m_diag_editor->deleteLater(); } m_diag_editor = new BaseDialog(this); @@ -1162,7 +1172,7 @@ void ParticipantWidget::on_btnQR_clicked() m_diag_editor->setWindowTitle(tr("Code QR du lien")); m_diag_editor->setFixedSize(this->height()/2 - 40, this->height()/2); - m_diag_editor->open(); + m_diag_editor->open();*/ } diff --git a/client/src/editors/ParticipantWidget.h b/client/src/editors/ParticipantWidget.h index d259b9ae..c34a3f4d 100644 --- a/client/src/editors/ParticipantWidget.h +++ b/client/src/editors/ParticipantWidget.h @@ -20,11 +20,10 @@ #include "dialogs/SessionLobbyDialog.h" #include "dialogs/EmailInviteDialog.h" #include "dialogs/DeviceAssignDialog.h" -#include "dialogs/BaseDialog.h" +#include "dialogs/QRCodeDialog.h" #include "widgets/AssetsWidget.h" #include "widgets/TestsWidget.h" -#include "widgets/QRWidget.h" #include "services/BaseServiceWidget.h" @@ -56,7 +55,7 @@ class ParticipantWidget : public DataEditorWidget QMap m_services_tabs; bool m_allowFileTransfers; // Allow to attach files to a session? - BaseDialog* m_diag_editor; + QRCodeDialog* m_diag_qr = nullptr; SessionLobbyDialog* m_sessionLobby; QHash m_ids_session_types; From a7054999ea792caab58918c8dcf712f3a314d868 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 7 Mar 2023 14:43:57 -0500 Subject: [PATCH 35/42] Refs #86. Prevented dragging participant in a different project. Prevented ServiceConfigWidget to display multiple config forms when clicking on a service icon. --- client/src/editors/ParticipantWidget.cpp | 10 --------- client/src/editors/ServiceConfigWidget.cpp | 7 ++++++- client/src/editors/ServiceConfigWidget.h | 1 + client/src/widgets/ProjectNavigatorTree.cpp | 23 +++++++++++++++------ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/client/src/editors/ParticipantWidget.cpp b/client/src/editors/ParticipantWidget.cpp index b98ba1ed..5959b820 100644 --- a/client/src/editors/ParticipantWidget.cpp +++ b/client/src/editors/ParticipantWidget.cpp @@ -410,16 +410,6 @@ void ParticipantWidget::refreshWebAccessUrl() if (index >= m_services.count() || index <0 || dataIsNew()) return; - /*TeraData* current_service = &m_services[index]; - QUrl server_url = m_comManager->getServerUrl(); - QString participant_endpoint = ""; - if (current_service->hasFieldName("service_endpoint_participant")) - participant_endpoint = current_service->getFieldValue("service_endpoint_participant").toString(); - QString service_url = "https://" + server_url.host() + ":" + QString::number(server_url.port()) + - //QString service_url = "https://" + current_service->getFieldValue("service_hostname").toString() + ":" + QString::number(server_url.port()) + - current_service->getFieldValue("service_clientendpoint").toString() + - participant_endpoint + "?token=" + - m_data->getFieldValue("participant_token").toString();*/ QString service_url = TeraData::getServiceParticipantUrl(m_services[index], m_comManager->getServerUrl(), m_data->getFieldValue("participant_token").toString()); diff --git a/client/src/editors/ServiceConfigWidget.cpp b/client/src/editors/ServiceConfigWidget.cpp index 329ba81e..dcea5a1c 100644 --- a/client/src/editors/ServiceConfigWidget.cpp +++ b/client/src/editors/ServiceConfigWidget.cpp @@ -247,10 +247,15 @@ void ServiceConfigWidget::connectSignals() void ServiceConfigWidget::on_lstServiceConfig_itemClicked(QListWidgetItem *item) { + int id_service = m_listServices_items.key(item); + if (id_service == m_currentIdService) + return; + + m_currentIdService = id_service; + ui->cmbSpecific->clear(); ui->frameSpecific->hide(); - int id_service = m_listServices_items.key(item); m_gotServiceForm = false; // Query form for that service diff --git a/client/src/editors/ServiceConfigWidget.h b/client/src/editors/ServiceConfigWidget.h index 3174880a..ab78edae 100644 --- a/client/src/editors/ServiceConfigWidget.h +++ b/client/src/editors/ServiceConfigWidget.h @@ -32,6 +32,7 @@ class ServiceConfigWidget : public DataEditorWidget QString m_idFieldName; int m_idFieldValue; + int m_currentIdService = -1; QMap m_listServices_items; bool m_gotServiceForm; //QMap m_servicesIdsData; diff --git a/client/src/widgets/ProjectNavigatorTree.cpp b/client/src/widgets/ProjectNavigatorTree.cpp index 17e4b8d9..f956d101 100644 --- a/client/src/widgets/ProjectNavigatorTree.cpp +++ b/client/src/widgets/ProjectNavigatorTree.cpp @@ -46,8 +46,8 @@ void ProjectNavigatorTree::dragMoveEvent(QDragMoveEvent *event) QTreeWidgetItem* dragged_item = currentItem(); TeraDataTypes dragged_item_type = m_projNav->getItemType(dragged_item); - if (dragged_item_type == TERADATA_PROJECT){ - // Projects can't be dragged! + if (dragged_item_type == TERADATA_PROJECT || dragged_item_type == TERADATA_GROUP){ + // Projects and participants groups can't be dragged! event->ignore(); return; } @@ -61,17 +61,28 @@ void ProjectNavigatorTree::dragMoveEvent(QDragMoveEvent *event) } TeraDataTypes target_item_type = m_projNav->getItemType(target_item); - if (dragged_item_type == TERADATA_GROUP){ + /*if (dragged_item_type == TERADATA_GROUP){ if (target_item_type == TERADATA_PROJECT){ event->accept(); return; } - } + }*/ if (dragged_item_type == TERADATA_PARTICIPANT){ if (target_item_type == TERADATA_PROJECT || target_item_type == TERADATA_GROUP){ - event->accept(); - return; + // Check if target item is in the same project + QTreeWidgetItem* target_project; + if (target_item_type == TERADATA_PROJECT){ + // We are already dragging over a project + target_project = target_item; + }else{ + target_project = m_projNav->getProjectForItem(target_item); + } + QTreeWidgetItem* dragged_project = m_projNav->getProjectForItem(dragged_item); + if (target_project == dragged_project){ + event->accept(); + return; + } } } } From 67d5296aab94bf8f831ab6d330b3096db03cb10e Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 7 Mar 2023 15:26:04 -0500 Subject: [PATCH 36/42] Refs #82. Cleaned default filename --- client/src/dialogs/QRCodeDialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/dialogs/QRCodeDialog.cpp b/client/src/dialogs/QRCodeDialog.cpp index f7f12e4e..2c12b4f9 100644 --- a/client/src/dialogs/QRCodeDialog.cpp +++ b/client/src/dialogs/QRCodeDialog.cpp @@ -5,6 +5,8 @@ #include #include +#include "Utils.h" + QRCodeDialog::QRCodeDialog(QString text, QWidget *parent) : QDialog(parent), ui(new Ui::QRCodeDialog) @@ -51,7 +53,9 @@ void QRCodeDialog::on_btnSave_clicked() QString base_path; if (!suggested_paths.isEmpty()) base_path = suggested_paths.first(); - QString save_file = QFileDialog::getSaveFileName(this, tr("Enregistrer le code QR"), base_path + "/" + m_context, tr("Images") + " (*.png)"); + + QString default_name = Utils::removeNonAlphanumerics(Utils::removeAccents(m_context)); + QString save_file = QFileDialog::getSaveFileName(this, tr("Enregistrer le code QR"), base_path + "/" + default_name, tr("Images") + " (*.png)"); if (!save_file.isEmpty()){ ui->wdgCode->grab().save(save_file, "PNG"); } From c92aafbc1bc2111c934cdd0ba2c3fd3022880380 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Tue, 7 Mar 2023 15:26:04 -0500 Subject: [PATCH 37/42] Refs #71. Cleaned default filename --- client/src/dialogs/QRCodeDialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/dialogs/QRCodeDialog.cpp b/client/src/dialogs/QRCodeDialog.cpp index f7f12e4e..2c12b4f9 100644 --- a/client/src/dialogs/QRCodeDialog.cpp +++ b/client/src/dialogs/QRCodeDialog.cpp @@ -5,6 +5,8 @@ #include #include +#include "Utils.h" + QRCodeDialog::QRCodeDialog(QString text, QWidget *parent) : QDialog(parent), ui(new Ui::QRCodeDialog) @@ -51,7 +53,9 @@ void QRCodeDialog::on_btnSave_clicked() QString base_path; if (!suggested_paths.isEmpty()) base_path = suggested_paths.first(); - QString save_file = QFileDialog::getSaveFileName(this, tr("Enregistrer le code QR"), base_path + "/" + m_context, tr("Images") + " (*.png)"); + + QString default_name = Utils::removeNonAlphanumerics(Utils::removeAccents(m_context)); + QString save_file = QFileDialog::getSaveFileName(this, tr("Enregistrer le code QR"), base_path + "/" + default_name, tr("Images") + " (*.png)"); if (!save_file.isEmpty()){ ui->wdgCode->grab().save(save_file, "PNG"); } From 799f9ca6592caea2e6844c68b709e37feb64a9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20L=C3=A9tourneau?= Date: Tue, 7 Mar 2023 15:43:52 -0500 Subject: [PATCH 38/42] updated messages to latest version --- external/messages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/messages b/external/messages index 5fafa1b7..0dd93f60 160000 --- a/external/messages +++ b/external/messages @@ -1 +1 @@ -Subproject commit 5fafa1b781cbb13ed78be57d05c9613f432df720 +Subproject commit 0dd93f60037cad2e4a0f453a323e1ceafa98fbd8 From 01b3bfee214009dbe5808726d35f4e0cfdee0dcf Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 8 Mar 2023 13:37:54 -0500 Subject: [PATCH 39/42] Fixed issue that allowed to start a session with 6 invitees (instead of the 5 limit) when using the Start Session button in GroupWidget. --- client/src/editors/GroupWidget.cpp | 71 ++---------------------------- 1 file changed, 4 insertions(+), 67 deletions(-) diff --git a/client/src/editors/GroupWidget.cpp b/client/src/editors/GroupWidget.cpp index 69f5c5cd..5cabded7 100644 --- a/client/src/editors/GroupWidget.cpp +++ b/client/src/editors/GroupWidget.cpp @@ -104,71 +104,6 @@ void GroupWidget::updateFieldsValue(){ } } -/* -void GroupWidget::updateParticipant(TeraData *participant) -{ - int id_participant = participant->getId(); - QTableWidgetItem* item; - if (m_listParticipants_items.contains(id_participant)){ - item = m_listParticipants_items[id_participant]; - - }else{ - ui->tableParticipants->setRowCount(ui->tableParticipants->rowCount()+1); - item = new QTableWidgetItem(QIcon(TeraData::getIconFilenameForDataType(TERADATA_PARTICIPANT)),""); - ui->tableParticipants->setItem(ui->tableParticipants->rowCount()-1, 0, item); - m_listParticipants_items[id_participant] = item; - - QTableWidgetItem* item2 = new QTableWidgetItem(""); - item2->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - ui->tableParticipants->setItem(ui->tableParticipants->rowCount()-1, 1, item2); - item2 = new QTableWidgetItem(""); - item2->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - ui->tableParticipants->setItem(ui->tableParticipants->rowCount()-1, 2, item2); - item2 = new QTableWidgetItem(""); - item2->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - ui->tableParticipants->setItem(ui->tableParticipants->rowCount()-1, 3, item2); - } - - // Update values - item->setText(participant->getName()); - if (participant->isEnabled()){ - ui->tableParticipants->item(item->row(), 1)->setText(tr("Actif")); - ui->tableParticipants->item(item->row(), 1)->setForeground(Qt::green); - }else{ - ui->tableParticipants->item(item->row(), 1)->setText(tr("Inactif")); - ui->tableParticipants->item(item->row(), 1)->setForeground(Qt::red); - } - QString date_val_str = tr("Aucune connexion"); - if (participant->hasFieldName("participant_lastonline")){ - if (!participant->getFieldValue("participant_lastonline").isNull()){ - date_val_str = participant->getFieldValue("participant_lastonline").toDateTime().toLocalTime().toString("dd MMMM yyyy - hh:mm"); - } - } - ui->tableParticipants->item(item->row(), 2)->setText(date_val_str); - date_val_str = tr("Aucune séance"); - if (participant->hasFieldName("participant_lastsession")){ - if (!participant->getFieldValue("participant_lastsession").isNull()){ - QDateTime date_val = participant->getFieldValue("participant_lastsession").toDateTime().toLocalTime(); - date_val_str = date_val.toString("dd MMMM yyyy - hh:mm"); - if (participant->isEnabled()){ - // Set background color for last session date - QColor back_color = TeraForm::getGradientColor(0, 3, 7, static_cast(date_val.daysTo(QDateTime::currentDateTime()))); - back_color.setAlphaF(0.5); - ui->tableParticipants->item(item->row(), 3)->setBackground(back_color); - } - } - } - ui->tableParticipants->item(item->row(), 3)->setText(date_val_str); -}*/ -/* -void GroupWidget::updateStats() -{ - - - ui->lblParticipant->setText(QString::number(ui->tableParticipants->rowCount()) + tr(" participant(s)")); - - -}*/ void GroupWidget::setData(const TeraData *data) { @@ -195,8 +130,10 @@ bool GroupWidget::canStartSession() return false; } - if (m_activeParticipants.count() > MAX_INVITEES_IN_SESSION){ - ui->lblInfos->setText(tr("Trop de participants actifs dans ce groupe")); + if (m_activeParticipants.count() + 1 > MAX_INVITEES_IN_SESSION){ // +1 since including the current user! + int max_participants = int(MAX_INVITEES_IN_SESSION) - 1; + ui->lblInfos->setText(tr("Trop de participants actifs dans ce groupe pour démarrer une séance (max ") + + QString::number(max_participants) + " " + tr("participants") + ")"); return false; } From d1312ef2c1560787758d883eab84af0de9d2d4b8 Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 8 Mar 2023 14:12:59 -0500 Subject: [PATCH 40/42] Updated translations --- .../resources/translations/openteraplus_en.ts | 269 ++++++++++-------- .../resources/translations/openteraplus_fr.ts | 267 +++++++++-------- 2 files changed, 305 insertions(+), 231 deletions(-) diff --git a/client/resources/translations/openteraplus_en.ts b/client/resources/translations/openteraplus_en.ts index aa5609fd..db6986c6 100644 --- a/client/resources/translations/openteraplus_en.ts +++ b/client/resources/translations/openteraplus_en.ts @@ -1418,55 +1418,64 @@ p, li { white-space: pre-wrap; } GroupWidget - + Aucun participant actif dans ce groupe No active participant in that group - Trop de participants actifs dans ce groupe - Too many active participants in that group + Too many active participants in that group - + + Trop de participants actifs dans ce groupe pour démarrer une séance (max + Too many active participants in that group to start a session (max + + + + participants + participants + + + Participants Participants - + Participants actifs Active Participants - + Séances planifiées ou réalisées Planned Session or Completed - + Actif Active - + Inactif Inactive - - + + Suppression? Deletion? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete - + Êtes-vous sûrs de vouloir supprimer tous les participants sélectionnés? Are you sure you want to delete all selected participants? @@ -2731,32 +2740,32 @@ Would you like to disconnect to apply the changes? Items - + Participants Participants - + Utilisateurs Users - + Appareils Devices - + Déconnecter Disconnect - + Déconnecter? Disconnect? - + Êtes-vous sûrs de vouloir déconnecter Are you sure you want to disconnect @@ -2764,22 +2773,22 @@ Would you like to disconnect to apply the changes? ParticipantWidget - + Déassignation? Unassign? - + Êtes-vous sûrs de vouloir désassigner Are you sure you want to unassign - + Confirmation Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2792,92 +2801,92 @@ If a the link is re-activated, you will need the newly generated URL to the part Do you want to continue? - + Code utilisateur manquant<br/> Username missing<br/> - + Participant désactivé Participant disabled - + Participant en séance Participant already in session - + Le participant n'a pas d'accès (web ou identification) Participant doesn't have any access (web or by identification) - + Aucun type de séance associé au projet No session type associated to project - + Ce type de séance n'est pas supporté dans cette version Unsupported session type in that version - + Type de séance inconnu Unknown session type - + Impossible de démarrer cette séance Unable to start this session - + Impossible de démarrer cette séance. Unable to start this session. - + Aucun mot de passe spécifié. No specified password. - + Informations manquantes Missing information - + Les informations suivantes sont incorrectes: The following information is missing: - + existe déjà. already exists. - + a été réalisée récemment et n'a pas été terminée. has been realised lately but not finished. - + a été planifiée. has been planned. - + Reprendre une séance? Resume session? - + Un séance de ce type, A session of the type, - + Souhaitez-vous continuer cette séance? @@ -2886,9 +2895,8 @@ Souhaitez-vous continuer cette séance? Would you like to continue this session? - Code QR du lien - QR code for the link + QR code for the link @@ -3004,7 +3012,7 @@ Would you like to continue this session? Settings - + Séances Sessions @@ -3122,32 +3130,32 @@ Would you like to continue this session? Group - + Participant Participant - + Participants Participants - + Utilisateurs Users - + Appareils Devices - + Suppression? Delete? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete @@ -3262,29 +3270,29 @@ ou réalisées or done - + Suppression de service associé Associated service deletion - + Au moins un service a été retiré de ce project. S'il y a des types de séances qui utilisent ce service, elles ne seront plus accessibles. Souhaitez-vous continuer? At least one service has been removed from this project. If there's session types who are associated to this service, they won't be available anymore. Do you want to continue? - + Seuls les groupes utilisateurs ayant un accès au projet sont affichés. Only user groups with access to this project are displayed. - + Suppression d'appareil associé Related device association - + Au moins un appareil associé à un / des participants a été retiré de ce project. Ces participants ne pourront plus utiliser cet appareil. Souhaitez-vous continuer? @@ -3293,23 +3301,23 @@ Those participants won't be able to use that device anymore. Do you want to continue? - - + + Suppression? Deletion? - + Êtes-vous sûrs de vouloir supprimer Are you sure you want to delete - + Êtes-vous sûrs de vouloir supprimer tous les participants sélectionnés? Are you sure you want to delete all selected participants? - + Seuls les utilisateurs ayant un accès au projet sont affichés. Only users with access to this project are displayed. @@ -3324,190 +3332,223 @@ Do you want to continue? Inactive - + Appareils Devices - + Form Form - + Projet Project - + Éditer Edit - + Sauvegarder Save - + Annuler Cancel - + XXXX Séances XXXX Sessions - + XXXX Groupes XXXX Groups - + XXXX Participants XXXX Participants - + XXXX Utilisateurs XXXX Users - + Participant Participant - + État State - + Séances Sessions - + Première séance First Session - + Dernière séance Last Session - + Nouveau participant New participant - + Nouveau groupe New group - + Supprimer Delete - + Informations Informations - + Gérer les utilisateurs Manage users - + Gérer les groupes utilisateurs Manage Users Groups - + Groupes utilisateurs User groups - + Mettre à jour les types de séances associés Update associated session types - + Types de séances Session types - + Mettre à jour les appareils associés Update associated devices - + Dernière connexion Last connection - + Résumé Summary - + Utilisateur User - - + + Rôle Role - - - + + + Utilisateurs Users - + Groupe Utilisateur User Group - + La modification des accès est désactivée pour les groupes utilisateurs dont l'accès au projet provient du site (i.e. administrateurs du site associé au projet) Access modification is disabled for users groups which project access is specified in the site (i.e. project's administrators) - + Mettre à jour les rôles Update roles - + Groupes Utilisateurs Users Groups - + Mettre à jour les services associés Updated associated services + - Services Services + + QRCodeDialog + + + Enregistrer le code QR + Save QR code + + + + Images + Images + + + + Code QR + QR Code + + + + Copier + Copy + + + + Enregistrer + Save + + + + Fermer + Close + + ResultMessageWidget @@ -3564,12 +3605,12 @@ Do you want to continue? Global - + Les champs suivants doivent être complétés: The following fields must be entered: - + Champs invalides Invalid fields @@ -4225,48 +4266,48 @@ You must select at least one site. Active Participants - + Suppression de service associé Associated service deletion - + Au moins un service a été retiré de ce site. S'il y a des projets qui utilisent ce service, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? At least one service was removed from this site. If there's projects using that service, they won't be able to use it anymore. Do you want to continue? - + Suppression d'appareil associé Device association deletion - + Au moins un appareil a été retiré de ce site. S'il y a des projets qui utilisent cet appareil, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? At least one device was removed from this site. If there is projects using that device, they won't be able to use it anymore. Do you want to continue? - + Suppression de types de séances associés Associated session types deletion - + Au moins un type de séance a été retiré de ce site. S'il y a des projets qui utilisent ce type, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? At least one session type was removed from this site. If there are projects using that type, they won't be able to use it anymore. Do you want to continue? - + Seuls les types de séances associés au site sont affichés. Only session types related to the site are displayed. - + Types de séances Session types @@ -4284,22 +4325,22 @@ realized sessions Devices - + Seuls les appareils associés au site sont affichés. Only associated devices are displayed. - + Seuls les utilisateurs ayant un accès au site sont affichés. Only users with site access are displayed. - + Seuls les groupes utilisateurs ayant un accès au site sont affichés. Only users groups with site access are displayed. - + Groupes Utilisateurs Users Groups @@ -4445,7 +4486,7 @@ realized sessions Users Groups - + Appareils Devices @@ -4666,7 +4707,7 @@ realized sessions TeraForm - + Choisir la couleur Chose a color diff --git a/client/resources/translations/openteraplus_fr.ts b/client/resources/translations/openteraplus_fr.ts index f0fb17dc..d2024629 100644 --- a/client/resources/translations/openteraplus_fr.ts +++ b/client/resources/translations/openteraplus_fr.ts @@ -1397,54 +1397,59 @@ p, li { white-space: pre-wrap; } GroupWidget - + Aucun participant actif dans ce groupe - - Trop de participants actifs dans ce groupe + + Trop de participants actifs dans ce groupe pour démarrer une séance (max - + + participants + + + + Participants - + Participants actifs - + Séances planifiées ou réalisées - + Actif - + Inactif - - + + Suppression? - + Êtes-vous sûrs de vouloir supprimer - + Êtes-vous sûrs de vouloir supprimer tous les participants sélectionnés? @@ -2703,32 +2708,32 @@ Souhaitez-vous vous déconnecter pour appliquer les changements? - + Participants - + Utilisateurs - + Appareils - + Déconnecter - + Déconnecter? - + Êtes-vous sûrs de vouloir déconnecter @@ -2736,62 +2741,62 @@ Souhaitez-vous vous déconnecter pour appliquer les changements? ParticipantWidget - + Déassignation? - + Êtes-vous sûrs de vouloir désassigner - + Participant désactivé - + Participant en séance - + Le participant n'a pas d'accès (web ou identification) - + Aucun type de séance associé au projet - + Ce type de séance n'est pas supporté dans cette version - + Type de séance inconnu - + Impossible de démarrer cette séance - + Impossible de démarrer cette séance. - + Confirmation - + En désactivant l'accès web, le lien sera supprimé. Si un accès est à nouveau créé, le lien sera différent et il faudra envoyer à nouveau le lien au participant. @@ -2800,62 +2805,57 @@ Souhaitez-vous continuer? - + Code utilisateur manquant<br/> - + Aucun mot de passe spécifié. - + Informations manquantes - + Les informations suivantes sont incorrectes: - + existe déjà. - + a été réalisée récemment et n'a pas été terminée. - + a été planifiée. - + Reprendre une séance? - + Un séance de ce type, - + Souhaitez-vous continuer cette séance? - - - Code QR du lien - - Form @@ -2970,7 +2970,7 @@ Souhaitez-vous continuer cette séance? - + Séances @@ -3088,32 +3088,32 @@ Souhaitez-vous continuer cette séance? - + Participant - + Participants - + Utilisateurs - + Appareils - + Suppression? - + Êtes-vous sûrs de vouloir supprimer @@ -3237,239 +3237,272 @@ ou réalisées - + Suppression de service associé - + Au moins un service a été retiré de ce project. S'il y a des types de séances qui utilisent ce service, elles ne seront plus accessibles. Souhaitez-vous continuer? - + Seuls les groupes utilisateurs ayant un accès au projet sont affichés. - + Suppression d'appareil associé - + Au moins un appareil associé à un / des participants a été retiré de ce project. Ces participants ne pourront plus utiliser cet appareil. Souhaitez-vous continuer? - - + + Suppression? - + Êtes-vous sûrs de vouloir supprimer - + Êtes-vous sûrs de vouloir supprimer tous les participants sélectionnés? - + Seuls les utilisateurs ayant un accès au projet sont affichés. - + Appareils - + Form - + Projet - + Éditer - + Sauvegarder - + Annuler - + XXXX Séances - + XXXX Groupes - + XXXX Participants - + XXXX Utilisateurs - + Participant - + État - + Séances - + Première séance - + Dernière séance - + Dernière connexion - + Résumé - + Nouveau participant - + Nouveau groupe - + Supprimer - + Informations - + Gérer les utilisateurs - + Groupes utilisateurs - + Mettre à jour les types de séances associés - + Types de séances - + Mettre à jour les appareils associés - + Utilisateur - - + + Rôle - - - + + + Utilisateurs - + Gérer les groupes utilisateurs - + Groupe Utilisateur - + La modification des accès est désactivée pour les groupes utilisateurs dont l'accès au projet provient du site (i.e. administrateurs du site associé au projet) - + Mettre à jour les rôles - + Groupes Utilisateurs - + Mettre à jour les services associés + - Services + + QRCodeDialog + + + Enregistrer le code QR + + + + + Images + + + + + Code QR + + + + + Copier + + + + + Enregistrer + + + + + Fermer + + + ResultMessageWidget @@ -3526,12 +3559,12 @@ Souhaitez-vous continuer? - + Les champs suivants doivent être complétés: - + Champs invalides @@ -4197,66 +4230,66 @@ ou réalisées - + Suppression de service associé - + Au moins un service a été retiré de ce site. S'il y a des projets qui utilisent ce service, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? - + Suppression d'appareil associé - + Au moins un appareil a été retiré de ce site. S'il y a des projets qui utilisent cet appareil, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? - + Suppression de types de séances associés - + Au moins un type de séance a été retiré de ce site. S'il y a des projets qui utilisent ce type, ils ne pourront plus l'utiliser. Souhaitez-vous continuer? - + Seuls les types de séances associés au site sont affichés. - + Types de séances - + Seuls les appareils associés au site sont affichés. - + Seuls les utilisateurs ayant un accès au site sont affichés. - + Seuls les groupes utilisateurs ayant un accès au site sont affichés. - + Groupes Utilisateurs @@ -4402,7 +4435,7 @@ Souhaitez-vous continuer? - + Appareils @@ -4623,7 +4656,7 @@ Souhaitez-vous continuer? TeraForm - + Choisir la couleur From 3f152d08c23af1f8f86f8c552fd9b3e058289d4d Mon Sep 17 00:00:00 2001 From: Simon Briere Date: Wed, 8 Mar 2023 15:25:16 -0500 Subject: [PATCH 41/42] Adjusted QRCodeDialog size. --- client/src/dialogs/QRCodeDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/dialogs/QRCodeDialog.cpp b/client/src/dialogs/QRCodeDialog.cpp index 2c12b4f9..48000d6c 100644 --- a/client/src/dialogs/QRCodeDialog.cpp +++ b/client/src/dialogs/QRCodeDialog.cpp @@ -14,7 +14,7 @@ QRCodeDialog::QRCodeDialog(QString text, QWidget *parent) : ui->setupUi(this); setFixedSize(size()); - ui->wdgCode->setFixedSize(this->height() - 60, this->height() - 60); + ui->wdgCode->setFixedSize(this->height() - 80, this->height() - 80); connect(ui->btnClose, &QPushButton::clicked, this, &QDialog::accept); From 787fda43536e53b6ce0466978fb5dff37e51696a Mon Sep 17 00:00:00 2001 From: SBriere Date: Wed, 8 Mar 2023 16:06:29 -0500 Subject: [PATCH 42/42] RPath fix for packaging for Mac OS --- package/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package/CMakeLists.txt b/package/CMakeLists.txt index 9bfe7bfc..d35b8e07 100644 --- a/package/CMakeLists.txt +++ b/package/CMakeLists.txt @@ -21,6 +21,7 @@ if (APPLE) set(CPACK_DMG_BACKGROUND_IMAGE ${CMAKE_CURRENT_SOURCE_DIR}/../client/resources/logos/LogoOpenTeraPlus.png) set(CPACK_DMG_FORMAT UDZO) set(CPACK_MONOLITHIC_INSTALL 1) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # add_custom_target(mac_deploy_qt # COMMAND ${qt5_install_prefix}/bin/macdeployqt "$/../.." -always-overwrite -codesign="" @@ -28,7 +29,7 @@ if (APPLE) # DEPENDS OpenTeraPlus # ) - set(MACSIGNID "S636XF5A8U") + set(MACSIGNID "") set(MACQTDEPLOY_COMMAND "${qt5_install_prefix}/bin/macdeployqt $/../.. -always-overwrite -verbose=1") # message(STATUS "********** ${MACQTDEPLOY_COMMAND}")