diff --git a/net-voip/yate/additional-files/yate.rdef.in b/net-voip/yate/additional-files/yate.rdef.in new file mode 100644 index 000000000..1b462f9fa --- /dev/null +++ b/net-voip/yate/additional-files/yate.rdef.in @@ -0,0 +1,18 @@ + +resource app_flags B_SINGLE_LAUNCH; + +resource app_version { + major = @MAJOR@, + middle = @MIDDLE@, + minor = @MINOR@, + + variety = B_APPV_FINAL, + internal = 0, + + short_info = "Yate", + long_info = "@LONG_INFO@" +}; + +resource app_signature "@APP_SIGNATURE@"; + + diff --git a/net-voip/yate/patches/yate-6.1.1~git.patchset b/net-voip/yate/patches/yate-6.1.1~git.patchset deleted file mode 100644 index fb6bda0d0..000000000 --- a/net-voip/yate/patches/yate-6.1.1~git.patchset +++ /dev/null @@ -1,154 +0,0 @@ -From d35adb5da7f1b060c52c857bf22808628478ffff Mon Sep 17 00:00:00 2001 -From: Gerasim Troeglazov <3dEyes@gmail.com> -Date: Wed, 29 Apr 2020 14:25:19 +0000 -Subject: Fix build on Haiku - - -diff --git a/clients/qtclient.pro b/clients/qtclient.pro -index 60c1b2a..48b9142 100644 ---- a/clients/qtclient.pro -+++ b/clients/qtclient.pro -@@ -17,7 +17,7 @@ RCC_DIR = $${DESTDIR}/clients - UI_DIR = $${DESTDIR}/clients - - INCLUDEPATH += .. qt4 ../modules/qt4 --unix:!mac:LIBS += -lpthread -lasound -+unix:!mac:!haiku:LIBS += -lpthread -lasound - mac:LIBS += -framework CoreFoundation -framework CoreServices -framework CoreAudio -framework AudioUnit -framework AudioToolbox - LIBS += -L$$DESTDIR -lyate -lbasemodules - -@@ -32,7 +32,7 @@ SOURCES += main-qt4.cpp \ - - mac:SOURCES += ../modules/client/coreaudio.cpp - win32:SOURCES += ../modules/client/dsoundchan.cpp --unix:!mac:SOURCES += ../modules/client/alsachan.cpp -+unix:!mac:!haiku:SOURCES += ../modules/client/alsachan.cpp - - HEADERS += qt4/qt4client.h \ - ../modules/qt4/widgetlist.h \ -@@ -103,4 +103,4 @@ mac { - - unix:!mac { - QMAKE_POST_LINK += $$quote(ln -s libbasemodules.so.1.0.0 $${DESTDIR}/libbasemodules.yate $$escape_expand(\n\t)) --} -\ No newline at end of file -+} -diff --git a/engine/Mutex.cpp b/engine/Mutex.cpp -index 40c5d95..2ecccd7 100644 ---- a/engine/Mutex.cpp -+++ b/engine/Mutex.cpp -@@ -32,7 +32,7 @@ typedef HANDLE HSEMAPHORE; - - #ifdef MUTEX_HACK - extern "C" { --#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__HAIKU__) - extern int pthread_mutexattr_settype(pthread_mutexattr_t *__attr, int __kind); - #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE - #else -diff --git a/engine/Thread.cpp b/engine/Thread.cpp -index 4cf7120..dd77714 100644 ---- a/engine/Thread.cpp -+++ b/engine/Thread.cpp -@@ -162,6 +162,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority - default: - break; - } -+#ifndef __HAIKU__ - int err = ::pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED); - if (!err) - err = ::pthread_attr_setschedpolicy(&attr,policy); -@@ -179,6 +180,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority - #ifdef XDEBUG - else - Debug(DebugInfo,"Successfully set high thread priority %d",prio); -+#endif - #endif - } - #endif /* _WINDOWS */ -@@ -211,7 +213,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority - } - #else /* _WINDOWS */ - e = ::pthread_create(&p->thread,&attr,startFunc,p); --#ifdef PTHREAD_INHERIT_SCHED -+#if defined(PTHREAD_INHERIT_SCHED) && !defined(__HAIKU__) - if ((0 == i) && (EPERM == e) && (prio > Thread::Normal)) { - Debug(DebugWarn,"Failed to create thread with priority %d, trying with inherited",prio); - ::pthread_attr_setinheritsched(&attr,PTHREAD_INHERIT_SCHED); -diff --git a/engine/engine.pro b/engine/engine.pro -index 1c6a7e2..ef5178f 100644 ---- a/engine/engine.pro -+++ b/engine/engine.pro -@@ -12,7 +12,7 @@ mac|linux:DEFINES += ATOMIC_OPS HAVE_GMTOFF HAVE_INT_TZ HAVE_POLL HAVE_NTOP HAVE - # engine/regex/regex.c - mac:DEFINES += STDC_HEADERS - # engine/Mutex.cpp --mac:DEFINES += MUTEX_HACK -+mac|haiku:DEFINES += MUTEX_HACK - linux:DEFINES += HAVE_TIMEDLOCK HAVE_TIMEDWAIT - # engine/Thread.cpp - linux:DEFINES += HAVE_PRCTL -@@ -25,8 +25,9 @@ mac:INCLUDEPATH += ./macosx - linux:INCLUDEPATH -= ./regex - win32:INCLUDEPATH += ../windows - --LIBS += -lresolv -lpthread -+!haiku:LIBS += -lresolv -lpthread - mac:LIBS += -lobjc -framework Foundation -+haiku:LIBS += -lnetwork - - SOURCES += $$files(*.cpp) $$files(regex/*.c) - mac:SOURCES += $$files(macosx/*.mm) -@@ -34,4 +35,4 @@ linux:SOURCES -= $$files(regex/*.c) - - HEADERS += $$files(*.h) $$files(tables/*.h) $$files(regex/*.h) - mac:HEADERS += $$files(macosx/*.h) --linux:HEADERS -= $$files(regex/*.h) -\ No newline at end of file -+linux:HEADERS -= $$files(regex/*.h) -diff --git a/modules/modules.pro b/modules/modules.pro -index e2700dd..5d23226 100644 ---- a/modules/modules.pro -+++ b/modules/modules.pro -@@ -7,7 +7,7 @@ DESTDIR = ../build - OBJECTS_DIR = $${DESTDIR}/modules - - # unix and (:) not mac --unix:!mac:DEFINES += HAVE_MALLINFO USE_TLS_METHOD -+unix:!mac:!haiku:DEFINES += HAVE_MALLINFO USE_TLS_METHOD - unix:mac:DEFINES += NO_AESCTR - INCLUDEPATH += .. ../libs/yrtp ../libs/ysip ../libs/ysdp ../libs/yiax ../libs/yjabber - mac:INCLUDEPATH += /opt/local/include -diff --git a/modules/rmanager.cpp b/modules/rmanager.cpp -index 3c3fe50..3bc236d 100644 ---- a/modules/rmanager.cpp -+++ b/modules/rmanager.cpp -@@ -29,6 +29,11 @@ - #include - #include - -+#ifdef __HAIKU__ -+#undef HAVE_MALLINFO -+#undef HAVE_COREDUMPER -+#endif -+ - #ifdef NDEBUG - #undef HAVE_MALLINFO - #undef HAVE_COREDUMPER -diff --git a/yatemath.h b/yatemath.h -index 2a42426..7207e3e 100644 ---- a/yatemath.h -+++ b/yatemath.h -@@ -25,6 +25,9 @@ - #include - #include - #include -+#ifdef __HAIKU__ -+#undef bzero -+#endif - - namespace TelEngine { - --- -2.26.0 - diff --git a/net-voip/yate/patches/yate-6.4.0-qt5.patchset b/net-voip/yate/patches/yate-6.4.0-qt5.patchset new file mode 100644 index 000000000..c2e13e335 --- /dev/null +++ b/net-voip/yate/patches/yate-6.4.0-qt5.patchset @@ -0,0 +1,24834 @@ +From 83553f76a3a9a26bf760b249eb0d6e19f4297feb Mon Sep 17 00:00:00 2001 +From: Octavian +Date: Wed, 22 Dec 2021 14:59:14 +0300 +Subject: Add Qt5 port + + +diff --git a/clients/Makefile.in b/clients/Makefile.in +index 9c2a3c1..6a3a6aa 100644 +--- a/clients/Makefile.in ++++ b/clients/Makefile.in +@@ -10,13 +10,13 @@ DEBUG := + CXX := @CXX@ -Wall + SED := sed + DEFS := +-MOC := @QT4_MOC@ +-QT4_INC := @QT4_INC@ +-QT4_LIB := @QT4_LIB@ +-QT4_INC_NET := @QT4_INC_NET@ +-QT4_LIB_NET := @QT4_LIB_NET@ +-QT4_CLIENT_DEPS := ../libyateqt4.so +-QT4_CLIENT_LIBS := -lyateqt4 ++MOC := @QT5_MOC@ ++QT5_INC := @QT5_INC@ ++QT5_LIB := @QT5_LIB@ ++QT5_INC_NET := @QT5_INC_NET@ ++QT5_LIB_NET := @QT5_LIB_NET@ ++QT5_CLIENT_DEPS := ../libyateqt5.so ++QT5_CLIENT_LIBS := -lyateqt5 + LIBTHR:= @THREAD_LIB@ + INCLUDES := -I.. -I@top_srcdir@ + CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@ +@@ -31,21 +31,21 @@ LIBS := + MENUFILES := + DESKFILES := + +-ifneq (@HAVE_QT4@,no) +-SUBDIRS := $(SUBDIRS) qt4 +-PROGS := $(PROGS) yate-qt4 +-MENUFILES := $(MENUFILES) yate-qt4.menu +-DESKFILES := $(DESKFILES) yate-qt4.desktop ++ifneq (@HAVE_QT5@,no) ++SUBDIRS := $(SUBDIRS) qt5 ++PROGS := $(PROGS) yate-qt5 ++MENUFILES := $(MENUFILES) yate-qt5.menu ++DESKFILES := $(DESKFILES) yate-qt5.desktop + ICONFILES := $(ICONFILES) null_team-16.png null_team-32.png null_team-48.png null_team-64.png null_team-128.png + +-ifneq (@QT4_STATIC_MODULES@,no) +-ifeq (@QT4_STATIC_MODULES@,yes) +-QT4_CLIENT_LIBS := customtable customtext customtree widgetlist clientarchive ++ifneq (@QT5_STATIC_MODULES@,no) ++ifeq (@QT5_STATIC_MODULES@,yes) ++QT5_CLIENT_LIBS := customtable customtext customtree widgetlist clientarchive + else +-QT4_CLIENT_LIBS := $(strip @QT4_STATIC_MODULES@) ++QT5_CLIENT_LIBS := $(strip @QT5_STATIC_MODULES@) + endif +-QT4_CLIENT_LIBS := $(foreach mod,$(QT4_CLIENT_LIBS),../modules/qt4/$(mod).o) qt4/qt4client.a +-QT4_CLIENT_DEPS := $(QT4_CLIENT_LIBS) ++QT5_CLIENT_LIBS := $(foreach mod,$(QT5_CLIENT_LIBS),../modules/qt5/$(mod).o) qt5/qt5client.a ++QT5_CLIENT_DEPS := $(QT5_CLIENT_LIBS) + endif + + endif +@@ -57,8 +57,8 @@ EXTERNLIBS = + COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) + LINK = $(CXX) $(LDFLAGS) + +-ifneq (x@QT4_VER@,x) +-DEFS := $(DEFS) -DQT4_VER=@QT4_VER@ ++ifneq (x@QT5_VER@,x) ++DEFS := $(DEFS) -DQT5_VER=@QT5_VER@ + endif + + prefix = @prefix@ +@@ -135,17 +135,17 @@ uninstall: do-uninstall + rmdir "$(DESTDIR)$(icondir)" \ + ) + +-../modules/qt4/%.o: @top_srcdir@/modules/qt4/%.cpp +- $(MAKE) -C ../modules qt4/$(notdir $@) ++../modules/qt5/%.o: @top_srcdir@/modules/qt5/%.cpp ++ $(MAKE) -C ../modules qt5/$(notdir $@) + + %.o: @srcdir@/%.cpp $(MKDEPS) $(INCFILES) + $(COMPILE) -c $< + + %.moc.o: %.moc $(INCFILES) +- $(COMPILE) $(QT4_INC) -o $@ -c -x c++ $< ++ $(COMPILE) $(QT5_INC) -o $@ -c -x c++ $< + + %.moc: @srcdir@/%.h +- $(MOC) $(DEFS) $(INCLUDES) $(QT4_INC) -o $@ $< ++ $(MOC) $(DEFS) $(INCLUDES) $(QT5_INC) -o $@ $< + + do-all do-strip do-clean do-install do-uninstall: + $(if $(SUBDIRS),\ +@@ -160,12 +160,12 @@ do-all do-strip do-clean do-install do-uninstall: + Makefile: @srcdir@/Makefile.in $(MKDEPS) + cd .. && ./config.status + +-yate-qt4: $(QT4_CLIENT_DEPS) +-yate-qt4: EXTERNFLAGS = $(QT4_INC) +-yate-qt4: EXTERNLIBS = $(QT4_CLIENT_LIBS) $(QT4_LIB) ++yate-qt5: $(QT5_CLIENT_DEPS) ++yate-qt5: EXTERNFLAGS = $(QT5_INC) ++yate-qt5: EXTERNLIBS = $(QT5_CLIENT_LIBS) $(QT5_LIB) + +-qt4/qt4client.a: @srcdir@/qt4/qt4client.h @srcdir@/qt4/qt4client.cpp +- $(MAKE) -C qt4 $(notdir $@) ++qt5/qt5client.a: @srcdir@/qt5/qt5client.h @srcdir@/qt5/qt5client.cpp ++ $(MAKE) -C qt5 $(notdir $@) + + yate-%: @srcdir@/main-%.cpp $(MKDEPS) ../libyate.so $(INCFILES) + $(COMPILE) -o $@ $(LOCALFLAGS) $(EXTERNFLAGS) $< $(LDFLAGS) $(LIBTHR) $(LOCALLIBS) $(YATELIBS) $(EXTERNLIBS) +diff --git a/clients/main-qt5.cpp b/clients/main-qt5.cpp +new file mode 100644 +index 0000000..996cc0d +--- /dev/null ++++ b/clients/main-qt5.cpp +@@ -0,0 +1,89 @@ ++/** ++ * main-qt5.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * A Qt-5 based universal telephony client ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include ++#include "qt5/qt5client.h" ++ ++#define WAIT_ENGINE 10000 //wait 10 seconds for engine to halt ++ ++using namespace TelEngine; ++ ++class EngineThread; ++ ++static QtDriver qtdriver(false); ++static EngineThread* s_engineThread = 0; ++ ++class EngineThread : public Thread ++{ ++public: ++ inline EngineThread() ++ : Thread("Engine") ++ { } ++ virtual void run(); ++ virtual void cleanup(); ++}; ++ ++void EngineThread::run() ++{ ++ Engine::self()->run(); ++ Debug(DebugAll,"Engine stopped running"); ++} ++ ++void EngineThread::cleanup() ++{ ++ Debug(DebugAll,"EngineThread::cleanup() [%p]",this); ++ if (QtClient::self()) ++ QtClient::self()->quit(); ++ s_engineThread = 0; ++} ++ ++static int mainLoop() ++{ ++ // create engine from this thread ++ Engine::self(); ++ s_engineThread = new EngineThread; ++ if (!s_engineThread->startup()) ++ return EINVAL; ++ ++ // build client if the driver didn't ++ if (!QtClient::self()) ++ QtClient::setSelf(new QtClient()); ++ ++ // run the client ++ if (!Engine::exiting()) ++ QtClient::self()->run(); ++ // the client finished running, do cleanup ++ QtClient::self()->cleanup(); ++ ++ Engine::halt(0); ++ unsigned long count = WAIT_ENGINE / Thread::idleMsec(); ++ while (s_engineThread && count--) ++ Thread::idle(); ++ ++ return 0; ++} ++ ++extern "C" int main(int argc, const char** argv, const char** envp) ++{ ++ TelEngine::Engine::extraPath("qt5"); ++ return TelEngine::Engine::main(argc,argv,envp,TelEngine::Engine::Client,&mainLoop); ++} ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/clients/qt5/Makefile.in b/clients/qt5/Makefile.in +new file mode 100644 +index 0000000..a77b9f0 +--- /dev/null ++++ b/clients/qt5/Makefile.in +@@ -0,0 +1,118 @@ ++# Makefile ++# This file holds the make rules for the Qt5 client support ++ ++# override DEBUG at compile time to enable full debug or remove it all ++DEBUG := ++ ++CXX := @CXX@ -Wall ++AR := ar ++MOC := @QT5_MOC@ ++QT5_INC := @QT5_INC@ ++QT5_LIB := @QT5_LIB@ ++QT5_INC_NET := @QT5_INC_NET@ ++QT5_LIB_NET := @QT5_LIB_NET@ ++DEFS:= ++ ++INCLUDES:=-I. -I@srcdir@ -I@top_srcdir@ $(QT5_INC) ++CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@ ++LDFLAGS:= @LDFLAGS@ ++SONAME_OPT := @SONAME_OPT@ ++YATELIBS := -L../.. -lyate @LIBS@ ++INCFILES := @top_srcdir@/yateclass.h @top_srcdir@/yatecbase.h @srcdir@/qt5client.h ++ ++PROGS= ++LIBS = qt5client.a ++SOURCES = qt5client.cpp ++OBJS = $(SOURCES:.cpp=.o) qt5client.moc.o ++INST:= ++LIBD_DEV:= libyateqt5.so ++LIBD_VER:= $(LIBD_DEV).@PACKAGE_VERSION@ ++ifeq (@QT5_STATIC_MODULES@,no) ++LIBD:= ../../$(LIBD_VER) ../../$(LIBD_DEV) ++INST:= $(LIBD_VER) $(LIBD_DEV) ++endif ++ ++LOCALFLAGS = ++LOCALLIBS = ++COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) ++LINK = $(CXX) $(LDFLAGS) ++ ++ifneq (x@QT5_VER@,x) ++DEFS := $(DEFS) -DQT5_VER=@QT5_VER@ ++endif ++ ++prefix = @prefix@ ++exec_prefix = @exec_prefix@ ++datarootdir = @datarootdir@ ++ ++bindir = @bindir@ ++libdir = @libdir@ ++incdir = @includedir@/yate ++ ++# include optional local make rules ++-include YateLocal.mak ++ ++.PHONY: all debug ddebug xdebug ++all: $(LIBS) $(LIBD) $(PROGS) ++ ++debug: ++ $(MAKE) all DEBUG=-g3 MODSTRIP= ++ ++ddebug: ++ $(MAKE) all DEBUG='-g3 -DDEBUG' MODSTRIP= ++ ++xdebug: ++ $(MAKE) all DEBUG='-g3 -DXDEBUG' MODSTRIP= ++ ++.PHONY: strip ++strip: all ++ strip --strip-debug --discard-locals $(PROGS) ++ ++.PHONY: clean ++clean: ++ @-$(RM) $(PROGS) $(LIBS) $(LIBD) $(OBJS) core 2>/dev/null ++ ++.PHONY: install uninstall ++install: all ++ $(if $(INST),\ ++ @mkdir -p "$(DESTDIR)$(libdir)" && \ ++ for i in $(INST) ; do \ ++ if [ -h "../../$$i" ]; then \ ++ f=`readlink "../../$$i"` ; \ ++ ln -sf "$$f" "$(DESTDIR)$(libdir)/$$i" ; \ ++ else \ ++ install @INSTALL_L@ ../../$$i "$(DESTDIR)$(libdir)/" ; \ ++ fi \ ++ done; \ ++ mkdir -p "$(DESTDIR)$(incdir)" && \ ++ install -m 0644 @srcdir@/qt5client.h "$(DESTDIR)$(incdir)/" \ ++ ) ++ ++uninstall: ++ $(if $(INST),\ ++ @-for i in $(INST) ; do \ ++ rm "$(DESTDIR)$(libdir)/$$i" ; \ ++ done; \ ++ rm "$(DESTDIR)$(incdir)/qt5client.h" && rmdir "$(DESTDIR)$(libdir)" \ ++ ) ++ ++%.o: @srcdir@/%.cpp $(INCFILES) ++ $(COMPILE) -c $< ++ ++%.moc.o: %.moc $(INCFILES) ++ $(COMPILE) -o $@ -c -x c++ $< ++ ++%.moc: @srcdir@/%.h ++ $(MOC) $(DEFS) $(INCLUDES) -o $@ $< ++ ++Makefile: @srcdir@/Makefile.in ../../config.status ++ cd ../.. && ./config.status ++ ++../../$(LIBD_VER): $(OBJS) ++ $(LINK) -o $@ $(SONAME_OPT)$(LIBD_VER) $^ $(YATELIBS) $(QT5_LIB) ++ ++../../$(LIBD_DEV): ../../$(LIBD_VER) ++ cd ../.. && ln -sf $(LIBD_VER) $(LIBD_DEV) ++ ++$(LIBS): $(OBJS) ++ $(AR) rcs $@ $^ +diff --git a/clients/qt5/qt5client.cpp b/clients/qt5/qt5client.cpp +new file mode 100644 +index 0000000..13c4c77 +--- /dev/null ++++ b/clients/qt5/qt5client.cpp +@@ -0,0 +1,5846 @@ ++/** ++ * qt5client.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * A Qt-5 based universal telephony client ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "qt5client.h" ++#include ++ ++#ifdef _WINDOWS ++#define DEFAULT_DEVICE "dsound/*" ++#define PLATFORM_LOWERCASE_NAME "windows" ++#elif defined(__APPLE__) ++#define DEFAULT_DEVICE "coreaudio/*" ++#define PLATFORM_LOWERCASE_NAME "apple" ++#elif defined(__linux__) ++#define DEFAULT_DEVICE "alsa/default" ++#define PLATFORM_LOWERCASE_NAME "linux" ++#else ++#define DEFAULT_DEVICE "oss//dev/dsp" ++#define PLATFORM_LOWERCASE_NAME "unknown" ++#endif ++ ++namespace TelEngine { ++ ++static unsigned int s_allHiddenQuit = 0; // Quit on all hidden notification if this counter is 0 ++ ++// Factory used to create objects in client's thread ++class Qt5ClientFactory : public UIFactory ++{ ++public: ++ Qt5ClientFactory(const char* name = "Qt5ClientFactory"); ++ virtual void* create(const String& type, const char* name, NamedList* params = 0); ++}; ++ ++// Class used for temporary operations on QT widgets ++// Keeps a pointer to a widget and its type ++// NOTE: The methods of this class don't check the widget pointer ++class QtWidget ++{ ++public: ++ enum Type { ++ PushButton = 0, ++ CheckBox = 1, ++ Table = 2, ++ ListBox = 3, ++ ComboBox = 4, ++ Tab = 5, ++ StackWidget = 6, ++ TextEdit = 7, ++ Label = 8, ++ LineEdit = 9, ++ AbstractButton = 10, ++ Slider = 11, ++ ProgressBar = 12, ++ SpinBox = 13, ++ Calendar = 14, ++ Splitter = 15, ++ TextBrowser = 16, ++ Unknown, // Unknown type ++ Action, // QAction descendant ++ CustomTable, // QtTable descendant ++ CustomTree, // QtTree descendant ++ CustomWidget, // QtCustomWidget descendant ++ CustomObject, // QtCustomObject descendant ++ Missing // Invalid pointer ++ }; ++ // Set widget from object ++ inline QtWidget(QObject* w) ++ : m_widget(0), m_action(0), m_object(0), m_type(Missing) { ++ if (!w) ++ return; ++ if (w->inherits("QWidget")) ++ m_widget = static_cast(w); ++ else if (w->inherits("QAction")) ++ m_action = static_cast(w); ++ m_type = getType(); ++ } ++ // Set widget from object and type ++ inline QtWidget(QWidget* w, int t) ++ : m_widget(w), m_action(0), m_object(0), m_type(t) { ++ if (!m_widget) ++ m_type = Missing; ++ } ++ // Set widget/action from object and name ++ inline QtWidget(QObject* parent, const String& name) ++ : m_widget(0), m_action(0), m_object(0), m_type(Missing) { ++ QString what = QtClient::setUtf8(name); ++ m_widget = parent->findChild(what); ++ if (!m_widget) { ++ m_action = parent->findChild(what); ++ if (!m_action) ++ m_object = parent->findChild(what); ++ } ++ m_type = getType(); ++ } ++ inline bool valid() const ++ { return type() != Missing; } ++ inline bool invalid() const ++ { return type() == Missing; } ++ inline int type() const ++ { return m_type; } ++ inline operator QWidget*() ++ { return m_widget; } ++ inline bool inherits(const char* classname) ++ { return m_widget && m_widget->inherits(classname); } ++ inline bool inherits(Type t) ++ { return inherits(s_types[t]); } ++ inline QWidget* widget() ++ { return m_widget; } ++ inline QWidget* operator ->() ++ { return m_widget; } ++ // Static cast methods ++ inline QPushButton* button() ++ { return static_cast(m_widget); } ++ inline QCheckBox* check() ++ { return static_cast(m_widget); } ++ inline QTableWidget* table() ++ { return static_cast(m_widget); } ++ inline QListWidget* list() ++ { return static_cast(m_widget); } ++ inline QComboBox* combo() ++ { return static_cast(m_widget); } ++ inline QTabWidget* tab() ++ { return static_cast(m_widget); } ++ inline QStackedWidget* stackWidget() ++ { return static_cast(m_widget); } ++ inline QTextEdit* textEdit() ++ { return static_cast(m_widget); } ++ inline QLabel* label() ++ { return static_cast(m_widget); } ++ inline QLineEdit* lineEdit() ++ { return static_cast(m_widget); } ++ inline QAbstractButton* abstractButton() ++ { return static_cast(m_widget); } ++ inline QSlider* slider() ++ { return static_cast(m_widget); } ++ inline QProgressBar* progressBar() ++ { return static_cast(m_widget); } ++ inline QSpinBox* spinBox() ++ { return static_cast(m_widget); } ++ inline QCalendarWidget* calendar() ++ { return static_cast(m_widget); } ++ inline QSplitter* splitter() ++ { return static_cast(m_widget); } ++ inline QtTable* customTable() ++ { return qobject_cast(m_widget); } ++ inline QtTree* customTree() ++ { return qobject_cast(m_widget); } ++ inline QtCustomWidget* customWidget() ++ { return qobject_cast(m_widget); } ++ inline QtCustomObject* customObject() ++ { return qobject_cast(m_object); } ++ inline UIWidget* uiWidget() { ++ switch (type()) { ++ case CustomTable: ++ return static_cast(customTable()); ++ case CustomWidget: ++ return static_cast(customWidget()); ++ case CustomObject: ++ return static_cast(customObject()); ++ case CustomTree: ++ return static_cast(customTree()); ++ } ++ return 0; ++ } ++ ++ inline QAction* action() ++ { return m_action; } ++ ++ // Find a combo box item ++ inline int findComboItem(const String& item) { ++ QComboBox* c = combo(); ++ return c ? c->findText(QtClient::setUtf8(item)) : -1; ++ } ++ // Add an item to a combo box ++ inline bool addComboItem(const String& item, bool atStart) { ++ QComboBox* c = combo(); ++ if (!c) ++ return false; ++ QString it(QtClient::setUtf8(item)); ++ if (atStart) ++ c->insertItem(0,it); ++ else ++ c->addItem(it); ++ return true; ++ } ++ // Find a list box item ++ inline int findListItem(const String& item) { ++ QListWidget* l = list(); ++ if (!l) ++ return -1; ++ QString it(QtClient::setUtf8(item)); ++ for (int i = l->count(); i >= 0 ; i--) { ++ QListWidgetItem* tmp = l->item(i); ++ if (tmp && it == tmp->text()) ++ return i; ++ } ++ return -1; ++ } ++ // Add an item to a list box ++ inline bool addListItem(const String& item, bool atStart) { ++ QListWidget* l = list(); ++ if (!l) ++ return false; ++ QString it(QtClient::setUtf8(item)); ++ if (atStart) ++ l->insertItem(0,it); ++ else ++ l->addItem(it); ++ return true; ++ } ++ ++ int getType() { ++ if (m_widget) { ++ String cls = m_widget->metaObject()->className(); ++ for (int i = 0; i < Unknown; i++) ++ if (s_types[i] == cls) ++ return i; ++ if (customTable()) ++ return CustomTable; ++ if (customWidget()) ++ return CustomWidget; ++ if (customTree()) ++ return CustomTree; ++ return Unknown; ++ } ++ if (m_action && m_action->inherits("QAction")) ++ return Action; ++ if (customObject()) ++ return CustomObject; ++ return Missing; ++ } ++ static String s_types[Unknown]; ++protected: ++ QWidget* m_widget; ++ QAction* m_action; ++ QObject* m_object; ++ int m_type; ++private: ++ QtWidget() {} ++}; ++ ++// Class used for temporary operations on QTableWidget objects ++// NOTE: The methods of this class don't check the table pointer ++class TableWidget : public GenObject ++{ ++public: ++ TableWidget(QTableWidget* table, bool tmp = true); ++ TableWidget(QWidget* wid, const String& name, bool tmp = true); ++ TableWidget(QtWidget& table, bool tmp = true); ++ ~TableWidget(); ++ inline QTableWidget* table() ++ { return m_table; } ++ inline bool valid() ++ { return m_table != 0; } ++ inline QtTable* customTable() ++ { return (valid()) ? qobject_cast(m_table) : 0; } ++ inline const String& name() ++ { return m_name; } ++ inline int rowCount() ++ { return m_table->rowCount(); } ++ inline int columnCount() ++ { return m_table->columnCount(); } ++ inline void setHeaderText(int col, const char* text) { ++ if (col < columnCount()) ++ m_table->setHorizontalHeaderItem(col, ++ new QTableWidgetItem(QtClient::setUtf8(text))); ++ } ++ inline bool getHeaderText(int col, String& dest, bool lower = true) { ++ QTableWidgetItem* item = m_table->horizontalHeaderItem(col); ++ if (item) { ++ QtClient::getUtf8(dest,item->text()); ++ if (lower) ++ dest.toLower(); ++ } ++ return item != 0; ++ } ++ // Get the current selection's row ++ inline int crtRow() { ++ QList items = m_table->selectedItems(); ++ if (items.size()) ++ return items[0]->row(); ++ return -1; ++ } ++ inline void repaint() ++ { m_table->repaint(); } ++ inline void addRow(int index) ++ { m_table->insertRow(index); } ++ inline void delRow(int index) { ++ if (index >= 0) ++ m_table->removeRow(index); ++ } ++ inline void addColumn(int index, int width = -1, const char* name = 0) { ++ m_table->insertColumn(index); ++ if (width >= 0) ++ m_table->setColumnWidth(index,width); ++ setHeaderText(index,name); ++ } ++ inline void setImage(int row, int col, const String& image) { ++ QTableWidgetItem* item = m_table->item(row,col); ++ if (item) ++ item->setIcon(QIcon(QtClient::setUtf8(image))); ++ } ++ inline void addCell(int row, int col, const String& value) { ++ QTableWidgetItem* item = new QTableWidgetItem(QtClient::setUtf8(value)); ++ m_table->setItem(row,col,item); ++ } ++ inline void setCell(int row, int col, const String& value, bool addNew = true) { ++ QTableWidgetItem* item = m_table->item(row,col); ++ if (item) ++ item->setText(QtClient::setUtf8(value)); ++ else if (addNew) ++ addCell(row,col,value); ++ } ++ inline bool getCell(int row, int col, String& dest, bool lower = false) { ++ QTableWidgetItem* item = m_table->item(row,col); ++ if (item) { ++ QtClient::getUtf8(dest,item->text()); ++ if (lower) ++ dest.toLower(); ++ return true; ++ } ++ return false; ++ } ++ inline void setID(int row, const String& value) ++ { setCell(row,0,value); } ++ // Add or set a row ++ void updateRow(const String& item, const NamedList* data, bool atStart); ++ // Update a row from a list of parameters ++ void updateRow(int row, const NamedList& data); ++ // Find a row by the first's column value. Return -1 if not found ++ int getRow(const String& item); ++ // Find a column by its label. Return -1 if not found ++ int getColumn(const String& name, bool caseInsentive = true); ++protected: ++ void init(bool tmp); ++private: ++ QTableWidget* m_table; // The table ++ String m_name; // Table's name ++ int m_sortControl; // Flag used to set/reset sorting attribute of the table ++}; ++ ++// Store an UI loaded from file to avoid loading it again ++class UIBuffer : public String ++{ ++public: ++ inline UIBuffer(const String& name, QByteArray* buf) ++ : String(name), m_buffer(buf) ++ { s_uiCache.append(this); } ++ inline QByteArray* buffer() ++ { return m_buffer; } ++ // Remove from list. Release memory ++ virtual void destruct(); ++ // Return an already loaded UI. Load from file if not found. ++ // Add URLs paths when missing ++ static UIBuffer* build(const String& name); ++ // Find a buffer ++ static UIBuffer* find(const String& name); ++ // Buffer cache ++ static ObjList s_uiCache; ++private: ++ QByteArray* m_buffer; // The buffer ++}; ++ ++}; // namespace TelEngine ++ ++using namespace TelEngine; ++ ++// Dynamic properies ++static const String s_propsSave = "_yate_save_props"; // Save properties property name ++static const String s_propColWidths = "_yate_col_widths"; // Column widths ++static const String s_propSorting = "_yate_sorting"; // Table/List sorting ++static const String s_propSizes = "_yate_sizes"; // Size int array ++static const String s_propShowWndWhenActive = "_yate_showwnd_onactive"; // Show another window when a window become active ++static String s_propHHeader = "dynamicHHeader"; // Tables: show/hide the horizontal header ++static String s_propAction = "dynamicAction"; // Prefix for properties that would trigger some action ++static String s_propWindowFlags = "_yate_windowflags"; // Window flags ++static const String s_propContextMenu = "_yate_context_menu"; // Context menu name ++static String s_propHideInactive = "dynamicHideOnInactive"; // Hide inactive window ++static const String s_yatePropPrefix = "_yate_"; // Yate dynamic properties prefix ++static NamedList s_qtStyles(""); // Qt styles classname -> internal name ++// ++static Qt5ClientFactory s_qt5Factory; ++static Configuration s_cfg; ++static Configuration s_save; ++ObjList UIBuffer::s_uiCache; ++ ++// Values used to configure window title bar and border ++static TokenDict s_windowFlags[] = { ++ // Window type ++ {"popup", Qt::Popup}, ++ {"tool", Qt::Tool}, ++ {"subwindow", Qt::SubWindow}, ++#ifdef _WINDOWS ++ {"notificationtype", Qt::Tool}, ++#else ++ {"notificationtype", Qt::SubWindow}, ++#endif ++ // Window flags ++ {"title", Qt::WindowTitleHint}, ++ {"sysmenu", Qt::WindowSystemMenuHint}, ++ {"maximize", Qt::WindowMaximizeButtonHint}, ++ {"minimize", Qt::WindowMinimizeButtonHint}, ++ {"help", Qt::WindowContextHelpButtonHint}, ++ {"stayontop", Qt::WindowStaysOnTopHint}, ++ {"frameless", Qt::FramelessWindowHint}, ++#if QT_VERSION >= 0x040500 ++ {"close", Qt::WindowCloseButtonHint}, ++#endif ++ {0,0} ++}; ++ ++// Widget attribute names ++static const TokenDict s_widgetAttributes[] = { ++ {"macshowfocusrect", Qt::WA_MacShowFocusRect}, ++ {0,0} ++}; ++ ++String QtWidget::s_types[QtWidget::Unknown] = { ++ "QPushButton", ++ "QCheckBox", ++ "QTableWidget", ++ "QListWidget", ++ "QComboBox", ++ "QTabWidget", ++ "QStackedWidget", ++ "QTextEdit", ++ "QLabel", ++ "QLineEdit", ++ "QAbstractButton", ++ "QSlider", ++ "QProgressBar", ++ "QSpinBox", ++ "QCalendarWidget", ++ "QSplitter", ++ "QTextBrowser", ++}; ++ ++// QVariant type translation dictionary ++static const TokenDict s_qVarType[] = { ++ {"string", QVariant::String}, ++ {"bool", QVariant::Bool}, ++ {"int", QVariant::Int}, ++ {"uint", QVariant::UInt}, ++ {"stringlist", QVariant::StringList}, ++ {"icon", QVariant::Icon}, ++ {"pixmap", QVariant::Pixmap}, ++ {"double", QVariant::Double}, ++ {"keysequence", QVariant::KeySequence}, ++ {0,0} ++}; ++ ++// Qt alignment flags translation ++static const TokenDict s_qAlign[] = { ++ {"left", Qt::AlignLeft}, ++ {"right", Qt::AlignRight}, ++ {"hcenter", Qt::AlignHCenter}, ++ {"justify", Qt::AlignJustify}, ++ {"top", Qt::AlignTop}, ++ {"bottom", Qt::AlignBottom}, ++ {"vcenter", Qt::AlignVCenter}, ++ {"center", Qt::AlignCenter}, ++ {"absolute", Qt::AlignAbsolute}, ++ {0,0} ++}; ++ ++// Qt alignment flags translation ++static const TokenDict s_qEditTriggers[] = { ++ {"currentchanged", QAbstractItemView::CurrentChanged}, ++ {"doubleclick", QAbstractItemView::DoubleClicked}, ++ {"selclick", QAbstractItemView::SelectedClicked}, ++ {"editkeypress", QAbstractItemView::EditKeyPressed}, ++ {"anykeypress", QAbstractItemView::AnyKeyPressed}, ++ {"all", QAbstractItemView::AllEditTriggers}, ++ {0,0} ++}; ++ ++// QtClientSort name ++static const TokenDict s_sorting[] = { ++ {"ascending", QtClient::SortAsc}, ++ {"descending", QtClient::SortDesc}, ++ {"none", QtClient::SortNone}, ++ {0,0} ++}; ++ ++// Handler for QT library messages ++static void qtMsgHandler(QtMsgType type, const QMessageLogContext& context, const QString& text) ++{ ++ int dbg = DebugAll; ++ switch (type) { ++ case QtDebugMsg: ++ case QtInfoMsg: ++ dbg = DebugInfo; ++ break; ++ case QtWarningMsg: ++ dbg = DebugWarn; ++ break; ++ case QtCriticalMsg: ++ dbg = DebugCrit; ++ break; ++ case QtFatalMsg: ++ dbg = DebugFail; ++ break; ++ } ++ QByteArray local(text.toLocal8Bit()); ++ Debug("QT",dbg,"%s",local.data()); ++} ++ ++// Build a list of parameters from a string ++// Return the number of parameters found ++static unsigned int str2Params(NamedList& params, const String& buf, char sep = '|') ++{ ++ ObjList* list = 0; ++ // Check if we have another separator ++ if (buf.startsWith("separator=")) { ++ sep = buf.at(10); ++ list = buf.substr(11).split(sep,false); ++ } ++ else ++ list = buf.split(sep,false); ++ unsigned int n = 0; ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ String* s = static_cast(o->get()); ++ int pos = s->find('='); ++ if (pos < 1) ++ continue; ++ params.addParam(s->substr(0,pos),s->substr(pos + 1)); ++ n++; ++ } ++ TelEngine::destruct(list); ++ return n; ++} ++ ++// Utility: fix QT path separator on Windows ++// (display paths using only one separator to the user) ++static inline QString fixPathSep(QString str) ++{ ++#ifdef _WINDOWS ++ QString tmp = str; ++ tmp.replace(QChar('/'),QtClient::setUtf8(Engine::pathSeparator())); ++ return tmp; ++#else ++ return str; ++#endif ++} ++ ++// Utility: find a stacked widget's page with the given name ++static int findStackedWidget(QStackedWidget& w, const String& name) ++{ ++ QString n(QtClient::setUtf8(name)); ++ for (int i = 0; i < w.count(); i++) { ++ QWidget* page = w.widget(i); ++ if (page && n == page->objectName()) ++ return i; ++ } ++ return -1; ++} ++ ++// Utility function used to get the name of a control ++// The name of the control indicates actions, toggles ... ++// The action name alias can contain parameters ++static bool translateName(QtWidget& w, String& name, NamedList** params = 0) ++{ ++ if (w.invalid()) ++ return false; ++ if (w.type() != QtWidget::Action) ++ QtClient::getIdentity(w.widget(),name); ++ else ++ QtClient::getIdentity(w.action(),name); ++ if (!name) ++ return true; ++ // Check params ++ int pos = name.find('|'); ++ if (pos < 1) ++ return true; ++ if (params) { ++ *params = new NamedList(""); ++ if (!str2Params(**params,name.substr(pos + 1))) ++ TelEngine::destruct(*params); ++ } ++ name = name.substr(0,pos); ++ return true; ++} ++ ++// Utility: raise a select event if a list is empty ++static inline void raiseSelectIfEmpty(int count, Window* wnd, const String& name) ++{ ++ if (!Client::exiting() && count <= 0 && Client::self()) ++ Client::self()->select(wnd,name,String::empty()); ++} ++ ++// Add dynamic properties from a list of parameters ++// Parameter format: ++// property_name:property_type=property_value ++static void addDynamicProps(QObject* obj, NamedList& props) ++{ ++ static String typeString = "string"; ++ static String typeBool = "bool"; ++ static String typeInt = "int"; ++ ++ if (!obj) ++ return; ++ unsigned int n = props.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = props.getParam(i); ++ if (!(ns && ns->name())) ++ continue; ++ int pos = ns->name().find(':'); ++ if (pos < 1) ++ continue; ++ ++ String prop = ns->name().substr(0,pos); ++ String type = ns->name().substr(pos + 1); ++ QVariant var; ++ if (type == typeString) ++ var.setValue(QString(ns->c_str())); ++ else if (type == typeBool) ++ var.setValue(ns->toBoolean()); ++ else if (type == typeInt) ++ var.setValue(ns->toInteger()); ++ ++ if (var.type() != QVariant::Invalid) { ++ obj->setProperty(prop,var); ++ DDebug(ClientDriver::self(),DebugAll, ++ "Object '%s': added dynamic property %s='%s' type=%s", ++ YQT_OBJECT_NAME(obj),prop.c_str(),ns->c_str(),var.typeName()); ++ } ++ else ++ Debug(ClientDriver::self(),DebugStub, ++ "Object '%s': dynamic property '%s' type '%s' is not supported", ++ YQT_OBJECT_NAME(obj),prop.c_str(),type.c_str()); ++ } ++} ++ ++// Find a QSystemTrayIcon child of an object ++static inline QSystemTrayIcon* findSysTrayIcon(QObject* obj, const char* name) ++{ ++ return obj->findChild(QtClient::setUtf8(name)); ++} ++ ++// Utility used to create an object's property if not found ++// Add it to a list of strings ++// Return true if the list changed ++static bool createProperty(QObject* obj, const char* name, QVariant::Type t, ++ QtWindow* wnd, QStringList* list) ++{ ++ if (!obj || TelEngine::null(name)) ++ return false; ++ QVariant var = obj->property(name); ++ if (var.type() == QVariant::Invalid) ++ obj->setProperty(name,QVariant(t)); ++ else if (var.type() != t) { ++ if (wnd) ++ Debug(QtDriver::self(),DebugNote, ++ "Window(%s) child '%s' already has a %s property '%s' [%p]", ++ wnd->toString().c_str(),YQT_OBJECT_NAME(obj),var.typeName(),name,wnd); ++ return false; ++ } ++ if (!list) ++ return false; ++ QString s = QtClient::setUtf8(name); ++ if (list->contains(s)) ++ return false; ++ *list << s; ++ return true; ++} ++ ++// Replace file path in URLs in a character array ++static void addFilePathUrl(QByteArray& a, const String& file) ++{ ++ if (!file) ++ return; ++ QString path = QDir::fromNativeSeparators(QtClient::setUtf8(file)); ++ // Truncate after last path separator (lastIndexOf() returns -1 if not found) ++ path.truncate(path.lastIndexOf(QString("/")) + 1); ++ if (!path.size()) ++ return; ++ int start = 0; ++ int end = -1; ++ while ((start = a.indexOf("url(",end + 1)) > 0) { ++ start += 4; ++ end = a.indexOf(")",start); ++ if (end <= start) ++ break; ++ // Add ++ int len = end - start; ++ QByteArray tmp = a.mid(start,len); ++ if (tmp.indexOf('/') != -1) ++ continue; ++ tmp.insert(0,path.toUtf8()); ++ a.replace(start,len,tmp); ++ } ++} ++ ++// Read data from file and append it to a string buffer ++// Optionally append suffix characters to file name ++static bool appendStyleSheet(QString& buf, const char* file, ++ const char* suffix1 = 0, const char* suffix2 = 0) ++{ ++ if (TelEngine::null(file)) ++ return false; ++ String shf = file; ++ const char* oper = 0; ++ int pos = shf.rfind('/'); ++ if (pos < 0) ++ pos = shf.rfind('\\'); ++ if (pos < 0) ++ shf = Client::s_skinPath + shf; ++ int level = DebugNote; ++ if (!(TelEngine::null(suffix1) && TelEngine::null(suffix2))) { ++ level = DebugAll; ++ int dotPos = shf.rfind('.'); ++ if (dotPos > pos) { ++ String tmp = shf.substr(0,dotPos); ++ tmp.append(suffix1,"_"); ++ tmp.append(suffix2,"_"); ++ shf = tmp + shf.substr(dotPos); ++ } ++ } ++ DDebug(ClientDriver::self(),DebugAll,"Loading stylesheet file '%s'",shf.c_str()); ++ QFile f(QtClient::setUtf8(shf)); ++ if (f.open(QIODevice::ReadOnly)) { ++ QByteArray a = f.readAll(); ++ if (a.size()) { ++ addFilePathUrl(a,shf); ++ buf += QString::fromUtf8(a.constData()); ++ } ++ else if (f.error() != QFile::NoError) ++ oper = "read"; ++ } ++ else ++ oper = "open"; ++ if (!oper) ++ return true; ++ Debug(ClientDriver::self(),level,"Failed to %s stylesheet file '%s': %d '%s'", ++ oper,shf.c_str(),f.error(),f.errorString().toUtf8().constData()); ++ return false; ++} ++ ++// Split an integer string list ++// Result list length can be set by indicating a length ++static QList buildIntList(const String& buf, int len = 0) ++{ ++ QList ret; ++ ObjList* list = buf.split(','); ++ int pos = 0; ++ ObjList* o = list; ++ while (o || pos < len) { ++ int val = 0; ++ if (o) { ++ if (o->get()) ++ val = o->get()->toString().toInteger(); ++ o = o->next(); ++ } ++ ret.append(val); ++ pos++; ++ if (pos == len) ++ break; ++ } ++ TelEngine::destruct(list); ++ return ret; ++} ++ ++// Retrieve an object's property ++// Check platform dependent value ++static inline bool getPropPlatform(QObject* obj, const String& name, String& val) ++{ ++ if (!(obj && name)) ++ return false; ++ if (QtClient::getProperty(obj,name,val)) ++ return true; ++ return QtClient::getProperty(obj,name + "_os" + PLATFORM_LOWERCASE_NAME,val); ++} ++ ++ ++/** ++ * Qt5ClientFactory ++ */ ++Qt5ClientFactory::Qt5ClientFactory(const char* name) ++ : UIFactory(name) ++{ ++ m_types.append(new String("QSound")); ++} ++ ++// Build QSound ++void* Qt5ClientFactory::create(const String& type, const char* name, NamedList* params) ++{ ++ if (type == YSTRING("QSound")) ++ return new QSound(QtClient::setUtf8(name)); ++ return 0; ++} ++ ++ ++/** ++ * TableWidget ++ */ ++TableWidget::TableWidget(QTableWidget* table, bool tmp) ++ : m_table(table), m_sortControl(-1) ++{ ++ if (!m_table) ++ return; ++ init(tmp); ++} ++ ++TableWidget::TableWidget(QWidget* wid, const String& name, bool tmp) ++ : m_table(0), m_sortControl(-1) ++{ ++ if (wid) ++ m_table = wid->findChild(QtClient::setUtf8(name)); ++ if (!m_table) ++ return; ++ init(tmp); ++} ++ ++TableWidget::TableWidget(QtWidget& table, bool tmp) ++ : m_table(static_cast((QWidget*)table)), m_sortControl(-1) ++{ ++ if (m_table) ++ init(tmp); ++} ++ ++TableWidget::~TableWidget() ++{ ++ if (!m_table) ++ return; ++ if (m_sortControl >= 0) ++ m_table->setSortingEnabled((bool)m_sortControl); ++ m_table->repaint(); ++} ++ ++// Add or set a row ++void TableWidget::updateRow(const String& item, const NamedList* data, bool atStart) ++{ ++ int row = getRow(item); ++ // Add a new one ? ++ if (row < 0) { ++ row = atStart ? 0 : rowCount(); ++ addRow(row); ++ setID(row,item); ++ } ++ // Update ++ if (data) ++ updateRow(row,*data); ++} ++ ++// Update a row from a list of parameters ++void TableWidget::updateRow(int row, const NamedList& data) ++{ ++ int ncol = columnCount(); ++ for (int i = 0; i < ncol; i++) { ++ String header; ++ if (!getHeaderText(i,header)) ++ continue; ++ NamedString* tmp = data.getParam(header); ++ if (tmp) ++ setCell(row,i,*tmp); ++ // Set image ++ tmp = data.getParam(header + "_image"); ++ if (tmp) ++ setImage(row,i,*tmp); ++ } ++ // Init vertical header ++ String* rowText = data.getParam(YSTRING("row_text")); ++ String* rowImg = data.getParam(YSTRING("row_image")); ++ if (rowText || rowImg) { ++ QTableWidgetItem* item = m_table->verticalHeaderItem(row); ++ if (!item) { ++ item = new QTableWidgetItem; ++ m_table->setVerticalHeaderItem(row,item); ++ } ++ if (rowText) ++ item->setText(QtClient::setUtf8(*rowText)); ++ if (rowImg) ++ item->setIcon(QIcon(QtClient::setUtf8(*rowImg))); ++ } ++} ++ ++// Find a row by the first's column value. Return -1 if not found ++int TableWidget::getRow(const String& item) ++{ ++ int n = rowCount(); ++ for (int i = 0; i < n; i++) { ++ String val; ++ if (getCell(i,0,val) && item == val) ++ return i; ++ } ++ return -1; ++} ++ ++// Find a column by its label. Return -1 if not found ++int TableWidget::getColumn(const String& name, bool caseInsensitive) ++{ ++ int n = columnCount(); ++ for (int i = 0; i < n; i++) { ++ String val; ++ if (!getHeaderText(i,val,false)) ++ continue; ++ if ((caseInsensitive && (name &= val)) || (!caseInsensitive && name == val)) ++ return i; ++ } ++ return -1; ++} ++ ++void TableWidget::init(bool tmp) ++{ ++ QtClient::getUtf8(m_name,m_table->objectName()); ++ if (tmp) { ++ m_sortControl = m_table->isSortingEnabled() ? 1 : 0; ++ if (m_sortControl) ++ m_table->setSortingEnabled(false); ++ } ++} ++ ++/** ++ * UIBuffer ++ */ ++// Remove from list. Release memory ++void UIBuffer::destruct() ++{ ++ s_uiCache.remove(this,false); ++ if (m_buffer) { ++ delete m_buffer; ++ m_buffer = 0; ++ } ++ String::destruct(); ++} ++ ++// Return an already loaded UI. Load from file if not found. ++// Add URLs paths when missing ++UIBuffer* UIBuffer::build(const String& name) ++{ ++ // Check if already loaded from the same location ++ UIBuffer* buf = find(name); ++ if (buf) ++ return buf; ++ ++ // Load ++ QFile file(QtClient::setUtf8(name)); ++ file.open(QIODevice::ReadOnly); ++ QByteArray* qArray = new QByteArray; ++ *qArray = file.readAll(); ++ file.close(); ++ if (!qArray->size()) { ++ delete qArray; ++ return 0; ++ } ++ // Add URLs path when missing ++ addFilePathUrl(*qArray,name); ++ return new UIBuffer(name,qArray); ++} ++ ++// Find a buffer ++UIBuffer* UIBuffer::find(const String& name) ++{ ++ ObjList* o = s_uiCache.find(name); ++ return o ? static_cast(o->get()) : 0; ++} ++ ++ ++/** ++ * QtWindow ++ */ ++QtWindow::QtWindow() ++ : m_x(0), m_y(0), m_width(0), m_height(0), ++ m_maximized(false), m_mainWindow(false), m_moving(0) ++{ ++} ++ ++QtWindow::QtWindow(const char* name, const char* description, const char* alias, QtWindow* parent) ++ : QWidget(parent, Qt::Window), ++ Window(alias ? alias : name), m_description(description), m_oldId(name), ++ m_x(0), m_y(0), m_width(0), m_height(0), ++ m_maximized(false), m_mainWindow(false), m_moving(0) ++{ ++ setObjectName(QtClient::setUtf8(m_id)); ++} ++ ++QtWindow::~QtWindow() ++{ ++ // Update all hidden counter for tray icons owned by this window ++ QList trayIcons = findChildren(); ++ if (trayIcons.size() > 0) { ++ if (s_allHiddenQuit >= (unsigned int)trayIcons.size()) ++ s_allHiddenQuit -= trayIcons.size(); ++ else { ++ Debug(QtDriver::self(),DebugFail, ++ "QtWindow(%s) destroyed with all hidden counter %u greater then tray icons %d [%p]", ++ m_id.c_str(),s_allHiddenQuit,trayIcons.size(),this); ++ s_allHiddenQuit = 0; ++ } ++ } ++ ++ // Save settings ++ if (m_saveOnClose) { ++ m_maximized = isMaximized(); ++ s_save.setValue(m_id,"maximized",String::boolText(m_maximized)); ++ // Don't save position if maximized: keep the old one ++ if (!m_maximized) { ++ s_save.setValue(m_id,"x",m_x); ++ s_save.setValue(m_id,"y",m_y); ++ s_save.setValue(m_id,"width",m_width); ++ s_save.setValue(m_id,"height",m_height); ++ } ++ s_save.setValue(m_id,"visible",m_visible); ++ // Set dynamic properties to be saved for native QT objects ++ QList tables = findChildren(); ++ for (int i = 0; i < tables.size(); i++) { ++ if (qobject_cast(tables[i])) ++ continue; ++ // Column widths ++ unsigned int n = tables[i]->columnCount(); ++ String widths; ++ for (unsigned int j = 0; j < n; j++) ++ widths.append(String(tables[i]->columnWidth(j)),",",true); ++ tables[i]->setProperty(s_propColWidths,QVariant(QtClient::setUtf8(widths))); ++ // Sorting ++ String sorting; ++ if (tables[i]->isSortingEnabled()) { ++ QHeaderView* h = tables[i]->horizontalHeader(); ++ int col = h ? h->sortIndicatorSection() : -1; ++ if (col >= 0) ++ sorting << col << "," << ++ String::boolText(Qt::AscendingOrder == h->sortIndicatorOrder()); ++ } ++ tables[i]->setProperty(s_propSorting,QVariant(QtClient::setUtf8(sorting))); ++ } ++ QList spl = findChildren(); ++ for (int i = 0; i < spl.size(); i++) { ++ String sizes; ++ QtClient::intList2str(sizes,spl[i]->sizes()); ++ QtClient::setProperty(spl[i],s_propSizes,sizes); ++ } ++ // Save child objects properties ++ QList child = findChildren(); ++ for (int i = 0; i < child.size(); i++) { ++ NamedList props(""); ++ if (!QtClient::getProperty(child[i],s_propsSave,props)) ++ continue; ++ unsigned int n = props.length(); ++ for (unsigned int j = 0; j < n; j++) { ++ NamedString* ns = props.getParam(j); ++ if (ns && ns->name()) ++ QtClient::saveProperty(child[i],ns->name(),this); ++ } ++ } ++ } ++} ++ ++// Set windows title ++void QtWindow::title(const String& text) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::title(%s) [%p]",text.c_str(),this); ++ Window::title(text); ++ QWidget::setWindowTitle(QtClient::setUtf8(text)); ++} ++ ++void QtWindow::context(const String& text) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::context(%s) [%p]",text.c_str(),this); ++ m_context = text; ++} ++ ++bool QtWindow::setParams(const NamedList& params) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setParams() [%p]",this); ++ ++ setUpdatesEnabled(false); ++ // Check for custom widget params ++ if (params == YSTRING("customwidget")) { ++ // Each parameter is a list of parameters for a custom widget ++ // Parameter name is the widget's name ++ unsigned int n = params.length(); ++ bool ok = true; ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ NamedList* nl = static_cast(ns ? ns->getObject(YATOM("NamedList")) : 0); ++ if (!(nl && ns->name())) ++ continue; ++ // Find the widget and set its params ++ QtWidget w(this,ns->name()); ++ if (w.type() == QtWidget::CustomTable) ++ ok = w.customTable()->setParams(*nl) && ok; ++ else if (w.type() == QtWidget::CustomWidget) ++ ok = w.customWidget()->setParams(*nl) && ok; ++ else if (w.type() == QtWidget::CustomObject) ++ ok = w.customObject()->setParams(*nl) && ok; ++ else ++ ok = false; ++ } ++ setUpdatesEnabled(true); ++ return ok; ++ } ++ // Check for system tray icon params ++ if (params == YSTRING("systemtrayicon")) { ++ // Each parameter is a list of parameters for a system tray icon ++ // Parameter name is the widget's name ++ // Parameter value indicates delete/create/set an existing one ++ unsigned int n = params.length(); ++ bool ok = false; ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ if (!(ns && ns->name())) ++ continue; ++ QSystemTrayIcon* trayIcon = findSysTrayIcon(this,ns->name()); ++ // Delete ++ if (ns->null()) { ++ if (trayIcon) { ++ // Reactivate program termination when the last window was hidden ++ if (s_allHiddenQuit) ++ s_allHiddenQuit--; ++ else ++ Debug(QtDriver::self(),DebugFail, ++ "QtWindow(%s) all hidden counter is 0 while deleting '%s' tray icon [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(trayIcon),this); ++ QtClient::deleteLater(trayIcon); ++ } ++ continue; ++ } ++ NamedList* nl = YOBJECT(NamedList,ns); ++ if (!nl) ++ continue; ++ // Create a new one if needed ++ if (!trayIcon) { ++ if (!ns->toBoolean()) ++ continue; ++ trayIcon = new QSystemTrayIcon(this); ++ trayIcon->setObjectName(QtClient::setUtf8(ns->name())); ++ QtClient::connectObjects(trayIcon,SIGNAL(activated(QSystemTrayIcon::ActivationReason)), ++ this,SLOT(sysTrayIconAction(QSystemTrayIcon::ActivationReason))); ++ // Deactivate program termination when the last window was hidden ++ s_allHiddenQuit++; ++ } ++ ok = true; ++ // Add dynamic properties ++ // TODO: track the properties, clear the old ones if needed ++ addDynamicProps(trayIcon,*nl); ++ // Set icon and tooltip ++ NamedString* tmp = nl->getParam(YSTRING("icon")); ++ if (tmp && *tmp) ++ trayIcon->setIcon(QIcon(QtClient::setUtf8(*tmp))); ++ tmp = nl->getParam(YSTRING("tooltip")); ++ if (tmp && *tmp) ++ trayIcon->setToolTip(QtClient::setUtf8(*tmp)); ++ // Check context menu ++ NamedString* menu = nl->getParam(YSTRING("menu")); ++ if (menu) { ++ QMenu* oldMenu = trayIcon->contextMenu(); ++ NamedList* nlMenu = YOBJECT(NamedList,menu); ++ trayIcon->setContextMenu(nlMenu ? QtClient::buildMenu(*nlMenu,*menu,this, ++ SLOT(action()),SLOT(toggled(bool)),this) : 0); ++ delete oldMenu; ++ // Momentarily hide the system tray icon (if visible) because ++ // the change of menu does not take effect until the icon is ++ // made visible again, at least with the dbus system tray icon ++ // backend in Qt 5.15.2 ++ trayIcon->setVisible(false); ++ } ++ if (nl->getBoolValue(YSTRING("show"),true)) ++ trayIcon->setVisible(true); ++ } ++ setUpdatesEnabled(true); ++ return ok; ++ } ++ // Parameters for the widget whose name is the list name ++ if(params) { ++ QtWidget w(this, params); ++ // Handle UIWidget descendants ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) { ++ bool ok = uiw->setParams(params); ++ setUpdatesEnabled(true); ++ return ok; ++ } ++ if (w.type() == QtWidget::Calendar) { ++ int year = params.getIntValue(YSTRING("year")); ++ int month = params.getIntValue(YSTRING("month")); ++ int day = params.getIntValue(YSTRING("day")); ++ w.calendar()->setCurrentPage(year, month); ++ w.calendar()->setSelectedDate(QDate(year, month, day)); ++ setUpdatesEnabled(true); ++ return true; ++ } ++ } ++ ++ // Window or other parameters ++ if (params.getBoolValue(YSTRING("modal"))) { ++ if (parentWindow()) ++ setWindowModality(Qt::WindowModal); ++ else ++ setWindowModality(Qt::ApplicationModal); ++ } ++ if (params.getBoolValue(YSTRING("minimized"))) ++ QWidget::setWindowState(Qt::WindowMinimized); ++ bool ok = Window::setParams(params); ++ setUpdatesEnabled(true); ++ return ok; ++} ++ ++void QtWindow::setOver(const Window* parent) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setOver(%p) [%p]",parent,this); ++ QWidget::raise(); ++} ++ ++bool QtWindow::hasElement(const String& name) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::hasElement(%s) [%p]",name.c_str(),this); ++ QtWidget w(this,name); ++ return w.valid(); ++} ++ ++bool QtWindow::setActive(const String& name, bool active) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setActive(%s,%s) [%p]", ++ name.c_str(),String::boolText(active),this); ++ bool ok = (name == m_id); ++ if (ok) { ++ if (QWidget::isMinimized()) ++ QWidget::showNormal(); ++ QWidget::activateWindow(); ++ QWidget::raise(); ++ } ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return ok; ++ if (w.type() != QtWidget::Action) ++ w->setEnabled(active); ++ else ++ w.action()->setEnabled(active); ++ return true; ++} ++ ++bool QtWindow::setFocus(const String& name, bool select) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setFocus(%s,%s) [%p]", ++ name.c_str(),String::boolText(select),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ w->setFocus(); ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ if (w.combo()->isEditable() && select) ++ w.combo()->lineEdit()->selectAll(); ++ break; ++ } ++ return true; ++} ++ ++bool QtWindow::setShow(const String& name, bool visible) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setShow(%s,%s) [%p]", ++ name.c_str(),String::boolText(visible),this); ++ // Check system tray icons ++ QSystemTrayIcon* trayIcon = findSysTrayIcon(this,name); ++ if (trayIcon) { ++ trayIcon->setVisible(visible); ++ return true; ++ } ++ // Widgets ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ setUpdatesEnabled(false); ++ if (w.type() != QtWidget::Action) ++ w->setVisible(visible); ++ else ++ w.action()->setVisible(visible); ++ setUpdatesEnabled(true); ++ return true; ++} ++ ++bool QtWindow::setText(const String& name, const String& text, ++ bool richText) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setText(%s,%s) [%p]", ++ m_id.c_str(),name.c_str(),text.c_str(),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->setText(text,richText); ++ switch (w.type()) { ++ case QtWidget::CheckBox: ++ w.check()->setText(QtClient::setUtf8(text)); ++ return true; ++ case QtWidget::LineEdit: ++ w.lineEdit()->setText(QtClient::setUtf8(text)); ++ return true; ++ case QtWidget::TextBrowser: ++ case QtWidget::TextEdit: ++ if (richText) { ++ w.textEdit()->clear(); ++ w.textEdit()->insertHtml(QtClient::setUtf8(text)); ++ } ++ else ++ w.textEdit()->setText(QtClient::setUtf8(text)); ++ { ++ QScrollBar* bar = w.textEdit()->verticalScrollBar(); ++ if (bar) ++ bar->setSliderPosition(bar->maximum()); ++ } ++ return true; ++ case QtWidget::Label: ++ w.label()->setText(QtClient::setUtf8(text)); ++ return true; ++ case QtWidget::ComboBox: ++ if (w.combo()->lineEdit()) ++ w.combo()->lineEdit()->setText(QtClient::setUtf8(text)); ++ else ++ setSelect(name,text); ++ return true; ++ case QtWidget::Action: ++ w.action()->setText(QtClient::setUtf8(text)); ++ return true; ++ case QtWidget::SpinBox: ++ w.spinBox()->setValue(text.toInteger()); ++ return true; ++ } ++ ++ // Handle some known base classes having a setText() method ++ if (w.inherits(QtWidget::AbstractButton)) ++ w.abstractButton()->setText(QtClient::setUtf8(text)); ++ else ++ return false; ++ return true; ++} ++ ++bool QtWindow::setCheck(const String& name, bool checked) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setCheck(%s,%s) [%p]", ++ name.c_str(),String::boolText(checked),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ if (w.inherits(QtWidget::AbstractButton)) ++ w.abstractButton()->setChecked(checked); ++ else if (w.type() == QtWidget::Action) ++ w.action()->setChecked(checked); ++ else ++ return false; ++ return true; ++} ++ ++bool QtWindow::setSelect(const String& name, const String& item) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setSelect(%s,%s) [%p]", ++ name.c_str(),item.c_str(),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->setSelect(item); ++ ++ int d = 0; ++ switch (w.type()) { ++ case QtWidget::Table: ++ { ++ TableWidget t(w); ++ int row = t.getRow(item); ++ if (row < 0) ++ return false; ++ t.table()->setCurrentCell(row,0); ++ return true; ++ } ++ case QtWidget::ComboBox: ++ if (item) { ++ d = w.findComboItem(item); ++ if (d < 0) ++ return false; ++ w.combo()->setCurrentIndex(d); ++ } ++ else if (w.combo()->lineEdit()) ++ w.combo()->lineEdit()->setText(""); ++ else ++ return false; ++ return true; ++ case QtWidget::ListBox: ++ d = w.findListItem(item); ++ if (d >= 0) ++ w.list()->setCurrentRow(d); ++ return d >= 0; ++ case QtWidget::Slider: ++ w.slider()->setValue(item.toInteger()); ++ return true; ++ case QtWidget::StackWidget: ++ d = item.toInteger(-1); ++ while (d < 0) { ++ d = findStackedWidget(*(w.stackWidget()),item); ++ if (d >= 0) ++ break; ++ // Check for a default widget ++ String def = YQT_OBJECT_NAME(w.stackWidget()); ++ def << "_default"; ++ d = findStackedWidget(*(w.stackWidget()),def); ++ break; ++ } ++ if (d >= 0 && d < w.stackWidget()->count()) { ++ w.stackWidget()->setCurrentIndex(d); ++ return true; ++ } ++ return false; ++ case QtWidget::ProgressBar: ++ d = item.toInteger(); ++ if (d >= w.progressBar()->minimum() && d <= w.progressBar()->maximum()) ++ w.progressBar()->setValue(d); ++ else if (d < w.progressBar()->minimum()) ++ w.progressBar()->setValue(w.progressBar()->minimum()); ++ else ++ w.progressBar()->setValue(w.progressBar()->maximum()); ++ return true; ++ case QtWidget::Tab: ++ d = w.tab()->count() - 1; ++ for (QString tmp = QtClient::setUtf8(item); d >= 0; d--) { ++ QWidget* wid = w.tab()->widget(d); ++ if (wid && wid->objectName() == tmp) ++ break; ++ } ++ if (d >= 0 && d < w.tab()->count()) { ++ w.tab()->setCurrentIndex(d); ++ return true; ++ } ++ return false; ++ ++ } ++ return false; ++} ++ ++bool QtWindow::setUrgent(const String& name, bool urgent) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::setUrgent(%s,%s) [%p]", ++ name.c_str(),String::boolText(urgent),this); ++ ++ if (name == m_id) { ++ QApplication::alert(this,0); ++ return true; ++ } ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ w->raise(); ++ return true; ++} ++ ++bool QtWindow::hasOption(const String& name, const String& item) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::hasOption(%s,%s) [%p]", ++ name.c_str(),item.c_str(),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ return -1 != w.findComboItem(item); ++ case QtWidget::Table: ++ return getTableRow(name,item); ++ case QtWidget::ListBox: ++ return -1 != w.findListItem(item); ++ } ++ return false; ++} ++ ++bool QtWindow::addOption(const String& name, const String& item, bool atStart, ++ const String& text) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addOption(%s,%s,%s,%s) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(), ++ String::boolText(atStart),text.c_str(),this); ++ ++ QtWidget w(this,name); ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ w.addComboItem(item,atStart); ++ if (atStart && w.combo()->lineEdit()) ++ w.combo()->lineEdit()->setText(w.combo()->itemText(0)); ++ return true; ++ case QtWidget::Table: ++ return addTableRow(name,item,0,atStart); ++ case QtWidget::ListBox: ++ return w.addListItem(item,atStart); ++ } ++ return false; ++} ++ ++bool QtWindow::delOption(const String& name, const String& item) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) delOption(%s,%s) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(),this); ++ return delTableRow(name,item); ++} ++ ++bool QtWindow::getOptions(const String& name, NamedList* items) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getOptions(%s,%p) [%p]", ++ m_id.c_str(),name.c_str(),items,this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ if (!items) ++ return true; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->getOptions(*items); ++ ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ for (int i = 0; i < w.combo()->count(); i++) ++ QtClient::getUtf8(*items,"",w.combo()->itemText(i),false); ++ break; ++ case QtWidget::Table: ++ { ++ TableWidget t(w.table(),false); ++ for (int i = 0; i < t.rowCount(); i++) { ++ String item; ++ if (t.getCell(i,0,item) && item) ++ items->addParam(item,""); ++ } ++ } ++ break; ++ case QtWidget::ListBox: ++ for (int i = 0; i < w.list()->count(); i++) { ++ QListWidgetItem* tmp = w.list()->item(i); ++ if (tmp) ++ QtClient::getUtf8(*items,"",tmp->text(),false); ++ } ++ break; ++ } ++ return true; ++} ++ ++// Append or insert text lines to a widget ++bool QtWindow::addLines(const String& name, const NamedList* lines, unsigned int max, ++ bool atStart) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"QtWindow(%s) addLines('%s',%p,%u,%s) [%p]", ++ m_id.c_str(),name.c_str(),lines,max,String::boolText(atStart),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ if (!lines) ++ return true; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->addLines(*lines,max,atStart); ++ unsigned int count = lines->length(); ++ if (!count) ++ return true; ++ ++ switch (w.type()) { ++ case QtWidget::TextBrowser: ++ case QtWidget::TextEdit: ++ // Limit the maximum number of paragraphs ++ if (max) { ++ QTextDocument* doc = w.textEdit()->document(); ++ if (!doc) ++ return false; ++ doc->setMaximumBlockCount((int)max); ++ } ++ { ++ // FIXME: delete lines from begining if appending and the number ++ // of lines exceeds the maximum allowed ++ QString s = w.textEdit()->toPlainText(); ++ int pos = atStart ? 0 : s.length(); ++ for (unsigned int i = 0; i < count; i++) { ++ NamedString* ns = lines->getParam(i); ++ if (!ns) ++ continue; ++ if (ns->name().endsWith("\n")) ++ s.insert(pos,QtClient::setUtf8(ns->name())); ++ else { ++ String tmp = ns->name() + "\n"; ++ s.insert(pos,QtClient::setUtf8(tmp)); ++ pos++; ++ } ++ pos += (int)ns->name().length(); ++ } ++ w.textEdit()->setText(s); ++ // Scroll down if added at end ++ if (!atStart) { ++ QScrollBar* bar = w.textEdit()->verticalScrollBar(); ++ if (bar) ++ bar->setSliderPosition(bar->maximum()); ++ } ++ } ++ return true; ++ case QtWidget::Table: ++ // TODO: implement ++ break; ++ case QtWidget::ComboBox: ++ if (atStart) { ++ for (; count; count--) { ++ NamedString* ns = lines->getParam(count - 1); ++ if (ns) ++ w.combo()->insertItem(0,QtClient::setUtf8(ns->name())); ++ } ++ if (w.combo()->lineEdit()) ++ w.combo()->lineEdit()->setText(w.combo()->itemText(0)); ++ } ++ else { ++ for (unsigned int i = 0; i < count; i++) { ++ NamedString* ns = lines->getParam(i); ++ if (ns) ++ w.combo()->addItem(QtClient::setUtf8(ns->name())); ++ } ++ } ++ return true; ++ case QtWidget::ListBox: ++ // TODO: implement ++ break; ++ } ++ return false; ++} ++ ++bool QtWindow::addTableRow(const String& name, const String& item, ++ const NamedList* data, bool atStart) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addTableRow(%s,%s,%p,%s) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->addTableRow(item,data,atStart); ++ // Handle basic QTableWidget ++ if (w.type() != QtWidget::Table) ++ return false; ++ TableWidget tbl(w.table()); ++ int row = atStart ? 0 : tbl.rowCount(); ++ tbl.addRow(row); ++ // Set item (the first column) and the rest of data ++ tbl.setID(row,item); ++ if (data) ++ tbl.updateRow(row,*data); ++ return true; ++} ++ ++// Insert or update multiple rows in a single operation ++bool QtWindow::setMultipleRows(const String& name, const NamedList& data, const String& prefix) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setMultipleRows('%s',%p,'%s') [%p]", ++ m_id.c_str(),name.c_str(),&data,prefix.c_str(),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ return uiw && uiw->setMultipleRows(data,prefix); ++} ++ ++// Insert a row into a table owned by this window ++bool QtWindow::insertTableRow(const String& name, const String& item, ++ const String& before, const NamedList* data) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) insertTableRow(%s,%s,%s,%p) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(),before.c_str(),data,this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->insertTableRow(item,before,data); ++ if (w.type() != QtWidget::Table) ++ return false; ++ TableWidget tbl(w.table()); ++ int row = tbl.getRow(before); ++ if (row == -1) ++ row = tbl.rowCount(); ++ tbl.addRow(row); ++ // Set item (the first column) and the rest of data ++ tbl.setID(row,item); ++ if (data) ++ tbl.updateRow(row,*data); ++ return true; ++} ++ ++bool QtWindow::delTableRow(const String& name, const String& item) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::delTableRow(%s,%s) [%p]", ++ name.c_str(),item.c_str(),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ int row = -1; ++ int n = 0; ++ switch (w.type()) { ++ case QtWidget::Table: ++ case QtWidget::CustomTable: ++ { ++ TableWidget tbl(w.table()); ++ QtTable* custom = tbl.customTable(); ++ if (custom) { ++ if (custom->delTableRow(item)) ++ row = 0; ++ } ++ else { ++ row = tbl.getRow(item); ++ if (row >= 0) ++ tbl.delRow(row); ++ } ++ n = tbl.rowCount(); ++ } ++ break; ++ case QtWidget::ComboBox: ++ row = w.findComboItem(item); ++ if (row >= 0) { ++ w.combo()->removeItem(row); ++ n = w.combo()->count(); ++ } ++ break; ++ case QtWidget::ListBox: ++ row = w.findListItem(item); ++ if (row >= 0) { ++ QStringListModel* model = (QStringListModel*)w.list()->model(); ++ if (!(model && model->removeRow(row))) ++ row = -1; ++ n = w.list()->count(); ++ } ++ break; ++ default: ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw && uiw->delTableRow(item)) { ++ row = 0; ++ // Don't notify empty: we don't know it ++ n = 1; ++ } ++ } ++ if (row < 0) ++ return false; ++ if (!n) ++ raiseSelectIfEmpty(0,this,name); ++ return true; ++} ++ ++bool QtWindow::setTableRow(const String& name, const String& item, const NamedList* data) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setTableRow(%s,%s,%p) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(),data,this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->setTableRow(item,data); ++ if (w.type() != QtWidget::Table) ++ return false; ++ TableWidget tbl(w.table()); ++ int row = tbl.getRow(item); ++ if (row < 0) ++ return false; ++ if (data) ++ tbl.updateRow(row,*data); ++ return true; ++} ++ ++bool QtWindow::getTableRow(const String& name, const String& item, NamedList* data) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::getTableRow(%s,%s,%p) [%p]", ++ name.c_str(),item.c_str(),data,this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->getTableRow(item,data); ++ if (w.type() != QtWidget::Table) ++ return false; ++ TableWidget tbl(w.table(),false); ++ int row = tbl.getRow(item); ++ if (row < 0) ++ return false; ++ if (!data) ++ return true; ++ int n = tbl.columnCount(); ++ for (int i = 0; i < n; i++) { ++ String name; ++ if (!tbl.getHeaderText(i,name)) ++ continue; ++ String value; ++ if (tbl.getCell(row,i,value)) ++ data->setParam(name,value); ++ } ++ return true; ++} ++ ++// Set a table row or add a new one if not found ++bool QtWindow::updateTableRow(const String& name, const String& item, ++ const NamedList* data, bool atStart) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRow('%s','%s',%p,%s) [%p]", ++ m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ switch (w.type()) { ++ case QtWidget::Table: ++ case QtWidget::CustomTable: ++ { ++ TableWidget tbl(w.table()); ++ QtTable* custom = tbl.customTable(); ++ if (custom) { ++ if (custom->getTableRow(item)) ++ return custom->setTableRow(item,data); ++ return custom->addTableRow(item,data,atStart); ++ } ++ tbl.updateRow(item,data,atStart); ++ return true; ++ } ++ case QtWidget::CustomTree: ++ { ++ QtTree* custom = w.customTree(); ++ if (custom) { ++ if (custom->getTableRow(item)) ++ return custom->setTableRow(item,data); ++ return custom->addTableRow(item,data,atStart); ++ } ++ return false; ++ } ++ case QtWidget::ComboBox: ++ return w.findComboItem(item) >= 0 || w.addComboItem(item,atStart); ++ case QtWidget::ListBox: ++ return w.findListItem(item) >= 0 || w.addListItem(item,atStart); ++ } ++ return false; ++} ++ ++// Add or set one or more table row(s). Screen update is locked while changing the table. ++// Each data list element is a NamedPointer carrying a NamedList with item parameters. ++// The name of an element is the item to update. ++// Element's value not empty: update the item ++// Else: delete it ++bool QtWindow::updateTableRows(const String& name, const NamedList* data, bool atStart) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRows('%s',%p,%s) [%p]", ++ m_id.c_str(),name.c_str(),data,String::boolText(atStart),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) { ++ bool ok = uiw->updateTableRows(data,atStart); ++ QtTable* ct = w.customTable(); ++ if (ct) ++ raiseSelectIfEmpty(ct->rowCount(),this,name); ++ return ok; ++ } ++ if (w.type() != QtWidget::Table) ++ return false; ++ if (!data) ++ return true; ++ TableWidget tbl(w.table()); ++ bool ok = true; ++ tbl.table()->setUpdatesEnabled(false); ++ ObjList add; ++ unsigned int n = data->length(); ++ for (unsigned int i = 0; i < n; i++) { ++ if (Client::exiting()) ++ break; ++ // Get item and the list of parameters ++ NamedString* ns = data->getParam(i); ++ if (!ns) ++ continue; ++ // Delete ? ++ if (ns->null()) { ++ int row = tbl.getRow(ns->name()); ++ if (row >= 0) ++ tbl.delRow(row); ++ else ++ ok = false; ++ continue; ++ } ++ // Set existing row or postpone add ++ int row = tbl.getRow(ns->name()); ++ if (row >= 0) { ++ const NamedList* params = YOBJECT(NamedList,ns); ++ if (params) ++ tbl.updateRow(row,*params); ++ } ++ else if (ns->toBoolean()) ++ add.append(ns)->setDelete(false); ++ else ++ ok = false; ++ } ++ n = add.count(); ++ if (n) { ++ int row = tbl.rowCount(); ++ if (row < 0) ++ row = 0; ++ // Append if not requested to insert at start or table is empty ++ if (!(atStart && row)) ++ tbl.table()->setRowCount(row + n); ++ else { ++ for (unsigned int i = 0; i < n; i++) ++ tbl.table()->insertRow(0); ++ } ++ for (ObjList* o = add.skipNull(); o; row++, o = o->skipNext()) { ++ NamedString* ns = static_cast(o->get()); ++ tbl.setID(row,ns->name()); ++ const NamedList* params = YOBJECT(NamedList,ns); ++ if (params) ++ tbl.updateRow(row,*params); ++ } ++ } ++ tbl.table()->setUpdatesEnabled(true); ++ raiseSelectIfEmpty(tbl.rowCount(),this,name); ++ return ok; ++} ++ ++bool QtWindow::clearTable(const String& name) ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow::clearTable(%s) [%p]",name.c_str(),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->clearTable(); ++ bool ok = true; ++ if (w.widget()) ++ w->setUpdatesEnabled(false); ++ switch (w.type()) { ++ case QtWidget::Table: ++ w.table()->setRowCount(0); ++ break; ++ case QtWidget::TextBrowser: ++ case QtWidget::TextEdit: ++ w.textEdit()->clear(); ++ break; ++ case QtWidget::ListBox: ++ w.list()->clear(); ++ break; ++ case QtWidget::ComboBox: ++ w.combo()->clear(); ++ break; ++ default: ++ ok = false; ++ } ++ if (w.widget()) ++ w->setUpdatesEnabled(true); ++ return ok; ++} ++ ++// Show or hide control busy state ++bool QtWindow::setBusy(const String& name, bool on) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setBusy(%s,%u) [%p]", ++ m_id.c_str(),name.c_str(),on,this); ++ if (name == m_id) ++ return QtBusyWidget::showBusyChild(this,on); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->setBusy(on); ++ if (w.widget()) ++ return QtBusyWidget::showBusyChild(w.widget(),on); ++ return false; ++} ++ ++bool QtWindow::getText(const String& name, String& text, bool richText) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getText(%s) [%p]", ++ m_id.c_str(),name.c_str(),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->getText(text,richText); ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ QtClient::getUtf8(text,w.combo()->currentText()); ++ return true; ++ case QtWidget::LineEdit: ++ QtClient::getUtf8(text,w.lineEdit()->text()); ++ return true; ++ case QtWidget::TextBrowser: ++ case QtWidget::TextEdit: ++ if (!richText) ++ QtClient::getUtf8(text,w.textEdit()->toPlainText()); ++ else ++ QtClient::getUtf8(text,w.textEdit()->toHtml()); ++ return true; ++ case QtWidget::Label: ++ QtClient::getUtf8(text,w.label()->text()); ++ return true; ++ case QtWidget::Action: ++ QtClient::getUtf8(text,w.action()->text()); ++ return true; ++ case QtWidget::SpinBox: ++ text = w.spinBox()->value(); ++ return true; ++ default: ++ if (w.inherits(QtWidget::AbstractButton)) { ++ QtClient::getUtf8(text,w.abstractButton()->text()); ++ return true; ++ } ++ } ++ return false; ++} ++ ++bool QtWindow::getCheck(const String& name, bool& checked) ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow::getCheck(%s) [%p]",name.c_str(),this); ++ ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ if (w.inherits(QtWidget::AbstractButton)) ++ checked = w.abstractButton()->isChecked(); ++ else if (w.type() == QtWidget::Action) ++ checked = w.action()->isChecked(); ++ else ++ return false; ++ return true; ++} ++ ++bool QtWindow::getSelect(const String& name, String& item) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%s) [%p]",name.c_str(),this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->getSelect(item); ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ if (w.combo()->lineEdit() && w.combo()->lineEdit()->selectedText().isEmpty()) ++ return false; ++ QtClient::getUtf8(item,w.combo()->currentText()); ++ return true; ++ case QtWidget::Table: ++ { ++ TableWidget t(w); ++ int row = t.crtRow(); ++ return row >= 0 ? t.getCell(row,0,item) : false; ++ } ++ case QtWidget::ListBox: ++ { ++ QListWidgetItem* crt = w.list()->currentItem(); ++ if (!crt) ++ return false; ++ QtClient::getUtf8(item,crt->text()); ++ } ++ return true; ++ case QtWidget::Slider: ++ item = w.slider()->value(); ++ return true; ++ case QtWidget::ProgressBar: ++ item = w.progressBar()->value(); ++ return true; ++ case QtWidget::Tab: ++ { ++ item = ""; ++ QWidget* wid = w.tab()->currentWidget(); ++ if (wid) ++ QtClient::getUtf8(item,wid->objectName()); ++ } ++ return true; ++ case QtWidget::StackWidget: ++ { ++ item = ""; ++ QWidget* wid = w.stackWidget()->currentWidget(); ++ if (wid) ++ QtClient::getUtf8(item,wid->objectName()); ++ } ++ return true; ++ } ++ return false; ++} ++ ++// Retrieve an element's multiple selection ++bool QtWindow::getSelect(const String& name, NamedList& items) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%p) [%p]",&items,this); ++ QtWidget w(this,name); ++ if (w.invalid()) ++ return false; ++ UIWidget* uiw = w.uiWidget(); ++ if (uiw) ++ return uiw->getSelect(items); ++ switch (w.type()) { ++ case QtWidget::ComboBox: ++ case QtWidget::Table: ++ case QtWidget::ListBox: ++ case QtWidget::Slider: ++ case QtWidget::ProgressBar: ++ case QtWidget::Tab: ++ case QtWidget::StackWidget: ++ DDebug(QtDriver::self(),DebugStub,"QtWindow::getSelect(%p) not implemented for '%s; [%p]", ++ &items,w.widget()->metaObject()->className(),this); ++ } ++ return false; ++} ++ ++// Build a menu from a list of parameters ++bool QtWindow::buildMenu(const NamedList& params) ++{ ++ QWidget* parent = this; ++ // Retrieve the owner ++ const String& owner = params[YSTRING("owner")]; ++ if (owner && owner != m_id) { ++ parent = findChild(QtClient::setUtf8(owner)); ++ if (!parent) { ++ DDebug(QtDriver::self(),DebugNote, ++ "QtWindow(%s) buildMenu(%s) owner '%s' not found [%p]", ++ m_id.c_str(),params.c_str(),owner.c_str(),this); ++ return false; ++ } ++ } ++ QWidget* target = parent; ++ const String& t = params[YSTRING("target")]; ++ if (t) { ++ target = findChild(QtClient::setUtf8(t)); ++ if (!target) { ++ DDebug(QtDriver::self(),DebugNote, ++ "QtWindow(%s) buildMenu(%s) target '%s' not found [%p]", ++ m_id.c_str(),params.c_str(),t.c_str(),this); ++ return false; ++ } ++ } ++ // Remove existing menu ++ removeMenu(params); ++ QMenu* menu = QtClient::buildMenu(params,params.getValue(YSTRING("title"),params),this, ++ SLOT(action()),SLOT(toggled(bool)),parent); ++ if (!menu) { ++ DDebug(QtDriver::self(),DebugNote, ++ "QtWindow(%s) failed to build menu '%s' target='%s' [%p]", ++ m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this); ++ return false; ++ } ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) built menu '%s' target='%s' [%p]", ++ m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this); ++ QMenuBar* mbOwner = qobject_cast(target); ++ QMenu* mOwner = !mbOwner ? qobject_cast(target) : 0; ++ if (mbOwner || mOwner) { ++ QAction* before = 0; ++ const String& bef = params[YSTRING("before")]; ++ // Retrieve the action to insert before ++ if (bef) { ++ QString cmp = QtClient::setUtf8(bef); ++ QList list = target->actions(); ++ for (int i = 0; !before && i < list.size(); i++) { ++ // Check action name or menu name if the action is associated with a menu ++ if (list[i]->objectName() == cmp) ++ before = list[i]; ++ else if (list[i]->menu() && list[i]->menu()->objectName() == cmp) ++ before = list[i]->menu()->menuAction(); ++ if (before && i && list[i - 1]->isSeparator() && ++ params.getBoolValue(YSTRING("before_separator"),true)) ++ before = list[i - 1]; ++ } ++ } ++ // Insert the menu ++ if (mbOwner) ++ mbOwner->insertMenu(before,menu); ++ else ++ mOwner->insertMenu(before,menu); ++ } ++ else { ++ QToolButton* tb = qobject_cast(target); ++ if (tb) ++ tb->setMenu(menu); ++ else { ++ QPushButton* pb = qobject_cast(target); ++ if (pb) ++ pb->setMenu(menu); ++ else if (!QtClient::setProperty(target,s_propContextMenu,params)) ++ target->addAction(menu->menuAction()); ++ } ++ } ++ return true; ++} ++ ++// Remove a menu ++bool QtWindow::removeMenu(const NamedList& params) ++{ ++ QWidget* parent = this; ++ // Retrieve the owner ++ const String& owner = params[YSTRING("owner")]; ++ if (owner && owner != m_id) { ++ parent = findChild(QtClient::setUtf8(owner)); ++ if (!parent) ++ return false; ++ } ++ QMenu* menu = parent->findChild(QtClient::setUtf8(params)); ++ if (!menu) ++ return false; ++ QtClient::deleteLater(menu); ++ return true; ++} ++ ++// Set an element's image ++bool QtWindow::setImage(const String& name, const String& image, bool fit) ++{ ++ if (!name) ++ return false; ++ if (name == m_id) ++ return QtClient::setImage(this,image); ++ QObject* obj = findChild(QtClient::setUtf8(name)); ++ return obj && QtClient::setImage(obj,image,fit); ++} ++ ++// Set a property for this window or for a widget owned by it ++bool QtWindow::setProperty(const String& name, const String& item, const String& value) ++{ ++ if (name == m_id) ++ return QtClient::setProperty(wndWidget(),item,value); ++ QObject* obj = findChild(QtClient::setUtf8(name)); ++ return obj ? QtClient::setProperty(obj,item,value) : false; ++} ++ ++// Get a property from this window or from a widget owned by it ++bool QtWindow::getProperty(const String& name, const String& item, String& value) ++{ ++ if (name == m_id) ++ return QtClient::getProperty(wndWidget(),item,value); ++ QObject* obj = findChild(QtClient::setUtf8(name)); ++ return obj ? QtClient::getProperty(obj,item,value) : false; ++} ++ ++void QtWindow::moveEvent(QMoveEvent* event) ++{ ++ QWidget::moveEvent(event); ++ // Don't update pos if not shown normal ++ if (!isShownNormal()) ++ return; ++ m_x = pos().x(); ++ m_y = pos().y(); ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) moved x=%d y=%d [%p]", ++ m_id.c_str(),m_x,m_y,this); ++} ++ ++void QtWindow::resizeEvent(QResizeEvent* event) ++{ ++ QWidget::resizeEvent(event); ++ // Don't update size if not shown normal ++ if (!isShownNormal()) ++ return; ++ m_width = width(); ++ m_height = height(); ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) resized width=%d height=%d [%p]", ++ m_id.c_str(),m_width,m_height,this); ++} ++ ++bool QtWindow::event(QEvent* ev) ++{ ++ static const String s_activeChg("window_active_changed"); ++ if (ev->type() == QEvent::WindowDeactivate) { ++ String hideProp; ++ QtClient::getProperty(wndWidget(),s_propHideInactive,hideProp); ++ if (hideProp && hideProp.toBoolean()) ++ setVisible(false); ++ m_active = false; ++ Client::self()->toggle(this,s_activeChg,false); ++ } ++ else if (ev->type() == QEvent::WindowActivate) { ++ m_active = true; ++ Client::self()->toggle(this,s_activeChg,true); ++ String wName; ++ if (getPropPlatform(wndWidget(),s_propShowWndWhenActive,wName) && wName) ++ Client::setVisible(wName); ++ } ++ else if (ev->type() == QEvent::ApplicationDeactivate) { ++ if (m_active) { ++ m_active = false; ++ Client::self()->toggle(this,s_activeChg,true); ++ } ++ } ++ return QWidget::event(ev); ++} ++ ++void QtWindow::closeEvent(QCloseEvent* event) ++{ ++ // NOTE: Don't access window's data after calling hide(): ++ // some logics might destroy the window when hidden ++ ++ // Notify window closed ++ String tmp; ++ if (Client::self() && ++ QtClient::getProperty(wndWidget(),"_yate_windowclosedaction",tmp)) ++ Client::self()->action(this,tmp); ++ ++ // Hide the window when requested ++ if (QtClient::getBoolProperty(wndWidget(),"_yate_hideonclose")) { ++ event->ignore(); ++ hide(); ++ return; ++ } ++ ++ QWidget::closeEvent(event); ++ if (m_mainWindow && Client::self()) { ++ Client::self()->quit(); ++ return; ++ } ++ if (QtClient::getBoolProperty(wndWidget(),"_yate_destroyonclose")) { ++ XDebug(QtDriver::self(),DebugAll, ++ "Window(%s) closeEvent() set delete later [%p]",m_id.c_str(),this); ++ QObject::deleteLater(); ++ // Safe to call hide(): the window will be deleted when control returns ++ // to the main loop ++ } ++ hide(); ++} ++ ++void QtWindow::changeEvent(QEvent* event) ++{ ++ if (event->type() == QEvent::WindowStateChange) ++ m_maximized = isMaximized(); ++ QWidget::changeEvent(event); ++} ++ ++void QtWindow::action() ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) action() sender=%s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(sender()),this); ++ if (!QtClient::self() || QtClient::changing()) ++ return; ++ String name; ++ NamedList* params = 0; ++ if (!QtClient::getBoolProperty(sender(),"_yate_translateidentity")) ++ QtClient::getIdentity(sender(),name); ++ else { ++ QtWidget w(sender()); ++ translateName(w,name,¶ms); ++ } ++ if (name) ++ QtClient::self()->action(this,name,params); ++ TelEngine::destruct(params); ++} ++ ++// Toggled actions ++void QtWindow::toggled(bool on) ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) toggled=%s sender=%s [%p]", ++ m_id.c_str(),String::boolText(on),YQT_OBJECT_NAME(sender()),this); ++ QtClient::updateToggleImage(sender()); ++ if (!QtClient::self() || QtClient::changing()) ++ return; ++ QtWidget w(sender()); ++ String name; ++ if (translateName(w,name)) ++ QtClient::self()->toggle(this,name,on); ++} ++ ++// System tray actions ++void QtWindow::sysTrayIconAction(QSystemTrayIcon::ActivationReason reason) ++{ ++ String action; ++ switch (reason) { ++ case QSystemTrayIcon::Context: ++ QtClient::getProperty(sender(),s_propAction + "Context",action); ++ break; ++ case QSystemTrayIcon::DoubleClick: ++ QtClient::getProperty(sender(),s_propAction + "DoubleClick",action); ++ break; ++ case QSystemTrayIcon::Trigger: ++ QtClient::getProperty(sender(),s_propAction + "Trigger",action); ++ break; ++ case QSystemTrayIcon::MiddleClick: ++ QtClient::getProperty(sender(),s_propAction + "MiddleClick",action); ++ break; ++ default: ++ return; ++ } ++ if (action) ++ Client::self()->action(this,action); ++} ++ ++// Choose file window was accepted ++void QtWindow::chooseFileAccepted() ++{ ++ QFileDialog* dlg = qobject_cast(sender()); ++ if (!dlg) ++ return; ++ String action; ++ QtClient::getUtf8(action,dlg->objectName()); ++ if (!action) ++ return; ++ NamedList params(""); ++ QDir dir = dlg->directory(); ++ if (dir.absolutePath().length()) ++ QtClient::getUtf8(params,"dir",fixPathSep(dir.absolutePath())); ++ QStringList files = dlg->selectedFiles(); ++ for (int i = 0; i < files.size(); i++) ++ QtClient::getUtf8(params,"file",fixPathSep(files[i])); ++ if (dlg->fileMode() != QFileDialog::DirectoryOnly && ++ dlg->fileMode() != QFileDialog::Directory) { ++ QString filter = dlg->selectedNameFilter(); ++ if (filter.length()) ++ QtClient::getUtf8(params,"filter",filter); ++ } ++ Client::self()->action(this,action,¶ms); ++} ++ ++// Choose file window was cancelled ++void QtWindow::chooseFileRejected() ++{ ++ QFileDialog* dlg = qobject_cast(sender()); ++ if (!dlg) ++ return; ++ String action; ++ QtClient::getUtf8(action,dlg->objectName()); ++ if (!action) ++ return; ++ Client::self()->action(this,action,0); ++} ++ ++void QtWindow::openUrl(const QString& link) ++{ ++ QDesktopServices::openUrl(QUrl(link)); ++} ++ ++void QtWindow::doubleClick() ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) doubleClick() sender=%s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(sender()),this); ++ if (QtClient::self() && sender()) ++ Client::self()->action(this,YQT_OBJECT_NAME(sender())); ++} ++ ++// A widget's selection changed ++void QtWindow::selectionChanged() ++{ ++ XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) selectionChanged() sender=%s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(sender()),this); ++ if (!(QtClient::self() && sender())) ++ return; ++ String name = YQT_OBJECT_NAME(sender()); ++ QtWidget w(sender()); ++ if (w.type() != QtWidget::Calendar) { ++ String item; ++ getSelect(name,item); ++ Client::self()->select(this,name,item); ++ } ++ else { ++ NamedList p(""); ++ QDate d = w.calendar()->selectedDate(); ++ p.addParam("year",String(d.year())); ++ p.addParam("month",String(d.month())); ++ p.addParam("day",String(d.day())); ++ Client::self()->action(this,name,&p); ++ } ++} ++ ++// Connect an object's text changed signal to window's slot ++bool QtWindow::connectTextChanged(QObject* obj) ++{ ++ if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify"))) ++ return false; ++ QComboBox* combo = qobject_cast(obj); ++ if (combo) ++ return QtClient::connectObjects(combo,SIGNAL(editTextChanged(const QString&)), ++ this,SLOT(textChanged(const QString&))); ++ QLineEdit* lineEdit = qobject_cast(obj); ++ if (lineEdit) ++ return QtClient::connectObjects(lineEdit,SIGNAL(textChanged(const QString&)), ++ this,SLOT(textChanged(const QString&))); ++ QTextEdit* textEdit = qobject_cast(obj); ++ if (textEdit) ++ return QtClient::connectObjects(textEdit,SIGNAL(textChanged()), ++ this,SLOT(textChanged())); ++ const QMetaObject* meta = obj->metaObject(); ++ Debug(DebugStub,"connectTextChanged() not implemented for class '%s'", ++ meta ? meta->className() : ""); ++ return false; ++} ++ ++// Notify text changed to the client ++void QtWindow::notifyTextChanged(QObject* obj, const QString& text) ++{ ++ if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify"))) ++ return; ++ // Detect QtUIWidget item. Get its container identity if found ++ String item; ++ QtUIWidget::getListItemProp(obj,item); ++ QtUIWidget* uiw = item ? QtUIWidget::container(obj) : 0; ++ String name; ++ if (!uiw) ++ QtClient::getIdentity(obj,name); ++ else ++ uiw->getIdentity(obj,name); ++ if (!name) ++ return; ++ NamedList p(""); ++ p.addParam("sender",name); ++ if (text.size()) ++ QtClient::getUtf8(p,"text",text); ++ Client::self()->action(this,YSTRING("textchanged"),&p); ++} ++ ++// Load a widget from file ++QWidget* QtWindow::loadUI(const char* fileName, QWidget* parent, ++ const char* uiName, const char* path) ++{ ++ if (Client::exiting()) ++ return 0; ++ if (!(fileName && *fileName && parent)) ++ return 0; ++ ++ if (!(path && *path)) ++ path = Client::s_skinPath.c_str(); ++ UIBuffer* buf = UIBuffer::build(fileName); ++ const char* err = 0; ++ if (buf && buf->buffer()) { ++ QBuffer b(buf->buffer()); ++ QUiLoader loader; ++ loader.setWorkingDirectory(QDir(QtClient::setUtf8(path))); ++ QWidget* w = loader.load(&b,parent); ++ if (w) ++ return w; ++ err = "loader failed"; ++ } ++ else ++ err = buf ? "file is empty" : "file not found"; ++ // Error ++ TelEngine::destruct(buf); ++ Debug(DebugWarn,"Failed to load widget '%s' file='%s' path='%s': %s", ++ uiName,fileName,path,err); ++ return 0; ++} ++ ++// Clear the UI cache ++void QtWindow::clearUICache(const char* fileName) ++{ ++ if (!fileName) ++ UIBuffer::s_uiCache.clear(); ++ else ++ TelEngine::destruct(UIBuffer::s_uiCache.find(fileName)); ++} ++ ++// Filter events ++bool QtWindow::eventFilter(QObject* obj, QEvent* event) ++{ ++ if (!obj) ++ return false; ++ // Apply dynamic properties changes ++ if (event->type() == QEvent::DynamicPropertyChange) { ++ String name = YQT_OBJECT_NAME(obj); ++ QDynamicPropertyChangeEvent* ev = static_cast(event); ++ String prop = ev->propertyName().constData(); ++ // Handle only yate dynamic properties ++ if (!prop.startsWith(s_yatePropPrefix,false)) ++ return QWidget::eventFilter(obj,event); ++ XDebug(QtDriver::self(),DebugAll,"Window(%s) eventFilter(%s) prop=%s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(obj),prop.c_str(),this); ++ // Return false for now on: it's our property ++ QtWidget w(obj); ++ if (w.invalid()) ++ return false; ++ String value; ++ if (!QtClient::getProperty(obj,prop,value)) ++ return false; ++ bool ok = true; ++ bool handled = true; ++ if (prop == s_propColWidths) { ++ if (w.type() == QtWidget::Table) { ++ QHeaderView* hdr = w.table()->horizontalHeader(); ++ bool skipLast = hdr && hdr->stretchLastSection(); ++ ObjList* list = value.split(',',false); ++ int col = 0; ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext(), col++) { ++ if (skipLast && col == w.table()->columnCount() - 1) ++ break; ++ int width = (static_cast(o->get()))->toInteger(-1); ++ if (width >= 0) ++ w.table()->setColumnWidth(col,width); ++ } ++ TelEngine::destruct(list); ++ } ++ } ++ else if (prop == s_propSorting) { ++ if (w.type() == QtWidget::Table) { ++ ObjList* list = value.split(',',false); ++ String* tmp = static_cast((*list)[0]); ++ int col = tmp ? tmp->toInteger(-1) : -1; ++ if (col >= 0) { ++ tmp = static_cast((*list)[1]); ++ bool asc = tmp ? tmp->toBoolean(true) : true; ++ w.table()->sortItems(col,asc ? Qt::AscendingOrder : Qt::DescendingOrder); ++ } ++ TelEngine::destruct(list); ++ } ++ } ++ else if (prop == s_propSizes) { ++ if (w.type() == QtWidget::Splitter) { ++ QList list = QtClient::str2IntList(value); ++ w.splitter()->setSizes(list); ++ } ++ } ++ else if (prop == s_propWindowFlags) { ++ QWidget* wid = (name == m_id || name == m_oldId) ? this : w.widget(); ++ QtClient::applyWindowFlags(wid,value); ++ } ++ else if (prop == s_propHHeader) { ++ // Show/hide the horizontal header ++ ok = ((w.type() == QtWidget::Table || w.type() == QtWidget::CustomTable) && ++ value.isBoolean() && w.table()->horizontalHeader()); ++ if (ok) ++ w.table()->horizontalHeader()->setVisible(value.toBoolean()); ++ } ++ else ++ ok = handled = false; ++ if (ok) ++ DDebug(ClientDriver::self(),DebugAll, ++ "Applied dynamic property %s='%s' for object='%s'", ++ prop.c_str(),value.c_str(),name.c_str()); ++ else if (handled) ++ Debug(ClientDriver::self(),DebugMild, ++ "Failed to apply dynamic property %s='%s' for object='%s'", ++ prop.c_str(),value.c_str(),name.c_str()); ++ return false; ++ } ++ if (event->type() == QEvent::KeyPress) { ++ String action; ++ bool filter = false; ++ if (!QtClient::filterKeyEvent(obj,static_cast(event), ++ action,filter,this)) ++ return QWidget::eventFilter(obj,event); ++ if (action && Client::self()) ++ Client::self()->action(this,action); ++ return filter; ++ } ++ if (event->type() == QEvent::ContextMenu) { ++ if (handleContextMenuEvent(static_cast(event),obj)) ++ return false; ++ } ++ if (event->type() == QEvent::Enter) { ++ QtClient::updateImageFromMouse(obj,true,true); ++ return QWidget::eventFilter(obj,event); ++ } ++ if (event->type() == QEvent::Leave) { ++ QtClient::updateImageFromMouse(obj,true,false); ++ return QWidget::eventFilter(obj,event); ++ } ++ if (event->type() == QEvent::MouseButtonPress) { ++ QtClient::updateImageFromMouse(obj,false,true); ++ return QWidget::eventFilter(obj,event); ++ } ++ if (event->type() == QEvent::MouseButtonRelease) { ++ QtClient::updateImageFromMouse(obj,false,false); ++ return QWidget::eventFilter(obj,event); ++ } ++ return QWidget::eventFilter(obj,event); ++} ++ ++// Handle key pressed events ++void QtWindow::keyPressEvent(QKeyEvent* event) ++{ ++ if (!(Client::self() && event)) { ++ QWidget::keyPressEvent(event); ++ return; ++ } ++ QVariant var = this->property("_yate_keypress_redirect"); ++ QString child = var.toString(); ++ if (child.size() > 0 && QtClient::sendEvent(*event,this,child)) { ++ QWidget* wid = findChild(child); ++ if (wid) ++ wid->setFocus(); ++ return; ++ } ++ if (event->key() == Qt::Key_Backspace) ++ Client::self()->backspace(m_id,this); ++ QWidget::keyPressEvent(event); ++} ++ ++// Show hide window. Notify the client ++void QtWindow::setVisible(bool visible) ++{ ++ // Override position for notification windows ++ if (visible && isShownNormal() && ++ QtClient::getBoolProperty(wndWidget(),"_yate_notificationwindow")) { ++ // Don't move ++ m_moving = -1; ++#ifndef Q_WS_MAC ++ // Detect unavailable screen space position and move the window in the apropriate position ++ // bottom/right/none: move it in the right/bottom corner. ++ // top: move it in the right/top corner. ++ // left: move it in the left/bottom corner. ++ int pos = QtClient::PosNone; ++ if (QtClient::getScreenUnavailPos(this,pos)) { ++ if (0 != (pos & (QtClient::PosBottom | QtClient::PosRight)) || pos == QtClient::PosNone) ++ QtClient::moveWindow(this,QtClient::CornerBottomRight); ++ else if (0 != (pos & QtClient::PosTop)) ++ QtClient::moveWindow(this,QtClient::CornerTopRight); ++ else ++ QtClient::moveWindow(this,QtClient::CornerBottomLeft); ++ } ++#else ++ QtClient::moveWindow(this,QtClient::CornerTopRight); ++#endif ++ } ++ if (visible && isMinimized()) ++ showNormal(); ++ else ++ QWidget::setVisible(visible); ++ // Notify the client on window visibility changes ++ bool changed = (m_visible != visible); ++ m_visible = visible; ++ if (changed && Client::self()) { ++ QVariant var; ++ if (this) ++ var = this->property("dynamicUiActionVisibleChanged"); ++ if (!var.toBool()) ++ Client::self()->toggle(this,YSTRING("window_visible_changed"),m_visible); ++ else { ++ Message* m = new Message("ui.action"); ++ m->addParam("action","window_visible_changed"); ++ m->addParam("visible",String::boolText(m_visible)); ++ m->addParam("window",m_id); ++ Engine::enqueue(m); ++ } ++ } ++ if (!m_visible && QtClient::getBoolProperty(wndWidget(),"_yate_destroyonhide")) { ++ DDebug(QtDriver::self(),DebugAll, ++ "Window(%s) setVisible(false) set delete later [%p]",m_id.c_str(),this); ++ QObject::deleteLater(); ++ } ++ // Destroy owned dialogs ++ if (!m_visible) { ++ QList d = findChildren(); ++ for (int i = 0; i < d.size(); i++) ++ d[i]->deleteLater(); ++ } ++} ++ ++// Show the window ++void QtWindow::show() ++{ ++ setVisible(true); ++ m_maximized = m_maximized || isMaximized(); ++ if (m_maximized) ++ setWindowState(Qt::WindowMaximized); ++} ++ ++// Hide the window ++void QtWindow::hide() ++{ ++ setVisible(false); ++} ++ ++void QtWindow::size(int width, int height) ++{ ++ Debug(QtDriver::self(),DebugStub,"QtWindow(%s)::size(%d,%d) [%p]",m_id.c_str(),width,height,this); ++} ++ ++void QtWindow::move(int x, int y) ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s)::move(%d,%d) [%p]",m_id.c_str(),x,y,this); ++ QWidget::move(x,y); ++} ++ ++void QtWindow::moveRel(int dx, int dy) ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow::moveRel(%d,%d) [%p]",dx,dy,this); ++} ++ ++bool QtWindow::related(const Window* wnd) const ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow::related(%p) [%p]",wnd,this); ++ return false; ++} ++ ++void QtWindow::menu(int x, int y) ++{ ++ DDebug(QtDriver::self(),DebugAll,"QtWindow::menu(%d,%d) [%p]",x,y,this); ++} ++ ++// Create a modal dialog ++bool QtWindow::createDialog(const String& name, const String& title, const String& alias, ++ const NamedList* params) ++{ ++ QtDialog* d = new QtDialog(this); ++ if (d->show(name,title,alias,params)) ++ return true; ++ d->deleteLater(); ++ return false; ++} ++ ++// Destroy a modal dialog ++bool QtWindow::closeDialog(const String& name) ++{ ++ QDialog* d = findChild(QtClient::setUtf8(name)); ++ if (!d) ++ return false; ++ d->deleteLater(); ++ return true; ++} ++ ++// Load UI file and setup the window ++void QtWindow::doPopulate() ++{ ++ Debug(QtDriver::self(),DebugAll,"Populating window '%s' [%p]",m_id.c_str(),this); ++ QWidget* formWidget = loadUI(m_description,this,m_id); ++ if (!formWidget) ++ return; ++ // Set window title decoration flags to avoid pos/size troubles with late decoration ++ QVariant var = formWidget->property(s_propWindowFlags); ++ if (var.type() == QVariant::Invalid) { ++ String flgs = "title,sysmenu,minimize,close"; ++ // Add maximize only if allowed ++ if (formWidget->maximumWidth() == QWIDGETSIZE_MAX || ++ formWidget->maximumHeight() == QWIDGETSIZE_MAX) ++ flgs.append("maximize",","); ++ formWidget->setProperty(s_propWindowFlags,QVariant(QtClient::setUtf8(flgs))); ++ } ++ setMinimumSize(formWidget->minimumSize().width(),formWidget->minimumSize().height()); ++ setMaximumSize(formWidget->maximumSize().width(),formWidget->maximumSize().height()); ++ m_x = formWidget->pos().x(); ++ m_y = formWidget->pos().y(); ++ m_width = formWidget->width(); ++ m_height = formWidget->height(); ++ move(m_x,m_y); ++ QWidget::resize(m_width,m_height); ++ QtClient::setWidget(this,formWidget); ++ m_widget = YQT_OBJECT_NAME(formWidget); ++ String wTitle; ++ QtClient::getUtf8(wTitle,formWidget->windowTitle()); ++ title(wTitle); ++ setWindowIcon(formWidget->windowIcon()); ++ setStyleSheet(formWidget->styleSheet()); ++} ++ ++// Initialize window ++void QtWindow::doInit() ++{ ++ DDebug(QtDriver::self(),DebugAll,"Initializing window '%s' [%p]", ++ m_id.c_str(),this); ++ ++ // Create window's dynamic properties from config ++ Configuration cfg(Engine::configFile(m_oldId),false); ++ NamedList* sectGeneral = cfg.getSection("general"); ++ if (sectGeneral) ++ addDynamicProps(this,*sectGeneral); ++ ++ // Load window data ++ m_mainWindow = s_cfg.getBoolValue(m_oldId,"mainwindow"); ++ m_saveOnClose = s_cfg.getBoolValue(m_oldId,"save",true); ++ if (m_id != m_oldId) ++ m_saveOnClose = s_cfg.getBoolValue(m_oldId,"savealias",m_saveOnClose); ++ NamedList* sect = s_save.getSection(m_id); ++ if (sect) { ++ m_maximized = sect->getBoolValue("maximized"); ++ m_x = sect->getIntValue("x",m_x); ++ m_y = sect->getIntValue("y",m_y); ++ m_width = sect->getIntValue("width",m_width); ++ m_height = sect->getIntValue("height",m_height); ++ m_visible = sect->getBoolValue("visible"); ++ } ++ else { ++ if (m_saveOnClose) ++ Debug(QtDriver::self(),DebugNote,"Window(%s) not found in config [%p]", ++ m_id.c_str(),this); ++ m_visible = s_cfg.getBoolValue(m_oldId,"visible"); ++ // Make sure the window is shown in the available geometry ++ QDesktopWidget* d = QApplication::desktop(); ++ if (d) { ++ QRect r = d->availableGeometry(this); ++ m_x = r.x(); ++ m_y = r.y(); ++ } ++ } ++ m_visible = m_mainWindow || m_visible; ++ if (!m_width) ++ m_width = this->width(); ++ if (!m_height) ++ m_height = this->height(); ++ move(m_x,m_y); ++ QWidget::resize(m_width,m_height); ++ ++ // Build custom UI widgets from frames owned by this widget ++ QtClient::buildFrameUiWidgets(this); ++ ++ // Create custom widgets from ++ // _yate_identity=customwidget|[separator=sep|] sep widgetclass sep widgetname [sep param=value] ++ QList frm = findChildren(); ++ for (int i = 0; i < frm.size(); i++) { ++ String create; ++ QtClient::getProperty(frm[i],"_yate_identity",create); ++ if (!create.startSkip("customwidget|",false)) ++ continue; ++ char sep = '|'; ++ // Check if we have another separator ++ if (create.startSkip("separator=",false)) { ++ if (create.length() < 2) ++ continue; ++ sep = create.at(0); ++ create = create.substr(2); ++ } ++ ObjList* list = create.split(sep,false); ++ String type; ++ String name; ++ NamedList params(""); ++ int what = 0; ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext(), what++) { ++ GenObject* p = o->get(); ++ if (what == 0) ++ type = p->toString(); ++ else if (what == 1) ++ name = p->toString(); ++ else { ++ // Decode param ++ int pos = p->toString().find('='); ++ if (pos != -1) ++ params.addParam(p->toString().substr(0,pos),p->toString().substr(pos + 1)); ++ } ++ } ++ TelEngine::destruct(list); ++ params.addParam("parentwindow",m_id); ++ NamedString* pw = new NamedString("parentwidget"); ++ QtClient::getUtf8(*pw,frm[i]->objectName()); ++ params.addParam(pw); ++ QObject* obj = (QObject*)UIFactory::build(type,name,¶ms); ++ if (!obj) ++ continue; ++ QWidget* wid = qobject_cast(obj); ++ if (wid) ++ QtClient::setWidget(frm[i],wid); ++ else { ++ obj->setParent(frm[i]); ++ QtCustomObject* customObj = qobject_cast(obj); ++ if (customObj) ++ customObj->parentChanged(); ++ } ++ } ++ ++ // Add the first menubar to layout ++ QList menuBars = findChildren(); ++ if (menuBars.size() && layout()) { ++ layout()->setMenuBar(menuBars[0]); ++ // Decrease minimum size policy to make sure the layout is made properly ++ if (wndWidget()) { ++ int h = menuBars[0]->height(); ++ int min = wndWidget()->minimumHeight(); ++ if (min > h) ++ wndWidget()->setMinimumHeight(min - h); ++ else ++ wndWidget()->setMinimumHeight(0); ++ } ++#ifdef Q_WS_MAC ++ if (m_mainWindow) { ++ // Create a parentless menu bar to be set as the default application menu by copying it from the main window menu ++ DDebug(QtDriver::self(),DebugAll,"Setting as default menu bar the menu bar of window '%s' [%p]", ++ m_id.c_str(),this); ++ QMenuBar* mainMenu = menuBars[0]; ++ QMenuBar* defaultMenu = new QMenuBar(0); ++ QList topActions = mainMenu->actions(); ++ for (int i = 0; i < topActions.count(); i++) { ++ QMenu* menu = topActions[i]->menu(); ++ if (menu) { ++ QMenu* m = new QMenu(menu->title(),defaultMenu); ++ String tmp; ++ QtClient::getProperty(menu,YSTRING("_yate_menuNoCopy"),tmp); ++ if (tmp.toBoolean()) ++ continue; ++ defaultMenu->addMenu(m); ++ QList actions = menu->actions(); ++ for (int j = 0; j < actions.count(); j++) { ++ QAction* act = actions[j]; ++ tmp.clear(); ++ QtClient::getProperty(act,YSTRING("_yate_menuNoCopy"),tmp); ++ if (tmp.toBoolean()) ++ continue; ++ m->addAction(act); ++ } ++ } ++ } ++ } ++#endif ++ } ++ ++ // Create window's children dynamic properties from config ++ unsigned int n = cfg.sections(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedList* sect = cfg.getSection(i); ++ if (sect && *sect && *sect != "general") ++ addDynamicProps(findChild(sect->c_str()),*sect); ++ } ++ ++ // Process "_yate_setaction" property for our children ++ QtClient::setAction(this); ++ ++ // Connect actions' signal ++ QList actions = findChildren(); ++ for (int i = 0; i < actions.size(); i++) { ++ String addToWidget; ++ QtClient::getProperty(actions[i],"dynamicAddToParent",addToWidget); ++ if (addToWidget && addToWidget.toBoolean()) ++ QWidget::addAction(actions[i]); ++ if (actions[i]->isCheckable()) ++ QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool))); ++ else ++ QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action())); ++ } ++ ++ // Connect combo boxes signals ++ QList combos = findChildren(); ++ for (int i = 0; i < combos.size(); i++) { ++ QtClient::connectObjects(combos[i],SIGNAL(activated(int)),this,SLOT(selectionChanged())); ++ connectTextChanged(combos[i]); ++ } ++ ++ // Connect abstract buttons (check boxes and radio/push/tool buttons) signals ++ QList buttons = findChildren(); ++ for(int i = 0; i < buttons.size(); i++) ++ if (QtClient::autoConnect(buttons[i])) ++ connectButton(buttons[i]); ++ ++ // Connect group boxes signals ++ QList grp = findChildren(); ++ for(int i = 0; i < grp.size(); i++) ++ if (grp[i]->isCheckable()) ++ QtClient::connectObjects(grp[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool))); ++ ++ // Connect sliders signals ++ QList sliders = findChildren(); ++ for (int i = 0; i < sliders.size(); i++) ++ QtClient::connectObjects(sliders[i],SIGNAL(valueChanged(int)),this,SLOT(selectionChanged())); ++ ++ // Connect calendar widget signals ++ QList cals = findChildren(); ++ for (int i = 0; i < cals.size(); i++) ++ QtClient::connectObjects(cals[i],SIGNAL(selectionChanged()),this,SLOT(selectionChanged())); ++ ++ // Connect list boxes signals ++ QList lists = findChildren(); ++ for (int i = 0; i < lists.size(); i++) { ++ QtClient::connectObjects(lists[i],SIGNAL(itemDoubleClicked(QListWidgetItem*)), ++ this,SLOT(doubleClick())); ++ QtClient::connectObjects(lists[i],SIGNAL(itemActivated(QListWidgetItem*)), ++ this,SLOT(doubleClick())); ++ QtClient::connectObjects(lists[i],SIGNAL(currentRowChanged(int)), ++ this,SLOT(selectionChanged())); ++ } ++ ++ // Connect tab widget signals ++ QList tabs = findChildren(); ++ for (int i = 0; i < tabs.size(); i++) ++ QtClient::connectObjects(tabs[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged())); ++ ++ // Connect stacked widget signals ++ QList sw = findChildren(); ++ for (int i = 0; i < sw.size(); i++) ++ QtClient::connectObjects(sw[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged())); ++ ++ // Connect line edit signals ++ QList le = findChildren(); ++ for (int i = 0; i < le.size(); i++) ++ connectTextChanged(le[i]); ++ ++ // Connect text edit signals ++ QList te = findChildren(); ++ for (int i = 0; i < te.size(); i++) ++ connectTextChanged(te[i]); ++ ++ // Process tables: ++ // Insert a column and connect signals ++ // Hide columns starting with "hidden:" ++ QList tables = findChildren(); ++ for (int i = 0; i < tables.size(); i++) { ++ bool nonCustom = (0 == qobject_cast(tables[i])); ++ // Horizontal header ++ QHeaderView* hdr = tables[i]->horizontalHeader(); ++ // Stretch last column ++ bool b = QtClient::getBoolProperty(tables[i],"_yate_horizontalstretch",true); ++ hdr->setStretchLastSection(b); ++ String tmp; ++ QtClient::getProperty(tables[i],"_yate_horizontalheader_align",tmp); ++ if (tmp) { ++ int def = hdr->defaultAlignment(); ++ hdr->setDefaultAlignment((Qt::Alignment)QtClient::str2align(tmp,def)); ++ } ++ if (!QtClient::getBoolProperty(tables[i],"_yate_horizontalheader",true)) ++ hdr->hide(); ++ // Vertical header ++ hdr = tables[i]->verticalHeader(); ++ int itemH = QtClient::getIntProperty(tables[i],"_yate_rowheight"); ++ if (itemH > 0) ++ hdr->setDefaultSectionSize(itemH); ++ if (!QtClient::getBoolProperty(tables[i],"_yate_verticalheader")) ++ hdr->hide(); ++ else { ++ int width = QtClient::getIntProperty(tables[i],"_yate_verticalheaderwidth"); ++ if (width > 0) ++ hdr->setFixedWidth(width); ++ if (!QtClient::getBoolProperty(tables[i],"_yate_allowvheaderresize")) ++ hdr->setSectionResizeMode(QHeaderView::Fixed); ++ } ++ if (nonCustom) { ++ // Set _yate_save_props ++ QVariant var = tables[i]->property(s_propsSave); ++ if (var.type() != QVariant::StringList) { ++ // Create the property if not found, ignore it if not a string list ++ if (var.type() == QVariant::Invalid) ++ var = QVariant(QVariant::StringList); ++ else ++ Debug(QtDriver::self(),DebugNote, ++ "Window(%s) table '%s' already has a non string list property %s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(tables[i]),s_propsSave.c_str(),this); ++ } ++ if (var.type() == QVariant::StringList) { ++ // Make sure saved properties exists to allow them to be restored ++ QStringList sl = var.toStringList(); ++ bool changed = createProperty(tables[i],s_propColWidths,QVariant::String,this,&sl); ++ changed = createProperty(tables[i],s_propSorting,QVariant::String,this,&sl) || changed; ++ if (changed) ++ tables[i]->setProperty(s_propsSave,QVariant(sl)); ++ } ++ } ++ TableWidget t(tables[i]); ++ // Insert the column containing the ID ++ t.addColumn(0,0,"hidden:id"); ++ // Hide columns ++ for (int i = 0; i < t.columnCount(); i++) { ++ String name; ++ t.getHeaderText(i,name,false); ++ if (name.startsWith("hidden:")) ++ t.table()->setColumnHidden(i,true); ++ } ++ // Connect signals ++ QtClient::connectObjects(t.table(),SIGNAL(cellDoubleClicked(int,int)), ++ this,SLOT(doubleClick())); ++#if 0 ++ // This would generate action() twice since QT will signal both cell and ++ // table item double click ++ QtClient::connectObjects(t.table(),SIGNAL(itemDoubleClicked(QTableWidgetItem*)), ++ this,SLOT(doubleClick())); ++#endif ++ String noSel; ++ getProperty(t.name(),"dynamicNoItemSelChanged",noSel); ++ if (!noSel.toBoolean()) ++ QtClient::connectObjects(t.table(),SIGNAL(itemSelectionChanged()), ++ this,SLOT(selectionChanged())); ++ // Optionally connect cell clicked ++ // This is done when we want to generate a select() or action() from cell clicked ++ String cellClicked; ++ getProperty(t.name(),"dynamicCellClicked",cellClicked); ++ if (cellClicked) { ++ if (cellClicked == "selectionChanged") ++ QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)), ++ this,SLOT(selectionChanged())); ++ else if (cellClicked == "doubleClick") ++ QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)), ++ this,SLOT(doubleClick())); ++ } ++ } ++ ++ // Restore saved children properties ++ if (sect) { ++ unsigned int n = sect->length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = sect->getParam(i); ++ if (!ns) ++ continue; ++ String prop(ns->name()); ++ if (!prop.startSkip("property:",false)) ++ continue; ++ int pos = prop.find(":"); ++ if (pos > 0) { ++ String wName = prop.substr(0,pos); ++ String pName = prop.substr(pos + 1); ++ DDebug(QtDriver::self(),DebugAll, ++ "Window(%s) restoring property %s=%s for child '%s' [%p]", ++ m_id.c_str(),pName.c_str(),ns->c_str(),wName.c_str(),this); ++ setProperty(wName,pName,*ns); ++ } ++ } ++ } ++ ++ // Install event filter and apply dynamic properties ++ QList w = findChildren(); ++ w.append(this); ++ for (int i = 0; i < w.size(); i++) { ++ QList props = w[i]->dynamicPropertyNames(); ++ // Check for our dynamic properties ++ int j = 0; ++ for (j = 0; j < props.size(); j++) ++ if (props[j].startsWith(s_yatePropPrefix)) ++ break; ++ if (j == props.size()) ++ continue; ++ // Add event hook to be used when a dynamic property changes ++ w[i]->installEventFilter(this); ++ // Fake dynamic property change to apply them ++ for (j = 0; j < props.size(); j++) { ++ if (!props[j].startsWith(s_yatePropPrefix)) ++ continue; ++ QDynamicPropertyChangeEvent ev(props[j]); ++ eventFilter(w[i],&ev); ++ } ++ } ++ ++ qRegisterMetaType("QModelIndex"); ++ qRegisterMetaType("QTextCursor"); ++ ++ // Force window visibility change notification by changing the visibility flag ++ // Some controls might need to be updated ++ m_visible = !m_visible; ++ if (m_visible) { ++ // Disable _yate_destroyonhide property: avoid destroying the window now ++ String tmp; ++ getProperty(m_id,"_yate_destroyonhide",tmp); ++ if (tmp) ++ setProperty(m_id,"_yate_destroyonhide",String::boolText(false)); ++ hide(); ++ if (tmp) ++ setProperty(m_id,"_yate_destroyonhide",tmp); ++ } ++ else ++ show(); ++} ++ ++// Mouse button pressed notification ++void QtWindow::mousePressEvent(QMouseEvent* event) ++{ ++ if (m_moving >= 0 && Qt::LeftButton == event->button() && isShownNormal()) { ++ m_movePos = event->globalPos(); ++ m_moving = 1; ++ } ++} ++ ++// Mouse button release notification ++void QtWindow::mouseReleaseEvent(QMouseEvent* event) ++{ ++ if (m_moving >= 0 && Qt::LeftButton == event->button()) ++ m_moving = 0; ++} ++ ++// Move the window if the moving flag is set ++void QtWindow::mouseMoveEvent(QMouseEvent* event) ++{ ++ if (m_moving <= 0 || Qt::LeftButton != event->buttons() || !isShownNormal()) ++ return; ++ int cx = event->globalPos().x() - m_movePos.x(); ++ int cy = event->globalPos().y() - m_movePos.y(); ++ if (cx || cy) { ++ m_movePos = event->globalPos(); ++ QWidget::move(x() + cx,y() + cy); ++ } ++} ++ ++// Handle context menu events. Return true if handled ++bool QtWindow::handleContextMenuEvent(QContextMenuEvent* event, QObject* obj) ++{ ++ if (!(event && obj)) ++ return false; ++ String mname; ++ QtClient::getProperty(obj,s_propContextMenu,mname); ++ XDebug(ClientDriver::self(),DebugAll, ++ "Window(%s) handleContextMenuEvent() obj=%s menu=%s [%p]", ++ m_id.c_str(),YQT_OBJECT_NAME(obj),mname.c_str(),this); ++ QMenu* m = mname ? findChild(QtClient::setUtf8(mname)) : 0; ++ if (m) ++ m->exec(event->globalPos()); ++ return m != 0; ++} ++ ++ ++/* ++ * QtDialog ++ */ ++// Destructor. Notify the client if not exiting ++QtDialog::~QtDialog() ++{ ++ QtWindow* w = parentWindow(); ++ if (w && m_notifyOnClose && Client::valid()) ++ QtClient::self()->action(w,buildActionName(m_notifyOnClose,m_notifyOnClose)); ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) QtDialog(%s) destroyed [%p]", ++ w ? w->id().c_str() : "",YQT_OBJECT_NAME(this),w); ++} ++ ++// Initialize dialog. Load the widget. ++// Connect non checkable actions to own slot. ++// Connect checkable actions/buttons to parent window's slot ++// Display the dialog on success ++bool QtDialog::show(const String& name, const String& title, const String& alias, ++ const NamedList* params) ++{ ++ QtWindow* w = parentWindow(); ++ if (!w) ++ return false; ++ QWidget* widget = QtWindow::loadUI(Client::s_skinPath + s_cfg.getValue(name,"description"),this,name); ++ if (!widget) ++ return false; ++ QtClient::getProperty(widget,"_yate_notifyonclose",m_notifyOnClose); ++ setObjectName(QtClient::setUtf8(alias ? alias : name)); ++ setMinimumSize(widget->minimumSize().width(),widget->minimumSize().height()); ++ setMaximumSize(widget->maximumSize().width(),widget->maximumSize().height()); ++ resize(widget->width(),widget->height()); ++ QtClient::setWidget(this,widget); ++ if (title) ++ setWindowTitle(QtClient::setUtf8(title)); ++ else if (widget->windowTitle().length()) ++ setWindowTitle(widget->windowTitle()); ++ else ++ setWindowTitle(w->windowTitle()); ++ // Connect abstract buttons (check boxes and radio/push/tool buttons) signals ++ QList buttons = widget->findChildren(); ++ for(int i = 0; i < buttons.size(); i++) { ++ if (!QtClient::autoConnect(buttons[i])) ++ continue; ++ if (!buttons[i]->isCheckable()) ++ QtClient::connectObjects(buttons[i],SIGNAL(clicked()),this,SLOT(action())); ++ else ++ QtClient::connectObjects(buttons[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool))); ++ } ++ // Connect actions' signal ++ QList actions = widget->findChildren(); ++ for (int i = 0; i < actions.size(); i++) { ++ if (!QtClient::autoConnect(actions[i])) ++ continue; ++ if (!actions[i]->isCheckable()) ++ QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action())); ++ else ++ QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool))); ++ } ++ String* flags = 0; ++ String tmp; ++ QtClient::getProperty(widget,s_propWindowFlags,tmp); ++ if (tmp) ++ flags = &tmp; ++ if (params) { ++ if (!flags) ++ flags = params->getParam(s_propWindowFlags); ++ m_closable = params->getBoolValue(YSTRING("closable"),"true"); ++ w->setParams(*params); ++ } ++ if (flags) ++ QtClient::applyWindowFlags(this,*flags); ++ setWindowModality(Qt::WindowModal); ++ QDialog::show(); ++ return true; ++} ++ ++// Notify client ++void QtDialog::action() ++{ ++ QtWindow* w = parentWindow(); ++ if (!w) ++ return; ++ DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) dialog action '%s' [%p]", ++ w->id().c_str(),YQT_OBJECT_NAME(sender()),w); ++ if (!QtClient::self() || QtClient::changing()) ++ return; ++ String name; ++ QtClient::getIdentity(sender(),name); ++ if (name && QtClient::self()->action(w,buildActionName(name,name))) ++ deleteLater(); ++} ++ ++// Delete the dialog ++void QtDialog::closeEvent(QCloseEvent* event) ++{ ++ if (m_closable) { ++ QDialog::closeEvent(event); ++ deleteLater(); ++ } ++ else ++ event->ignore(); ++} ++ ++// Destroy the dialog ++void QtDialog::reject() ++{ ++ if (!m_closable) ++ return; ++ QDialog::reject(); ++ deleteLater(); ++} ++ ++ ++/** ++ * QtClient ++ */ ++QtClient::QtClient() ++ : Client("Qt Client") ++{ ++ m_oneThread = Engine::config().getBoolValue("client","onethread",true); ++ ++ s_save = Engine::configFile("qt5client",true); ++ s_save.load(); ++ // Fill QT styles ++ s_qtStyles.addParam("IaOraKde","iaorakde"); ++ s_qtStyles.addParam("QWindowsStyle","windows"); ++ s_qtStyles.addParam("QMacStyle","mac"); ++ s_qtStyles.addParam("QMotifStyle","motif"); ++ s_qtStyles.addParam("QCDEStyle","cde"); ++ s_qtStyles.addParam("QWindowsXPStyle","windowsxp"); ++ s_qtStyles.addParam("QCleanlooksStyle","cleanlooks"); ++ s_qtStyles.addParam("QPlastiqueStyle","plastique"); ++ s_qtStyles.addParam("QGtkStyle","gtk"); ++ s_qtStyles.addParam("IaOraQt","iaoraqt"); ++ s_qtStyles.addParam("OxygenStyle","oxygen"); ++ s_qtStyles.addParam("PhaseStyle","phase"); ++} ++ ++QtClient::~QtClient() ++{ ++} ++ ++void QtClient::cleanup() ++{ ++ Client::cleanup(); ++ m_events.clear(); ++ Client::save(s_save); ++ QtWindow::clearUICache(); ++ m_app->quit(); ++ if (!m_app->startingUp()) ++ delete m_app; ++} ++ ++void QtClient::run() ++{ ++ const char* style = Engine::config().getValue("client","style"); ++ if (style && !QApplication::setStyle(QString::fromUtf8(style))) ++ Debug(ClientDriver::self(),DebugWarn,"Could not set Qt style '%s'",style); ++ int argc = 0; ++ char* argv = 0; ++ m_app = new QApplication(argc,&argv); ++ m_app->setQuitOnLastWindowClosed(false); ++ updateAppStyleSheet(); ++ String imgRead; ++ QList imgs = QImageReader::supportedImageFormats(); ++ for (int i = 0; i < imgs.size(); i++) ++ imgRead.append(imgs[i].constData(),","); ++ imgRead = "read image formats '" + imgRead + "'"; ++ Debug(ClientDriver::self(),DebugInfo,"QT client start running (version=%s) %s", ++ qVersion(),imgRead.c_str()); ++ if (!QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).isEmpty()) ++ Debug(ClientDriver::self(),DebugWarn,"QT sounds are not available"); ++ // Create events proxy ++ m_events.append(new QtEventProxy(QtEventProxy::Timer)); ++ m_events.append(new QtEventProxy(QtEventProxy::AllHidden,m_app)); ++ if (Engine::exiting()) ++ return; ++ Client::run(); ++} ++ ++void QtClient::main() ++{ ++ if (!Engine::exiting()) ++ m_app->exec(); ++} ++ ++void QtClient::lock() ++{} ++ ++void QtClient::unlock() ++{} ++ ++void QtClient::allHidden() ++{ ++ Debug(QtDriver::self(),DebugInfo,"QtClient::allHiden() counter=%d",s_allHiddenQuit); ++ if (s_allHiddenQuit > 0) ++ return; ++ quit(); ++} ++ ++bool QtClient::createWindow(const String& name, const String& alias) ++{ ++ String parent = s_cfg.getValue(name,"parent"); ++ QtWindow* parentWnd = 0; ++ if (!TelEngine::null(parent)) { ++ ObjList* o = m_windows.find(parent); ++ if (o) ++ parentWnd = YOBJECT(QtWindow,o->get()); ++ } ++ QtWindow* w = new QtWindow(name,s_skinPath + s_cfg.getValue(name,"description"),alias,parentWnd); ++ if (w) { ++ Debug(QtDriver::self(),DebugAll,"Created window name=%s alias=%s with parent=(%s [%p]) (%p)", ++ name.c_str(),alias.c_str(),parent.c_str(),parentWnd,w); ++ // Remove the old window ++ ObjList* o = m_windows.find(w->id()); ++ if (o) ++ Client::self()->closeWindow(w->id(),false); ++ w->populate(); ++ m_windows.append(w); ++ return true; ++ } ++ else ++ Debug(QtDriver::self(),DebugCrit,"Could not create window name=%s alias=%s", ++ name.c_str(),alias.c_str()); ++ return false; ++} ++ ++void QtClient::loadWindows(const char* file) ++{ ++ if (!file) ++ s_cfg = s_skinPath + "qt5client.rc"; ++ else ++ s_cfg = String(file); ++ s_cfg.load(); ++ Debug(QtDriver::self(),DebugInfo,"Loading Windows"); ++ unsigned int n = s_cfg.sections(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedList* l = s_cfg.getSection(i); ++ if (l && l->getBoolValue(YSTRING("enabled"),true)) ++ createWindow(*l); ++ } ++} ++ ++bool QtClient::isUIThread() ++{ ++ return (QApplication::instance() && QApplication::instance()->thread() == QThread::currentThread()); ++} ++ ++// Open a file open dialog window ++// Parameters that can be specified include 'caption', ++// 'dir', 'filter', 'selectedfilter', 'confirmoverwrite', 'choosedir' ++bool QtClient::chooseFile(Window* parent, NamedList& params) ++{ ++ QtWindow* wnd = static_cast(parent); ++ QFileDialog* dlg = new QFileDialog(wnd,setUtf8(params.getValue(YSTRING("caption"))), ++ setUtf8(params.getValue(YSTRING("dir")))); ++ ++ if (wnd) ++ dlg->setWindowIcon(wnd->windowIcon()); ++ ++ // Connect signals ++ String* action = params.getParam(YSTRING("action")); ++ if (wnd && !null(action)) { ++ dlg->setObjectName(setUtf8(*action)); ++ QtClient::connectObjects(dlg,SIGNAL(accepted()),wnd,SLOT(chooseFileAccepted())); ++ QtClient::connectObjects(dlg,SIGNAL(rejected()),wnd,SLOT(chooseFileRejected())); ++ } ++ ++ // Destroy it when closed ++ dlg->setAttribute(Qt::WA_DeleteOnClose); ++ // This dialog should always stay on top ++ dlg->setWindowFlags(dlg->windowFlags() | Qt::WindowStaysOnTopHint); ++ ++ if (params.getBoolValue(YSTRING("modal"),true)) ++ dlg->setWindowModality(Qt::WindowModal); ++ ++ // Filters ++ NamedString* f = params.getParam(YSTRING("filters")); ++ if (f) { ++ QStringList filters; ++ ObjList* obj = f->split('|',false); ++ for (ObjList* o = obj->skipNull(); o; o = o->skipNext()) ++ filters.append(QtClient::setUtf8(o->get()->toString())); ++ TelEngine::destruct(obj); ++ dlg->setNameFilters(filters); ++ } ++ QString flt = QtClient::setUtf8(params.getValue(YSTRING("selectedfilter"))); ++ if (flt.length()) ++ dlg->selectNameFilter(flt); ++ ++ if (params.getBoolValue(YSTRING("save"))) ++ dlg->setAcceptMode(QFileDialog::AcceptSave); ++ else ++ dlg->setAcceptMode(QFileDialog::AcceptOpen); ++ ++ // Choose options ++ if (params.getBoolValue(YSTRING("choosefile"),true)) { ++ if (params.getBoolValue(YSTRING("chooseanyfile"))) ++ dlg->setFileMode(QFileDialog::AnyFile); ++ else if (params.getBoolValue(YSTRING("multiplefiles"))) ++ dlg->setFileMode(QFileDialog::ExistingFiles); ++ else ++ dlg->setFileMode(QFileDialog::ExistingFile); ++ } ++ else ++ dlg->setFileMode(QFileDialog::DirectoryOnly); ++ ++ dlg->selectFile(QtClient::setUtf8(params.getValue(YSTRING("selectedfile")))); ++ ++ dlg->setVisible(true); ++ return true; ++} ++ ++bool QtClient::action(Window* wnd, const String& name, NamedList* params) ++{ ++ String tmp = name; ++ if (tmp.startSkip("openurl:",false)) ++ return openUrl(tmp); ++ return Client::action(wnd,name,params); ++} ++ ++// Create a sound object. Append it to the global list ++bool QtClient::createSound(const char* name, const char* file, const char* device) ++{ ++ if (!(QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).isEmpty() && ++ name && *name && file && *file)) ++ return false; ++ Lock lock(ClientSound::s_soundsMutex); ++ if (ClientSound::s_sounds.find(name)) ++ return false; ++ ClientSound::s_sounds.append(new QtSound(name,file,device)); ++ DDebug(ClientDriver::self(),DebugAll,"Added sound=%s file=%s device=%s", ++ name,file,device); ++ return true; ++} ++ ++// Build a date/time string from UTC time ++bool QtClient::formatDateTime(String& dest, unsigned int secs, ++ const char* format, bool utc) ++{ ++ if (!(format && *format)) ++ return false; ++ QtClient::getUtf8(dest,formatDateTime(secs,format,utc)); ++ return true; ++} ++ ++// Build a date/time QT string from UTC time ++QString QtClient::formatDateTime(unsigned int secs, const char* format, bool utc) ++{ ++ QDateTime time; ++ if (utc) ++ time.setTimeSpec(Qt::UTC); ++ time.setTime_t(secs); ++ return time.toString(format); ++} ++ ++// Retrieve an object's QtWindow parent ++QtWindow* QtClient::parentWindow(QObject* obj) ++{ ++ for (; obj; obj = obj->parent()) { ++ QtWindow* w = qobject_cast(obj); ++ if (w) ++ return w; ++ } ++ return 0; ++} ++ ++// Save an object's property into parent window's section. Clear it on failure ++bool QtClient::saveProperty(QObject* obj, const String& prop, QtWindow* owner) ++{ ++ if (!obj) ++ return false; ++ if (!owner) ++ owner = parentWindow(obj); ++ if (!owner) ++ return false; ++ String value; ++ bool ok = getProperty(obj,prop,value); ++ String pName; ++ pName << "property:" << YQT_OBJECT_NAME(obj) << ":" << prop; ++ if (ok) ++ s_save.setValue(owner->id(),pName,value); ++ else ++ s_save.clearKey(owner->id(),pName); ++ return ok; ++} ++ ++// Set or an object's property ++bool QtClient::setProperty(QObject* obj, const char* name, const String& value) ++{ ++ if (!(obj && name && *name)) ++ return false; ++ QVariant var = obj->property(name); ++ const char* err = 0; ++ bool ok = false; ++ switch (var.type()) { ++ case QVariant::String: ++ ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value))); ++ break; ++ case QVariant::Bool: ++ ok = obj->setProperty(name,QVariant(value.toBoolean())); ++ break; ++ case QVariant::Int: ++ ok = obj->setProperty(name,QVariant(value.toInteger())); ++ break; ++ case QVariant::UInt: ++ ok = obj->setProperty(name,QVariant((unsigned int)value.toInteger())); ++ break; ++ case QVariant::Icon: ++ ok = obj->setProperty(name,QVariant(QIcon(QtClient::setUtf8(value)))); ++ break; ++ case QVariant::Pixmap: ++ ok = obj->setProperty(name,QVariant(QPixmap(QtClient::setUtf8(value)))); ++ break; ++ case QVariant::Double: ++ ok = obj->setProperty(name,QVariant(value.toDouble())); ++ break; ++ case QVariant::KeySequence: ++ ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value))); ++ break; ++ case QVariant::StringList: ++ { ++ QStringList qList; ++ if (value) ++ qList.append(setUtf8(value)); ++ ok = obj->setProperty(name,QVariant(qList)); ++ } ++ break; ++ case QVariant::Invalid: ++ err = "no such property"; ++ break; ++ default: ++ err = "unsupported type"; ++ } ++ YIGNORE(err); ++ if (ok) ++ DDebug(ClientDriver::self(),DebugAll,"Set property %s=%s for object '%s'", ++ name,value.c_str(),YQT_OBJECT_NAME(obj)); ++ else ++ DDebug(ClientDriver::self(),DebugNote, ++ "Failed to set %s=%s (type=%s) for object '%s': %s", ++ name,value.c_str(),var.typeName(),YQT_OBJECT_NAME(obj),err); ++ return ok; ++} ++ ++// Get an object's property ++bool QtClient::getProperty(QObject* obj, const char* name, String& value) ++{ ++ if (!(obj && name && *name)) ++ return false; ++ QVariant var = obj->property(name); ++ if (var.type() == QVariant::StringList) { ++ NamedList* l = static_cast(value.getObject(YATOM("NamedList"))); ++ if (l) ++ copyParams(*l,var.toStringList()); ++ else ++ getUtf8(value,var.toStringList().join(",")); ++ DDebug(ClientDriver::self(),DebugAll,"Got list property %s for object '%s'", ++ name,YQT_OBJECT_NAME(obj)); ++ return true; ++ } ++ if (var.canConvert(QVariant::String)) { ++ QtClient::getUtf8(value,var.toString()); ++ DDebug(ClientDriver::self(),DebugAll,"Got property %s=%s for object '%s'", ++ name,value.c_str(),YQT_OBJECT_NAME(obj)); ++ return true; ++ } ++ DDebug(ClientDriver::self(),DebugNote, ++ "Failed to get property '%s' (type=%s) for object '%s': %s", ++ name,var.typeName(),YQT_OBJECT_NAME(obj), ++ ((var.type() == QVariant::Invalid) ? "no such property" : "unsupported type")); ++ return false; ++} ++ ++// Copy a string list to a list of parameters ++void QtClient::copyParams(NamedList& dest, const QStringList& src) ++{ ++ for (int i = 0; i < src.size(); i++) { ++ if (!src[i].length()) ++ continue; ++ int pos = src[i].indexOf('='); ++ String name; ++ if (pos >= 0) { ++ getUtf8(name,src[i].left(pos)); ++ getUtf8(dest,name,src[i].right(src[i].length() - pos - 1)); ++ } ++ else { ++ getUtf8(name,src[i]); ++ dest.addParam(name,""); ++ } ++ } ++} ++ ++// Copy a list of parameters to string list ++void QtClient::copyParams(QStringList& dest, const NamedList& src) ++{ ++ unsigned int n = src.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = src.getParam(i); ++ if (ns) ++ dest.append(setUtf8(ns->name() + "=" + *ns)); ++ } ++} ++ ++// Build QObject properties from list ++void QtClient::buildProps(QObject* obj, const String& props) ++{ ++ if (!(obj && props)) ++ return; ++ ObjList* list = props.split(',',false); ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ String* s = static_cast(o->get()); ++ int pos = s->find('='); ++ if (pos < 1) ++ continue; ++ String ptype = s->substr(pos + 1); ++ QVariant::Type t = (QVariant::Type)lookup(ptype,s_qVarType,QVariant::Invalid); ++ if (t == QVariant::Invalid) { ++ Debug(ClientDriver::self(),DebugStub, ++ "QtClient::buildProps() unhandled type '%s'",ptype.c_str()); ++ continue; ++ } ++ String pname = s->substr(0,pos); ++ QVariant existing = obj->property(pname); ++ if (existing.type() == QVariant::Invalid) { ++ obj->setProperty(pname,QVariant(t)); ++ continue; ++ } ++ Debug(ClientDriver::self(),DebugNote, ++ "Can't create property '%s' type=%s for object (%p,%s): already exists", ++ pname.c_str(),ptype.c_str(),obj,YQT_OBJECT_NAME(obj)); ++ } ++ TelEngine::destruct(list); ++} ++ ++// Build custom UI widgets from frames owned by a widget ++void QtClient::buildFrameUiWidgets(QWidget* parent) ++{ ++ if (!parent) ++ return; ++ QList frm = parent->findChildren(); ++ for (int i = 0; i < frm.size(); i++) { ++ if (!getBoolProperty(frm[i],"_yate_uiwidget")) ++ continue; ++ String name; ++ String type; ++ getProperty(frm[i],"_yate_uiwidget_name",name); ++ getProperty(frm[i],"_yate_uiwidget_class",type); ++ if (!(name && type)) ++ continue; ++ NamedList params(""); ++ getProperty(frm[i],"_yate_uiwidget_params",params); ++ QtWindow* w = static_cast(parent->window()); ++ if (w) ++ params.setParam("parentwindow",w->id()); ++ getUtf8(params,"parentwidget",frm[i]->objectName(),true); ++ QObject* obj = (QObject*)UIFactory::build(type,name,¶ms); ++ if (!obj) ++ continue; ++ QWidget* wid = qobject_cast(obj); ++ if (wid) ++ QtClient::setWidget(frm[i],wid); ++ else { ++ obj->setParent(frm[i]); ++ QtCustomObject* customObj = qobject_cast(obj); ++ if (customObj) ++ customObj->parentChanged(); ++ } ++ } ++} ++ ++// Associate actions to buttons with '_yate_setaction' property set ++void QtClient::setAction(QWidget* parent) ++{ ++ if (!parent) ++ return; ++ QList tb = parent->findChildren(); ++ for (int i = 0; i < tb.size(); i++) { ++ QVariant var = tb[i]->property("_yate_setaction"); ++ if (var.toString().isEmpty()) ++ continue; ++ QAction* a = parent->findChild(var.toString()); ++ if (a) ++ tb[i]->setDefaultAction(a); ++ } ++} ++ ++// Build a menu object from a list of parameters ++QMenu* QtClient::buildMenu(const NamedList& params, const char* text, QObject* receiver, ++ const char* triggerSlot, const char* toggleSlot, QWidget* parent, ++ const char* aboutToShowSlot) ++{ ++ QMenu* menu = 0; ++ unsigned int n = params.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* param = params.getParam(i); ++ if (!(param && param->name().startsWith("item:"))) ++ continue; ++ ++ if (!menu) ++ menu = new QMenu(setUtf8(text),parent); ++ ++ NamedList* p = YOBJECT(NamedList,param); ++ if (p) { ++ QMenu* subMenu = buildMenu(*p,*param ? param->c_str() : p->getValue(YSTRING("title"),*p), ++ receiver,triggerSlot,toggleSlot,menu); ++ if (subMenu) ++ menu->addMenu(subMenu); ++ continue; ++ } ++ String name = param->name().substr(5); ++ if (*param) { ++ QAction* a = menu->addAction(QtClient::setUtf8(*param)); ++ a->setObjectName(QtClient::setUtf8(name)); ++ a->setParent(menu); ++ setImage(a,params["image:" + name]); ++ } ++ else if (!name) ++ menu->addSeparator()->setParent(menu); ++ else { ++ // Check if the action is already there ++ QAction* a = 0; ++ if (parent && parent->window()) ++ a = parent->window()->findChild(QtClient::setUtf8(name)); ++ if (a) ++ menu->addAction(a); ++ else ++ Debug(ClientDriver::self(),DebugNote, ++ "buildMenu(%s) action '%s' not found",params.c_str(),name.c_str()); ++ } ++ } ++ ++ if (!menu) ++ return 0; ++ ++ // Set name ++ menu->setObjectName(setUtf8(params)); ++ setImage(menu,params["image:" + params]); ++ // Apply properties ++ // Format: property:object_name:property_name=value ++ if (parent) ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* param = params.getParam(i); ++ if (!(param && param->name().startsWith("property:"))) ++ continue; ++ int pos = param->name().find(':',9); ++ if (pos < 9) ++ continue; ++ QObject* obj = parent->findChild(setUtf8(param->name().substr(9,pos - 9))); ++ if (obj) ++ setProperty(obj,param->name().substr(pos + 1),*param); ++ } ++ // Connect signals (direct children only: actions from sub-menus are already connected) ++ QList list = menu->findChildren(); ++ for (int i = 0; i < list.size(); i++) { ++ if (list[i]->isSeparator() || list[i]->parent() != menu) ++ continue; ++ if (list[i]->isCheckable()) ++ QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),receiver,toggleSlot); ++ else ++ QtClient::connectObjects(list[i],SIGNAL(triggered()),receiver,triggerSlot); ++ } ++ if (!TelEngine::null(aboutToShowSlot)) ++ QtClient::connectObjects(menu,SIGNAL(aboutToShow()),receiver,aboutToShowSlot); ++ ++ return menu; ++} ++ ++// Wrapper for QObject::connect() used to put a debug mesage on failure ++bool QtClient::connectObjects(QObject* sender, const char* signal, ++ QObject* receiver, const char* slot) ++{ ++ if (!(sender && signal && *signal && receiver && slot && *slot)) ++ return false; ++ bool ok = QObject::connect(sender,signal,receiver,slot); ++ if (ok) ++ DDebug(QtDriver::self(),DebugAll, ++ "Connected sender=%s signal=%s to receiver=%s slot=%s", ++ YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot); ++ else ++ Debug(QtDriver::self(),DebugWarn, ++ "Failed to connect sender=%s signal=%s to receiver=%s slot=%s", ++ YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot); ++ return ok; ++} ++ ++// Insert a widget into another one replacing any existing children ++bool QtClient::setWidget(QWidget* parent, QWidget* child) ++{ ++ if (!(parent && child)) ++ return false; ++ QVBoxLayout* layout = new QVBoxLayout; ++ layout->setSpacing(0); ++ String margins; ++ QtClient::getProperty(parent,"_yate_layout_margins",margins); ++ if (!margins) ++ layout->setContentsMargins(0,0,0,0); ++ else { ++ QList m = buildIntList(margins,4); ++ layout->setContentsMargins(m[0],m[1],m[2],m[3]); ++ } ++ layout->addWidget(child); ++ QLayout* l = parent->layout(); ++ if (l) ++ delete l; ++ parent->setLayout(layout); ++ return true; ++} ++ ++// Set an object's image property from image file ++bool QtClient::setImage(QObject* obj, const String& img, bool fit) ++{ ++ if (!obj) ++ return false; ++ QPixmap pixmap(setUtf8(img)); ++ return setImage(obj,pixmap,fit); ++} ++ ++// Set an object's image property from raw data. ++bool QtClient::setImage(QObject* obj, const DataBlock& data, const String& format, bool fit) ++{ ++ if (!obj) ++ return false; ++ QPixmap pixmap; ++ String f = format; ++ f.startSkip("image/",false); ++ if (!pixmap.loadFromData((const uchar*)data.data(),data.length(),f)) ++ return false; ++ return setImage(obj,pixmap,fit); ++} ++ ++// Set an object's image property from QPixmap ++bool QtClient::setImage(QObject* obj, const QPixmap& img, bool fit) ++{ ++ if (!obj) ++ return false; ++ if (obj->isWidgetType()) { ++ QLabel* l = qobject_cast(obj); ++ if (l) { ++ if (fit && !l->hasScaledContents() && ++ (img.width() > l->width() || img.height() > l->height())) { ++ QPixmap tmp; ++ if (l->width() <= l->height()) ++ tmp = img.scaledToWidth(l->width()); ++ else ++ tmp = img.scaledToHeight(l->height()); ++ l->setPixmap(tmp); ++ } ++ else ++ l->setPixmap(img); ++ } ++ else { ++ QAbstractButton* b = qobject_cast(obj); ++ if (b) ++ b->setIcon(img); ++ else { ++ QMenu* m = qobject_cast(obj); ++ if (m) ++ m->setIcon(img); ++ else ++ return false; ++ } ++ } ++ return true; ++ } ++ QAction* a = qobject_cast(obj); ++ if (a) { ++ a->setIcon(img); ++ return true; ++ } ++ return false; ++} ++ ++// Update a toggable object's image from properties ++void QtClient::updateToggleImage(QObject* obj) ++{ ++ QtWidget w(obj); ++ QAbstractButton* b = 0; ++ if (w.inherits(QtWidget::AbstractButton)) ++ b = w.abstractButton(); ++ if (!(b && b->isCheckable())) ++ return; ++ String icon; ++ bool set = false; ++ if (b->isChecked()) ++ set = QtClient::getProperty(w,"_yate_pressed_icon",icon); ++ else ++ set = QtClient::getProperty(w,"_yate_normal_icon",icon); ++ if (set) ++ QtClient::setImage(obj,Client::s_skinPath + icon); ++} ++ ++// Update an object's image from properties on mouse events ++void QtClient::updateImageFromMouse(QObject* obj, bool inOut, bool on) ++{ ++ QtWidget w(obj); ++ QAbstractButton* b = 0; ++ if (w.inherits(QtWidget::AbstractButton)) ++ b = w.abstractButton(); ++ if (!b) ++ return; ++ if (!b->isEnabled()) ++ return; ++ String icon; ++ bool set = false; ++ if (inOut) { ++ if (on) ++ set = QtClient::getProperty(obj,"_yate_hover_icon",icon); ++ else { ++ if (b->isCheckable() && b->isChecked()) ++ set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); ++ set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon); ++ } ++ } ++ else { ++ if (on) { ++ if (!b->isCheckable()) ++ set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); ++ } ++ else { ++ set = QtClient::getProperty(obj,"_yate_hover_icon",icon); ++ if (!set && b->isCheckable() && b->isChecked()) ++ set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); ++ set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon); ++ } ++ } ++ if (set) ++ QtClient::setImage(obj,Client::s_skinPath + icon); ++} ++ ++// Process a key press event. Retrieve an action associated with the key ++bool QtClient::filterKeyEvent(QObject* obj, QKeyEvent* event, String& action, ++ bool& filter, QObject* parent) ++{ ++ static const Qt::KeyboardModifiers::Int mask = Qt::SHIFT | Qt::CTRL | ++ Qt::ALT; ++ if (!(obj && event)) ++ return false; ++ // Try to match key and modifiers ++ QKeySequence ks(event->key()); ++ String prop; ++ getUtf8(prop,ks.toString()); ++ prop = "dynamicAction" + prop; ++ // Get modifiers from property and check them against event ++ QVariant v = obj->property(prop + "Modifiers"); ++ Qt::KeyboardModifiers::Int tmp = 0; ++ if (v.type() == QVariant::String) { ++ QKeySequence ks(v.toString()); ++ for (int i = 0; i < ks.count(); i++) ++ tmp |= ks[i]; ++ } ++ if (tmp != (mask & event->modifiers())) ++ return false; ++ // We matched the key and modifiers ++ // Set filter flag ++ filter = getBoolProperty(obj,prop + "Filter"); ++ // Retrieve the action ++ getProperty(obj,prop,action); ++ if (!action) ++ return true; ++ if (!parent) ++ return true; ++ parent = parent->findChild(setUtf8(action)); ++ if (!parent) ++ return true; ++ // Avoid notifying a disabled action ++ bool ok = true; ++ if (parent->isWidgetType()) ++ ok = (qobject_cast(parent))->isEnabled(); ++ else { ++ QAction* a = qobject_cast(parent); ++ ok = !a || a->isEnabled(); ++ } ++ if (!ok) ++ action.clear(); ++ return true; ++} ++ ++// Safely delete a QObject (reset its parent, calls it's deleteLater() method) ++void QtClient::deleteLater(QObject* obj) ++{ ++ if (!obj) ++ return; ++ obj->disconnect(); ++ if (obj->isWidgetType()) ++ (static_cast(obj))->setParent(0); ++ else ++ obj->setParent(0); ++ obj->deleteLater(); ++} ++ ++// Retrieve unavailable space position (if any) in the screen containing a given widget. ++QDesktopWidget* QtClient::getScreenUnavailPos(QWidget* w, int& pos) ++{ ++ if (!w) ++ return 0; ++ QDesktopWidget* d = QApplication::desktop(); ++ if (!d) ++ return 0; ++ pos = PosNone; ++ QRect rScreen = d->screenGeometry(w); ++ QRect rClient = d->availableGeometry(w); ++ int dx = rClient.x() - rScreen.x(); ++ if (dx > 0) ++ pos |= PosLeft; ++ int dy = rClient.y() - rScreen.y(); ++ if (dy > 0) ++ pos |= PosTop; ++ int dw = rScreen.width() - rClient.width(); ++ if (dw > 0 && (!dx || (dx > 0 && dw > dx))) ++ pos |= PosRight; ++ int dh = rScreen.height() - rClient.height(); ++ if (dh > 0 && (!dy || (dy > 0 && dh > dy))) ++ pos |= PosBottom; ++ return d; ++} ++ ++// Move a window to a specified position ++void QtClient::moveWindow(QtWindow* w, int pos) ++{ ++ if (!w) ++ return; ++ QDesktopWidget* d = QApplication::desktop(); ++ if (!d) ++ return; ++ QRect r = d->availableGeometry(w); ++ int x = r.x(); ++ int y = r.y(); ++ QSize sz = w->frameSize(); ++ if (pos == CornerBottomRight) { ++ if (r.width() > sz.width()) ++ x += r.width() - sz.width(); ++ if (r.height() > sz.height()) ++ y += r.height() - sz.height(); ++ } ++ else if (pos == CornerTopRight) { ++ if (r.width() > sz.width()) ++ x += r.width() - sz.width(); ++ } ++ else if (pos == CornerBottomLeft) { ++ if (r.height() > sz.height()) ++ y += r.height() - sz.height(); ++ } ++ else if (pos != CornerTopLeft) ++ return; ++ w->move(x,y); ++} ++ ++// Build a QStringList from a list of strings ++QStringList QtClient::str2list(const String& str, char sep, bool emptyOk) ++{ ++ QStringList l; ++ if (!str) ++ return l; ++ ObjList* list = str.split(sep,emptyOk); ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) ++ l.append(setUtf8(static_cast(o->get())->c_str())); ++ TelEngine::destruct(list); ++ return l; ++} ++ ++// Split a string. Returns a list of int values ++QList QtClient::str2IntList(const String& str, int defVal, bool emptyOk) ++{ ++ QList list; ++ ObjList* l = str.split(',',emptyOk); ++ for (ObjList* o = l->skipNull(); o; o = o->skipNext()) ++ list.append(o->get()->toString().toInteger(defVal)); ++ TelEngine::destruct(l); ++ return list; ++} ++ ++// Build a comma separated list of integers ++void QtClient::intList2str(String& str, QList list) ++{ ++ for (int i = 0; i < list.size(); i++) ++ str.append(String(list[i]),","); ++} ++ ++// Get sorting from string ++int QtClient::str2sort(const String& str, int defVal) ++{ ++ return lookup(str,s_sorting,defVal); ++} ++ ++// Apply a comma separated list of window flags to a widget ++void QtClient::applyWindowFlags(QWidget* w, const String& value) ++{ ++ if (!w) ++ return; ++ // Set window flags from enclosed widget: ++ // custom window title/border/sysmenu config ++ ObjList* f = value.split(',',false); ++ int flags = Qt::CustomizeWindowHint | w->windowFlags(); ++ // Clear settable flags ++ TokenDict* dict = s_windowFlags; ++ for (int i = 0; dict[i].token; i++) ++ flags &= ~dict[i].value; ++ // Set flags ++ for (ObjList* o = f->skipNull(); o; o = o->skipNext()) ++ flags |= lookup(o->get()->toString(),s_windowFlags,0); ++ TelEngine::destruct(f); ++ w->setWindowFlags((Qt::WindowFlags)flags); ++} ++ ++// Build a QT Alignment mask from a comma separated list of flags ++int QtClient::str2align(const String& flags, int initVal) ++{ ++ ObjList* list = flags.split(',',false); ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ int val = ::lookup((static_cast(o->get()))->c_str(),s_qAlign); ++ if (0 != (val & Qt::AlignHorizontal_Mask)) ++ initVal &= ~Qt::AlignHorizontal_Mask; ++ if (0 != (val & Qt::AlignVertical_Mask)) ++ initVal &= ~Qt::AlignVertical_Mask; ++ initVal |= val; ++ } ++ TelEngine::destruct(list); ++ return initVal; ++} ++ ++// Retrieve QT selection mode from a string value ++QAbstractItemView::SelectionMode QtClient::str2selmode(const String& value, ++ QAbstractItemView::SelectionMode defVal) ++{ ++ if (!value) ++ return defVal; ++ if (value == YSTRING("none")) ++ return QAbstractItemView::NoSelection; ++ if (value == YSTRING("single")) ++ return QAbstractItemView::SingleSelection; ++ if (value == YSTRING("multi")) ++ return QAbstractItemView::MultiSelection; ++ if (value == YSTRING("extended")) ++ return QAbstractItemView::ExtendedSelection; ++ if (value == YSTRING("contiguous")) ++ return QAbstractItemView::ContiguousSelection; ++ return defVal; ++} ++ ++// Retrieve QT edit triggers from a string value ++QAbstractItemView::EditTriggers QtClient::str2editTriggers(const String& value, ++ QAbstractItemView::EditTrigger defVal) ++{ ++ return (QAbstractItemView::EditTriggers)Client::decodeFlags(s_qEditTriggers,value,defVal); ++} ++ ++// Send an event to an object's child ++bool QtClient::sendEvent(QEvent& e, QObject* parent, const QString& name) ++{ ++ if (!(parent && e.isAccepted())) ++ return false; ++ QObject* child = parent->findChild(name); ++ if (!child) ++ return false; ++ e.setAccepted(false); ++ bool ok = QCoreApplication::sendEvent(child,&e); ++ if (!ok) ++ e.setAccepted(true); ++ return ok; ++} ++ ++// Retrieve a pixmap from global application cache. ++// Load and add it to the cache if not found ++bool QtClient::getPixmapFromCache(QPixmap& pixmap, const QString& file) ++{ ++ if (file.isEmpty()) ++ return false; ++ if (QPixmapCache::find(file,&pixmap)) { ++ return true; ++ } ++ if (!pixmap.load(file)) ++ return false; ++#ifdef XDEBUG ++ String f; ++ getUtf8(f,file); ++ Debug(ClientDriver::self(),DebugAll,"Loaded '%s' in pixmap cache",f.c_str()); ++#endif ++ QPixmapCache::insert(file,pixmap); ++ return true; ++} ++ ++// Update application style sheet from config ++// Build style sheet from files: ++// stylesheet.css ++// stylesheet_stylename.css ++// stylesheet_osname.css ++// stylesheet_osname_stylename.css ++void QtClient::updateAppStyleSheet() ++{ ++ if (!qApp) { ++ Debug(ClientDriver::self(),DebugWarn,"Update app stylesheet called without app"); ++ return; ++ } ++ String shf = Engine::config().getValue("client","stylesheet_file","stylesheet.css"); ++ if (!shf) ++ return; ++ QString sh; ++ if (!appendStyleSheet(sh,shf)) ++ return; ++ String styleName; ++ QStyle* style = qApp->style(); ++ const QMetaObject* meta = style ? style->metaObject() : 0; ++ if (meta) { ++ styleName = s_qtStyles.getValue(meta->className()); ++ if (!styleName) ++ styleName = meta->className(); ++ } ++ if (styleName) ++ appendStyleSheet(sh,shf,styleName); ++ String osname; ++ osname << "os" << PLATFORM_LOWERCASE_NAME; ++ appendStyleSheet(sh,shf,osname); ++ if (styleName) ++ appendStyleSheet(sh,shf,osname,styleName); ++ qApp->setStyleSheet(sh); ++} ++ ++// Set widget attributes from list ++void QtClient::setWidgetAttributes(QWidget* w, const String& attrs) ++{ ++ if (!(w && attrs)) ++ return; ++ ObjList* list = attrs.split(',',false); ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ const String& attr = *static_cast(o->get()); ++ bool on = (attr[0] != '!'); ++ const char* name = attr.c_str(); ++ int val = lookup(on ? name : name + 1,s_widgetAttributes); ++ if (val) ++ w->setAttribute((Qt::WidgetAttribute)val,on); ++ } ++ TelEngine::destruct(list); ++} ++ ++// Adjust widget height ++void QtClient::setWidgetHeight(QWidget* w, const String& height) ++{ ++ if (!w) ++ return; ++ int h = 0; ++ if (height.isBoolean()) { ++ h = QtClient::getIntProperty(w,"_yate_height_delta",-1); ++ if (h > 0) { ++ if (height.toBoolean()) ++ h += w->height(); ++ else if (h < w->height()) ++ h = w->height() - h; ++ else ++ h = 0; ++ } ++ } ++ else ++ h = height.toInteger(); ++ if (h < 0) ++ return; ++ QSizePolicy sp = w->sizePolicy(); ++ sp.setVerticalPolicy(QSizePolicy::Fixed); ++ w->setSizePolicy(sp); ++ w->setMinimumHeight(h); ++ w->setMaximumHeight(h); ++} ++ ++// Build a busy widget child for a given widget ++QWidget* QtClient::buildBusy(QWidget* parent, QWidget* target, const String& ui, ++ const NamedList& params) ++{ ++ QtBusyWidget* w = new QtBusyWidget(parent); ++ w->init(ui,params,target); ++ return w; ++} ++ ++// Load a movie ++QMovie* QtClient::loadMovie(const char* file, QObject* parent, const char* path) ++{ ++ static NamedList s_failed(""); ++ ++ if (TelEngine::null(file)) ++ return 0; ++ String tmp = path; ++ if (!path) ++ tmp = Client::s_skinPath; ++ else if (tmp && !tmp.endsWith(Engine::pathSeparator())) ++ tmp << Engine::pathSeparator(); ++ tmp << file; ++ QMovie* m = new QMovie(setUtf8(tmp),QByteArray(),parent); ++ NamedString* ns = s_failed.getParam(tmp); ++ if (m->isValid()) { ++ if (ns) ++ s_failed.clearParam(ns); ++ return m; ++ } ++ if (!ns) { ++ s_failed.addParam(tmp,""); ++ String error; ++ error << "Failed to load movie '" << tmp << "'"; ++ Debug(QtDriver::self(),DebugNote,"%s",error.c_str()); ++ if (self()) ++ self()->addToLog(error); ++ } ++ delete m; ++ return 0; ++} ++ ++// Fill a list from URL parameters ++void QtClient::fillUrlParams(const QUrl& url, NamedList& list, QString* path, ++ bool pathToList) ++{ ++ safeGetUtf8(list,"protocol",url.scheme()); ++ safeGetUtf8(list,"host",url.host()); ++ if (url.port() >= 0) ++ list.addParam("port",String(url.port())); ++ safeGetUtf8(list,"username",url.userName()); ++ safeGetUtf8(list,"password",url.password()); ++ QString tmp; ++ if (!path) { ++ tmp = url.path(); ++ path = &tmp; ++ } ++ if (pathToList) ++ list.assign(path->toUtf8().constData()); ++ else ++ safeGetUtf8(list,"path",*path); ++ QUrlQuery query( url ); ++ QList > items = query.queryItems(); ++ for (int i = 0; i < items.size(); i++) ++ list.addParam(items[i].first.toUtf8().constData(),items[i].second.toUtf8().constData()); ++} ++ ++// Dump MIME data for debug purposes ++void QtClient::dumpMime(String& buf, const QMimeData* m) ++{ ++ static const char* indent = "\r\n "; ++ if (!m) ++ return; ++ QStringList fmts = m->formats(); ++ if (fmts.size() > 0) { ++ buf.append("FORMATS:","\r\n") << indent; ++ QString s = fmts.join(indent); ++ buf << s.toUtf8().constData(); ++ } ++ if (m->html().length() > 0) ++ buf.append("HTML: ","\r\n") << m->html().toUtf8().constData(); ++ if (m->text().length() > 0) ++ buf.append("TEXT: ","\r\n") << m->text().toUtf8().constData(); ++ QList urls = m->urls(); ++ if (urls.size() > 0) { ++ buf.append("URLS:","\r\n"); ++ for (int i = 0; i < urls.size(); i++) ++ buf << indent << urls[i].toString().toUtf8().constData(); ++ } ++} ++ ++ ++/** ++ * QtDriver ++ */ ++QtDriver::QtDriver(bool buildClientThread) ++ : m_init(false), m_clientThread(buildClientThread) ++{ ++ qInstallMessageHandler(qtMsgHandler); ++} ++ ++QtDriver::~QtDriver() ++{ ++ qInstallMessageHandler(0); ++} ++ ++void QtDriver::initialize() ++{ ++ Output("Initializing module Qt5 client"); ++ s_device = Engine::config().getValue("client","device",DEFAULT_DEVICE); ++ if (!QtClient::self()) { ++ debugCopy(); ++ QtClient::setSelf(new QtClient); ++ if (m_clientThread) ++ QtClient::self()->startup(); ++ } ++ if (!m_init) { ++ m_init = true; ++ setup(); ++ } ++} ++ ++/** ++ * QtEventProxy ++ */ ++QtEventProxy::QtEventProxy(Type type, QApplication* app) ++{ ++#define SET_NAME(n) { m_name = n; setObjectName(QtClient::setUtf8(m_name)); } ++ switch (type) { ++ case Timer: ++ SET_NAME("qtClientTimerProxy"); ++ { ++ QTimer* timer = new QTimer(this); ++ timer->setObjectName("qtClientIdleTimer"); ++ QtClient::connectObjects(timer,SIGNAL(timeout()),this,SLOT(timerTick())); ++ timer->start(0); ++ } ++ break; ++ case AllHidden: ++ SET_NAME("qtClientAllHidden"); ++ if (app) ++ QtClient::connectObjects(app,SIGNAL(lastWindowClosed()),this,SLOT(allHidden())); ++ break; ++ default: ++ return; ++ } ++#undef SET_NAME ++} ++ ++void QtEventProxy::timerTick() ++{ ++ if (Client::self()) ++ Client::self()->idleActions(); ++ Thread::idle(); ++} ++ ++void QtEventProxy::allHidden() ++{ ++ if (Client::self()) ++ Client::self()->allHidden(); ++} ++ ++ ++// ++// QtUrlBuilder ++// ++QtUrlBuilder::QtUrlBuilder(QObject* parent, const String& format, ++ const String& queryParams) ++ : QObject(parent), ++ m_format(format), ++ m_queryParams(0) ++{ ++ if (queryParams) { ++ m_queryParams = queryParams.split(',',false); ++ if (!m_queryParams->skipNull()) ++ TelEngine::destruct(m_queryParams); ++ } ++} ++ ++QtUrlBuilder::~QtUrlBuilder() ++{ ++ TelEngine::destruct(m_queryParams); ++} ++ ++// Build URL ++QUrl QtUrlBuilder::build(const NamedList& params) const ++{ ++ String tmp; ++ if (m_format) { ++ tmp = m_format; ++ params.replaceParams(tmp); ++ } ++ QUrl url(QtClient::setUtf8(tmp)); ++ if (m_queryParams) { ++ QUrlQuery urlQuery(url); ++ NamedIterator iter(params); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) ++ if (m_queryParams->find(ns->name())) ++ urlQuery.addQueryItem(QtClient::setUtf8(ns->name()),QtClient::setUtf8(*ns)); ++ url.setQuery(urlQuery); ++ } ++ return url; ++} ++ ++ ++/* ++ * QtUIWidget ++ */ ++// Retrieve item type definition from [type:]value. Create it if not found ++QtUIWidgetItemProps* QtUIWidget::getItemProps(QString& in, String& value) ++{ ++ String type; ++ int pos = in.indexOf(':'); ++ if (pos >= 0) { ++ QtClient::getUtf8(type,in.left(pos)); ++ QtClient::getUtf8(value,in.right(in.length() - pos - 1)); ++ } ++ else ++ QtClient::getUtf8(value,in); ++ QtUIWidgetItemProps* p = QtUIWidget::getItemProps(type); ++ if (!p) { ++ p = new QtUIWidgetItemProps(type); ++ m_itemProps.append(p); ++ } ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) getItemProps(%s,%s) got (%p) ui=%s [%p]", ++ name().c_str(),in.toUtf8().constData(),value.c_str(),p,p->m_ui.c_str(),this); ++ return p; ++} ++ ++// Set widget's parameters. ++// Handle an 'applyall' parameter carrying a NamedList to apply to all items ++bool QtUIWidget::setParams(const NamedList& params) ++{ ++ bool ok = false; ++ NamedIterator iter(params); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == YSTRING("applyall")) { ++ const NamedList* list = YOBJECT(NamedList,ns); ++ if (list) { ++ ok = true; ++ applyAllParams(*list); ++ } ++ } ++ else if (ns->name().startsWith("beginedit:")) ++ beginEdit(ns->name().substr(10),ns); ++ } ++ return ok; ++} ++ ++// Apply a list of parameters to all container items ++void QtUIWidget::applyAllParams(const NamedList& params) ++{ ++ QList list = getContainerItems(); ++ for (int i = 0; i < list.size(); i++) ++ setParams(list[i],params); ++} ++ ++// Find an item widget by id ++QWidget* QtUIWidget::findItem(const String& id) ++{ ++ QString item = QtClient::setUtf8(id); ++ QList list = getContainerItems(); ++ for (int i = 0; i < list.size(); i++) { ++ if (!list[i]->isWidgetType()) ++ continue; ++ String item; ++ getListItemIdProp(list[i],item); ++ if (id == item) ++ return static_cast(list[i]); ++ } ++ return 0; ++} ++ ++// Retrieve the object identity from '_yate_identity' property or name ++// Retrieve the object item from '_yate_widgetlistitem' property. ++// Set 'identity' to object_identity[:item_name] ++void QtUIWidget::getIdentity(QObject* obj, String& identity) ++{ ++ if (!obj) ++ return; ++ String ident; ++ QtClient::getIdentity(obj,ident); ++ if (!ident) ++ return; ++ String item; ++ getListItemProp(obj,item); ++ identity.append(ident,":"); ++ identity.append(item,":"); ++} ++ ++// Update a widget and children from a list a parameters ++bool QtUIWidget::setParams(QObject* parent, const NamedList& params) ++{ ++ static const String s_property = "property"; ++ static const String s_active = "active"; ++ static const String s_image = "image"; ++ static const String s_show = "show"; ++ static const String s_display = "display"; ++ static const String s_check = "check"; ++ static const String s_select = "select"; ++ static const String s_addlines = "addlines"; ++ static const String s_setrichtext = "setrichtext"; ++ static const String s_updatetablerows = "updatetablerows"; ++ static const String s_cleartable = "cleartable"; ++ static const String s_rawimage = "rawimage"; ++ static const String s_setparams = "setparams"; ++ static const String s_setmenu = "setmenu"; ++ static const String s_height = "height"; ++ ++ if (!parent) ++ return false; ++ QtWindow* wnd = QtClient::parentWindow(parent); ++ if (!wnd) ++ return false; ++#ifdef DEBUG ++ String tmp; ++ params.dump(tmp," "); ++ Debug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::setParams(%p,%s) %s", ++ name().c_str(),parent,YQT_OBJECT_NAME(parent),tmp.c_str()); ++#endif ++ String pName(YQT_OBJECT_NAME(parent)); ++ bool ok = true; ++ unsigned int n = params.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ if (!ns) ++ continue; ++ XDebug(ClientDriver::self(),DebugInfo,"QtUIWidget(%s)::setParams() %s=%s", ++ name().c_str(),ns->name().c_str(),ns->c_str()); ++ String buf; ++ int pos = ns->name().find(':'); ++ if (pos < 0) { ++ if (ns->name() != s_setmenu) ++ ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; ++ else ++ buildWidgetItemMenu(qobject_cast(parent),YOBJECT(NamedList,ns)); ++ continue; ++ } ++ String n(ns->name().substr(0,pos)); ++ String cName = ns->name().substr(pos + 1); ++ if (n == s_property) { ++ // Handle property[:child]:property_name ++ int pos = cName.find(':'); ++ if (pos >= 0) { ++ QString tmp = buildQChildName(pName,cName.substr(0,pos)); ++ QObject* c = parent->findChild(tmp); ++ ok = c && QtClient::setProperty(c,cName.substr(pos + 1),*ns) && ok; ++ } ++ else ++ ok = QtClient::setProperty(parent,cName,*ns) && ok; ++ } ++ else if (n == s_active) ++ ok = wnd->setActive(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; ++ else if (n == s_image) ++ ok = wnd->setImage(buildChildName(buf,pName,cName),*ns) && ok; ++ else if (n == s_show || n == s_display) ++ ok = wnd->setShow(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; ++ else if (n == s_check) ++ ok = wnd->setCheck(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; ++ else if (n == s_select) ++ ok = wnd->setSelect(buildChildName(buf,pName,cName),*ns) && ok; ++ if (n == s_setparams) { ++ NamedList* p = YOBJECT(NamedList,ns); ++ if (!p) ++ continue; ++ QtWidget w(parent,buildChildName(buf,pName,cName)); ++ UIWidget* uiw = w.uiWidget(); ++ ok = uiw && uiw->setParams(*p) && ok; ++ } ++ else if (n == s_addlines) { ++ NamedList* p = YOBJECT(NamedList,ns); ++ if (p) ++ ok = wnd->addLines(buildChildName(buf,pName,cName),p,0,ns->toBoolean()) && ok; ++ } ++ else if (n == s_setrichtext) ++ ok = wnd->setText(buildChildName(buf,pName,cName),*ns,true) && ok; ++ else if (n == s_updatetablerows) { ++ NamedList* p = YOBJECT(NamedList,ns); ++ if (p) ++ ok = wnd->updateTableRows(buildChildName(buf,pName,cName),p,ns->toBoolean()) && ok; ++ } ++ else if (n == s_cleartable) ++ ok = wnd->clearTable(buildChildName(buf,pName,cName)) && ok; ++ else if (n == s_rawimage) { ++ DataBlock* data = YOBJECT(DataBlock,ns); ++ if (data) { ++ QString tmp = buildQChildName(pName,cName.substr(0,pos)); ++ QObject* c = parent->findChild(tmp); ++ ok = c && QtClient::setImage(c,*data,*ns) && ok; ++ } ++ } ++ else if (n == s_setmenu) ++ buildWidgetItemMenu(qobject_cast(parent),YOBJECT(NamedList,ns),cName); ++ else if (n == s_height) { ++ QString tmp = buildQChildName(pName,cName); ++ QWidget* w = qobject_cast(parent)->findChild(tmp); ++ QtClient::setWidgetHeight(w,*ns); ++ } ++ else ++ ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; ++ } ++ // Set item parameters ++ NamedString* yparams = params.getParam(YSTRING("_yate_itemparams")); ++ if (!TelEngine::null(yparams)) { ++ QVariant var = parent->property(yparams->name().c_str()); ++ if (var.type() == QVariant::Invalid || var.type() == QVariant::StringList) { ++ QStringList list; ++ if (var.type() == QVariant::StringList) ++ list = var.toStringList(); ++ NamedList tmp(""); ++ tmp.copyParams(params,*yparams); ++ QtClient::copyParams(list,tmp); ++ parent->setProperty(yparams->name().c_str(),QVariant(list)); ++ } ++ else ++ ok = false; ++ } ++ return ok; ++} ++ ++// Get an item object's parameters ++bool QtUIWidget::getParams(QObject* parent, NamedList& params) ++{ ++ static const String s_property = "property"; ++ static const String s_getcheck = "getcheck"; ++ static const String s_getselect = "getselect"; ++ static const String s_getrichtext = "getrichtext"; ++ ++ if (!parent) ++ return false; ++ QtWindow* wnd = QtClient::parentWindow(parent); ++ if (!wnd) ++ return false; ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::getParams(%p,%s)", ++ name().c_str(),parent,YQT_OBJECT_NAME(parent)); ++ String pName; ++ QtClient::getUtf8(pName,parent->objectName()); ++ bool ok = true; ++ unsigned int n = params.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ if (!ns) ++ continue; ++ String buf; ++ int pos = ns->name().find(':'); ++ if (pos < 0) { ++ ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; ++ continue; ++ } ++ String n(ns->name().substr(0,pos)); ++ String cName = ns->name().substr(pos + 1); ++ if (n == s_property) { ++ // Handle property[:child]:property_name ++ int pos = cName.find(':'); ++ if (pos >= 0) { ++ QString tmp = buildQChildName(pName,cName.substr(0,pos)); ++ QObject* c = parent->findChild(tmp); ++ ok = c && QtClient::getProperty(c,cName.substr(pos + 1),*ns) && ok; ++ } ++ else ++ ok = QtClient::getProperty(parent,cName,*ns) && ok; ++ } ++ else if (n == s_getselect) ++ ok = wnd->getSelect(buildChildName(buf,pName,cName),*ns) && ok; ++ else if (n == s_getcheck) { ++ bool on = false; ++ ok = wnd->getCheck(buildChildName(buf,pName,cName),on) && ok; ++ *ns = String::boolText(on); ++ } ++ else if (n == s_getrichtext) ++ ok = wnd->getText(buildChildName(buf,pName,cName),*ns,true) && ok; ++ else ++ ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; ++ } ++ // Get item parameters ++ QtClient::getProperty(parent,"_yate_itemparams",params); ++ return ok; ++} ++ ++// Show or hide control busy state ++bool QtUIWidget::setBusy(bool on) ++{ ++ QObject* o = getQObject(); ++ QWidget* w = (o && o->isWidgetType()) ? static_cast(o) : 0; ++ return w && QtBusyWidget::showBusyChild(w,on); ++} ++ ++// Apply properties for QAbstractItemView descendents ++void QtUIWidget::applyItemViewProps(const NamedList& params) ++{ ++ static const String s_selMode = "_yate_selection_mode"; ++ static const String s_editTriggers = "_yate_edit_triggers"; ++ ++ QObject* obj = getQObject(); ++ QAbstractItemView* av = qobject_cast(obj); ++ if (!av) ++ return; ++ NamedIterator iter(params); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == s_selMode) ++ av->setSelectionMode(QtClient::str2selmode(*ns)); ++ else if (ns->name() == s_editTriggers) ++ av->setEditTriggers(QtClient::str2editTriggers(*ns)); ++ } ++} ++ ++// Begin item edit. The default behaviour start edit for QAbstractItemView descendants ++bool QtUIWidget::beginEdit(const String& item, const String* what) ++{ ++ QObject* obj = getQObject(); ++ QAbstractItemView* av = qobject_cast(obj); ++ if (!av) ++ return false; ++ QModelIndex idx = modelIndex(item,what); ++ if (!idx.isValid()) ++ return false; ++ av->setCurrentIndex(idx); ++ av->edit(idx); ++ return true; ++} ++ ++// Build item widget menu ++QMenu* QtUIWidget::buildWidgetItemMenu(QWidget* w, const NamedList* params, ++ const String& child, bool set) ++{ ++ if (!(w && params)) ++ return 0; ++ QWidget* parent = w; ++ // Retrieve the item owner ++ QWidget* pItem = 0; ++ String item; ++ getListItemIdProp(w,item); ++ if (item) ++ pItem = findItem(item); ++ else { ++ getListItemProp(w,item); ++ pItem = item ? findItem(item) : 0; ++ } ++ XDebug(ClientDriver::self(),DebugAll, ++ "QtUIWidget(%s)::buildMenu() widget=%s item=%s [%p]", ++ this->name().c_str(),YQT_OBJECT_NAME(w),item.c_str(),this); ++ String pName(YQT_OBJECT_NAME(w)); ++ const String& owner = (*params)[YSTRING("owner")]; ++ if (owner && owner != item) { ++ QString tmp = buildQChildName(pName,owner); ++ parent = w->findChild(tmp); ++ if (!parent) { ++ Debug(QtDriver::self(),DebugNote, ++ "QtUIWidget(%s) buildMenu() owner '%s' not found [%p]", ++ name().c_str(),owner.c_str(),this); ++ return 0; ++ } ++ } ++ QWidget* target = parent; ++ String t = child ? child : (*params)[YSTRING("target")]; ++ if (t) { ++ QString tmp = buildQChildName(pName,t); ++ target = w->findChild(tmp); ++ if (!target) { ++ Debug(QtDriver::self(),DebugNote, ++ "QtUIWidget(%s) buildMenu() target '%s' not found [%p]", ++ name().c_str(),t.c_str(),this); ++ return 0; ++ } ++ } ++ QString menuName = buildQChildName(pName,t + "_menu"); ++ // Remove existing menu ++ QMenu* menu = parent->findChild(menuName); ++ if (menu) { ++ delete menu; ++ menu = 0; ++ } ++ // Build the menu ++ QObject* thisObj = getQObject(); ++ if (!thisObj) ++ return 0; ++ String actionSlot; ++ String toggleSlot; ++ String selectSlot; ++ getSlots(actionSlot,toggleSlot,selectSlot); ++ if (!(actionSlot || toggleSlot)) ++ return 0; ++ bool addActions = set && target->contextMenuPolicy() == Qt::ActionsContextMenu; ++ unsigned int n = params->length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* param = params->getParam(i); ++ if (!(param && param->name().startsWith("item:"))) ++ continue; ++ if (!menu) ++ menu = new QMenu(QtClient::setUtf8(params->getValue(YSTRING("title"))),parent); ++ NamedList* p = YOBJECT(NamedList,param); ++ if (p) { ++ QMenu* subMenu = QtClient::buildMenu(*p, ++ *param ? param->c_str() : p->getValue(YSTRING("title"),*p), ++ thisObj,actionSlot,toggleSlot,menu); ++ if (subMenu) { ++ menu->addMenu(subMenu); ++ if (addActions) ++ target->addAction(subMenu->menuAction()); ++ } ++ continue; ++ } ++ QAction* a = 0; ++ String name = param->name().substr(5); ++ if (*param) { ++ a = menu->addAction(QtClient::setUtf8(*param)); ++ a->setObjectName(buildQChildName(pName,name)); ++ a->setParent(menu); ++ QtClient::setImage(a,(*params)["image:" + name]); ++ } ++ else if (!name) { ++ a = menu->addSeparator(); ++ a->setParent(menu); ++ } ++ else if (pItem) { ++ // Check if the action is already there ++ QString aName = buildQChildName(pItem->objectName(),QtClient::setUtf8(name)); ++ a = pItem->findChild(aName); ++ if (a) ++ menu->addAction(a); ++ } ++ if (a) { ++ if (addActions) ++ target->addAction(a); ++ } ++ else ++ Debug(ClientDriver::self(),DebugNote, ++ "QtUIWidget(%s)::buildMenu() action '%s' not found for item=%s [%p]", ++ this->name().c_str(),name.c_str(),item.c_str(),this); ++ } ++ if (!menu) ++ return 0; ++ // Set name ++ menu->setObjectName(menuName); ++ // Apply properties ++ // Format: property:object_name:property_name=value ++ if (parent) ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* param = params->getParam(i); ++ if (!(param && param->name().startsWith("property:"))) ++ continue; ++ int pos = param->name().find(':',9); ++ if (pos < 9) ++ continue; ++ QString n = buildQChildName(pName,param->name().substr(9,pos - 9)); ++ QObject* obj = parent->findChild(n); ++ if (obj) ++ QtClient::setProperty(obj,param->name().substr(pos + 1),*param); ++ } ++ // Connect signals (direct children only: actions from sub-menus are already connected) ++ QList list = menu->findChildren(); ++ for (int i = 0; i < list.size(); i++) { ++ if (list[i]->isSeparator() || list[i]->parent() != menu) ++ continue; ++ if (list[i]->isCheckable()) ++ QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),thisObj,toggleSlot); ++ else ++ QtClient::connectObjects(list[i],SIGNAL(triggered()),thisObj,actionSlot); ++ } ++ if (addActions) ++ return menu; ++ QMenu* mOwner = qobject_cast(target); ++ if (mOwner) ++ mOwner->insertMenu(0,menu); ++ else { ++ QToolButton* tb = qobject_cast(target); ++ if (tb) ++ tb->setMenu(menu); ++ else { ++ QPushButton* pb = qobject_cast(target); ++ if (pb) ++ pb->setMenu(menu); ++ else if (!QtClient::setProperty(target,s_propContextMenu,params)) ++ target->addAction(menu->menuAction()); ++ } ++ } ++ return menu; ++} ++ ++// Build a container child name from parent property ++bool QtUIWidget::buildQChildNameProp(QString& dest, QObject* parent, const char* prop) ++{ ++ if (!(parent && prop)) ++ return false; ++ QVariant var = parent->property(prop); ++ if (!var.isValid() || var.toString().size() <= 0) ++ return false; ++ dest = buildQChildName(parent->objectName(),var.toString()); ++ return true; ++} ++ ++// Retrieve the top level QtUIWidget container parent of an object ++QtUIWidget* QtUIWidget::container(QObject* obj) ++{ ++ if (!obj) ++ return 0; ++ QtUIWidget* uiw = 0; ++ while (0 != (obj = obj->parent())) { ++ QtWidget w(obj); ++ UIWidget* u = w.uiWidget(); ++ if (u) ++ uiw = static_cast(u); ++ } ++ return uiw; ++} ++ ++// Utility used in QtUIWidget::initNavigation ++static bool initNavAction(QObject* obj, const String& name, const String& actionSlot) ++{ ++ if (!(obj && name)) ++ return false; ++ QtWindow* wnd = QtClient::parentWindow(obj); ++ QObject* child = wnd->findChild(QtClient::setUtf8(name)); ++ if (!child) ++ return false; ++ QAbstractButton* b = 0; ++ QAction* a = 0; ++ if (child->isWidgetType()) ++ b = qobject_cast(child); ++ else ++ a = qobject_cast(child); ++ if (b || a) { ++ if (b) ++ QtClient::connectObjects(b,SIGNAL(clicked()),obj,actionSlot); ++ else ++ QtClient::connectObjects(a,SIGNAL(triggered()),obj,actionSlot); ++ } ++ return b || a; ++} ++ ++// Initialize navigation controls ++void QtUIWidget::initNavigation(const NamedList& params) ++{ ++ String actionSlot; ++ String toggleSlot; ++ String selectSlot; ++ getSlots(actionSlot,toggleSlot,selectSlot); ++ QObject* qObj = getQObject(); ++ if (qObj && actionSlot) { ++ m_prev = params.getValue(YSTRING("navigate_prev")); ++ if (!initNavAction(qObj,m_prev,actionSlot)) ++ m_prev = ""; ++ m_next = params.getValue(YSTRING("navigate_next")); ++ if (!initNavAction(qObj,m_next,actionSlot)) ++ m_next = ""; ++ } ++ m_info = params.getValue(YSTRING("navigate_info")); ++ m_infoFormat = params.getValue(YSTRING("navigate_info_format")); ++ m_title = params.getValue(YSTRING("navigate_title")); ++ updateNavigation(); ++} ++ ++// Update navigation controls ++void QtUIWidget::updateNavigation() ++{ ++ if (!(m_prev || m_next || m_info || m_title)) ++ return; ++ QtWindow* wnd = QtClient::parentWindow(getQObject()); ++ if (!wnd) ++ return; ++ NamedList p(""); ++ int crt = currentItemIndex(); ++ if (crt < 0) ++ crt = 0; ++ else ++ crt++; ++ int n = itemCount(); ++ if (n < crt) ++ n = crt; ++ if (m_prev || m_next) { ++ if (m_prev) ++ p.addParam("active:" + m_prev,String::boolText(crt > 1)); ++ if (m_next) ++ p.addParam("active:" + m_next,String::boolText(crt < n)); ++ } ++ if (m_info) { ++ String tmp = m_infoFormat; ++ NamedList pp(""); ++ pp.addParam("index",String(crt)); ++ pp.addParam("count",String(n)); ++ pp.replaceParams(tmp); ++ p.addParam(m_info,tmp); ++ } ++ if (m_title) { ++ String crt; ++ getSelect(crt); ++ NamedList pp(""); ++ if (crt) ++ getTableRow(crt,&pp); ++ p.addParam(m_title,pp[YSTRING("title")]); ++ } ++ wnd->setParams(p); ++} ++ ++// Trigger a custom action from an item ++bool QtUIWidget::triggerAction(const String& item, const String& action, QObject* sender, ++ NamedList* params) ++{ ++ if (!(Client::self() && action)) ++ return false; ++ if (!sender) ++ sender = getQObject(); ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return false; ++ NamedList p(""); ++ if (!params) ++ params = &p; ++ params->addParam("item",item,false); ++ params->addParam("widget",s); ++ return QtClient::self()->action(QtClient::parentWindow(sender),action,params); ++} ++ ++// Trigger a custom action from already built list params ++bool QtUIWidget::triggerAction(const String& action, NamedList& params, QObject* sender) ++{ ++ if (!(Client::self() && action)) ++ return false; ++ if (!sender) ++ sender = getQObject(); ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return false; ++ params.setParam("widget",s); ++ return QtClient::self()->action(QtClient::parentWindow(sender),action,¶ms); ++} ++ ++// Handle a child's action ++void QtUIWidget::onAction(QObject* sender) ++{ ++ if (!Client::self()) ++ return; ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return; ++ int dir = 0; ++ if (s == m_next) ++ dir = 1; ++ else if (s == m_prev) ++ dir = -1; ++ if (dir) { ++ int crt = currentItemIndex(); ++ if (crt >= 0) ++ setSelectIndex(crt + dir); ++ return; ++ } ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising action %s", ++ name().c_str(),s.c_str()); ++ Client::self()->action(QtClient::parentWindow(sender),s); ++} ++ ++// Handle a child's toggle notification ++void QtUIWidget::onToggle(QObject* sender, bool on) ++{ ++ if (!Client::self()) ++ return; ++ QtClient::updateToggleImage(sender); ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return; ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising toggle %s", ++ name().c_str(),s.c_str()); ++ Client::self()->toggle(QtClient::parentWindow(sender),s,on); ++} ++ ++// Handle a child's selection change ++void QtUIWidget::onSelect(QObject* sender, const String* item) ++{ ++ if (!Client::self()) ++ return; ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return; ++ QtWindow* wnd = QtClient::parentWindow(sender); ++ String tmp; ++ if (!item) { ++ item = &tmp; ++ if (wnd) ++ wnd->getSelect(YQT_OBJECT_NAME(sender),tmp); ++ } ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select %s", ++ name().c_str(),s.c_str()); ++ Client::self()->select(wnd,s,*item); ++} ++ ++// Handle a child's multiple selection change ++void QtUIWidget::onSelectMultiple(QObject* sender, const NamedList* items) ++{ ++ if (!Client::self()) ++ return; ++ String s; ++ getIdentity(sender,s); ++ if (!s) ++ return; ++ QtWindow* wnd = QtClient::parentWindow(sender); ++ DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select multiple", ++ name().c_str()); ++ if (items) { ++ Client::self()->select(wnd,s,*items); ++ return; ++ } ++ NamedList tmp(""); ++ if (wnd) ++ wnd->getSelect(YQT_OBJECT_NAME(sender),tmp); ++ Client::self()->select(wnd,s,tmp); ++} ++ ++// Filter wathed events for children. ++// Handle child image changing on mouse events ++bool QtUIWidget::onChildEvent(QObject* watched, QEvent* event) ++{ ++ if (event->type() == QEvent::Enter) ++ QtClient::updateImageFromMouse(watched,true,true); ++ else if (event->type() == QEvent::Leave) ++ QtClient::updateImageFromMouse(watched,true,false); ++ else if (event->type() == QEvent::MouseButtonPress) ++ QtClient::updateImageFromMouse(watched,false,true); ++ else if (event->type() == QEvent::MouseButtonRelease) ++ QtClient::updateImageFromMouse(watched,false,false); ++ return false; ++} ++ ++// Load an item's widget. Rename children. Connect actions ++QWidget* QtUIWidget::loadWidget(QWidget* parent, const String& name, const String& ui) ++{ ++ // Build a new widget name to make sure there are no duplicates: ++ // Some containers (like QTreeWidget) calls deleteLater() for widget's ++ // set to items which might lead to wrong widget update ++ // Make sure the widget name contains only 'standard' characters ++ // to avoid errors when replaced in style sheets ++ MD5 md5(name); ++ String wName; ++ buildChildName(wName,md5.hexDigest()); ++ wName << "_" << (unsigned int)Time::now(); ++ QWidget* w = QtWindow::loadUI(Client::s_skinPath + ui,parent,ui); ++ DDebug(ClientDriver::self(),w ? DebugAll : DebugNote, ++ "QtUIWidget(%s)::loadWidget(%p,%s,%s) widget=%p", ++ this->name().c_str(),parent,wName.c_str(),ui.c_str(),w); ++ if (!w) ++ return 0; ++ QObject* qObj = getQObject(); ++ QtWindow* wnd = getWindow(); ++ // Install event filter in parent window ++ if (!m_wndEvHooked && wnd && qObj) { ++ QVariant var = w->property("_yate_keypress_redirect"); ++ if (var.isValid()) { ++ m_wndEvHooked = true; ++ wnd->installEventFilter(qObj); ++ } ++ } ++ String actionSlot; ++ String toggleSlot; ++ String selectSlot; ++ getSlots(actionSlot,toggleSlot,selectSlot); ++ QString wListItem = QtClient::setUtf8(name); ++ w->setObjectName(QtClient::setUtf8(wName)); ++ setListItemIdProp(w,wListItem); ++ // Build custom UI widgets ++ QtClient::buildFrameUiWidgets(w); ++ // Process "_yate_setaction" property before changing names ++ QtClient::setAction(w); ++ // Process children ++ QList c = w->findChildren(); ++ for (int i = 0; i < c.size(); i++) { ++ // Set object item owner name ++ setListItemProp(c[i],wListItem); ++ // Rename child ++ String n; ++ QtClient::getUtf8(n,c[i]->objectName()); ++ c[i]->setObjectName(buildQChildName(wName,n)); ++ // Install event filters ++ if (qObj && QtClient::getBoolProperty(c[i],"_yate_filterevents")) ++ c[i]->installEventFilter(qObj); ++ // Connect text changed to window's slot ++ bool connect = QtClient::autoConnect(c[i]); ++ if (wnd && connect) ++ wnd->connectTextChanged(c[i]); ++ // Connect signals ++ if (!(qObj && connect && (actionSlot || toggleSlot || selectSlot))) ++ continue; ++ // Use isWidgetType() (faster then qobject_cast) ++ if (c[i]->isWidgetType()) { ++ // Connect abstract buttons (check boxes and radio/push/tool buttons) signals ++ QAbstractButton* b = qobject_cast(c[i]); ++ if (b) { ++ if (!b->isCheckable()) ++ QtClient::connectObjects(b,SIGNAL(clicked()),qObj,actionSlot); ++ else ++ QtClient::connectObjects(b,SIGNAL(toggled(bool)),qObj,toggleSlot); ++ continue; ++ } ++ // Connect group boxes ++ QGroupBox* gb = qobject_cast(c[i]); ++ if (gb) { ++ if (gb->isCheckable()) ++ QtClient::connectObjects(gb,SIGNAL(toggled(bool)),qObj,toggleSlot); ++ continue; ++ } ++ // Connect combo boxes ++ QComboBox* combo = qobject_cast(c[i]); ++ if (combo) { ++ QtClient::connectObjects(combo,SIGNAL(activated(int)),qObj,selectSlot); ++ continue; ++ } ++ // Connect list boxes ++ QListWidget* lst = qobject_cast(c[i]); ++ if (lst) { ++ QtClient::connectObjects(lst,SIGNAL(currentRowChanged(int)),qObj,selectSlot); ++ continue; ++ } ++ // Connect sliders ++ QSlider* sld = qobject_cast(c[i]); ++ if (sld) { ++ QtClient::connectObjects(sld,SIGNAL(valueChanged(int)),qObj,selectSlot); ++ continue; ++ } ++ continue; ++ } ++ // Connect actions signals ++ QAction* a = qobject_cast(c[i]); ++ if (a) { ++ if (!a->isCheckable()) ++ QtClient::connectObjects(a,SIGNAL(triggered()),qObj,actionSlot); ++ else ++ QtClient::connectObjects(a,SIGNAL(toggled(bool)),qObj,toggleSlot); ++ continue; ++ } ++ } ++ return w; ++} ++ ++// Apply a QWidget style sheet. Replace ${name} with widget name in style ++void QtUIWidget::applyWidgetStyle(QWidget* w, const String& style) ++{ ++ if (!(w && style)) ++ return; ++ QString s = QtClient::setUtf8(style); ++ s.replace("${name}",w->objectName()); ++ w->setStyleSheet(s); ++} ++ ++// Filter key press events. Retrieve an action associated with the key. ++// Check if the object is allowed to process the key. ++// Raise the action ++bool QtUIWidget::filterKeyEvent(QObject* watched, QKeyEvent* event, bool& filter) ++{ ++ String action; ++ if (!QtClient::filterKeyEvent(watched,event,action,filter)) ++ return false; ++ if (!action) ++ return true; ++ String item; ++ getListItemProp(watched,item); ++ // Avoid raising a disabled actions ++ if (item) { ++ bool ok = true; ++ QWidget* w = findItem(item); ++ if (w) { ++ QString n = buildQChildName(w->objectName(),QtClient::setUtf8(action)); ++ QObject* act = w->findChild(n); ++ if (act) { ++ if (act->isWidgetType()) ++ ok = (qobject_cast(act))->isEnabled(); ++ else { ++ QAction* a = qobject_cast(act); ++ ok = !a || a->isEnabled(); ++ } ++ } ++ } ++ if (!ok) ++ return true; ++ // Append container item to action ++ action.append(item,":"); ++ } ++ Client::self()->action(QtClient::parentWindow(getQObject()),action); ++ return true; ++} ++ ++ ++/** ++ * QtSound ++ */ ++bool QtSound::doStart() ++{ ++ doStop(); ++ if (Client::self()) ++ Client::self()->createObject((void**)&m_sound,"QSound",m_file); ++ if (m_sound) ++ DDebug(ClientDriver::self(),DebugAll,"Sound(%s) started file=%s", ++ c_str(),m_file.c_str()); ++ else { ++ Debug(ClientDriver::self(),DebugNote,"Sound(%s) failed to start file=%s", ++ c_str(),m_file.c_str()); ++ return false; ++ } ++ m_sound->setLoops(m_repeat ? m_repeat : -1); ++ m_sound->play(); ++ return true; ++} ++ ++void QtSound::doStop() ++{ ++ if (!m_sound) ++ return; ++ m_sound->stop(); ++ delete m_sound; ++ DDebug(ClientDriver::self(),DebugAll,"Sound(%s) stopped",c_str()); ++ m_sound = 0; ++} ++ ++ ++// ++// QtDragAndDrop ++// ++// Reset data ++void QtDragAndDrop::reset() ++{ ++ m_started = false; ++} ++ ++// Check a string value for 'drag', 'drop', 'both' ++void QtDragAndDrop::checkEnable(const String& s, bool& drag, bool& drop) ++{ ++ drag = (s == YSTRING("drag")); ++ drop = !drag && (s == YSTRING("drop")); ++ if (!(drag || drop)) ++ drag = drop = (s == YSTRING("both")); ++} ++ ++// ++// QtDrop ++// ++const String QtDrop::s_askClientAcceptDrop = "_yate_event_drop_accept"; ++const String QtDrop::s_notifyClientDrop = "_yate_event_drop"; ++const QString QtDrop::s_fileScheme = "file"; ++ ++const TokenDict QtDrop::s_acceptDropName[] = { ++ {"always", Always}, ++ {"ask", Ask}, ++ {"none", 0}, ++ {0,0} ++}; ++ ++QtDrop::QtDrop(QObject* parent, const NamedList* params) ++ : QtDragAndDrop(parent), ++ m_dropParams(""), ++ m_acceptFiles(false), ++ m_acceptDirs(false) ++{ ++ if (!params) ++ return; ++ NamedIterator iter(*params); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == YSTRING("_yate_accept_drop_schemes")) ++ QtClient::addStrListUnique(m_schemes,QtClient::str2list(*ns)); ++ else if (ns->name() == YSTRING("_yate_accept_drop_file")) ++ m_acceptFiles = ns->toBoolean(); ++ else if (ns->name() == YSTRING("_yate_accept_drop_dir")) ++ m_acceptDirs = ns->toBoolean(); ++ } ++} ++ ++// Update parameters from drag enter event ++bool QtDrop::start(QDragEnterEvent& e) ++{ ++ static const String s_prefix = "drop:"; ++ ++ reset(); ++ const QMimeData* m = e.mimeData(); ++ if (!(m && m->hasUrls())) ++ return false; ++ int nUrls = m->urls().size(); ++ unsigned int nItems = 0; ++ for (int i = 0; i < nUrls; i++) { ++ QString scheme = m->urls()[i].scheme(); ++ if (m_schemes.size() > 0 && !m_schemes.contains(scheme)) { ++ reset(); ++ return false; ++ } ++ QString path = m->urls()[i].path(); ++ String what = scheme.toUtf8().constData(); ++ if (scheme == s_fileScheme) { ++#ifdef _WINDOWS ++ path = path.mid(1); ++#endif ++ path = QDir::toNativeSeparators(path); ++ QFileInfo fi(path); ++ if (fi.isDir()) { ++ if (!m_acceptDirs) { ++ reset(); ++ return false; ++ } ++ what = "directory"; ++ } ++ else if (fi.isFile() && !m_acceptFiles) { ++ reset(); ++ return false; ++ } ++ } ++ nItems++; ++ NamedList* nl = new NamedList(""); ++ QtClient::fillUrlParams(m->urls()[i],*nl,&path); ++ m_dropParams.addParam(new NamedPointer(s_prefix + what,nl,*nl)); ++ } ++ if (!nItems) { ++ reset(); ++ return false; ++ } ++ if (e.source()) { ++ QtWindow* wnd = QtClient::parentWindow(e.source()); ++ if (wnd) { ++ m_dropParams.addParam("source_window",wnd->toString()); ++ QtClient::getUtf8(m_dropParams,"source",e.source()->objectName()); ++ } ++ } ++ m_started = true; ++ return true; ++} ++ ++// Reset data ++void QtDrop::reset() ++{ ++ m_dropParams.clearParams(); ++ QtDragAndDrop::reset(); ++} ++ ++ ++// ++// QtListDrop ++// ++QtListDrop::QtListDrop(QObject* parent, const NamedList* params) ++ : QtDrop(parent,params), ++ m_acceptOnEmpty(None) ++{ ++} ++ ++// Update accept ++void QtListDrop::updateAcceptType(const String list, int type) ++{ ++ if (!list) ++ return; ++ ObjList* l = list.split(',',false); ++ for (ObjList* o = l->skipNull(); o; o = o->skipNext()) { ++ NamedInt* ni = new NamedInt(*static_cast(o->get()),type); ++ NamedInt::addToListUniqueName(m_acceptItemTypes,ni); ++ } ++ TelEngine::destruct(l); ++} ++ ++// Update accept from parameters list ++void QtListDrop::updateAccept(const NamedList& params) ++{ ++ NamedIterator iter(params); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == YSTRING("_yate_accept_drop_onempty")) ++ m_acceptOnEmpty = this->acceptDropType(*ns,None); ++ else if (ns->name() == YSTRING("_yate_accept_drop_item_type_always")) ++ updateAcceptType(*ns,Always); ++ else if (ns->name() == YSTRING("_yate_accept_drop_item_type_none")) ++ updateAcceptType(*ns,None); ++ else if (ns->name() == YSTRING("_yate_accept_drop_item_type_ask")) ++ updateAcceptType(*ns,Ask); ++ } ++} ++ ++// Reset data ++void QtListDrop::reset() ++{ ++ m_acceptItemTypes.clear(); ++ QtDrop::reset(); ++} ++ ++ ++// ++// QtBusyWidget ++// ++const QString QtBusyWidget::s_busySuffix("_yate_busy_widget_generated"); ++ ++// Constructor ++QtBusyWidget::QtBusyWidget(QWidget* parent) ++ : QtCustomWidget(0,parent), ++ m_target(0), m_shown(false), m_delayMs(0), m_delayTimer(0), ++ m_movieLabel(0) ++{ ++ if (parent) ++ setObjectName(parent->objectName() + s_busySuffix); ++ QWidget::hide(); ++} ++ ++// Initialize ++void QtBusyWidget::init(const String& ui, const NamedList& params, QWidget* target) ++{ ++ hideBusy(); ++ m_target = target; ++ m_movieLabel = 0; ++ unsigned int delay = 0; ++ QWidget* w = ui ? loadWidget(this,"",ui) : 0; ++ if (w) { ++ QtClient::setWidget(this,w); ++ int tmp = QtClient::getIntProperty(w,"_yate_busywidget_delay"); ++ if (tmp > 0) ++ delay = tmp; ++ QList c = w->findChildren(); ++ for (int i = 0; i < c.size(); i++) { ++ QLabel* l = qobject_cast(c[i]); ++ if (l) { ++ if (!m_movieLabel) { ++ String file; ++ QtClient::getProperty(l,"_yate_movie_file",file); ++ if (file) { ++ l->setMovie(QtClient::loadMovie(file,l)); ++ if (l->movie()) ++ m_movieLabel = l; ++ } ++ } ++ } ++ } ++ } ++ m_delayMs = params.getIntValue(YSTRING("_yate_busywidget_delay"),delay,0); ++} ++ ++// Show the widget ++void QtBusyWidget::showBusy() ++{ ++ if (m_shown) ++ return; ++ m_shown = true; ++ if (m_delayMs) ++ m_delayTimer = startTimer(m_delayMs); ++ if (!m_delayTimer) ++ internalShow(); ++} ++ ++// Hide the widget ++void QtBusyWidget::hideBusy() ++{ ++ if (!m_shown) ++ return; ++ m_shown = false; ++ stopDelayTimer(); ++ if (m_target) ++ m_target->removeEventFilter(this); ++ setContent(false); ++ lower(); ++ hide(); ++} ++ ++// Filter wathed events ++bool QtBusyWidget::onChildEvent(QObject* watched, QEvent* event) ++{ ++ if (m_target && m_target == watched) { ++ if (event->type() == QEvent::Resize) ++ resize(m_target->size()); ++ } ++ return false; ++} ++ ++void QtBusyWidget::timerEvent(QTimerEvent* ev) ++{ ++ if (m_delayTimer && ev->timerId() == m_delayTimer) { ++ stopDelayTimer(); ++ internalShow(); ++ return; ++ } ++ QtCustomWidget::timerEvent(ev); ++} ++ ++// Show/hide busy content ++void QtBusyWidget::setContent(bool on) ++{ ++ QMovie* movie = m_movieLabel ? m_movieLabel->movie() : 0; ++ if (!movie) ++ return; ++ if (on) ++ movie->start(); ++ else ++ movie->stop(); ++} ++ ++void QtBusyWidget::internalShow() ++{ ++ if (m_target) { ++ resize(m_target->size()); ++ m_target->installEventFilter(this); ++ } ++ setContent(true); ++ raise(); ++ show(); ++} ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/clients/qt5/qt5client.h b/clients/qt5/qt5client.h +new file mode 100644 +index 0000000..0e9f889 +--- /dev/null ++++ b/clients/qt5/qt5client.h +@@ -0,0 +1,2115 @@ ++/** ++ * qt5client.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * A Qt-5 based universal telephony client ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __QT5CLIENT_H ++#define __QT5CLIENT_H ++ ++#include ++ ++#ifdef _WINDOWS ++ ++#ifdef LIBYQT5_EXPORTS ++#define YQT5_API __declspec(dllexport) ++#else ++#ifndef LIBYQT5_STATIC ++#define YQT5_API __declspec(dllimport) ++#endif ++#endif ++ ++#endif /* _WINDOWS */ ++ ++#ifndef YQT5_API ++#define YQT5_API ++#endif ++ ++#undef open ++#undef read ++#undef close ++#undef write ++#undef mkdir ++#include ++#include ++#include ++ ++#define QT_NO_DEBUG ++#define QT_DLL ++#define QT_GUI_LIB ++#define QT_CORE_LIB ++#define QT_THREAD_SUPPORT ++ ++#include ++#include ++#include ++ ++namespace TelEngine { ++ ++class QtRefObjectHolder; // A QObject holding a RefPointer ++class QtEventProxy; // Proxy to global QT events ++class QtUrlBuilder; // QUrl builder ++class QtClient; // The QT based client ++class QtDriver; // The QT based telephony driver ++class QtWindow; // A QT window ++class QtDialog; // A custom modal dialog ++class QtUIWidgetItemProps; // Widget container item properties ++class QtUIWidget; // A widget container ++class QtCustomObject; // A custom QT object ++class QtCustomWidget; // A custom QT widget ++class QtTable; // A custom QT table widget ++class QtSound; // A QT client sound ++class QtDragAndDrop; // Base class for Drag&Drop operations ++class QtDrop; // Drop data holder ++class QtListDrop; // Drop data holder for widget list items ++class QtBusyWidget; // Busy widget to show over controls ++ ++// Macro used to get a QT object's name ++// Can't use an inline function: the QByteArray object returned by toUtf8() ++// would be destroyed on exit ++#define YQT_OBJECT_NAME(qobject) ((qobject) ? (qobject)->objectName().toUtf8().constData() : "") ++ ++ ++/** ++ * A QObject holding a RefPointer. Suitable to be set in QVariant ++ * @short A QObject holding a RefPointer ++ */ ++class YQT5_API QtRefObjectHolder : public QObject ++{ ++ Q_CLASSINFO("QtRefObjectHolder","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ */ ++ inline QtRefObjectHolder() ++ {} ++ ++ /** ++ * Constructor ++ * @param obj Object to set ++ */ ++ inline QtRefObjectHolder(RefObject* obj) ++ : m_refObj(obj) ++ {} ++ ++ /** ++ * Copy constructor ++ * @param other Source object ++ */ ++ inline QtRefObjectHolder(const QtRefObjectHolder& other) ++ : m_refObj((RefObject*)other.m_refObj) ++ {} ++ ++ /** ++ * Build a variant from RefObject ++ * @param obj Object to build from ++ * @param force True to build empty variant, false (default) to fail if obj is 0 ++ * @return QVariant ++ */ ++ static inline QVariant setVariant(RefObject* obj, bool force = false) { ++ QtRefObjectHolder data(obj); ++ if (data.m_refObj) ++ return QVariant::fromValue(data); ++ return QVariant(); ++ } ++ ++ RefPointer m_refObj; ++}; ++ ++/** ++ * Proxy to global QT events ++ * @short A QT proxy class ++ */ ++class YQT5_API QtEventProxy : public QObject, public GenObject ++{ ++ YCLASS(QtEventProxy,GenObject) ++ Q_CLASSINFO("QtEventProxy","Yate") ++ Q_OBJECT ++ ++public: ++ enum Type { ++ Timer, ++ AllHidden, ++ }; ++ ++ /** ++ * Constructor ++ * @param Event type ++ * @param pointer to QT application when needed ++ */ ++ QtEventProxy(Type type, QApplication* app = 0); ++ ++ /** ++ * Get a string representation of this object ++ * @return Object's name ++ */ ++ virtual const String& toString() const ++ { return m_name; } ++ ++private slots: ++ void timerTick(); // Idle timer ++ void allHidden(); // All windows closed notification ++ ++private: ++ String m_name; // Object name ++}; ++ ++/** ++ * This class holds data used to build an url ++ * @short QUrl builder ++ */ ++class YQT5_API QtUrlBuilder : public QObject, public GenObject ++{ ++ YCLASS(QtUrlBuilder,GenObject) ++ Q_CLASSINFO("QtUrlBuilder","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param parent Object parent ++ * @param format Format to use when building base URL ++ * @param queryParams Query params to add to URL ++ */ ++ QtUrlBuilder(QObject* parent, const String& format, const String& queryParams); ++ ++ /** ++ * Destructor ++ */ ++ ~QtUrlBuilder(); ++ ++ /** ++ * Build URL ++ * @param params URL params ++ * @return QUrl object ++ */ ++ virtual QUrl build(const NamedList& params) const; ++ ++protected: ++ String m_format; ++ ObjList* m_queryParams; ++}; ++ ++class YQT5_API QtClient : public Client ++{ ++ friend class QtWindow; ++public: ++ /** ++ * Generic position flags ++ */ ++ enum QtClientPos { ++ PosNone = 0, ++ PosLeft = 0x01, ++ PosRight = 0x02, ++ PosTop = 0x04, ++ PosBottom = 0x08, ++ // Corners ++ CornerTopLeft = PosTop | PosLeft, ++ CornerTopRight = PosTop | PosRight, ++ CornerBottomLeft = PosBottom | PosLeft, ++ CornerBottomRight = PosBottom | PosRight, ++ }; ++ ++ /** ++ * Sorting ++ */ ++ enum Sort { ++ SortNone = 0, ++ SortAsc, ++ SortDesc, ++ }; ++ ++ QtClient(); ++ virtual ~QtClient(); ++ virtual void run(); ++ virtual void cleanup(); ++ virtual void main(); ++ virtual void lock(); ++ virtual void unlock(); ++ virtual void allHidden(); ++ virtual bool createWindow(const String& name, ++ const String& alias = String::empty()); ++ virtual bool action(Window* wnd, const String& name, NamedList* params = 0); ++ virtual void quit() { ++ if (m_app) ++ m_app->quit(); ++ Engine::halt(0); ++ } ++ ++ /** ++ * Open an URL (link) ++ * @param url The URL to open ++ * @return True on success ++ */ ++ virtual bool openUrl(const String& url) ++ { return QDesktopServices::openUrl(QUrl(setUtf8(url))); } ++ ++ /** ++ * Show a file save/open dialog window. If the list of parameters contains an 'action' ++ * parameter, an action will be raised when the dialog will be closed. The action's ++ * parameter list pointer will be non 0 if the dialog was accepted and 0 if cancelled. ++ * The list will contain one or more 'file' parameter(s) with selected file(s) ++ * @param parent Dialog window's parent ++ * @param params Dialog window's params. Parameters that can be specified include 'caption', ++ * 'dir', 'filters', 'selectedfilter', 'choosefile' ++ * @return True on success (the dialog was opened) ++ */ ++ virtual bool chooseFile(Window* parent, NamedList& params); ++ ++ /** ++ * Create a sound object. Append it to the global list ++ * @param name The name of sound object ++ * @param file The file to play (should contain the whole path and the file name) ++ * @param device Optional device used to play the file. Set to 0 to use the default one ++ * @return True on success, false if a sound with the given name already exists ++ */ ++ virtual bool createSound(const char* name, const char* file, const char* device = 0); ++ ++ /** ++ * Build a date/time string from UTC time ++ * @param dest Destination string ++ * @param secs Seconds since EPOCH ++ * @param format Format string used to build the destination ++ * @param utc True to build UTC time instead of local time ++ * @return True on success ++ */ ++ virtual bool formatDateTime(String& dest, unsigned int secs, const char* format, ++ bool utc = false); ++ ++ /** ++ * Build a date/time QT string from UTC time ++ * @param secs Seconds since EPOCH ++ * @param format Format string ++ * @param utc True to build UTC time instead of local time ++ * @return The formated string ++ */ ++ static QString formatDateTime(unsigned int secs, const char* format, ++ bool utc = false); ++ ++ /** ++ * Get an UTF8 representation of a QT string ++ * @param dest Destination string ++ * @param src Source QT string ++ */ ++ static inline void getUtf8(String& dest, const QString& src) ++ { dest = src.toUtf8().constData(); } ++ ++ /** ++ * Get an UTF8 representation of a QT string and add it to a list of parameters ++ * @param dest Destination list ++ * @param param Parameter name/value ++ * @param src Source QT string ++ * @param setValue True to set the QT string as parameter value, false to set it ++ * as parameter name ++ */ ++ static inline void getUtf8(NamedList& dest, const char* param, ++ const QString& src, bool setValue = true) { ++ if (setValue) ++ dest.addParam(param,src.toUtf8().constData()); ++ else ++ dest.addParam(src.toUtf8().constData(),param); ++ } ++ ++ /** ++ * Get an UTF8 representation of a QT string and add it to a list of parameters if not empty ++ * @param dest Destination list ++ * @param param Parameter name/value ++ * @param src Source QT string ++ * @param setValue True to set the QT string as parameter value, false to set it ++ * as parameter name ++ */ ++ static inline void safeGetUtf8(NamedList& dest, const char* param, ++ const QString& src, bool setValue = true) { ++ if (src.length() > 0) ++ getUtf8(dest,param,src,setValue); ++ } ++ ++ /** ++ * Set a QT string from an UTF8 char buffer ++ * @param str The buffer ++ * @return A QT string filled with the buffer ++ */ ++ static inline QString setUtf8(const char* str) ++ { return QString::fromUtf8(TelEngine::c_safe(str)); } ++ ++ /** ++ * Retrieve an object's QtWindow parent ++ * @param obj The object ++ * @return QtWindow pointer or 0 ++ */ ++ static QtWindow* parentWindow(QObject* obj); ++ ++ /** ++ * Set an object's property into parent window's section. Clear it on failure ++ * @param obj The object ++ * @param prop Property to save ++ * @param owner Optional window owning the object ++ * @return True on success ++ */ ++ static bool saveProperty(QObject* obj, const String& prop, QtWindow* owner = 0); ++ ++ /** ++ * Set or an object's property ++ * @param obj The object ++ * @param name Property's name ++ * @param value Property's value ++ * @return False if the property doesn't exist or has a type not supported by String ++ */ ++ static bool setProperty(QObject* obj, const char* name, const String& value); ++ ++ /** ++ * Get an object's property ++ * @param obj The object ++ * @param name Property's name ++ * @param value Property's value ++ * @return False if the property doesn't exist or has a type not supported by String ++ */ ++ static bool getProperty(QObject* obj, const char* name, String& value); ++ ++ /** ++ * Get an object's property and return its boolean conversion ++ * @param obj The object ++ * @param name Property name ++ * @param defVal Default value to return if the property is not found or has ++ * invalid boolean value ++ * @return The boolean conversion of the property or given default value ++ */ ++ static inline bool getBoolProperty(QObject* obj, const char* name, ++ bool defVal = false) { ++ String tmp; ++ if (!getProperty(obj,name,tmp)) ++ return defVal; ++ return tmp.toBoolean(defVal); ++ } ++ ++ /** ++ * Get an object's property and return its integer conversion ++ * @param obj The object ++ * @param name Property name ++ * @param defVal Default value to return if the property is not found or has ++ * invalid integer value ++ * @return The integer conversion of the property or given default value ++ */ ++ static inline int getIntProperty(QObject* obj, const char* name, ++ int defVal = 0) { ++ String tmp; ++ if (!getProperty(obj,name,tmp)) ++ return defVal; ++ return tmp.toInteger(defVal); ++ } ++ ++ /** ++ * Associate actions to buttons with '_yate_setaction' property set ++ * @param parent Parent widget ++ */ ++ static void setAction(QWidget* parent); ++ ++ /** ++ * Check if an object has '_yate_noautoconnect' boolean property set to true ++ * @param obj The object ++ * @return True if the object don't have the property or its value is not a boolean 'true' ++ */ ++ static inline bool autoConnect(QObject* obj) ++ { return !getBoolProperty(obj,"_yate_noautoconnect"); } ++ ++ /** ++ * Retrieve an object's identity from '_yate_identity' property or object name ++ * @param obj The object ++ * @param ident String to be filled with object identity ++ */ ++ static inline void getIdentity(QObject* obj, String& ident) { ++ if (obj && !(getProperty(obj,"_yate_identity",ident) && ident)) ++ getUtf8(ident,obj->objectName()); ++ } ++ ++ /** ++ * Copy a string list to a list of parameters ++ * @param dest Destination list ++ * @param src Source string list ++ */ ++ static void copyParams(NamedList& dest, const QStringList& src); ++ ++ /** ++ * Copy a list of parameters to string list ++ * @param dest Destination list ++ * @param src Source list ++ */ ++ static void copyParams(QStringList& dest, const NamedList& src); ++ ++ /** ++ * Build QObject properties from list ++ * @param obj The object ++ * @param props Comma separated list of properties. Format: name=type ++ */ ++ static void buildProps(QObject* obj, const String& props); ++ ++ /** ++ * Build custom UI widgets from frames owned by a widget ++ * @param parent Parent widget ++ */ ++ static void buildFrameUiWidgets(QWidget* parent); ++ ++ /** ++ * Build a menu object from a list of parameters. ++ * Each menu item is indicated by a parameter starting with 'item:". ++ * item:menu_name=Menu Text will create a menu item named 'menu_name' with ++ * 'Menu Text' as display name. ++ * If the item parameter is a NamedPointer a submenu will be created. ++ * Menu actions properties can be set from parameters with format: ++ * property:object_name:property_name=value ++ * @param params The menu parameters. The list name is the object name ++ * @param text The menu display text ++ * @param receiver Object receiving menu actions ++ * @param actionSlot The receiver's slot for menu signal triggered() ++ * @param toggleSlot The receiver's slot for menu signal toggled() ++ * @param aboutToShowSlot The receiver's slot for menu signal aboutToShow() ++ * @param parent Optional widget parent ++ * @return QMenu pointer or 0 if failed to build it ++ */ ++ static QMenu* buildMenu(const NamedList& params, const char* text, QObject* receiver, ++ const char* actionSlot, const char* toggleSlot, QWidget* parent = 0, ++ const char* aboutToShowSlot = 0); ++ ++ /** ++ * Insert a widget into another one replacing any existing children ++ * @param parent Parent widget ++ * @param child Widget to insert into parent ++ * @return True on success ++ */ ++ static bool setWidget(QWidget* parent, QWidget* child); ++ ++ /** ++ * Set an object's image property from image file ++ * @param obj The object ++ * @param img Image file to load ++ * @param fit True to adjust the image to target size if applicable (like ++ * a QLabel without scaled contents) ++ * @return True on success ++ */ ++ static bool setImage(QObject* obj, const String& img, bool fit = true); ++ ++ /** ++ * Set an object's image property from raw data ++ * @param obj The object ++ * @param data The image data ++ * @param format Image format if known ++ * @param fit True to adjust the image to target size if applicable (like ++ * a QLabel without scaled contents) ++ * @return True on success ++ */ ++ static bool setImage(QObject* obj, const DataBlock& data, ++ const String& format = String::empty(), bool fit = true); ++ ++ /** ++ * Set an object's image property from QPixmap ++ * @param obj The object ++ * @param img The image ++ * @param fit True to adjust the image to target size if applicable (like ++ * a QLabel without scaled contents) ++ * @return True on success ++ */ ++ static bool setImage(QObject* obj, const QPixmap& img, bool fit = true); ++ ++ /** ++ * Update a toggable object's image from properties ++ * @param obj The object ++ */ ++ static void updateToggleImage(QObject* obj); ++ ++ /** ++ * Update an object's image from properties on mouse events ++ * @param obj The object ++ * @param inOut True for mouse enter/leave, false for mouse press/release events ++ * @param on True for mouse enter/press, false for mouse leave/release ++ */ ++ static void updateImageFromMouse(QObject* obj, bool inOut, bool on); ++ ++ /** ++ * Filter key press events. Retrieve an action associated with the key. ++ * Check if the object is allowed to process the key ++ * @param obj The object ++ * @param event QKeyEvent event to process ++ * @param action Found action name ++ * @param filter Filter key or let the object process it ++ * @param parent Optional parent to look for the action and check its state ++ * @return True if key and modifiers were matched against object properties ++ * (the action parameter may be empty if true is returned and the action is disabled) ++ */ ++ static bool filterKeyEvent(QObject* obj, QKeyEvent* event, String& action, ++ bool& filter, QObject* parent = 0); ++ ++ /** ++ * Wrapper for QObject::connect() used to put a debug mesage on failure ++ */ ++ static bool connectObjects(QObject* sender, const char* signal, ++ QObject* receiver, const char* slot); ++ ++ /** ++ * Safely delete a QObject. Disconnect it, reset its parent, calls its deleteLater() method ++ * @param obj The object to delete ++ */ ++ static void deleteLater(QObject* obj); ++ ++ /** ++ * Retrieve unavailable space position (if any) in the screen containing a given widget. ++ * The positions are set using the difference between screen geometry and available geometry ++ * @param w The widget ++ * @param pos Unavailable screen space if any (QtClientPos combination) ++ * @return Valid pointer to global desktop widget on success ++ */ ++ static QDesktopWidget* getScreenUnavailPos(QWidget* w, int& pos); ++ ++ /** ++ * Move a window to a specified position ++ * @param w The window to move ++ * @param pos A corner position ++ */ ++ static void moveWindow(QtWindow* w, int pos); ++ ++ /** ++ * Append a non empty string to a list if not already there ++ * @param list Destination list ++ * @param str The string to append ++ */ ++ static inline void addStrUnique(QStringList& list, QString str) { ++ if (str.length() > 0 && !list.contains(str)) ++ list.append(str); ++ } ++ ++ /** ++ * Append non empty strings to a list if not already there ++ * @param list Destination list ++ * @param strs Source list ++ */ ++ static void addStrListUnique(QStringList& list, QStringList src) { ++ for (int i = 0; i < src.size(); i++) ++ addStrUnique(list,src[i]); ++ } ++ ++ /** ++ * Build a QStringList from a list of strings ++ * @param str The string ++ * @param sep The separator ++ * @param emptyOk True to process empty string items ++ * @return QStringList ++ */ ++ static QStringList str2list(const String& str, char sep = ',', bool emptyOk = true); ++ ++ /** ++ * Split an integer string list ++ * @param str The string ++ * @param defVal Default value for failed items ++ * @param emptyOk True to process empty string items ++ * @return A list of integers ++ */ ++ static QList str2IntList(const String& str, int defVal = 0, bool emptyOk = true); ++ ++ /** ++ * Build a comma separated list of integers ++ * @param str The destination string ++ * @param list The source integer list ++ */ ++ static void intList2str(String& str, QList list); ++ ++ /** ++ * Get sorting from string ++ * @param str Sorting name ++ * @param defVal Default value to return if invalid ++ * @return Sorting as QtClientSort enumeration ++ */ ++ static int str2sort(const String& str, int defVal = SortNone); ++ ++ /** ++ * Apply a comma separated list of window flags to a widget ++ * @param wid The widget ++ * @param str The list of flags ++ */ ++ static void applyWindowFlags(QWidget* w, const String& value); ++ ++ /** ++ * Build a QT Alignment mask from a comma separated list of flags ++ * @param flags The flags list ++ * @param initVal Initial value for the returned mask ++ * @return QT Alignment mask ++ */ ++ static int str2align(const String& flags, int initVal = 0); ++ ++ /** ++ * Retrieve QT selection mode from a string value ++ * @param value String value ++ * @param defVal Default value to return if invalid ++ * @return QAbstractItemView selection mode ++ */ ++ static QAbstractItemView::SelectionMode str2selmode(const String& value, ++ QAbstractItemView::SelectionMode defVal = QAbstractItemView::SingleSelection); ++ ++ /** ++ * Retrieve QT edit triggers from a string value ++ * @param value String value ++ * @param defVal Default value to set if invalid ++ * @return QAbstractItemView edit triggers mask ++ */ ++ static QAbstractItemView::EditTriggers str2editTriggers(const String& value, ++ QAbstractItemView::EditTrigger defVal = QAbstractItemView::NoEditTriggers); ++ ++ /** ++ * Send an event to an object's child. The event must be already accepted ++ * The event's accepted flag is set to false before sending it and restored on failure ++ * to avoid looping in event filters ++ * @param e The event to send ++ * @param parent The parent object ++ * @param name Child name ++ * @return True if the event was accepted by the target ++ */ ++ static bool sendEvent(QEvent& e, QObject* parent, const QString& name); ++ ++ /** ++ * Retrieve a pixmap from global application cache. ++ * Load and add it to the cache if not found ++ * @param pixmap Destination pixmap to set ++ * @param file File name to retrieve or load ++ * @return True on success, false if failed to load ++ */ ++ static bool getPixmapFromCache(QPixmap& pixmap, const QString& file); ++ ++ /** ++ * Retrieve a pixmap from global application cache. Add skin path to file name ++ * Load and add it to the cache if not found ++ * @param pixmap Destination pixmap to set ++ * @param file File name to retrieve or load ++ * @return True on success, false if failed to load ++ */ ++ static inline bool getSkinPathPixmapFromCache(QPixmap& pixmap, const String& file) { ++ if (!file) ++ return false; ++ return getPixmapFromCache(pixmap,setUtf8(s_skinPath + file)); ++ } ++ ++ /** ++ * Update application style sheet from config ++ */ ++ static void updateAppStyleSheet(); ++ ++ /** ++ * Set widget attributes from list ++ * @param w The widget ++ * @param attrs Comma separated list of attributes. ++ * To reset an attribute an item must start with '!' ++ */ ++ static void setWidgetAttributes(QWidget* w, const String& attrs); ++ ++ /** ++ * Set a widget's height ++ * @param w The widget ++ * @param height Height value. If boolean, Increase(true)/decrease(false) widget ++ * height from _yate_height_delta property. Set widget height otherwise ++ */ ++ static void setWidgetHeight(QWidget* w, const String& height); ++ ++ /** ++ * Build a busy widget child for a given widget ++ * @param parent Busy widget parent ++ * @param target Busy widget target ++ * @param ui UI file ++ * @param params Busy widget parameters ++ * @return Busy widget pointer or 0 on failure ++ */ ++ static QWidget* buildBusy(QWidget* parent, QWidget* target, const String& ui, ++ const NamedList& params); ++ ++ /** ++ * Load a movie ++ * @param file Movie file ++ * @param parent Movie parent ++ * @param path File path, 0 to use client skin path ++ * @return QMovie pointer or 0 ++ */ ++ static QMovie* loadMovie(const char* file, QObject* parent, const char* path = 0); ++ ++ /** ++ * Fill a list from URL parameters ++ * @param url URL to fill ++ * @param list Destination list ++ * @param path Optional URL path, user provided URL's path if 0 ++ * @param pathToList True to set path in list name, false to set as parameter ++ */ ++ static void fillUrlParams(const QUrl& url, NamedList& list, QString* path = 0, ++ bool pathToList = true); ++ ++ /** ++ * Dump MIME data for debug purposes ++ * @param buf Destination buffer ++ * @param m MIME data to dump ++ */ ++ static void dumpMime(String& buf, const QMimeData* m); ++ ++protected: ++ virtual void loadWindows(const char* file = 0); ++ virtual bool isUIThread(); ++private: ++ QApplication* m_app; ++ ObjList m_events; // Proxy events objects ++}; ++ ++class YQT5_API QtDriver : public ClientDriver ++{ ++public: ++ QtDriver(bool buildClientThread = true); ++ virtual ~QtDriver(); ++ virtual void initialize(); ++private: ++ bool m_init; // Already initialized flag ++ bool m_clientThread; // does the client need a thread to run on? ++}; ++ ++class YQT5_API QtWindow : public QWidget, public Window ++{ ++ YCLASS(QtWindow, Window) ++ Q_CLASSINFO("QtWindow", "Yate") ++ Q_OBJECT ++ ++ friend class QtClient; ++public: ++ QtWindow(); ++ QtWindow(const char* name, const char* description, const char* alias, QtWindow* parent = 0); ++ virtual ~QtWindow(); ++ ++ virtual void title(const String& text); ++ virtual void context(const String& text); ++ virtual bool setParams(const NamedList& params); ++ virtual void setOver(const Window* parent); ++ virtual bool hasElement(const String& name); ++ virtual bool setActive(const String& name, bool active); ++ virtual bool setFocus(const String& name, bool select = false); ++ virtual bool setShow(const String& name, bool visible); ++ ++ /** ++ * Set the displayed text of an element in the window ++ * @param name Name of the element ++ * @param text Text value to set in the element ++ * @param richText True if the text contains format data ++ * @return True if the operation was successfull ++ */ ++ virtual bool setText(const String& name, const String& text, ++ bool richText = false); ++ ++ virtual bool setCheck(const String& name, bool checked); ++ virtual bool setSelect(const String& name, const String& item); ++ virtual bool setUrgent(const String& name, bool urgent); ++ ++ virtual bool hasOption(const String& name, const String& item); ++ virtual bool addOption(const String& name, const String& item, bool atStart = false, const String& text = String::empty()); ++ virtual bool delOption(const String& name, const String& item); ++ virtual bool getOptions(const String& name, NamedList* items); ++ ++ /** ++ * Append or insert text lines to a widget ++ * @param name The name of the widget ++ * @param lines List containing the lines ++ * @param max The maximum number of lines allowed to be displayed. Set to 0 to ignore ++ * @param atStart True to insert, false to append ++ * @return True on success ++ */ ++ virtual bool addLines(const String& name, const NamedList* lines, unsigned int max, ++ bool atStart = false); ++ ++ virtual bool addTableRow(const String& name, const String& item, const NamedList* data = 0, bool atStart = false); ++ ++ virtual bool setMultipleRows(const String& name, const NamedList& data, const String& prefix); ++ ++ /** ++ * Insert a row into a table owned by this window ++ * @param name Name of the element ++ * @param item Name of the item to insert ++ * @param before Name of the item to insert before ++ * @param data Table's columns to set ++ * @return True if the operation was successfull ++ */ ++ virtual bool insertTableRow(const String& name, const String& item, ++ const String& before, const NamedList* data = 0); ++ ++ virtual bool delTableRow(const String& name, const String& item); ++ virtual bool setTableRow(const String& name, const String& item, const NamedList* data); ++ virtual bool getTableRow(const String& name, const String& item, NamedList* data = 0); ++ virtual bool clearTable(const String& name); ++ ++ /** ++ * Set a table row or add a new one if not found ++ * @param name Name of the element ++ * @param item Table item to set/add ++ * @param data Optional list of parameters used to set row data ++ * @param atStart True to add item at start, false to add them to the end ++ * @return True if the operation was successfull ++ */ ++ virtual bool updateTableRow(const String& name, const String& item, ++ const NamedList* data = 0, bool atStart = false); ++ ++ /** ++ * Add or set one or more table row(s). Screen update is locked while changing the table. ++ * Each data list element is a NamedPointer carrying a NamedList with item parameters. ++ * The name of an element is the item to update. ++ * Set element's value to boolean value 'true' to add a new item if not found ++ * or 'false' to set an existing one. Set it to empty string to delete the item ++ * @param name Name of the table ++ * @param data The list of items to add/set/delete ++ * @param atStart True to add new items at start, false to add them to the end ++ * @return True if the operation was successfull ++ */ ++ virtual bool updateTableRows(const String& name, const NamedList* data, ++ bool atStart = false); ++ ++ /** ++ * Show or hide control busy state ++ * @param name Name of the element ++ * @param on True to show, false to hide ++ * @return True if all the operations were successfull ++ */ ++ bool setBusy(const String& name, bool on); ++ ++ /** ++ * Get an element's text ++ * @param name Name of the element ++ * @param text The destination string ++ * @param richText True to get the element's roch text if supported. ++ * @return True if the operation was successfull ++ */ ++ virtual bool getText(const String& name, String& text, bool richText = false); ++ ++ virtual bool getCheck(const String& name, bool& checked); ++ virtual bool getSelect(const String& name, String& item); ++ ++ /** ++ * Retrieve an element's multiple selection ++ * @param name Name of the element ++ * @param items List to be to filled with selection's contents ++ * @return True if the operation was successfull ++ */ ++ virtual bool getSelect(const String& name, NamedList& items); ++ ++ /** ++ * Build a menu from a list of parameters. ++ * See Client::buildMenu() for more info ++ * @param params Menu build parameters ++ * @return True on success ++ */ ++ virtual bool buildMenu(const NamedList& params); ++ ++ /** ++ * Remove a menu from UI and memory ++ * See Client::removeMenu() for more info ++ * @param params Menu remove parameters ++ * @return True on success ++ */ ++ virtual bool removeMenu(const NamedList& params); ++ ++ /** ++ * Set an element's image ++ * @param name Name of the element ++ * @param image Image to set ++ * @param fit Fit image in element (defaults to false) ++ * @return True on success ++ */ ++ virtual bool setImage(const String& name, const String& image, bool fit = false); ++ ++ /** ++ * Set a property for this window or for a widget owned by it ++ * @param name Name of the element ++ * @param item Property's name ++ * @param value Property's value ++ * @return False if the property doesn't exist or has a type not supported by String ++ */ ++ virtual bool setProperty(const String& name, const String& item, const String& value); ++ ++ /** ++ * Get a property from this window or from a widget owned by it ++ * @param name Name of the element ++ * @param item Property's name ++ * @param value Property's value ++ * @return False if the property doesn't exist or has a type not supported by String ++ */ ++ virtual bool getProperty(const String& name, const String& item, String& value); ++ ++ virtual void show(); ++ virtual void hide(); ++ virtual void size(int width, int height); ++ virtual void move(int x, int y); ++ virtual void moveRel(int dx, int dy); ++ virtual bool related(const Window* wnd) const; ++ virtual void menu(int x, int y) ; ++ ++ /** ++ * Create a modal dialog ++ * @param name Dialog name (resource config section) ++ * @param title Dialog title ++ * @param alias Optional dialog alias (used as dialog object name) ++ * @param params Optional dialog parameters ++ * @return True on success ++ */ ++ virtual bool createDialog(const String& name, const String& title, ++ const String& alias = String::empty(), const NamedList* params = 0); ++ ++ /** ++ * Destroy a modal dialog ++ * @param name Dialog name ++ * @return True on success ++ */ ++ virtual bool closeDialog(const String& name); ++ ++ /** ++ * Connect an abstract button to window slots ++ * @param b The button to connect ++ * @return True on success ++ */ ++ inline bool connectButton(QAbstractButton* b) { ++ if (!b) ++ return false; ++ if (!b->isCheckable()) ++ return QtClient::connectObjects(b,SIGNAL(clicked()),this,SLOT(action())); ++ return QtClient::connectObjects(b,SIGNAL(toggled(bool)),this,SLOT(toggled(bool))); ++ } ++ ++ /** ++ * Connect an object's text changed signal to window's slot ++ * @param obj The object to connect ++ * @return True on success ++ */ ++ bool connectTextChanged(QObject* obj); ++ ++ /** ++ * Notify text changed to the client ++ * @param obj The object sending the notification ++ * @param text Optional object text ++ */ ++ void notifyTextChanged(QObject* obj, const QString& text = QString()); ++ ++ /** ++ * Load a widget from file ++ * @param fileName UI filename to load ++ * @param parent The widget holding the loaded widget's contents ++ * @param uiName The loaded widget's name (used for debug) ++ * @param path Optional fileName path. Set to 0 to use the default one ++ * @return QWidget pointer or 0 on failure ++ */ ++ static QWidget* loadUI(const char* fileName, QWidget* parent, ++ const char* uiName, const char* path = 0); ++ ++ /** ++ * Clear the UI cache ++ * @param fileName Optional UI filename to clear. Clear all if 0 ++ */ ++ static void clearUICache(const char* fileName = 0); ++ ++ /** ++ * Retrieve the parent window ++ * @return QtWindow pointer or 0 ++ */ ++ inline QtWindow* parentWindow() const ++ { return qobject_cast(parentWidget() ? parentWidget()->window() : 0); } ++ ++ /** ++ * Check if this window is shown normal (not maximixed, minimized or full screen) ++ * @return True if the window is not maximixed, minimized or full screen ++ */ ++ inline bool isShownNormal() const ++ { return !(isMaximized() || isMinimized() || isFullScreen()); } ++ ++protected: ++ // Notify client on selection changes ++ inline bool select(const String& name, const String& item, ++ const String& text = String::empty()) { ++ if (!QtClient::self() || QtClient::changing()) ++ return false; ++ return QtClient::self()->select(this,name,item,text); ++ } ++ ++ // Filter events to apply dynamic properties changes ++ bool eventFilter(QObject* watched, QEvent* event); ++ // Handle key pressed events ++ void keyPressEvent(QKeyEvent* event); ++ ++public slots: ++ void setVisible(bool visible); ++ // A widget was double clicked ++ void doubleClick(); ++ // A widget's selection changed ++ void selectionChanged(); ++ // Clicked actions ++ void action(); ++ // Toggled actions ++ void toggled(bool); ++ // System tray actions ++ void sysTrayIconAction(QSystemTrayIcon::ActivationReason reason); ++ // Choose file window was accepted ++ void chooseFileAccepted(); ++ // Choose file window was cancelled ++ void chooseFileRejected(); ++ // Text changed slot. Notify the client ++ void textChanged(const QString& text) ++ { notifyTextChanged(sender(),text); } ++ void textChanged() ++ { notifyTextChanged(sender()); } ++ ++private slots: ++ void openUrl(const QString& link); ++ ++protected: ++ virtual void doPopulate(); ++ virtual void doInit(); ++ // Methods inherited from QWidget ++ virtual void moveEvent(QMoveEvent* event); ++ virtual void resizeEvent(QResizeEvent* event); ++ virtual bool event(QEvent* ev); ++ virtual void mousePressEvent(QMouseEvent* event); ++ virtual void mouseReleaseEvent(QMouseEvent* event); ++ virtual void mouseMoveEvent(QMouseEvent* event); ++ virtual void closeEvent(QCloseEvent* event); ++ virtual void changeEvent(QEvent* event); ++ virtual void contextMenuEvent(QContextMenuEvent* ev) { ++ if (handleContextMenuEvent(ev,wndWidget())) ++ ev->accept(); ++ } ++ // Get the widget with this window's content ++ inline QWidget* wndWidget() ++ { return findChild(m_widget); } ++ // Handle context menu events. Return true if handled ++ bool handleContextMenuEvent(QContextMenuEvent* event, QObject* obj); ++ ++ String m_description; ++ String m_oldId; // Old id used to retreive the config section in .rc ++ int m_x; ++ int m_y; ++ int m_width; // Client area width ++ int m_height; // Client area height ++ bool m_maximized; ++ bool m_mainWindow; // Main window flag: close app when this window is closed ++ QString m_widget; // The widget with window's content ++ int m_moving; // Flag used to move the window on mouse move event ++ QPoint m_movePos; // Old position used when moving the window ++}; ++ ++/** ++ * This class encapsulates a custom modal dialog window. ++ * A dialog context can be set in '_yate_context' property ++ * Actions triggered by dialogs have the following format: dialog:dialog_name:action_name. ++ * The dialog will delete itself if an action is handled ++ * @short A custom modal dialog ++ */ ++class YQT5_API QtDialog : public QDialog ++{ ++ Q_CLASSINFO("QtDialog","Yate") ++ Q_OBJECT ++ Q_PROPERTY(QString _yate_context READ context WRITE setContext(QString)) ++public: ++ /** ++ * Constructor ++ * @param parent Parent widget ++ */ ++ inline QtDialog(QWidget* parent) ++ : QDialog(parent), m_closable(true) ++ {} ++ ++ /** ++ * Destructor. Notify the client if not exiting ++ */ ++ virtual ~QtDialog(); ++ ++ /** ++ * Retrieve the parent window ++ * @return QtWindow pointer or 0 ++ */ ++ inline QtWindow* parentWindow() const ++ { return qobject_cast(parentWidget() ? parentWidget()->window() : 0); } ++ ++ /** ++ * Initialize dialog. Load the widget. ++ * Connect non checkable actions to own slot. ++ * Connect checkable actions/buttons to parent window's slot ++ * Display the dialog on success ++ * @param name Object and config section name ++ * @param title Window title ++ * @param alias Object name to set if not empty ++ * @param params Optional parent window parameters ++ * @return True on success ++ */ ++ bool show(const String& name, const String& title, const String& alias, ++ const NamedList* params); ++ ++ /** ++ * Retrieve the context property ++ * @return The dialog context ++ */ ++ QString context() ++ { return m_context; } ++ ++ /** ++ * Set the dialog context ++ * @param c The new dialog context ++ */ ++ void setContext(QString c) ++ { m_context = c; } ++ ++ /** ++ * Build an action's name ++ * @param buf Destination buffer ++ * @param action Action name ++ * @return The destination string ++ */ ++ inline String& buildActionName(String& buf, const String& action) { ++ buf = String("dialog:") + YQT_OBJECT_NAME(this) + ":" + action; ++ return buf; ++ } ++ ++protected slots: ++ // Notify client ++ void action(); ++ ++protected: ++ // Destroy the dialog ++ virtual void closeEvent(QCloseEvent* event); ++ // Destroy the dialog ++ virtual void reject(); ++ ++ String m_notifyOnClose; // Action to notify when closed ++ QString m_context; // Dialog context ++ bool m_closable; // Allow the dialog to be closed by the user ++}; ++ ++/** ++ * This class holds data about a widget container item ++ * @short Widget container item properties ++ */ ++class QtUIWidgetItemProps : public String ++{ ++public: ++ /** ++ * Constructor ++ * @param type Item type ++ */ ++ explicit inline QtUIWidgetItemProps(const String& type) ++ : String(type), m_acceptDrop(0) ++ {} ++ ++ String m_ui; // Item UI file ++ String m_styleSheet; // Item style sheet when not selected ++ String m_selStyleSheet; // Item selected style ++ int m_acceptDrop; // Accept drop ++}; ++ ++/** ++ * This class holds a basic widget container with functions to rename children ++ * @short A widget container ++ */ ++class YQT5_API QtUIWidget : public UIWidget ++{ ++ YCLASS(QtUIWidget,UIWidget) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ * @param params Object parameters ++ * @param parent Optional parent ++ */ ++ inline QtUIWidget(const char* name) ++ : UIWidget(name), ++ m_wndEvHooked(false) ++ {} ++ ++ /** ++ * Build a child name from this one ++ * @param buf Destination buffer ++ * @param item Child name ++ * @return The destination buffer ++ */ ++ inline String& buildChildName(String& buf, const String& item) ++ { return buildChildName(buf,name(),item); } ++ ++ /** ++ * Build a container QString child name ++ * @param item Child name ++ * @return QString child name ++ */ ++ inline QString buildQChildName(const String& item) ++ { return buildQChildName(name(),item); } ++ ++ /** ++ * Retrieve item type definition ++ * @param type Item type name ++ * @return QtUIWidgetItemProps pointer or 0 ++ */ ++ inline QtUIWidgetItemProps* getItemProps(const String& type) const { ++ ObjList* o = m_itemProps.find(type); ++ return o ? static_cast(o->get()) : 0; ++ } ++ ++ /** ++ * Retrieve item type definition from [type:]value. Create it if not found ++ * @param in Input string ++ * @param value Item property value ++ * @return QtUIWidgetItemProps pointer or 0 ++ */ ++ virtual QtUIWidgetItemProps* getItemProps(QString& in, String& value); ++ ++ /** ++ * Retrieve the list of properties to save ++ * @return The list of properties to save ++ */ ++ QStringList saveProps() ++ { return m_saveProps; } ++ ++ /** ++ * Set the list of properties to save ++ * @param list The new list of properties to save ++ */ ++ void setSaveProps(QStringList list) { ++ if (list.size() != 1) ++ m_saveProps = list; ++ else ++ m_saveProps = list[0].split(QChar(','),Qt::SkipEmptyParts); ++ } ++ ++ /** ++ * Retrieve a QObject descendent of this object ++ * @return QObject pointer or 0 ++ */ ++ virtual QObject* getQObject() ++ { return 0; } ++ ++ /** ++ * Retrieve the window owning this object ++ * @return QtWindow pointer or 0 ++ */ ++ virtual QtWindow* getWindow() ++ { return QtClient::parentWindow(getQObject()); } ++ ++ /** ++ * Set widget's parameters. ++ * Handle an 'applyall' parameter carrying a NamedList to apply to all items ++ * @param params List of parameters ++ * @return True if all parameters could be set ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Retrieve a QObject list containing container items ++ * @return The list of container items ++ */ ++ virtual QList getContainerItems() ++ { return QList(); } ++ ++ /** ++ * Find an item widget by id ++ * @param id Item id ++ * @return QWidget pointer or 0 ++ */ ++ virtual QWidget* findItem(const String& id); ++ ++ /** ++ * Apply a list of parameters to all container items ++ * @return The list of parameters to apply ++ */ ++ virtual void applyAllParams(const NamedList& params); ++ ++ /** ++ * Retrieve the object identity from '_yate_identity' property or name ++ * Retrieve the object item from '_yate_widgetlistitem' property. ++ * Set 'identity' to object_identity[:item_name] ++ * @param obj The object ++ * @param identiy Destination buffer ++ */ ++ virtual void getIdentity(QObject* obj, String& identity); ++ ++ /** ++ * Update an item object and children from a list a parameters ++ * @param parent Parent object ++ * @param params The list of parameters ++ * @return True on success ++ */ ++ virtual bool setParams(QObject* parent, const NamedList& params); ++ ++ /** ++ * Get an item object's parameters ++ * @param parent The object ++ * @param params Parameter list ++ * @return True on success ++ */ ++ virtual bool getParams(QObject* parent, NamedList& params); ++ ++ /** ++ * Show or hide control busy state ++ * @param on True to show, false to hide ++ * @return True if all the operations were successfull ++ */ ++ virtual bool setBusy(bool on); ++ ++ /** ++ * Retrieve object slots ++ * @param actionSlot Action (triggerred) slot ++ * @param toggleSlot Toggled slot ++ * @param selectSlot Selection change slot ++ */ ++ virtual void getSlots(String& actionSlot, String& toggleSlot, String& selectSlot) { ++ actionSlot = SLOT(itemChildAction()); ++ toggleSlot = SLOT(itemChildToggle(bool)); ++ selectSlot = SLOT(itemChildSelect()); ++ } ++ ++ /** ++ * Select an item by its index ++ * @param index Item index to select ++ * @return True on success ++ */ ++ virtual bool setSelectIndex(int index) ++ { return false; } ++ ++ /** ++ * Retrieve the 0 based index of the current item ++ * @return The index of the current item (-1 on error or container empty) ++ */ ++ virtual int currentItemIndex() ++ { return -1; } ++ ++ /** ++ * Retrieve the number of items in container ++ * @return The number of items in container (-1 on error) ++ */ ++ virtual int itemCount() ++ { return -1; } ++ ++ /** ++ * Apply properties for QAbstractItemView descendents ++ * @param params List of parameters ++ * @param defVal Default value to set if not found or invalid ++ */ ++ virtual void applyItemViewProps(const NamedList& params); ++ ++ /** ++ * Begin item edit. The default behaviour start edit for QAbstractItemView descendants ++ * @param item Item to edit ++ * @param what Optional sub-item ++ * @return True on success ++ */ ++ virtual bool beginEdit(const String& item, const String* what = 0); ++ ++ /** ++ * Retrieve model index for a given item ++ * @param item Item to edit ++ * @param what Optional sub-item ++ * @return Model index for the item, can be invalid ++ */ ++ virtual QModelIndex modelIndex(const String& item, const String* what = 0) ++ { return QModelIndex(); } ++ ++ /** ++ * Build a child's widget menu. Connect actions to container slots ++ * @param w The widget ++ * @param params Menu params ++ * @param child Optional widget child target ++ * @param set True to set the menu, false to build it and just return it ++ * @return QMenu pointer or 0 ++ */ ++ QMenu* buildWidgetItemMenu(QWidget* w, const NamedList* params, ++ const String& child = String::empty(), bool set = true); ++ ++ /** ++ * Build a container child name ++ * @param buf Destination buffer ++ * @param name Container widget name ++ * @param item Child name ++ * @return The destination buffer ++ */ ++ static inline String& buildChildName(String& buf, const String& name, ++ const String& item) { ++ buf = name + "_" + item; ++ return buf; ++ } ++ ++ /** ++ * Build a container child name ++ * @param name Container widget name ++ * @param item Child name ++ * @return QString child name ++ */ ++ static inline QString buildQChildName(const QString& name, const QString& item) ++ { return name + "_" + item; } ++ ++ /** ++ * Build a container child name from parent property value ++ * @param dest Destination string ++ * @param parent Pointer to parent object ++ * @param prop Property name ++ * @return True on success ++ */ ++ static bool buildQChildNameProp(QString& dest, QObject* parent, const char* prop); ++ ++ /** ++ * Build a container QString child name ++ * @param name Container widget name ++ * @param item Child name ++ * @return QString child name ++ */ ++ static inline QString buildQChildName(const String& name, const String& item) { ++ String buf; ++ return QtClient::setUtf8(buildChildName(buf,name,item)); ++ } ++ ++ /** ++ * Set the list item id property to a list item object ++ * @param obj The object ++ * @param item Item id property value ++ */ ++ static inline void setListItemIdProp(QObject* obj, const QString& item) ++ { obj->setProperty("_yate_widgetlistitemid",QVariant(item)); } ++ ++ /** ++ * Retrieve the list item id property from a list item object ++ * @param obj The object ++ * @param item Destination string ++ */ ++ static inline void getListItemIdProp(QObject* obj, String& item) ++ { QtClient::getProperty(obj,"_yate_widgetlistitemid",item); } ++ ++ /** ++ * Set the list item property for an item's child object ++ * @param obj The object ++ * @param item Item property value ++ */ ++ static inline void setListItemProp(QObject* obj, const QString& item) ++ { obj->setProperty("_yate_widgetlistitem",QVariant(item)); } ++ ++ /** ++ * Retrieve the list item property from an item's child object ++ * @param obj The object ++ * @param item Destination string ++ */ ++ static inline void getListItemProp(QObject* obj, String& item) ++ { QtClient::getProperty(obj,"_yate_widgetlistitem",item); } ++ ++ /** ++ * Retrieve the top level QtUIWidget container parent of an object ++ * @param obj The object ++ * @return QtUIWidget pointer or 0 if not found ++ */ ++ static QtUIWidget* container(QObject* obj); ++ ++protected: ++ /** ++ * Default constructor ++ */ ++ QtUIWidget() ++ {} ++ ++ /** ++ * Initialize navigation controls ++ * @param params Parameter list ++ */ ++ void initNavigation(const NamedList& params); ++ ++ /** ++ * Update navigation controls ++ */ ++ void updateNavigation(); ++ ++ /** ++ * Trigger a custom action from an item. Build a list of parameters containing ++ * the 'item' and the 'list' object identity ++ * @param item The item id ++ * @param action The action name to trigger ++ * @param sender Optional sender (set it to 0 to use getQObject()) ++ * @param params Optional extra action parameters ++ * @return True if handled ++ */ ++ bool triggerAction(const String& item, const String& action, QObject* sender = 0, ++ NamedList* params = 0); ++ ++ /** ++ * Trigger a custom action from already built list params ++ * @param action The action name to trigger ++ * @param params Extra action parameters ++ * @param sender Optional sender (set it to 0 to use getQObject()) ++ * @return True if handled ++ */ ++ bool triggerAction(const String& action, NamedList& params, QObject* sender = 0); ++ ++ /** ++ * Handle a child's action. Retrieve the object identity (using getIdentity()) and ++ * notify the action 'sender_identity:sender_item_name' to the client ++ * Internally handle next/prev actions if set ++ * @param sender The sender ++ */ ++ virtual void onAction(QObject* sender); ++ ++ /** ++ * Handle a child's action. Retrieve the object identity (using getIdentity()) and ++ * notify the toggled 'sender_identity:sender_item_name' event to the client ++ * @param sender The sender ++ * @param on Toggle status ++ */ ++ virtual void onToggle(QObject* sender, bool on); ++ ++ /** ++ * Handle a child's selection change. Retrieve the object identity and ++ * notify the select 'sender_identity:sender_item_name' event to the client. ++ * @param sender The sender ++ * @param item Optional selected item if any. Set it to 0 to detect it ++ */ ++ virtual void onSelect(QObject* sender, const String* item = 0); ++ ++ /** ++ * Handle a child's multiple selection change. Retrieve the object identity and ++ * notify the select 'sender_identity:sender_item_name' event to the client. ++ * @param sender The sender ++ * @param items Optional selected items. Set it to 0 to detect it ++ */ ++ virtual void onSelectMultiple(QObject* sender, const NamedList* items = 0); ++ ++ /** ++ * Filter wathed events for children. ++ * Handle child image changing on mouse events ++ * @param watched The object ++ * @param event Event to process ++ * @return True if event filter was removed ++ */ ++ virtual bool onChildEvent(QObject* watched, QEvent* event); ++ ++ /** ++ * Load an item's widget. Rename children. ++ * Set '_yate_widgetlistitemid' widget property to given name. ++ * Set '_yate_widgetlistitem' to item for each child. ++ * Connect signals for children not having a '_yate_autoconnect' property set to false. ++ * Install event filter for children with '_yate_filterevents' property set to true. ++ * @param parent Parent widget ++ * @param name Widget name ++ * @param ui UI file to load ++ * @return QWidget pointer or 0 ++ */ ++ QWidget* loadWidget(QWidget* parent, const String& name, const String& ui); ++ ++ /** ++ * Load an item's widget using a given type ++ * @param parent Parent widget ++ * @param name Widget name ++ * @param type Item type ++ * @return QWidget pointer or 0 ++ */ ++ inline QWidget* loadWidgetType(QWidget* parent, const String& name, const String& type) { ++ QtUIWidgetItemProps* p = getItemProps(type); ++ if (p && p->m_ui) ++ return loadWidget(parent,name,p->m_ui); ++ return 0; ++ } ++ ++ /** ++ * Apply a QWidget style sheet. Replace ${name} with widget name in style ++ * @param name The widget ++ * @param style The style sheet to apply ++ */ ++ void applyWidgetStyle(QWidget* w, const String& style); ++ ++ /** ++ * Filter key press events. Retrieve an action associated with the key. ++ * Check if the object is allowed to process the key. ++ * Raise the action ++ * @param obj The object ++ * @param event QKeyEvent event to process ++ * @param filter Filter key or let the object process it ++ * @return True if processed, false if no key was filtered ++ */ ++ bool filterKeyEvent(QObject* watched, QKeyEvent* event, bool& filter); ++ ++ bool m_wndEvHooked; // Event filter already installed in parent window ++ ObjList m_itemProps; ++ QStringList m_saveProps; // List of properties to be automatically ++ // saved/restored when window owning ++ // this object is initialized/destroyed ++ // Navigation ++ String m_prev; // Goto previous item action ++ String m_next; // Goto next item action ++ String m_info; // Info widget: current index, total ... ++ String m_infoFormat; // Data to be displayed in info ++ String m_title; // Current item title widget name ++}; ++ ++/** ++ * This class encapsulates a custom QT object ++ * @short A custom QT object ++ */ ++class YQT5_API QtCustomObject : public QObject, public QtUIWidget ++{ ++ YCLASS(QtCustomObject,QtUIWidget) ++ Q_CLASSINFO("QtCustomObject","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param name Object's name ++ * @param parent Optional parent object ++ */ ++ inline QtCustomObject(const char* name, QObject* parent = 0) ++ : QObject(parent), QtUIWidget(name) ++ { setObjectName(name); } ++ ++ /** ++ * Retrieve a QObject from this one ++ * @return QObject pointer ++ */ ++ virtual QObject* getQObject() ++ { return static_cast(this); } ++ ++ /** ++ * Parent changed notification ++ */ ++ virtual void parentChanged() ++ {} ++ ++private: ++ QtCustomObject() {} // No default constructor ++}; ++ ++/** ++ * This class encapsulates a custom QT widget ++ * @short A custom QT widget ++ */ ++class YQT5_API QtCustomWidget : public QWidget, public QtUIWidget ++{ ++ YCLASS(QtCustomWidget,QtUIWidget) ++ Q_CLASSINFO("QtCustomWidget","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param name Widget's name ++ * @param parent Optional parent widget ++ */ ++ inline QtCustomWidget(const char* name, QWidget* parent = 0) ++ : QWidget(parent), QtUIWidget(name) ++ { setObjectName(name); } ++ ++ /** ++ * Retrieve a QObject from this one ++ * @return QObject pointer ++ */ ++ virtual QObject* getQObject() ++ { return static_cast(this); } ++ ++protected: ++ /** ++ * Filter events. Call parent onEventFilter(). Return QWidget's event filter ++ * Handle child image changing on mouse events ++ * @param watched The object ++ * @param event Event to process ++ */ ++ virtual bool eventFilter(QObject* watched, QEvent* event) { ++ bool ok = onChildEvent(watched,event); ++ return QWidget::eventFilter(watched,event) || ok; ++ } ++ ++private: ++ QtCustomWidget() {} // No default constructor ++}; ++ ++/** ++ * This class encapsulates a custom QT table ++ * @short A custom QT table widget ++ */ ++class YQT5_API QtTable : public QTableWidget, public QtUIWidget ++{ ++ YCLASS(QtTable,QtUIWidget) ++ Q_CLASSINFO("QtTable","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param name Table's name ++ * @param parent Optional parent widget ++ */ ++ inline QtTable(const char* name, QWidget* parent = 0) ++ : QTableWidget(parent), QtUIWidget(name) ++ { setObjectName(name); } ++ ++ /** ++ * Retrieve a QObject from this one ++ * @return QObject pointer ++ */ ++ virtual QObject* getQObject() ++ { return static_cast(this); } ++ ++protected: ++ /** ++ * Filter events. Call parent onEventFilter(). Return QWidget's event filter ++ * Handle child image changing on mouse events ++ * @param watched The object ++ * @param event Event to process ++ */ ++ virtual bool eventFilter(QObject* watched, QEvent* event) { ++ bool ok = onChildEvent(watched,event); ++ return QTableWidget::eventFilter(watched,event) || ok; ++ } ++ ++private: ++ QtTable() {} // No default constructor ++}; ++ ++/** ++ * This class encapsulates a custom QT tree ++ * @short A custom QT tree widget ++ */ ++class YQT5_API QtTree : public QTreeWidget, public QtUIWidget ++{ ++ YCLASS(QtTree,QtUIWidget) ++ Q_CLASSINFO("QtTree","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param name Tree's name ++ * @param parent Optional parent widget ++ */ ++ inline QtTree(const char* name, QWidget* parent = 0) ++ : QTreeWidget(parent), QtUIWidget(name) ++ { setObjectName(name); } ++ ++ /** ++ * Retrieve a QObject from this one ++ * @return QObject pointer ++ */ ++ virtual QObject* getQObject() ++ { return static_cast(this); } ++ ++protected: ++ /** ++ * Filter events. Call parent onEventFilter(). Return QWidget's event filter ++ * Handle child image changing on mouse events ++ * @param watched The object ++ * @param event Event to process ++ */ ++ virtual bool eventFilter(QObject* watched, QEvent* event) { ++ bool ok = onChildEvent(watched,event); ++ return QTreeWidget::eventFilter(watched,event) || ok; ++ } ++ ++private: ++ QtTree() {} // No default constructor ++}; ++ ++/** ++ * QT specific sound ++ * @short A QT client sound ++ */ ++class YQT5_API QtSound : public ClientSound ++{ ++ YCLASS(QtSound,ClientSound) ++public: ++ /** ++ * Constructor ++ * @param name The name of this object ++ * @param file The file to play (should contain the whole path and the file name) ++ * @param device Optional device used to play the file. Set to 0 to use the default one ++ */ ++ inline QtSound(const char* name, const char* file, const char* device = 0) ++ : ClientSound(name,file,device), m_sound(0) ++ { m_native = true; } ++ ++protected: ++ virtual bool doStart(); ++ virtual void doStop(); ++ ++private: ++ QSound* m_sound; ++}; ++ ++/** ++ * @short Base class for Drag&Drop operations ++ */ ++class YQT5_API QtDragAndDrop : public QObject, public GenObject ++{ ++ YCLASS(QtDragAndDrop,GenObject) ++ Q_CLASSINFO("QtDragAndDrop","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Accept drop enumeration ++ */ ++ enum AcceptDrop { ++ None = 0, ++ Always, ++ Ask, ++ }; ++ ++ /** ++ * Constructor ++ * @param parent Object parent ++ */ ++ inline QtDragAndDrop(QObject* parent) ++ : QObject(parent), ++ m_started(false) ++ {} ++ ++ /** ++ * Check if started ++ * @return True if started ++ */ ++ inline bool started() const ++ { return m_started; } ++ ++ /** ++ * Reset data ++ */ ++ virtual void reset(); ++ ++ /** ++ * Check a string value for 'drag', 'drop', 'both' ++ * @param s The string ++ * @param drag Boolean value to set if drag is enabled ++ * @param drop Boolean value to set if drop is enabled ++ */ ++ static void checkEnable(const String& s, bool& drag, bool& drop); ++ ++protected: ++ bool m_started; // Started flag ++}; ++ ++/** ++ * This class holds data used for Drop operation ++ * @short Drop data holder ++ */ ++class YQT5_API QtDrop : public QtDragAndDrop ++{ ++ YCLASS(QtDrop,QtDragAndDrop) ++ Q_CLASSINFO("QtDrop","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param parent Object parent ++ * @param params Optional pointer to object parameters ++ */ ++ QtDrop(QObject* parent, const NamedList* params = 0); ++ ++ /** ++ * Retrieve drop parameters ++ * @return Drop parameters ++ */ ++ inline NamedList& params() ++ { return m_dropParams; } ++ ++ /** ++ * Update parameters from drag enter event ++ * @param e The event ++ * @return True if accepted ++ */ ++ bool start(QDragEnterEvent& e); ++ ++ /** ++ * Reset data ++ */ ++ virtual void reset(); ++ ++ /** ++ * Get accept type ++ * @param type Type to check ++ * @param defVal Default value to return if not found ++ * @return Accept value ++ */ ++ static inline int acceptDropType(const char* type, int defVal) ++ { return lookup(type,s_acceptDropName,defVal); } ++ ++ static const String s_askClientAcceptDrop; ++ static const String s_notifyClientDrop; ++ static const QString s_fileScheme; ++ ++ static const TokenDict s_acceptDropName[]; ++ ++protected: ++ NamedList m_dropParams; // Drop parameters ++ QStringList m_schemes; // Known URL Schemes. Accept only these if not empty ++ bool m_acceptFiles; // Accept files on drop ++ bool m_acceptDirs; // Accept directories on drop ++}; ++ ++/** ++ * This class holds data used for Drop operation on widgets displaying a list of items ++ * @short Drop data holder for widget list items ++ */ ++class YQT5_API QtListDrop : public QtDrop ++{ ++ YCLASS(QtListDrop,QtDrop) ++ Q_CLASSINFO("QtListDrop","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param parent Object parent ++ * @param params Optional pointer to object parameters ++ */ ++ QtListDrop(QObject* parent, const NamedList* params = 0); ++ ++ /** ++ * Check if drop should be accepted on empty space ++ * @return True if drop should be accepted on empty space ++ */ ++ inline int acceptOnEmpty() const ++ { return m_acceptOnEmpty; } ++ ++ /** ++ * Set accept drop on empty space ++ * @param val New value for accept drop on empty space ++ */ ++ inline void setAcceptOnEmpty(int val) ++ { m_acceptOnEmpty = val; } ++ ++ /** ++ * Update accept ++ * @param list Comma separated list of item types ++ * @param type Accept drop type ++ */ ++ void updateAcceptType(const String list, int type); ++ ++ /** ++ * Update accept from parameters list ++ * @param params Parameters list ++ */ ++ void updateAccept(const NamedList& params); ++ ++ /** ++ * Check if an item type can be automatically accepted ++ * @param type Item type to check ++ * @param defVal Value to return if not found ++ * @return Accept value as AcceptDrop enumeration ++ */ ++ inline int getAcceptType(const String& type, int defVal = None) ++ { return NamedInt::lookup(m_acceptItemTypes,type,defVal);} ++ ++ /** ++ * Reset data ++ */ ++ virtual void reset(); ++ ++protected: ++ int m_acceptOnEmpty; // Accept drop on widget surface not occupied by any item ++ ObjList m_acceptItemTypes; // Item type to handle drop ++}; ++ ++/** ++ * Busy widget to show over controls ++ * @short Busy widget to show over controls ++ */ ++class YQT5_API QtBusyWidget : public QtCustomWidget ++{ ++ YCLASS(QtBusyWidget,QtCustomWidget) ++ Q_CLASSINFO("QtBusyWidget","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param parent Optional parent widget ++ */ ++ QtBusyWidget(QWidget* parent = 0); ++ ++ /** ++ * Initialize ++ * @param ui UI to load ++ * @param params Busy parameters ++ * @param target Target widget ++ */ ++ virtual void init(const String& ui, const NamedList& params, QWidget* target); ++ ++ /** ++ * Show or hide the widget ++ * @param on True to show, false to hide ++ */ ++ inline void showBusy(bool on) { ++ if (on) ++ showBusy(); ++ else ++ hideBusy(); ++ } ++ ++ /** ++ * Show the widget ++ */ ++ void showBusy(); ++ ++ /** ++ * Hide the widget ++ */ ++ void hideBusy(); ++ ++ /** ++ * Show or hide busy widget. ++ * The busy widget must be a target's child whose name is composed from ++ * target->objectName() + s_busySuffix ++ * @param target The widget to show busy ++ * @param on True to show, false to hide ++ */ ++ static inline bool showBusyChild(QWidget* target, bool on) { ++ QtBusyWidget* w = target ? target->findChild( ++ target->objectName() + s_busySuffix) : 0; ++ if (!w) ++ return false; ++ w->showBusy(on); ++ return true; ++ } ++ ++ /** ++ * Busy child name suffix ++ */ ++ static const QString s_busySuffix; ++ ++protected: ++ /** ++ * Filter wathed events ++ * @param watched The object ++ * @param event Event to process ++ * @return True if event filter was removed ++ */ ++ virtual bool onChildEvent(QObject* watched, QEvent* event); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void timerEvent(QTimerEvent* ev); ++ ++ /** ++ * Show/hide busy content ++ * @param on True to show, false to hide ++ */ ++ virtual void setContent(bool on); ++ ++ QWidget* m_target; // Widget to show over ++ bool m_shown; // Shown flag ++ unsigned int m_delayMs; // Delay show ++ int m_delayTimer; // Delay timer ++ QLabel* m_movieLabel; // Label showing animation ++ ++private: ++ inline void stopDelayTimer() { ++ if (!m_delayTimer) ++ return; ++ killTimer(m_delayTimer); ++ m_delayTimer = 0; ++ } ++ void internalShow(); ++}; ++ ++}; // namespace TelEngine ++ ++Q_DECLARE_METATYPE(TelEngine::QtRefObjectHolder) ++ ++#endif // __QT5CLIENT_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/clients/run-qt5 b/clients/run-qt5 +new file mode 100644 +index 0000000..e69a284 +--- /dev/null ++++ b/clients/run-qt5 +@@ -0,0 +1,29 @@ ++#!/bin/sh ++ ++# run-qt5 ++# This file is part of the YATE Project http://YATE.null.ro ++# ++# Yet Another Telephony Engine - a fully featured software PBX and IVR ++# Copyright (C) 2005-2020 Null Team ++# ++# This software is distributed under multiple licenses; ++# see the COPYING file in the main directory for licensing ++# information for this specific distribution. ++# ++# This use of this software may be subject to additional restrictions. ++# See the LEGAL file in the main directory for details. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ ++ ++# Script to run the Qt5 client from the build directory ++ ++if [ -x yate-qt5 -a -x ../run ]; then ++ # Need to put the path to extra Qt/KDE libraries here ++ # export LD_LIBRARY_PATH= ++ cd ..; exec ./run --executable clients/yate-qt5 "$@" ++else ++ echo "Could not find client executable or run script" >&2 ++fi +diff --git a/conf.d/yate-qt5.conf.default b/conf.d/yate-qt5.conf.default +new file mode 100644 +index 0000000..524eb83 +--- /dev/null ++++ b/conf.d/yate-qt5.conf.default +@@ -0,0 +1,15 @@ ++; This minimal file is here just to set the default skin. ++; You can replace it with a more complete version from yate.conf.sample ++ ++[localsym] ++h323chan.yate=yes ++ ++[client] ++;skin=default ++;style= ++;stylesheet_file= ++;device= ++;greeting=Yate ${version} - ${release} ++ ++[modules] ++yiaxchan.yate=no +diff --git a/configure b/configure +index 102e26d..00ad05f 100755 +--- a/configure ++++ b/configure +@@ -638,14 +638,14 @@ COREDUMPER_LIB + COREDUMPER_INC + HAVE_COREDUMPER + HAVE_MALLINFO +-QT4_STATIC_MODULES +-QT4_VER +-QT4_MOC +-QT4_LIB_NET +-QT4_INC_NET +-QT4_LIB +-QT4_INC +-HAVE_QT4 ++QT5_STATIC_MODULES ++QT5_VER ++QT5_MOC ++QT5_LIB_NET ++QT5_INC_NET ++QT5_LIB ++QT5_INC ++HAVE_QT5 + LIBUSB_LIB + LIBUSB_INC + HAVE_LIBUSB +@@ -824,7 +824,7 @@ with_openh323 + with_openssl + with_zlib + with_libusb +-with_libqt4 ++with_libqt5 + with_qtstatic + enable_mallinfo + with_coredumper +@@ -1496,7 +1496,7 @@ Optional Packages: + --with-zlib=DIR use zlib for data (de)compression from DIR (default + /usr) + --with-libusb=DIR use libusb DIR (default /usr) +- --with-libqt4 use Qt for graphical clients (default) ++ --with-libqt5 use Qt for graphical clients (default) + --with-qtstatic=MODULES link specific modules with static Qt + --with-coredumper use Google coredumper if available (default) + --with-doxygen=EXE use doxygen to generate API docs (default: PATH) +@@ -7750,20 +7750,20 @@ fi + + + +-HAVE_QT4=no +-QT4_INC="" +-QT4_LIB="" +-QT4_INC_NET="" +-QT4_LIB_NET="" +-QT4_MOC="" +-QT4_VER="" +-QT4_STATIC_MODULES="" ++HAVE_QT5=no ++QT5_INC="" ++QT5_LIB="" ++QT5_INC_NET="" ++QT5_LIB_NET="" ++QT5_MOC="" ++QT5_VER="" ++QT5_STATIC_MODULES="" + +-# Check whether --with-libqt4 was given. +-if test "${with_libqt4+set}" = set; then : +- withval=$with_libqt4; ac_cv_use_libqt4=$withval ++# Check whether --with-libqt5 was given. ++if test "${with_libqt5+set}" = set; then : ++ withval=$with_libqt5; ac_cv_use_libqt5=$withval + else +- ac_cv_use_libqt4=yes ++ ac_cv_use_libqt5=yes + fi + + +@@ -7778,31 +7778,31 @@ qtstatic="" + if [ "x$ac_cv_use_qtstatic" != "xno" ]; then + qtstatic="--static" + fi +-QT4_STATIC_MODULES=`echo "$ac_cv_use_qtstatic" | sed 's/,/ /g'` +-if [ "x$ac_cv_use_libqt4" = "xyes" ]; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt4 >= 4.3.0 using pkg-config" >&5 +-$as_echo_n "checking for Qt4 >= 4.3.0 using pkg-config... " >&6; } +- pkgd="/usr/lib/qt4/$ARCHLIB/pkgconfig" ++QT5_STATIC_MODULES=`echo "$ac_cv_use_qtstatic" | sed 's/,/ /g'` ++if [ "x$ac_cv_use_libqt5" = "xyes" ]; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt5 >= 5.0.0 using pkg-config" >&5 ++$as_echo_n "checking for Qt5 >= 5.0.0 using pkg-config... " >&6; } ++ pkgd="/usr/lib/qt5/$ARCHLIB/pkgconfig" + verqt=`(pkg-config --modversion QtCore) 2>/dev/null` + if [ -z "$verqt" -a -d "$pkgd" ]; then + export PKG_CONFIG_LIBDIR="$pkgd" + verqt=`(pkg-config --modversion QtCore) 2>/dev/null` + fi +- incqt=`(pkg-config --cflags QtNetwork QtGui QtXml QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` +- libqt=`(pkg-config --libs $qtstatic QtNetwork QtGui QtXml QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` ++ incqt=`(pkg-config --cflags QtNetwork QtGui QtWidgets QtXml QtMultimedia QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` ++ libqt=`(pkg-config --libs $qtstatic QtNetwork QtGui QtWidgets QtXml QtMultimedia QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` + if [ "x$incqt" != "x" -a "x$libqt" != "x" ]; then +- HAVE_QT4=yes +- QT4_INC="$incqt" +- QT4_LIB="$libqt" +- QT4_INC_NET=`(pkg-config --cflags QtNetwork) 2>/dev/null | grep -o '[^ ]*QtNetwork[^ ]*'` +- QT4_LIB_NET=`(pkg-config --libs QtNetwork) 2>/dev/null | grep -o '[^ ]*QtNetwork[^ ]*'` +- QT4_MOC=`echo "$incqt" | sed -n 's,^.*-I\([^ ]\+\)/include .*$,\1/bin/moc,p'` +- test -z "$QT4_MOC" && QT4_MOC=`(which moc-qt4) 2>/dev/null` +- test -z "$QT4_MOC" && QT4_MOC="moc" +- QT4_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` ++ HAVE_QT5=yes ++ QT5_INC="$incqt" ++ QT5_LIB="$libqt" ++ QT5_INC_NET=`(pkg-config --cflags QtNetwork) 2>/dev/null | grep -o '[^ ]*QtNetwork[^ ]*'` ++ QT5_LIB_NET=`(pkg-config --libs QtNetwork) 2>/dev/null | grep -o '[^ ]*QtNetwork[^ ]*'` ++ QT5_MOC=`echo "$incqt" | sed -n 's,^.*-I\([^ ]\+\)/include .*$,\1/bin/moc,p'` ++ test -z "$QT5_MOC" && QT5_MOC=`(which moc-qt5) 2>/dev/null` ++ test -z "$QT5_MOC" && QT5_MOC="moc" ++ QT5_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` + ac_cv_use_libqt="no" +- if [ 1$QT4_VER -lt 1040300 ]; then +- HAVE_QT4=no ++ if [ 1$QT5_VER -lt 1050000 ]; then ++ HAVE_QT5=no + verqt="too old ($verqt)" + fi + else +@@ -7812,9 +7812,9 @@ $as_echo_n "checking for Qt4 >= 4.3.0 using pkg-config... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $verqt" >&5 + $as_echo "$verqt" >&6; } + +- if [ "x$HAVE_QT4" = "xno" ]; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt4 >= 4.3.0 using qmake" >&5 +-$as_echo_n "checking for Qt4 >= 4.3.0 using qmake... " >&6; } ++ if [ "x$HAVE_QT0" = "xno" ]; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt0 >= 5.0.0 using qmake" >&5 ++$as_echo_n "checking for Qt5 >= 5.0.0 using qmake... " >&6; } + incqt=`(qmake -query QT_INSTALL_HEADERS) 2>/dev/null` + libqt=`(qmake -query QT_INSTALL_LIBS) 2>/dev/null` + if [ "x$incqt" = "x**Unknown**" -o "x$libqt" = "x**Unknown**" ]; then +@@ -7822,33 +7822,33 @@ $as_echo_n "checking for Qt4 >= 4.3.0 using qmake... " >&6; } + libqt="" + fi + if [ "x$incqt" != "x" -a "x$libqt" != "x" ]; then +- HAVE_QT4=yes +- QT4_INC="-I$incqt -I$incqt/QtUiTools -I$incqt/QtGui -I$incqt/QtXml -I$incqt/QtCore" ++ HAVE_QT5=yes ++ QT5_INC="-I$incqt -I$incqt/QtUiTools -I$incqt/QtGui -I$incqt/QtWidgets -I$incqt/QtXml -I$incqt/QtMultimedia -I$incqt/QtCore" + case "$uname_os" in + *Darwin) +- QT4_INC="-D__USE_WS_X11__ $QT4_INC" ++ QT5_INC="-D__USE_WS_X11__ $QT5_INC" + ;; + esac +- QT4_LIB="-L$libqt -lQtUiTools -lQtGui -lQtXml -lQtCore" +- QT4_INC_NET="-I$incqt/QtNetwork" +- QT4_LIB_NET="-L$libqt -lQtNetwork" ++ QT5_LIB="-L$libqt -lQt5UiTools -lQt5Gui -lQt5Widgets -lQt5Xml -lQt5Multimedia -lQt5Core" ++ QT5_INC_NET="-I$incqt/QtNetwork" ++ QT5_LIB_NET="-L$libqt -lQtNetwork" + case "$uname_os" in + *Darwin) + framework=`(ls "$libqt" | grep QtGui.framework) 2>/dev/null` + if [ "x$framework" != "x" ]; then +- QT4_INC="-I$incqt -I$incqt/QtUiTools -I$libqt/QtGui.framework/Headers -I$libqt/QtXml.framework/Headers -I$libqt/QtCore.framework/Headers" +- QT4_LIB="-L$libqt -F$libqt -lQtUiTools -framework QtGui -framework QtXml -framework QtCore" +- QT4_INC_NET="-I$libqt/QtNetwork.framework/Headers" +- QT4_LIB_NET="-framework QtNetwork" ++ QT5_INC="-I$incqt -I$incqt/QtUiTools -I$libqt/QtGui.framework/Headers -I$libqt/QtXml.framework/Headers -I$libqt/QtCore.framework/Headers" ++ QT5_LIB="-L$libqt -F$libqt -lQtUiTools -framework QtGui -framework QtXml -framework QtCore" ++ QT5_INC_NET="-I$libqt/QtNetwork.framework/Headers" ++ QT5_LIB_NET="-framework QtNetwork" + fi + ;; + esac +- QT4_MOC=`(qmake -query QT_INSTALL_BINS) 2>/dev/null` +- QT4_MOC="$QT4_MOC/moc" ++ QT5_MOC=`(qmake -query QT_INSTALL_BINS) 2>/dev/null` ++ QT5_MOC="$QT5_MOC/moc" + verqt=`(qmake -query QT_VERSION) 2>/dev/null` +- QT4_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` +- if [ 1$QT4_VER -lt 1040300 ]; then +- HAVE_QT4=no ++ QT5_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` ++ if [ 1$QT5_VER -lt 1050000 ]; then ++ HAVE_QT5=no + verqt="too old ($verqt)" + fi + else +@@ -8097,7 +8097,7 @@ fi + + + +-ac_config_files="$ac_config_files packing/rpm/yate.spec packing/portage/yate.ebuild yate.pc yateversn.h yateiss.inc Makefile engine/Makefile modules/Makefile modules/test/Makefile clients/Makefile clients/qt4/Makefile libs/ilbc/Makefile libs/ysip/Makefile libs/yrtp/Makefile libs/ysdp/Makefile libs/yiax/Makefile libs/yjabber/Makefile libs/yscript/Makefile libs/ymgcp/Makefile libs/ysig/Makefile libs/ypbx/Makefile libs/ymodem/Makefile libs/yasn/Makefile libs/ysnmp/Makefile libs/miniwebrtc/Makefile libs/yradio/Makefile share/Makefile share/scripts/Makefile share/skins/Makefile share/sounds/Makefile share/help/Makefile share/data/Makefile conf.d/Makefile" ++ac_config_files="$ac_config_files packing/rpm/yate.spec packing/portage/yate.ebuild yate.pc yateversn.h yateiss.inc Makefile engine/Makefile modules/Makefile modules/test/Makefile clients/Makefile clients/qt5/Makefile libs/ilbc/Makefile libs/ysip/Makefile libs/yrtp/Makefile libs/ysdp/Makefile libs/yiax/Makefile libs/yjabber/Makefile libs/yscript/Makefile libs/ymgcp/Makefile libs/ysig/Makefile libs/ypbx/Makefile libs/ymodem/Makefile libs/yasn/Makefile libs/ysnmp/Makefile libs/miniwebrtc/Makefile libs/yradio/Makefile share/Makefile share/scripts/Makefile share/skins/Makefile share/sounds/Makefile share/help/Makefile share/data/Makefile conf.d/Makefile" + + ac_config_files="$ac_config_files yate-config" + +@@ -8822,7 +8822,7 @@ do + "modules/Makefile") CONFIG_FILES="$CONFIG_FILES modules/Makefile" ;; + "modules/test/Makefile") CONFIG_FILES="$CONFIG_FILES modules/test/Makefile" ;; + "clients/Makefile") CONFIG_FILES="$CONFIG_FILES clients/Makefile" ;; +- "clients/qt4/Makefile") CONFIG_FILES="$CONFIG_FILES clients/qt4/Makefile" ;; ++ "clients/qt5/Makefile") CONFIG_FILES="$CONFIG_FILES clients/qt5/Makefile" ;; + "libs/ilbc/Makefile") CONFIG_FILES="$CONFIG_FILES libs/ilbc/Makefile" ;; + "libs/ysip/Makefile") CONFIG_FILES="$CONFIG_FILES libs/ysip/Makefile" ;; + "libs/yrtp/Makefile") CONFIG_FILES="$CONFIG_FILES libs/yrtp/Makefile" ;; +diff --git a/configure.ac b/configure.ac +index 3c5b90e..77e4287 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1598,44 +1598,44 @@ AC_SUBST(LIBUSB_INC) + AC_SUBST(LIBUSB_LIB) + + +-HAVE_QT4=no +-QT4_INC="" +-QT4_LIB="" +-QT4_INC_NET="" +-QT4_LIB_NET="" +-QT4_MOC="" +-QT4_VER="" +-QT4_STATIC_MODULES="" +-AC_ARG_WITH(libqt4,AC_HELP_STRING([--with-libqt4],[use Qt for graphical clients (default)]),[ac_cv_use_libqt4=$withval],[ac_cv_use_libqt4=yes]) ++HAVE_QT5=no ++QT5_INC="" ++QT5_LIB="" ++QT5_INC_NET="" ++QT5_LIB_NET="" ++QT5_MOC="" ++QT5_VER="" ++QT5_STATIC_MODULES="" ++AC_ARG_WITH(libqt5,AC_HELP_STRING([--with-libqt5],[use Qt for graphical clients (default)]),[ac_cv_use_libqt5=$withval],[ac_cv_use_libqt5=yes]) + AC_ARG_WITH(qtstatic,AC_HELP_STRING([--with-qtstatic=MODULES],[link specific modules with static Qt]),[ac_cv_use_qtstatic=$withval],[ac_cv_use_qtstatic=no]) + qtstatic="" + if [[ "x$ac_cv_use_qtstatic" != "xno" ]]; then + qtstatic="--static" + fi +-QT4_STATIC_MODULES=`echo "$ac_cv_use_qtstatic" | sed 's/,/ /g'` +-if [[ "x$ac_cv_use_libqt4" = "xyes" ]]; then +- AC_MSG_CHECKING([for Qt4 >= 4.3.0 using pkg-config]) +- pkgd="/usr/lib/qt4/$ARCHLIB/pkgconfig" ++QT5_STATIC_MODULES=`echo "$ac_cv_use_qtstatic" | sed 's/,/ /g'` ++if [[ "x$ac_cv_use_libqt5" = "xyes" ]]; then ++ AC_MSG_CHECKING([for Qt5 >= 5.0.0 using pkg-config]) ++ pkgd="/usr/lib/qt5/$ARCHLIB/pkgconfig" + verqt=`(pkg-config --modversion QtCore) 2>/dev/null` + if [[ -z "$verqt" -a -d "$pkgd" ]]; then + export PKG_CONFIG_LIBDIR="$pkgd" + verqt=`(pkg-config --modversion QtCore) 2>/dev/null` + fi +- incqt=`(pkg-config --cflags QtNetwork QtGui QtXml QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` +- libqt=`(pkg-config --libs $qtstatic QtNetwork QtGui QtXml QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` ++ incqt=`(pkg-config --cflags QtNetwork QtGui QtWidgets QtXml QtMultimedia QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` ++ libqt=`(pkg-config --libs $qtstatic QtNetwork QtGui QtWidgets QtXml QtMultimedia QtCore) 2>/dev/null | sed 's/QtNetwork/QtUiTools/'` + if [[ "x$incqt" != "x" -a "x$libqt" != "x" ]]; then +- HAVE_QT4=yes +- QT4_INC="$incqt" +- QT4_LIB="$libqt" +- QT4_INC_NET=`(pkg-config --cflags QtNetwork) 2>/dev/null | grep -o '[[^ ]]*QtNetwork[[^ ]]*'` +- QT4_LIB_NET=`(pkg-config --libs QtNetwork) 2>/dev/null | grep -o '[[^ ]]*QtNetwork[[^ ]]*'` +- QT4_MOC=`echo "$incqt" | sed -n 's,^.*-I\([[^ ]]\+\)/include .*$,\1/bin/moc,p'` +- test -z "$QT4_MOC" && QT4_MOC=`(which moc-qt4) 2>/dev/null` +- test -z "$QT4_MOC" && QT4_MOC="moc" +- QT4_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` ++ HAVE_QT5=yes ++ QT5_INC="$incqt" ++ QT5_LIB="$libqt" ++ QT5_INC_NET=`(pkg-config --cflags QtNetwork) 2>/dev/null | grep -o '[[^ ]]*QtNetwork[[^ ]]*'` ++ QT5_LIB_NET=`(pkg-config --libs QtNetwork) 2>/dev/null | grep -o '[[^ ]]*QtNetwork[[^ ]]*'` ++ QT5_MOC=`echo "$incqt" | sed -n 's,^.*-I\([[^ ]]\+\)/include .*$,\1/bin/moc,p'` ++ test -z "$QT5_MOC" && QT5_MOC=`(which moc-qt5) 2>/dev/null` ++ test -z "$QT5_MOC" && QT5_MOC="moc" ++ QT5_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` + ac_cv_use_libqt="no" +- if [[ 1$QT4_VER -lt 1040300 ]]; then +- HAVE_QT4=no ++ if [[ 1$QT5_VER -lt 1040300 ]]; then ++ HAVE_QT5=no + verqt="too old ($verqt)" + fi + else +@@ -1644,8 +1644,8 @@ if [[ "x$ac_cv_use_libqt4" = "xyes" ]]; then + unset PKG_CONFIG_LIBDIR + AC_MSG_RESULT([$verqt]) + +- if [[ "x$HAVE_QT4" = "xno" ]]; then +- AC_MSG_CHECKING([for Qt4 >= 4.3.0 using qmake]) ++ if [[ "x$HAVE_QT5" = "xno" ]]; then ++ AC_MSG_CHECKING([for Qt5 >= 5.0.0 using qmake]) + incqt=`(qmake -query QT_INSTALL_HEADERS) 2>/dev/null` + libqt=`(qmake -query QT_INSTALL_LIBS) 2>/dev/null` + if [[ "x$incqt" = "x**Unknown**" -o "x$libqt" = "x**Unknown**" ]]; then +@@ -1653,33 +1653,33 @@ if [[ "x$ac_cv_use_libqt4" = "xyes" ]]; then + libqt="" + fi + if [[ "x$incqt" != "x" -a "x$libqt" != "x" ]]; then +- HAVE_QT4=yes +- QT4_INC="-I$incqt -I$incqt/QtUiTools -I$incqt/QtGui -I$incqt/QtXml -I$incqt/QtCore" ++ HAVE_QT5=yes ++ QT5_INC="-I$incqt -I$incqt/QtUiTools -I$incqt/QtGui -I$incqt/QtWidgets -I$incqt/QtXml -I$incqt/QtMultimedia -I$incqt/QtCore" + case "$uname_os" in + *Darwin) +- QT4_INC="-D__USE_WS_X11__ $QT4_INC" ++ QT5_INC="-D__USE_WS_X11__ $QT5_INC" + ;; + esac +- QT4_LIB="-L$libqt -lQtUiTools -lQtGui -lQtXml -lQtCore" +- QT4_INC_NET="-I$incqt/QtNetwork" +- QT4_LIB_NET="-L$libqt -lQtNetwork" ++ QT5_LIB="-L$libqt -lQt5UiTools -lQt5Gui -lQt5Widgets -lQt5Xml -lQt5Multimedia -lQt5Core" ++ QT5_INC_NET="-I$incqt/QtNetwork" ++ QT5_LIB_NET="-L$libqt -lQt5Network" + case "$uname_os" in + *Darwin) + framework=`(ls "$libqt" | grep QtGui.framework) 2>/dev/null` + if [[ "x$framework" != "x" ]]; then +- QT4_INC="-I$incqt -I$incqt/QtUiTools -I$libqt/QtGui.framework/Headers -I$libqt/QtXml.framework/Headers -I$libqt/QtCore.framework/Headers" +- QT4_LIB="-L$libqt -F$libqt -lQtUiTools -framework QtGui -framework QtXml -framework QtCore" +- QT4_INC_NET="-I$libqt/QtNetwork.framework/Headers" +- QT4_LIB_NET="-framework QtNetwork" ++ QT5_INC="-I$incqt -I$incqt/QtUiTools -I$libqt/QtGui.framework/Headers -I$libqt/QtXml.framework/Headers -I$libqt/QtCore.framework/Headers" ++ QT5_LIB="-L$libqt -F$libqt -lQtUiTools -framework QtGui -framework QtXml -framework QtCore" ++ QT5_INC_NET="-I$libqt/QtNetwork.framework/Headers" ++ QT5_LIB_NET="-framework QtNetwork" + fi + ;; + esac +- QT4_MOC=`(qmake -query QT_INSTALL_BINS) 2>/dev/null` +- QT4_MOC="$QT4_MOC/moc" ++ QT5_MOC=`(qmake -query QT_INSTALL_BINS) 2>/dev/null` ++ QT5_MOC="$QT5_MOC/moc" + verqt=`(qmake -query QT_VERSION) 2>/dev/null` +- QT4_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` +- if [[ 1$QT4_VER -lt 1040300 ]]; then +- HAVE_QT4=no ++ QT5_VER=`echo "$verqt" | $csed "$vsed" | $csed "$vsed" | sed 's/\(..\)\.\(..\)\.\(..\)/\1\2\3/'` ++ if [[ 1$QT5_VER -lt 1050000 ]]; then ++ HAVE_QT5=no + verqt="too old ($verqt)" + fi + else +@@ -1689,14 +1689,14 @@ if [[ "x$ac_cv_use_libqt4" = "xyes" ]]; then + fi + fi + +-AC_SUBST(HAVE_QT4) +-AC_SUBST(QT4_INC) +-AC_SUBST(QT4_LIB) +-AC_SUBST(QT4_INC_NET) +-AC_SUBST(QT4_LIB_NET) +-AC_SUBST(QT4_MOC) +-AC_SUBST(QT4_VER) +-AC_SUBST(QT4_STATIC_MODULES) ++AC_SUBST(HAVE_QT5) ++AC_SUBST(QT5_INC) ++AC_SUBST(QT5_LIB) ++AC_SUBST(QT5_INC_NET) ++AC_SUBST(QT5_LIB_NET) ++AC_SUBST(QT5_MOC) ++AC_SUBST(QT5_VER) ++AC_SUBST(QT5_STATIC_MODULES) + + + HAVE_MALLINFO=no +@@ -1872,7 +1872,7 @@ AC_CONFIG_FILES([packing/rpm/yate.spec + modules/Makefile + modules/test/Makefile + clients/Makefile +- clients/qt4/Makefile ++ clients/qt5/Makefile + libs/ilbc/Makefile + libs/ysip/Makefile + libs/yrtp/Makefile +diff --git a/modules/Makefile.in b/modules/Makefile.in +index e1d6439..f6d7cf3 100644 +--- a/modules/Makefile.in ++++ b/modules/Makefile.in +@@ -13,12 +13,12 @@ YATE_REVISION:= @PACKAGE_REVISION@ + + CC := @CC@ -Wall + CXX := @CXX@ -Wall +-MOC := @QT4_MOC@ +-QT4_INC := @QT4_INC@ +-QT4_LIB := @QT4_LIB@ +-QT4_INC_NET := @QT4_INC_NET@ +-QT4_LIB_NET := @QT4_LIB_NET@ +-QT4_STATIC_MODULES := ++MOC := @QT5_MOC@ ++QT5_INC := @QT5_INC@ ++QT5_LIB := @QT5_LIB@ ++QT5_INC_NET := @QT5_INC_NET@ ++QT5_LIB_NET := @QT5_LIB_NET@ ++QT5_STATIC_MODULES := + HAVE_PGSQL := @HAVE_PGSQL@ + PGSQL_INC := @PGSQL_INC@ + PGSQL_LIB := @PGSQL_LIB@ +@@ -94,7 +94,7 @@ PROGS := cdrbuild.yate cdrcombine.yate cdrfile.yate regexroute.yate \ + radio/dummyradio.yate radio/radiotest.yate + + LIBS := +-DIRS := client server jabber qt4 sip sig radio ++DIRS := client server jabber qt5 sip sig radio + + ifneq ($(HAVE_PGSQL),no) + PROGS := $(PROGS) server/pgsqldb.yate +@@ -124,10 +124,10 @@ ifneq (@HAVE_COREAUDIO@,no) + PROGS := $(PROGS) client/coreaudio.yate + endif + +-ifneq (@HAVE_QT4@,no) +-ifeq (@QT4_STATIC_MODULES@,no) +-PROGS := $(PROGS) qt4/updater.yate qt4/customtable.yate qt4/customtext.yate \ +- qt4/customtree.yate qt4/widgetlist.yate qt4/clientarchive.yate ++ifneq (@HAVE_QT5@,no) ++ifeq (@QT5_STATIC_MODULES@,no) ++PROGS := $(PROGS) qt5/updater.yate qt5/customtable.yate qt5/customtext.yate \ ++ qt5/customtree.yate qt5/widgetlist.yate qt5/clientarchive.yate + endif + endif + +@@ -244,7 +244,7 @@ strip: all do-strip + + .PHONY: clean + clean: do-clean +- @-$(RM) $(PROGS) $(LIBS) *.o qt4/*.o qt4/*.moc core 2>/dev/null ++ @-$(RM) $(PROGS) $(LIBS) *.o qt5/*.o qt5/*.moc core 2>/dev/null + @-for i in $(PROGS) ; do \ + $(RM) -rf $$i.dSYM 2>/dev/null; \ + done; +@@ -273,9 +273,9 @@ uninstall: do-uninstall + subdirs: + @mkdir -p $(DIRS) + +-qt4/%.o: @srcdir@/qt4/%.cpp $(MKDEPS) $(INCFILES) ++qt5/%.o: @srcdir@/qt5/%.cpp $(MKDEPS) $(INCFILES) + $(MAKE) $(patsubst %.o,%.moc,$@) +- $(COMPILE) -c -o $@ $(QT4_INC) -I@top_srcdir@/clients/qt4 -I@srcdir@/qt4 $< ++ $(COMPILE) -c -o $@ $(QT5_INC) -I@top_srcdir@/clients/qt5 -I@srcdir@/qt5 $< + + %.o: @srcdir@/%.cpp $(MKDEPS) $(INCFILES) + $(COMPILE) -c $< +@@ -302,12 +302,12 @@ server/%.yate: @srcdir@/server/%.cpp $(MKDEPS) $(INCFILES) + client/%.yate: @srcdir@/client/%.cpp $(MKDEPS) $(INCFILES) + mkdir -p client && $(MODCOMP) -o $@ $(LOCALFLAGS) $(EXTERNFLAGS) $< $(LOCALLIBS) $(YATELIBS) $(EXTERNLIBS) + +-qt4/%.yate: @srcdir@/qt4/%.cpp ../libyateqt4.so $(MKDEPS) $(INCFILES) ++qt5/%.yate: @srcdir@/qt5/%.cpp ../libyateqt5.so $(MKDEPS) $(INCFILES) + $(MAKE) $(patsubst %.yate,%.moc,$@) +- $(MODCOMP) -o $@ $(LOCALFLAGS) $(QT4_INC) $(EXTERNFLAGS) -I@top_srcdir@/clients/qt4 -Iqt4 $< $(LOCALLIBS) ../libyateqt4.so $(YATELIBS) $(QT4_LIB) $(EXTERNLIBS) ++ $(MODCOMP) -o $@ $(LOCALFLAGS) $(QT5_INC) $(EXTERNFLAGS) -I@top_srcdir@/clients/qt5 -Iqt5 $< $(LOCALLIBS) ../libyateqt5.so $(YATELIBS) $(QT5_LIB) $(EXTERNLIBS) + +-qt4/%.moc: @srcdir@/qt4/%.h $(MKDEPS) $(INCFILES) +- mkdir -p qt4 && $(MOC) $(DEFS) $(INCLUDES) $(QT4_INC) -I@top_srcdir@/clients/qt4 -I@srcdir@/qt4 -o $@ $< ++qt5/%.moc: @srcdir@/qt5/%.h $(MKDEPS) $(INCFILES) ++ mkdir -p qt5 && $(MOC) $(DEFS) $(INCLUDES) $(QT5_INC) -I@top_srcdir@/clients/qt5 -I@srcdir@/qt5 -o $@ $< + + sig/%.yate: @srcdir@/sig/%.cpp $(MKDEPS) $(INCFILES) + mkdir -p sig && $(MODCOMP) -o $@ $(LOCALFLAGS) $(EXTERNFLAGS) $< $(LOCALLIBS) $(YATELIBS) $(EXTERNLIBS) +@@ -418,8 +418,8 @@ openssl.yate: EXTERNLIBS = $(OPENSSL_LIB) + rmanager.yate: EXTERNFLAGS = $(COREDUMP_INC) $(MALLINFO_DEF) + rmanager.yate: EXTERNLIBS = $(COREDUMP_LIB) + +-qt4/updater.yate: EXTERNFLAGS = $(QT4_INC_NET) +-qt4/updater.yate: EXTERNLIBS = $(QT4_LIB_NET) ++qt5/updater.yate: EXTERNFLAGS = $(QT5_INC_NET) ++qt5/updater.yate: EXTERNLIBS = $(QT5_LIB_NET) + + javascript.yate: ../libyatescript.so ../libs/ypbx/libyatepbx.a + javascript.yate: LOCALFLAGS = -I@top_srcdir@/libs/yscript -I@top_srcdir@/libs/ypbx +@@ -474,8 +474,8 @@ radio/ybladerf.yate: EXTERNLIBS = $(LIBUSB_LIB) + ../libs/ypbx/libyatepbx.a: @top_srcdir@/libs/ypbx/yatepbx.h + $(MAKE) -C ../libs/ypbx + +-../libyateqt4.so: @top_srcdir@/clients/qt4/qt4client.h +- $(MAKE) -C ../clients/qt4 ++../libyateqt5.so: @top_srcdir@/clients/qt5/qt5client.h ++ $(MAKE) -C ../clients/qt5 + + ../libyateasn.so ../libs/yasn/libyasn.a: @top_srcdir@/libs/yasn/yateasn.h + $(MAKE) -C ../libs/yasn +diff --git a/modules/qt5/clientarchive.cpp b/modules/qt5/clientarchive.cpp +new file mode 100644 +index 0000000..06902c3 +--- /dev/null ++++ b/modules/qt5/clientarchive.cpp +@@ -0,0 +1,2117 @@ ++/** ++ * clientarchive.cpp ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * Client archive management and UI logic ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++/** ++ * Chat log file format ++ * ++ * Header: ++ * versionNULLaccountNULLcontactNULLcontact_nameNULL{MARKUP_CHAT|MARKUP_ROOMCHAT|MARKUP_ROOMCHATPRIVATE}NULLNULL ++ * Session: ++ * MARKUP_SESSIONSTARTsession_timeMARKUP_SESSIONDESCdescNULLNULL ++ * Session items: ++ * item_time{MARKUP_SENT|MARKUP_RECEIVED|MARKUP_DELAYED}sender_nameNULLchat_textNULLNULL ++*/ ++ ++#include "clientarchive.h" ++ ++namespace { //anonymous ++using namespace TelEngine; ++ ++class CASearchThread; // Archive search worker thread ++class CARefreshThread; // Archive refresh worker thread ++class ChatSession; // A chat session entry ++class ChatItem; // A chat session item ++class ChatFile; // A contact's chat file ++class ChatArchive; // Chat archive management ++class CALogic; ++ ++#define READ_BUFFER 8192 // File read buffer ++ ++// Markups used in archive files ++#define MARKUP_SESSION_START '%' // Session start ++#define MARKUP_SESSION_DESC '!' // Session description start ++#define MARKUP_SENT '>' // Sent item ++#define MARKUP_RECV '<' // Received item ++#define MARKUP_DELAYED '|' // Delayed item ++#define MARKUP_CHAT 'c' // Regular chat ++#define MARKUP_ROOMCHAT 'r' // MUC room chat ++#define MARKUP_ROOMCHATPRIVATE 'p' // MUC private chat ++ ++enum CASearchRange { ++ CASearchRangeInvalid = 0, ++ CASearchRangeSession, ++ CASearchRangeContact, ++ CASearchRangeAll ++}; ++ ++// Archive search worker thread ++class CASearchThread : public Thread ++{ ++public: ++ CASearchThread(); ++ ~CASearchThread(); ++ void startSearching(const String& text, bool next); ++ virtual void run(); ++private: ++ void resetSearch(); ++ void searchAll(const String& what); ++ void searchCurrentContact(const String& what); ++ bool searchContact(ChatFile* f, const String& what, bool changed); ++ ++ bool m_startSearch; // Start search flag ++ bool m_searching; // Currently searching ++ bool m_next; ++ String m_what; ++ CASearchRange m_range; ++ String m_currentContact; ++ String m_currentSession; ++ bool m_currentSessionFull; ++ bool m_currentContactFull; ++}; ++ ++// Archive refresh worker thread ++class CARefreshThread : public Thread ++{ ++public: ++ CARefreshThread(); ++ ~CARefreshThread(); ++ virtual void run(); ++}; ++ ++// A chat session entry ++class ChatSession : public String ++{ ++public: ++ inline ChatSession(const String& id, const String& name, int64_t offset) ++ : String(id), m_name(name), m_offset(offset), m_length(0) ++ {} ++ String m_name; ++ String m_desc; // Description ++ int64_t m_offset; // File offset ++ int64_t m_length; // Session length (including header) ++}; ++ ++// A chat session entry ++class ChatItem : public GenObject ++{ ++public: ++ inline ChatItem(unsigned int time, int t) ++ : m_time(time), m_type(t) ++ {} ++ unsigned int m_time; // Entry time ++ int m_type; // Type ++ String m_senderName; // Sender name ++ String m_text; // Content ++ QString m_search; // QString to be used when searching ++}; ++ ++// A contact's chat (including the file) ++class ChatFile : public Mutex, public RefObject ++{ ++ friend class ChatArchive; ++public: ++ // File version. Old versions must be inserted before Current ++ enum Version { ++ Invalid = 0, ++ Current, ++ }; ++ // Init object ++ ChatFile(const String& dir, const String& fileName); ++ // Retrieve the file type ++ inline char type() const ++ { return m_type; } ++ // Retrieve the file account. Lock it before use ++ inline const String& account() const ++ { return m_account; } ++ // Retrieve the file contact. Lock it before use ++ inline const String& contact() const ++ { return m_contact; } ++ // Retrieve the file contact name. Lock it before use ++ inline const String& contactName() const ++ { return m_contactName; } ++ // Retrieve the file contact display name. Lock it before use ++ inline const String& contactDisplayName() const ++ { return m_contactName ? m_contactName : m_contact; } ++ // Retrieve the id of the room owning a private chat. Lock it before use ++ inline const String& roomId() const ++ { return m_roomId; } ++ // Retrieve the file sessions. Lock it before use ++ inline const ObjList& sessions() const ++ { return m_sessions; } ++ // Load the file. Created it if not found and params are given ++ // This method is thread safe ++ virtual bool loadFile(const NamedList* params, String* error); ++ // Write chat to file ++ // This method is thread safe ++ virtual bool writeChat(const NamedList& params); ++ // Load sessions from file ++ // This method is thread safe ++ virtual bool loadSessions(bool forceLoad = false, String* error = 0); ++ // Load a session from file ++ // This method is thread safe ++ virtual bool loadSession(const String& id, ObjList& list, String* error = 0, ++ QString* search = 0); ++ // Retrieve the last session. Lock the object before use ++ virtual ChatSession* lastSession(); ++ // Close current write session. Load it if sessions were loaded ++ // This method is thread safe ++ virtual bool closeSession(); ++ // Decode a ChatItem from a given buffer. Return it on success ++ ChatItem* decodeChat(bool search, int64_t offset, void* buffer, unsigned int len); ++ // Retrieve the id ++ virtual const String& toString() const ++ { return m_fileName; } ++protected: ++ virtual void destroyed() { ++ closeSession(); ++ RefObject::destroyed(); ++ } ++ // Set file last error. Close it if requested. Return false ++ bool setFileError(String* error, const char* oper, bool close = false, ++ bool del = false); ++ // Show a chat entry format error ++ inline void showEntryError(int level, const char* oper, int64_t offset) { ++ Debug(ClientDriver::self(),level, ++ "File '%s' chat entry (offset " FMT64 ") error: %s", ++ m_full.c_str(),offset,oper); ++ } ++ // Set file pos ++ inline bool seekFile(int64_t offset, String* error) { ++ bool ok = m_file.seek(Socket::SeekBegin,offset) >= 0; ++ if (!ok) ++ setFileError(0,"seek"); ++ return ok; ++ } ++ // Write a buffer to the file ++ int writeData(const void* buf, unsigned int len, String* error); ++ // Write file header. Close the file if fails ++ virtual bool readFileHeader(String* error); ++ // Update data. Write file header. Close the file and delete it if fails ++ virtual bool writeFileHeader(const NamedList& params, String* error); ++ ++ int m_version; ++ char m_type; ++ String m_account; ++ String m_contact; ++ String m_contactName; ++ String m_roomId; // Parent room id if this is a private room chat ++ String m_fileName; ++ String m_full; ++ File m_file; ++ unsigned int m_hdrLen; ++ int64_t m_newSessionOffset; // Recording session file offset ++ DataBlock m_writeBuffer; ++ bool m_sessionsLoaded; ++ ObjList m_sessions; ++}; ++ ++// The chat archive container ++class ChatArchive : public Mutex ++{ ++public: ++ ChatArchive(); ++ inline bool loaded() const ++ { return m_loaded; } ++ // Retrieve the files list. Lock it before use ++ inline const ObjList& items() const ++ { return m_items; } ++ // Init data when engine starts. Return the index file ++ void init(); ++ // Refresh the list. Re-load all archive ++ void refresh(); ++ // Clear all ++ void clear(bool memoryOnly); ++ // Clear all logs belonging to a given account ++ void clearAccount(const String& account, ObjList& removedItems); ++ // Remove an item and it's file ++ void delFile(const String& id); ++ // Retrieve a chat file. Return a referenced object ++ ChatFile* loadChatFile(const String& file, bool forceLoad = false); ++ // Retrieve a chat file. Return a referenced object ++ ChatFile* getChatFile(const String& id); ++ // Retrieve a chat file. Return a refferenced object ++ inline ChatFile* getChatFile(const NamedList& params) { ++ String id; ++ if (buildChatFileName(id,params)) ++ return getChatFile(id); ++ return 0; ++ } ++ // Retrieve a chat file from session id. Return a refferenced object ++ inline ChatFile* getChatFileBySession(const String& id) { ++ int pos = id.find('/'); ++ return (pos > 0) ? getChatFile(id.substr(0,pos)) : 0; ++ } ++ // Retrieve a chat file. Return a referenced object ++ ChatFile* getChatFile(const NamedList& params, const NamedList* createParams); ++ // Add a chat message to log ++ bool logChat(NamedList& params); ++ // Close a chat session. Return a referenced pointer if the item's last ++ // session was loaded into memory ++ ChatFile* closeChat(const NamedList& params); ++ // Build a file name from a list of parameters ++ static inline void buildChatFileName(String& buf, char type, const String& account, ++ const String& contact, const String& nick = String::empty()); ++ // Build a file name from a list of parameters ++ static inline bool buildChatFileName(String& buf, const NamedList& params); ++protected: ++ bool m_loaded; // Archive loaded ++ String m_dir; // Directory containing the archive ++ Configuration m_index; // Index file ++ ObjList m_items; ++}; ++ ++// The logic ++class CALogic : public ClientLogic ++{ ++public: ++ CALogic(int prio = 0); ++ ~CALogic(); ++ // Load notifications ++ virtual bool initializedClient(); ++ virtual void exitingClient(); ++ // Engine start notification ++ void engineStart(Message& msg); ++ // Actions from UI ++ virtual bool action(Window* wnd, const String& name, NamedList* params = 0); ++ virtual bool select(Window* wnd, const String& name, const String& item, ++ const String& text = String::empty()); ++ virtual bool toggle(Window* wnd, const String& name, bool active); ++ // Stop the search thread and wait for terminate ++ void searchStop(); ++ // Search thread terminated ++ void searchTerminated() ++ { m_searchThread = 0; } ++ // Start archive refresh ++ void refreshStart(const String* selected = 0); ++ // Archive refresh terminated. Refresh UI ++ void refreshTerminated(); ++ // Stop the refresh thread and wait for terminate ++ void refreshStop(); ++ // Set control highlight ++ bool setSearchHistory(const String& what, bool next); ++ // Reset control highlight ++ bool resetSearchHistory(bool reset = true); ++ // Select and set search history. Return true on success ++ bool setSearch(bool reset, const String& file, const String& session, ++ const String& what, bool next); ++protected: ++ // Load a chat item into UI ++ bool loadChat(const NamedList& params); ++ // Close a chat session ++ bool closeChat(const NamedList& params); ++ // Update sessions related to a given item ++ bool updateSessions(const String& id, Window* wnd); ++ // Update session content in UI ++ bool updateSession(const String& id, Window* wnd); ++ // Save current session ++ bool saveSession(Window* wnd, NamedList* params = 0); ++ // Delete selected contact ++ bool delContact(Window* wnd); ++ // Clear all archive ++ bool clearLog(Window* wnd); ++ ++ bool m_resetSearchOnSel; // Reset search when session selection changes ++ CASearchThread* m_searchThread; ++ CARefreshThread* m_refreshThread; ++ String m_selectAfterRefresh; ++ String m_searchText; ++}; ++ ++ ++/* ++ * Module data ++ */ ++// UI controls ++static const String s_wndArch = "archive"; ++// Prefixes ++static const String s_archPrefix = "archive:"; ++// Widgets ++static const String s_logList = "archive_logs_list"; ++static const String s_sessList = "archive_session_list"; ++static const String s_sessHistory = "archive_session_history"; ++static const String s_searchShow = "archive_search_show"; ++static const String s_searchHide = "archive_search_hide"; ++static const String s_searchEdit = "archive_search_edit"; ++static const String s_searchStart = "archive_search_start"; ++static const String s_searchPrev = "archive_search_prev"; ++static const String s_searchNext = "archive_search_next"; ++static const String s_searchRange = "archive_search_range"; ++static const String s_searchMatchCase = "archive_search_opt_matchcase"; ++static const String s_searchHighlightAll = "archive_search_opt_highlightall"; ++// Actions ++static const String& s_actionLogChat = "logchat"; ++static const String& s_actionSelectChat = "showchat"; ++static const String& s_actionCloseChat = "closechatsession"; ++static const String& s_actionRefresh = "archive_refresh"; ++static const String& s_actionClear = "clear"; ++static const String& s_actionClearNow = "clearnow"; ++static const String& s_actionClearAccNow = "clearaccountnow"; ++static const String& s_actionDelContact = "delcontact"; ++static const String& s_actionDelContactNow = "delcontactnow"; ++// Data ++static const DataBlock s_zeroDb(0,1); ++static const String s_crlf = "\r\n"; ++static Mutex s_mutex(true,"CALogic"); ++static CALogic s_logic(-50); // The logic ++static ChatArchive s_chatArchive; // Archive holder ++static CASearchRange s_range = CASearchRangeContact; ++static bool s_matchCase = false; ++static bool s_highlightAll = false; ++// Search range values ++static const TokenDict s_searchListRange[] = { ++ {"Current contact", CASearchRangeContact}, ++ {"Current session", CASearchRangeSession}, ++ {"All archive", CASearchRangeAll}, ++ {0,0}, ++}; ++ ++// Check if exiting: client is exiting or thread cancel requested ++static bool exiting() ++{ ++ return Client::exiting() || Thread::check(false); ++} ++ ++// Retrieve the window ++static inline Window* getWindow() ++{ ++ return Client::self() ? Client::self()->getWindow(s_wndArch) : 0; ++} ++ ++// Retrieve the chat type from a list of parameters ++static inline char chatType(const NamedList& params) ++{ ++ if (!params.getBoolValue("muc")) ++ return MARKUP_CHAT; ++ if (params.getBoolValue("roomchat",true)) ++ return MARKUP_ROOMCHAT; ++ return MARKUP_ROOMCHATPRIVATE; ++} ++ ++// Show a confirm dialog box in a given window ++static bool showConfirm(Window* wnd, const char* text, const char* context) ++{ ++ static const String name = "archive_confirm"; ++ if (!Client::valid()) ++ return false; ++ NamedList p(""); ++ p.addParam("text",text); ++ p.addParam("property:" + name + ":_yate_context",context); ++ return Client::self()->createDialog("confirm",wnd,String::empty(),name,&p); ++} ++ ++// Show an error dialog box in a given window ++static void showError(Window* wnd, const char* text) ++{ ++ static const String name = "archive_error"; ++ if (!Client::valid()) ++ return; ++ NamedList p(""); ++ p.addParam("text",text); ++ Client::self()->createDialog("message",wnd,String::empty(),name,&p); ++} ++ ++// Show a dialog used to notify a status and freeze the window ++static void showFreezeDlg(Window* w, const String& name, const char* text) ++{ ++ NamedList p(""); ++ p.addParam("text",text); ++ p.addParam("show:button_hide",String::boolText(false)); ++ p.addParam("_yate_windowflags","title"); ++ p.addParam("closable","false"); ++ Client::self()->createDialog("message",w,"Archive",name,&p); ++} ++ ++// Retrieve the previuos item from a list ++static ObjList* getListPrevItem(const ObjList& list, const String& value) ++{ ++ ObjList* last = 0; ++ ObjList* o = list.skipNull(); ++ for (; o; o = o->skipNext()) { ++ if (o->get()->toString() == value) ++ break; ++ last = o; ++ } ++ return o ? last : 0; ++} ++ ++// Retrieve the last item from a list ++static ObjList* getListLastItem(const ObjList& list) ++{ ++ ObjList* last = 0; ++ for (ObjList* o = list.skipNull(); o; o = o->skipNext()) ++ last = o; ++ return last; ++} ++ ++// Retrieve the chat type string ++inline const String& chatType(int type) ++{ ++ static const String s_out = "chat_out"; ++ static const String s_in = "chat_in"; ++ static const String s_delayed = "chat_delayed"; ++ if (type == MARKUP_SENT) ++ return s_out; ++ if (type == MARKUP_RECV) ++ return s_in; ++ if (type == MARKUP_DELAYED) ++ return s_delayed; ++ return String::empty(); ++} ++ ++// Retrieve the UI item type from chat file type ++static inline const char* uiItemType(char type) ++{ ++ if (type == MARKUP_CHAT) ++ return "chat"; ++ if (type == MARKUP_ROOMCHAT) ++ return "roomchat"; ++ return "roomprivchat"; ++} ++ ++// Find 2 NULL values in a buffer. Return buffer len if not found ++unsigned int find2Null(unsigned char* buf, unsigned int len) ++{ ++ for (unsigned int n = 0; n < len; n++) { ++ if (buf[n] == 0 && (n < len - 1) && (buf[n + 1] == 0)) ++ return n; ++ } ++ return len; ++} ++ ++// Find a line in text buffer (until CR/LF, single CR or LF). ++// Return the line length, excluding the line terminator ++unsigned int findLine(const char* buf, unsigned int len, unsigned int& eolnLen) ++{ ++ eolnLen = 0; ++ if (!buf) ++ return 0; ++ unsigned int i = 0; ++ for (; i < len; i++) { ++ if (buf[i] == '\r') { ++ if (i < len - 1 && buf[i + 1] == '\n') ++ eolnLen = 2; ++ else ++ eolnLen = 1; ++ return i; ++ } ++ if (buf[i] == '\n') { ++ eolnLen = 1; ++ return i; ++ } ++ } ++ return i; ++} ++ ++// Append a string to data block including the terminator ++static void appendString(DataBlock& buf, const String& src) ++{ ++ if (src) { ++ DataBlock tmp; ++ tmp.assign((void*)src.c_str(),src.length() + 1,false); ++ buf += tmp; ++ tmp.clear(false); ++ } ++ else ++ buf += s_zeroDb; ++} ++ ++// Append an integer value to a data block including a null terminator ++static inline void appendInt(DataBlock& buf, int value) ++{ ++ String tmp(value); ++ appendString(buf,tmp); ++} ++ ++// Build chat file UI params ++static NamedList* chatFileUiParams(ChatFile* f) ++{ ++ if (!f) ++ return 0; ++ Lock lock(f); ++ NamedList* upd = new NamedList(f->toString()); ++ upd->addParam("item_type",uiItemType(f->type())); ++ upd->addParam("account",f->account()); ++ upd->addParam("contact",f->contact()); ++ if (f->type() == MARKUP_CHAT) ++ upd->addParam("name",f->contactDisplayName()); ++ else if (f->type() == MARKUP_ROOMCHAT) ++ upd->addParam("name",f->contact()); ++ else { ++ upd->addParam("parent",f->roomId()); ++ upd->addParam("name",f->contactDisplayName()); ++ } ++ return upd; ++} ++ ++// Build a chat session UI params ++static NamedList* chatSessionUiParams(ChatSession* s) ++{ ++ if (!s) ++ return 0; ++ NamedList* upd = new NamedList(s->toString()); ++ String time; ++ Client::self()->formatDateTime(time,(unsigned int)s->m_name.toInteger(), ++ "yyyy.MM.dd hh:mm:ss",false); ++ // Show the first 2 lines from description ++ unsigned int len = s->m_desc.length(); ++ unsigned int tmp = 0; ++ unsigned int ln = findLine(s->m_desc.c_str(),len,tmp); ++ if (ln != len) { ++ len = ln + tmp; ++ unsigned int tmp2 = 0; ++ ln = findLine(s->m_desc.c_str() + len,s->m_desc.length() - len,tmp2); ++ if (!ln) ++ len -= tmp; ++ else ++ len += ln; ++ } ++ String desc; ++ if (len == s->m_desc.length()) ++ desc = s->m_desc; ++ else ++ desc = s->m_desc.substr(0,len); ++ desc.trimBlanks(); ++ upd->addParam("datetime",time); ++ upd->addParam("description",desc); ++ upd->addParam("property:toolTip",time + "\r\n" + s->m_desc); ++ return upd; ++} ++ ++// Enable/disable search ++static void enableSearch(bool ok) ++{ ++ Window* w = getWindow(); ++ if (!w) ++ return; ++ const char* text = String::boolText(ok); ++ NamedList p(""); ++ p.addParam("active:" + s_searchShow,text); ++ p.addParam("active:" + s_searchHide,text); ++ p.addParam("active:" + s_searchEdit,text); ++ p.addParam("active:" + s_searchStart,text); ++ p.addParam("active:" + s_searchPrev,text); ++ p.addParam("active:" + s_searchNext,text); ++ p.addParam("active:" + s_searchRange,text); ++ p.addParam("active:" + s_searchMatchCase,text); ++ p.addParam("active:" + s_searchHighlightAll,text); ++ p.addParam("active:" + s_actionRefresh,text); ++ Client::self()->setParams(&p,w); ++} ++ ++ ++/* ++ * ChatFile ++ */ ++// Init object ++ChatFile::ChatFile(const String& dir, const String& fileName) ++ : Mutex(true,"Archive::ChatFile"), ++ m_version(Current), ++ m_type(MARKUP_CHAT), ++ m_fileName(fileName), ++ m_full(dir + "/" + fileName), ++ m_hdrLen(0), ++ m_newSessionOffset(0), ++ m_sessionsLoaded(false) ++{ ++} ++ ++// Load the file. Created it if not found and params are given ++bool ChatFile::loadFile(const NamedList* params, String* error) ++{ ++ Lock lock(this); ++ closeSession(); ++ m_file.terminate(); ++ m_sessionsLoaded = false; ++ m_sessions.clear(); ++ bool ok = m_file.openPath(m_full,true,true,params != 0,true,true); ++ if (!ok) ++ return setFileError(error,"open",true); ++ int64_t sz = m_file.length(); ++ if (sz < 0) ++ return setFileError(error,"get length",true); ++ // Read/write file header ++ if (sz) { ++ if (!readFileHeader(error)) ++ return false; ++ } ++ else if (!(params && writeFileHeader(*params,error))) ++ return false; ++ m_roomId.clear(); ++ // Build the room id if this is a private chat ++ if (m_type == MARKUP_ROOMCHATPRIVATE) ++ ChatArchive::buildChatFileName(m_roomId,MARKUP_ROOMCHAT,m_account,m_contact); ++ return true; ++} ++ ++// Write chat to file ++bool ChatFile::writeChat(const NamedList& params) ++{ ++ Lock lock(this); ++ const String& text = params["text"]; ++ if (!text) ++ return false; ++ String time = params["time"]; ++ if (!time) ++ time = (int)Time::now(); ++ if (!m_newSessionOffset) { ++ m_newSessionOffset = m_file.seek(Socket::SeekEnd); ++ if (m_newSessionOffset < m_hdrLen) ++ return false; ++ String tmp; ++ tmp << MARKUP_SESSION_START << time; ++ tmp << MARKUP_SESSION_DESC << text; ++ m_writeBuffer.append(tmp); ++ m_writeBuffer += s_zeroDb; ++ m_writeBuffer += s_zeroDb; ++ } ++ m_writeBuffer.append(time); ++ String type; ++ if (params.getBoolValue("send")) ++ type = MARKUP_SENT; ++ else if (!params.getBoolValue("delayed")) ++ type = MARKUP_RECV; ++ else ++ type = MARKUP_DELAYED; ++ m_writeBuffer.append(type); ++ appendString(m_writeBuffer,params["sender"]); ++ appendString(m_writeBuffer,text); ++ m_writeBuffer += s_zeroDb; ++ int wr = writeData(m_writeBuffer.data(),m_writeBuffer.length(),0); ++ if (wr < 0) ++ return false; ++ if (wr) { ++ if (wr != (int)m_writeBuffer.length()) ++ m_writeBuffer.cut(-wr); ++ else ++ m_writeBuffer.clear(); ++ } ++ return true; ++} ++ ++// Load sessions from file ++bool ChatFile::loadSessions(bool forceLoad, String* error) ++{ ++ Lock lock(this); ++ if (m_sessionsLoaded && !forceLoad) ++ return true; ++ m_sessionsLoaded = true; ++ m_sessions.clear(); ++ int64_t offset = m_hdrLen; ++ if (!seekFile(offset,error)) ++ return false; ++ String prefix(toString() + "/"); ++ unsigned int index = 0; ++ char rdBuf[READ_BUFFER]; ++ DataBlock buf; ++ ChatSession* s = 0; ++ bool ok = true; ++ while (true) { ++ int rd = m_file.readData(rdBuf,sizeof(rdBuf)); ++ if (rd < 0) { ++ ok = setFileError(error,"read"); ++ break; ++ } ++ if (!rd) ++ break; ++ if (exiting()) ++ break; ++ buf.append(rdBuf,rd); ++ unsigned int n = find2Null((unsigned char*)buf.data(),buf.length()); ++ while (n < buf.length()) { ++ if (exiting()) ++ break; ++ String str((const char*)buf.data(),n); ++ if ((str.length() > 1) && (str[0] == MARKUP_SESSION_START)) { ++ if (s) ++ s->m_length = offset - s->m_offset; ++ int pos = str.find(MARKUP_SESSION_DESC); ++ s = new ChatSession(prefix + String(++index), ++ str.substr(1,pos > 0 ? pos - 1 : 0),offset); ++ if (pos > 0) ++ s->m_desc = str.substr(pos + 1); ++ m_sessions.append(s); ++ } ++ n += 2; ++ offset += n; ++ buf.cut(-(int)n); ++ n = find2Null((unsigned char*)buf.data(),buf.length()); ++ } ++ } ++ if (!exiting()) { ++ // Finalize the last session ++ if (s) ++ s->m_length = offset + buf.length() - s->m_offset; ++ } ++ else { ++ m_sessionsLoaded = false; ++ m_sessions.clear(); ++ } ++ return ok; ++} ++ ++// Load a session from file ++// This method is thread safe ++bool ChatFile::loadSession(const String& id, ObjList& list, String* error, ++ QString* search) ++{ ++ if (!id) ++ return false; ++ Lock lock(this); ++ ObjList* o = m_sessions.find(id); ++ if (!o) ++ return false; ++ ChatSession* s = static_cast(o->get()); ++ if (!seekFile(s->m_offset,error)) ++ return false; ++ bool find = search != 0; ++ Qt::CaseSensitivity cs = s_matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive; ++ char rdBuf[READ_BUFFER]; ++ DataBlock buf; ++ bool hdrFound = false; ++ bool ok = !find; ++ int64_t processed = 0; ++ while (processed < s->m_length && !exiting()) { ++ int rd = m_file.readData(rdBuf,sizeof(rdBuf)); ++ if (rd < 0) { ++ ok = setFileError(error,"read"); ++ break; ++ } ++ if (!rd) ++ break; ++ buf.append(rdBuf,rd); ++ unsigned int n = find2Null((unsigned char*)buf.data(),buf.length()); ++ while (n < buf.length()) { ++ if (exiting()) ++ break; ++ if (hdrFound) { ++ ChatItem* entry = decodeChat(find,s->m_offset + processed,buf.data(),n); ++ if (entry) { ++ if (!find) ++ list.append(entry); ++ else { ++ int pos = entry->m_search.indexOf(*search,0,cs); ++ TelEngine::destruct(entry); ++ if (pos >= 0) { ++ ok = true; ++ break; ++ } ++ } ++ } ++ } ++ else ++ hdrFound = true; ++ n += 2; ++ processed += n; ++ buf.cut(-(int)n); ++ if (processed >= s->m_length) ++ break; ++ n = find2Null((unsigned char*)buf.data(),buf.length()); ++ } ++ if (find && ok) ++ break; ++ } ++ if (!exiting()) { ++ if (processed < s->m_length && !(find && ok)) ++ Debug(ClientDriver::self(),DebugNote, ++ "File '%s' unexpected end of session at offset " FMT64U, ++ m_full.c_str(),s->m_offset + processed); ++ } ++ else ++ list.clear(); ++ return ok; ++} ++ ++// Retrieve the last session. Lock the object before use ++ChatSession* ChatFile::lastSession() ++{ ++ if (!m_sessionsLoaded) ++ loadSessions(); ++ ObjList* o = getListLastItem(m_sessions); ++ return o ? static_cast(o->get()) : 0; ++} ++ ++// Close current write session. Load it if sessions were loaded ++bool ChatFile::closeSession() ++{ ++ Lock lock(this); ++ if (m_newSessionOffset && m_writeBuffer.length()) ++ writeData(m_writeBuffer.data(),m_writeBuffer.length(),0); ++ m_writeBuffer.clear(); ++ bool ok = m_sessionsLoaded && m_newSessionOffset; ++ if (ok) { ++ m_sessionsLoaded = false; ++ m_sessions.clear(); ++ loadSessions(); ++ } ++ m_newSessionOffset = 0; ++ return ok; ++} ++ ++// Decode a ChatItem from a given buffer. Return it on success ++ChatItem* ChatFile::decodeChat(bool search, int64_t offset, void* buffer, ++ unsigned int len) ++{ ++ unsigned char* buf = (unsigned char*)buffer; ++ if (!(buf && len)) ++ return 0; ++ unsigned int i = 0; ++ // Get time ++ for (; i < len; i++) { ++ switch (buf[i]) { ++ case '0': ++ case '1': ++ case '2': ++ case '3': ++ case '4': ++ case '5': ++ case '6': ++ case '7': ++ case '8': ++ case '9': ++ continue; ++ } ++ break; ++ } ++ int time = 0; ++ if (i) { ++ String tmp((const char*)buf,i); ++ time = tmp.toInteger(); ++ } ++ else ++ showEntryError(DebugNote,"Invalid time",offset); ++ if (i == len) { ++ showEntryError(DebugNote,"Missing type",offset); ++ return 0; ++ } ++ int type = buf[i++]; ++ switch (type) { ++ case MARKUP_SENT: ++ case MARKUP_RECV: ++ case MARKUP_DELAYED: ++ break; ++ case 0: ++ showEntryError(DebugNote,"Missing type",offset); ++ return 0; ++ default: ++ showEntryError(DebugStub,"Unknown type",offset); ++ } ++ if (i == len) { ++ showEntryError(DebugNote,"Unexpected end of entry after type",offset); ++ return 0; ++ } ++ ChatItem* entry = new ChatItem(time,type); ++ entry->m_senderName.assign((const char*)buf + i,len - i); ++ i += entry->m_senderName.length(); ++ if (i >= len) { ++ showEntryError(DebugNote,"Unexpected end of chat item after sender name",offset); ++ return entry; ++ } ++ if (buf[i++] != 0) { ++ showEntryError(DebugMild,"Expecting NULL after sender name",offset); ++ return entry; ++ } ++ if (i == len) ++ return entry; ++ if (!search) { ++ entry->m_text.assign((const char*)buf + i,len - i); ++ i += entry->m_text.length(); ++ } ++ else { ++ unsigned int start = i; ++ while (i < len && buf[i]) ++ i++; ++ QByteArray a((const char*)buf + start,i - start); ++ entry->m_search = a; ++ } ++ if (i < len) ++ showEntryError(DebugStub,"Got garbage after text",offset); ++ return entry; ++} ++ ++// Set file last error. Close it if requested. Return false ++bool ChatFile::setFileError(String* error, const char* oper, bool close, bool del) ++{ ++ String tmp; ++ if (!error) ++ error = &tmp; ++ int code = Thread::lastError(); ++ Thread::errorString(*error,code); ++ Debug(ClientDriver::self(),DebugNote,"File '%s' %s error: %d %s",m_full.c_str(), ++ oper,code,error->c_str()); ++ if (close) { ++ Debug(ClientDriver::self(),DebugInfo,"Closing file '%s'",m_full.c_str()); ++ m_file.terminate(); ++ } ++ if (del) { ++ Debug(ClientDriver::self(),DebugInfo,"Removing file '%s'",m_full.c_str()); ++ File::remove(m_full); ++ } ++ return false; ++} ++ ++// Write a string to the file ++int ChatFile::writeData(const void* buf, unsigned int len, String* error) ++{ ++ if (m_file.seek(Stream::SeekEnd) <= 0) { ++ setFileError(error,"seek"); ++ return -1; ++ } ++ int wr = m_file.writeData(buf,len); ++ if (wr != (int)len && !m_file.canRetry()) ++ setFileError(error,"write"); ++ return wr; ++} ++ ++// Write file header. Close the file if fails ++bool ChatFile::readFileHeader(String* error) ++{ ++ m_hdrLen = 0; ++ m_version = Invalid; ++ if (!seekFile(0,error)) { ++ m_file.terminate(); ++ return false; ++ } ++ DataBlock buf; ++ unsigned char b[1024]; ++ while (true) { ++ int rd = m_file.readData(b,sizeof(b)); ++ if (rd < 0) ++ return setFileError(error,"read",true,false); ++ if (!rd) ++ return setFileError(error,"short header",true,false); ++ unsigned int n = find2Null(b,rd); ++ buf.append(b,n); ++ if (n < (unsigned int)rd) ++ break; ++ } ++ if (!buf.length()) ++ return setFileError(error,"short header",true,false); ++ unsigned int len = buf.length(); ++ const char* s = (const char*)buf.data(); ++ String str; ++ bool acc = false; ++ bool cont = false; ++ bool contName = false; ++ while (s) { ++ String str(s,len); ++ if (str.length() != len) { ++ len = len - str.length() - 1; ++ if (len) ++ s += str.length() + 1; ++ else ++ s = 0; ++ } ++ else { ++ len = 0; ++ s = 0; ++ } ++ if (m_version == Invalid) { ++ if (!str) ++ return setFileError(error,"invalid header",true,false); ++ m_version = str.toInteger(); ++ if (m_version == Invalid || m_version > Current) ++ return setFileError(error,"unsupported version",true,false); ++ } ++ else if (!acc) { ++ m_account = str; ++ acc = true; ++ } ++ else if (!cont) { ++ m_contact = str; ++ cont = true; ++ } ++ else if (!contName) { ++ m_contactName = str; ++ contName = true; ++ } ++ else { ++ m_type = 0; ++ if (str.length() == 1) ++ m_type = str[0]; ++ if (m_type != MARKUP_CHAT && m_type != MARKUP_ROOMCHAT && ++ m_type != MARKUP_ROOMCHATPRIVATE) ++ return setFileError(error,"unsupported chat type",true,false); ++ break; ++ } ++ } ++ m_hdrLen = buf.length() + 2; ++ return true; ++} ++ ++// Write file header. Close the file and delete it if fails ++bool ChatFile::writeFileHeader(const NamedList& params, String* error) ++{ ++ m_account = params["account"]; ++ m_contact = params["contact"]; ++ m_contactName = params["contactname"]; ++ m_type = chatType(params); ++ DataBlock buf; ++ appendInt(buf,m_version); ++ appendString(buf,m_account); ++ appendString(buf,m_contact); ++ appendString(buf,m_contactName); ++ buf.append(&m_type,1); ++ buf += s_zeroDb; ++ buf += s_zeroDb; ++ if (m_file.writeData(buf.data(),buf.length()) != (int)buf.length()) ++ return setFileError(error,"write",true,true); ++ m_hdrLen = buf.length(); ++ return true; ++} ++ ++ ++/* ++ * ChatArchive ++ */ ++ChatArchive::ChatArchive() ++ : Mutex(true,"ChatArchive"), ++ m_loaded(false) ++{ ++} ++ ++// Init data when client starts ++void ChatArchive::init() ++{ ++ m_dir = Engine::runParams().getValue("usercfgpath"); ++ m_dir << "/archive"; ++ if (!File::exists(m_dir)) ++ File::mkDir(m_dir); ++ m_index = m_dir + "/index.conf"; ++ m_index.load(); ++} ++ ++// Refresh the list. Re-load all archive ++void ChatArchive::refresh() ++{ ++ Lock lock(this); ++ m_loaded = true; ++ unsigned int n = m_index.sections(); ++ for (unsigned int i = 0; i < n; i++) { ++ if (exiting()) ++ break; ++ NamedList* sect = m_index.getSection(i); ++ if (!sect) ++ continue; ++ const String& type = (*sect)["type"]; ++ if (type.length() != 1) ++ continue; ++ if (type[0] != MARKUP_CHAT && type[0] != MARKUP_ROOMCHAT && ++ type[0] != MARKUP_ROOMCHATPRIVATE) ++ continue; ++ ChatFile* f = loadChatFile(*sect,true); ++ TelEngine::destruct(f); ++ } ++} ++ ++// Clear all ++void ChatArchive::clear(bool memoryOnly) ++{ ++ Lock lock(this); ++ m_items.clear(); ++ if (memoryOnly) ++ return; ++ unsigned int n = m_index.sections(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedList* f = m_index.getSection(i); ++ if (f) ++ File::remove(m_dir + "/" + *f); ++ } ++ m_index.clearSection(); ++ m_index.save(); ++} ++ ++// Clear all logs belonging to a given account ++void ChatArchive::clearAccount(const String& account, ObjList& removedItems) ++{ ++ if (!account) ++ return; ++ Lock lock(this); ++ String prefix("chat_" + String(account.hash()) + "_"); ++ unsigned int n = m_index.sections(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedList* f = m_index.getSection(i); ++ if (f && f->startsWith(prefix,false)) { ++ m_items.remove(*f); ++ removedItems.append(new String(*f)); ++ File::remove(m_dir + "/" + *f); ++ } ++ } ++ for (ObjList* o = removedItems.skipNull(); o; o = o->skipNext()) ++ m_index.clearSection(o->get()->toString()); ++ m_index.save(); ++} ++ ++// Remove an item and it's file ++void ChatArchive::delFile(const String& id) ++{ ++ if (!id) ++ return; ++ Lock lock(this); ++ m_items.remove(id); ++ File::remove(m_dir + "/" + id); ++ m_index.clearSection(id); ++ m_index.save(); ++} ++ ++// Retrieve a chat file. Return a referenced object ++ChatFile* ChatArchive::loadChatFile(const String& file, bool forceLoad) ++{ ++ Lock lock(this); ++ ChatFile* f = getChatFile(file); ++ if (!f) { ++ f = new ChatFile(m_dir,file); ++ if (!f->loadFile(0,0)) { ++ TelEngine::destruct(f); ++ return 0; ++ } ++ f->ref(); ++ m_items.append(f); ++ } ++ lock.drop(); ++ f->loadSessions(forceLoad); ++ return f; ++} ++ ++// Retrieve a chat file. Return a refferenced object ++ChatFile* ChatArchive::getChatFile(const String& id) ++{ ++ Lock lock(this); ++ ObjList* o = m_items.find(id); ++ if (!o) ++ return 0; ++ ChatFile* f = static_cast(o->get()); ++ f->ref(); ++ return f; ++ } ++ ++// Retrieve a chat file. Return a refferenced object ++ChatFile* ChatArchive::getChatFile(const NamedList& params, ++ const NamedList* createParams) ++{ ++ String fn; ++ buildChatFileName(fn,params); ++ Lock lock(this); ++ ChatFile* f = getChatFile(fn); ++ if (f) ++ return f; ++ f = new ChatFile(m_dir,fn); ++ if (!f->loadFile(createParams,0)) { ++ TelEngine::destruct(f); ++ return 0; ++ } ++ f->lock(); ++ m_index.setValue(fn,"type",String(f->type())); ++ m_index.setValue(fn,"account",f->account()); ++ m_index.setValue(fn,"contact",f->contact()); ++ if (f->contactName() && f->contactName() != m_index.getValue(fn,"contactname")) ++ m_index.setValue(fn,"contactname",f->m_contactName); ++ if (f->type() != MARKUP_ROOMCHATPRIVATE) ++ m_index.clearKey(fn,"room"); ++ else ++ m_index.setValue(fn,"room",f->roomId()); ++ f->unlock(); ++ m_index.save(); ++ m_items.append(f); ++ f->ref(); ++ return f; ++} ++ ++// Add a chat message to log ++bool ChatArchive::logChat(NamedList& params) ++{ ++ ChatFile* f = getChatFile(params,¶ms); ++ bool ok = f && f->writeChat(params); ++ TelEngine::destruct(f); ++ return ok; ++} ++ ++// Close a chat session. Add it to the ui if the contact is shown ++ChatFile* ChatArchive::closeChat(const NamedList& params) ++{ ++ ChatFile* f = getChatFile(params); ++ if (f && f->closeSession()) ++ return f; ++ TelEngine::destruct(f); ++ return 0; ++} ++ ++// Build a file name from a list of parameters ++void ChatArchive::buildChatFileName(String& buf, char type, const String& account, ++ const String& contact, const String& nick) ++{ ++ buf = "chat_"; ++ buf << account.hash() << "_" << String(contact).toLower().hash(); ++ if (type == MARKUP_ROOMCHATPRIVATE) ++ buf << "_" << nick.hash(); ++ buf << "_" << type; ++} ++ ++// Build a file name from a list of parameters ++bool ChatArchive::buildChatFileName(String& buf, const NamedList& params) ++{ ++ const String& account = params["account"]; ++ const String& contact = params["contact"]; ++ if (!(account && contact)) ++ return false; ++ char type = chatType(params); ++ const String& nick = (type != MARKUP_ROOMCHATPRIVATE) ? ++ String::empty() : params["contactname"]; ++ if (type == MARKUP_ROOMCHATPRIVATE && !nick) ++ return false; ++ buildChatFileName(buf,type,account,contact,nick); ++ return true; ++} ++ ++ ++/* ++ * CALogic ++ */ ++CALogic::CALogic(int prio) ++ : ClientLogic("clientarchive",prio), ++ m_resetSearchOnSel(true), ++ m_searchThread(0), ++ m_refreshThread(0) ++{ ++} ++ ++CALogic::~CALogic() ++{ ++} ++ ++bool CALogic::initializedClient() ++{ ++ Window* w = getWindow(); ++ // Update archive search range ++ for (const TokenDict* d = s_searchListRange; d->value; d++) ++ Client::self()->addOption(s_searchRange,d->token,false,String::empty(),w); ++ Client::self()->setSelect(s_searchRange,lookup(s_range,s_searchListRange),w); ++ // Load options ++ NamedList dummy(""); ++ NamedList* arch = Client::s_settings.getSection("clientarchive"); ++ if (!arch) ++ arch = &dummy; ++ // Setup window ++ if (w) { ++ const char* no = String::boolText(false); ++ NamedList p(""); ++ p.addParam("show:archive_frame_search",no); ++ Client::self()->setParams(&p,w); ++ } ++ return false; ++} ++ ++void CALogic::exitingClient() ++{ ++ Client::self()->setVisible(s_wndArch,false); ++ // Clear data now: close sessions ++ s_chatArchive.clear(true); ++ // Stop workers ++ searchStop(); ++ refreshStop(); ++} ++ ++void CALogic::engineStart(Message& msg) ++{ ++ s_chatArchive.init(); ++} ++ ++bool CALogic::action(Window* wnd, const String& name, NamedList* params) ++{ ++ String act = name; ++ if (act.startSkip(s_archPrefix,false)) { ++ // Chat log actions nedding parameters ++ if (params) { ++ if (act == s_actionLogChat) ++ return s_chatArchive.logChat(*params); ++ if (act == s_actionCloseChat) ++ return closeChat(*params); ++ if (act == s_actionSelectChat) { ++ Window* w = getWindow(); ++ if (w) { ++ String id; ++ ChatArchive::buildChatFileName(id,*params); ++ if (s_chatArchive.loaded()) ++ Client::self()->setSelect(s_logList,id,w); ++ else ++ refreshStart(&id); ++ Client::self()->setVisible(s_wndArch,true,true); ++ } ++ return w != 0; ++ } ++ if (act == s_actionClearAccNow) { ++ ObjList removed; ++ s_chatArchive.clearAccount((*params)["account"],removed); ++ Window* w = getWindow(); ++ if (w) ++ for (ObjList* o = removed.skipNull(); o; o = o->skipNext()) ++ Client::self()->delTableRow(s_logList,o->get()->toString(),w); ++ return true; ++ } ++ if (act == "savesession") ++ return saveSession(wnd,params); ++ return false; ++ } ++ bool confirm = (act == s_actionClear); ++ if (confirm || act == s_actionClearNow) ++ return clearLog(confirm ? wnd : 0); ++ confirm = (act == s_actionDelContact); ++ if (confirm || act == s_actionDelContactNow) ++ return delContact(confirm ? wnd : 0); ++ } ++ // Refresh all ++ if (name == s_actionRefresh) { ++ refreshStart(); ++ return true; ++ } ++ // Search ++ bool next = (name == s_searchNext || name == s_searchStart); ++ if (next || name == s_searchPrev) { ++ String tmp; ++ Client::self()->getText(s_searchEdit,tmp,false,wnd); ++ Lock lock(s_mutex); ++ if (m_searchThread) { ++ if (m_searchText != tmp) { ++ resetSearchHistory(); ++ m_searchText = tmp; ++ } ++ m_searchThread->startSearching(m_searchText,next); ++ } ++ return true; ++ } ++ bool showSearch = (name == s_searchShow); ++ if (showSearch || name == s_searchHide) { ++ searchStop(); ++ Window* w = getWindow(); ++ if (showSearch) { ++ if (!w) ++ return false; ++ Client::self()->setFocus(s_searchEdit,false,w); ++ Lock lock(s_mutex); ++ m_searchThread = new CASearchThread; ++ m_searchThread->startup(); ++ } ++ else ++ resetSearchHistory(); ++ Client::self()->setShow("archive_frame_search",showSearch,w); ++ return true; ++ } ++ if (name == "archive_save_session") ++ return saveSession(wnd); ++ return false; ++} ++ ++bool CALogic::select(Window* wnd, const String& name, const String& item, ++ const String& text) ++{ ++ // Selection changed in log list ++ if (name == s_logList) { ++ updateSessions(item,wnd); ++ return true; ++ } ++ // Selection changed in sessions list ++ if (name == s_sessList) { ++ if (m_resetSearchOnSel) ++ resetSearchHistory(false); ++ return updateSession(item,wnd); ++ } ++ // Search range ++ if (name == s_searchRange) { ++ int r = lookup(item,s_searchListRange); ++ if (r) ++ s_range = (CASearchRange)r; ++ return true; ++ } ++ return false; ++} ++ ++bool CALogic::toggle(Window* wnd, const String& name, bool active) ++{ ++ // Search options ++ if (name == s_searchMatchCase) { ++ s_matchCase = active; ++ return true; ++ } ++ if (name == s_searchHighlightAll) { ++ s_highlightAll = active; ++ return true; ++ } ++ // Window visibility changed ++ if (name == "window_visible_changed") { ++ if (wnd && wnd->id() == s_wndArch) { ++ if (active && !s_chatArchive.loaded()) ++ refreshStart(); ++ } ++ return false; ++ } ++ return false; ++} ++ ++// Stop the search thread and wait for terminate ++void CALogic::searchStop() ++{ ++ s_mutex.lock(); ++ if (m_searchThread) ++ m_searchThread->cancel(false); ++ s_mutex.unlock(); ++ while (m_searchThread) ++ Thread::idle(); ++} ++ ++// Start archive refresh ++void CALogic::refreshStart(const String* selected) ++{ ++ Window* w = getWindow(); ++ if (!w) ++ return; ++ Lock lock(s_mutex); ++ if (selected) ++ m_selectAfterRefresh = *selected; ++ if (m_refreshThread) ++ return; ++ m_refreshThread = new CARefreshThread; ++ lock.drop(); ++ showFreezeDlg(w,"archive_refresh","Refreshing ...."); ++ m_refreshThread->startup(); ++} ++ ++// Archive refresh terminated. Refresh UI ++void CALogic::refreshTerminated() ++{ ++ s_mutex.lock(); ++ String sel = m_selectAfterRefresh; ++ m_refreshThread = 0; ++ m_selectAfterRefresh.clear(); ++ Window* w = !exiting() ? getWindow() : 0; ++ s_mutex.unlock(); ++ if (!w) ++ return; ++ // Update UI ++ int count = 10; ++ s_chatArchive.lock(); ++ NamedList p(""); ++ for (ObjList* o = s_chatArchive.items().skipNull(); o; o = o->skipNext()) { ++ if (exiting()) ++ break; ++ ChatFile* f = static_cast(o->get()); ++ Lock lock(f); ++ f->loadSessions(); ++ NamedList* upd = chatFileUiParams(f); ++ // Check if the room is already displayed. Create it if not found ++ if (f->type() == MARKUP_ROOMCHATPRIVATE && f->roomId() && ++ !(p.getParam(f->roomId()) || ++ Client::self()->getTableRow(s_logList,f->roomId(),0,w))) { ++ NamedList* upd2 = 0; ++ ChatFile* parent = s_chatArchive.getChatFile(f->roomId()); ++ if (parent) ++ upd2 = chatFileUiParams(parent); ++ else { ++ upd2 = new NamedList(""); ++ upd2->addParam("item_type",uiItemType(MARKUP_ROOMCHAT)); ++ upd2->addParam("account",f->account()); ++ upd2->addParam("contact",f->contact()); ++ upd2->addParam("name",f->contact()); ++ } ++ p.addParam(new NamedPointer(f->roomId(),upd2,String::boolText(true))); ++ TelEngine::destruct(parent); ++ } ++ p.addParam(new NamedPointer(f->toString(),upd,String::boolText(true))); ++ count--; ++ if (!count) { ++ count = 10; ++ Client::self()->updateTableRows(s_logList,&p,false,w); ++ p.clear(); ++ } ++ } ++ s_chatArchive.unlock(); ++ if (!exiting()) { ++ Client::self()->updateTableRows(s_logList,&p,false,w); ++ if (sel) ++ Client::self()->setSelect(s_logList,sel,w); ++ } ++ Client::self()->closeDialog("archive_refresh",w); ++} ++ ++// Stop the refresh thread and wait for terminate ++void CALogic::refreshStop() ++{ ++ s_mutex.lock(); ++ if (m_refreshThread) ++ m_refreshThread->cancel(false); ++ s_mutex.unlock(); ++ while (m_refreshThread) ++ Thread::idle(); ++} ++ ++// Close a chat session ++bool CALogic::closeChat(const NamedList& params) ++{ ++ ChatFile* f = s_chatArchive.closeChat(params); ++ Window* w = f ? getWindow() : 0; ++ if (w) { ++ String tmp; ++ Client::self()->getSelect(s_logList,tmp,w); ++ if (tmp == f->toString()) { ++ NamedList p(""); ++ f->lock(); ++ ChatSession* s = f->lastSession(); ++ if (s) { ++ NamedList* upd = chatSessionUiParams(s); ++ p.addParam(new NamedPointer(s->toString(),upd,String::boolText(true))); ++ } ++ f->unlock(); ++ Client::self()->updateTableRows(s_sessList,&p,false,w); ++ } ++ } ++ TelEngine::destruct(f); ++ return true; ++} ++ ++// Update sessions related to a given item ++bool CALogic::updateSessions(const String& id, Window* wnd) ++{ ++ if (!Client::self()) ++ return false; ++ Client::self()->clearTable(s_sessList,wnd); ++ ChatFile* f = id ? s_chatArchive.getChatFile(id) : 0; ++ if (!f) ++ return true; ++ f->lock(); ++ NamedList p(""); ++ for (ObjList* o = f->sessions().skipNull(); o; o = o->skipNext()) { ++ ChatSession* s = static_cast(o->get()); ++ NamedList* upd = chatSessionUiParams(s); ++ p.addParam(new NamedPointer(s->toString(),upd,String::boolText(true))); ++ } ++ f->unlock(); ++ TelEngine::destruct(f); ++ Client::self()->updateTableRows(s_sessList,&p,false,wnd); ++ return true; ++} ++ ++// Update session content in UI ++bool CALogic::updateSession(const String& id, Window* wnd) ++{ ++ if (!Client::self()) ++ return false; ++ Client::self()->clearTable(s_sessHistory,wnd); ++ ChatFile* f = s_chatArchive.getChatFileBySession(id); ++ if (!f) ++ return true; ++ f->lock(); ++ ObjList list; ++ f->loadSession(id,list); ++ NamedList p(""); ++ for (ObjList* o = list.skipNull(); o; o = o->skipNext()) { ++ ChatItem* e = static_cast(o->get()); ++ NamedList* upd = new NamedList(""); ++ String time; ++ if (e->m_type != MARKUP_DELAYED) ++ Client::self()->formatDateTime(time,(unsigned int)e->m_time,"hh:mm:ss",false); ++ else ++ Client::self()->formatDateTime(time,(unsigned int)e->m_time,"dd.MM.yyyy hh:mm:ss",false); ++ upd->addParam("time",time); ++ upd->addParam("text",e->m_text); ++ NamedString* sender = new NamedString("sender",e->m_senderName); ++ if (sender->null()) { ++ if (e->m_type == MARKUP_SENT) ++ *sender = "me"; ++ else ++ *sender = f->contactDisplayName(); ++ } ++ upd->addParam(sender); ++ p.addParam(new NamedPointer(chatType(e->m_type),upd,String::boolText(true))); ++ } ++ f->unlock(); ++ TelEngine::destruct(f); ++ Client::self()->addLines(s_sessHistory,&p,0,false,wnd); ++ return true; ++} ++ ++// Set control highlight ++bool CALogic::setSearchHistory(const String& what, bool next) ++{ ++ Window* w = getWindow(); ++ if (!w) ++ return false; ++ NamedList p(s_sessHistory); ++ NamedList* upd = new NamedList(""); ++ p.addParam(new NamedPointer("search",upd,String::boolText(true))); ++ upd->addParam("find",what); ++ upd->addParam("matchcase",String::boolText(s_matchCase)); ++ upd->addParam("all",String::boolText(s_highlightAll)); ++ upd->addParam("next",String::boolText(next)); ++ return Client::self()->setParams(&p,w); ++} ++ ++// Reset control highlight ++bool CALogic::resetSearchHistory(bool reset) ++{ ++ Window* w = getWindow(); ++ if (!w) ++ return false; ++ NamedList p(s_sessHistory); ++ NamedList* upd = new NamedList(""); ++ p.addParam(new NamedPointer("search",upd,String::boolText(false))); ++ upd->addParam("reset",String::boolText(reset)); ++ return Client::self()->setParams(&p,w); ++} ++ ++// Select and set search history. Return true on success ++bool CALogic::setSearch(bool reset, const String& file, const String& session, ++ const String& what, bool next) ++{ ++ Window* w = getWindow(); ++ if (!w) ++ return false; ++ m_resetSearchOnSel = reset; ++ Client::self()->setSelect(s_logList,file,w); ++ bool ok = Client::self()->setSelect(s_sessList,session,w) && setSearchHistory(what,next); ++ m_resetSearchOnSel = true; ++ return ok; ++} ++ ++// Save current session ++bool CALogic::saveSession(Window* wnd, NamedList* params) ++{ ++ if (!Client::valid()) ++ return false; ++ String id; ++ Window* w = getWindow(); ++ if (!w) ++ return false; ++ Client::self()->getSelect(s_sessList,id,w); ++ if (!id) ++ return false; ++ if (!params && wnd) { ++ NamedList p(""); ++ p.addParam("action",s_archPrefix + "savesession"); ++ p.addParam("save",String::boolText(true)); ++ p.addParam("filters","Text files (*.txt)|All files (*)"); ++ p.addParam("chooseanyfile",String::boolText(true)); ++ return Client::self()->chooseFile(wnd,p); ++ } ++ if (!params) ++ return false; ++ const String& file = (*params)["file"]; ++ if (!file) ++ return true; ++ const char* oper = 0; ++ while (true) { ++ File::remove(file); ++ File f; ++ if (!f.openPath(file,true,false,true)) { ++ oper = "open"; ++ break; ++ } ++ String data; ++ Client::self()->getText(s_sessHistory,data,false,w); ++ int retry = 10; ++ unsigned int len = data.length(); ++ const char* s = data.c_str(); ++ String lineBuf; ++ while (retry && (len || lineBuf)) { ++ if (!lineBuf) { ++ unsigned int eolnLen = 0; ++ unsigned int ln = findLine(s,len,eolnLen); ++ if (eolnLen == 2) ++ lineBuf.assign(s,ln + 2); ++ else { ++ lineBuf.assign(s,ln); ++ lineBuf << "\r\n"; ++ } ++ ln += eolnLen; ++ s += ln; ++ len -= ln; ++ } ++ int wr = f.writeData(lineBuf.c_str(),lineBuf.length()); ++ if (wr > 0) { ++ if ((unsigned int)wr == lineBuf.length()) ++ lineBuf.clear(); ++ else ++ lineBuf = lineBuf.substr(wr); ++ } ++ else if (!wr) ++ Thread::msleep(2); ++ else if (f.canRetry()) ++ retry--; ++ else { ++ oper = "write"; ++ break; ++ } ++ } ++ break; ++ } ++ if (!oper) ++ return true; ++ String error; ++ Thread::errorString(error); ++ String text; ++ text << "Failed to " << oper << " '" << file << "'"; ++ text.append(error,"\r\n"); ++ showError(wnd,text); ++ return false; ++} ++ ++// Clear all archive ++bool CALogic::delContact(Window* wnd) ++{ ++ String id; ++ Window* w = getWindow(); ++ if (!w) ++ return false; ++ Client::self()->getSelect(s_logList,id,w); ++ if (!id) ++ return false; ++ if (wnd && ++ showConfirm(wnd,"Confirm selected contact log delete?",s_archPrefix + s_actionDelContactNow)) ++ return true; ++ s_chatArchive.delFile(id); ++ Client::self()->delTableRow(s_logList,id,w); ++ return true; ++} ++ ++// Clear all archive ++bool CALogic::clearLog(Window* wnd) ++{ ++ if (wnd && ++ showConfirm(wnd,"Confirm archive clear?",s_archPrefix + s_actionClearNow)) ++ return true; ++ refreshStop(); ++ Window* w = getWindow(); ++ if (w) { ++ // This will stop the search thread ++ Client::self()->setShow("archive_frame_search",false,w); ++ Client::self()->clearTable(s_logList,w); ++ Client::self()->clearTable(s_sessList,w); ++ Client::self()->clearTable(s_sessHistory,w); ++ } ++ s_chatArchive.clear(false); ++ return true; ++} ++ ++ ++/* ++ * CASearchThread ++ */ ++CASearchThread::CASearchThread() ++ : Thread("CASearchThread"), ++ m_startSearch(false), ++ m_searching(false), ++ m_next(true), ++ m_range(CASearchRangeInvalid), ++ m_currentSessionFull(false), ++ m_currentContactFull(false) ++{ ++} ++ ++CASearchThread::~CASearchThread() ++{ ++ s_logic.searchTerminated(); ++} ++ ++void CASearchThread::startSearching(const String& text, bool next) ++{ ++ CASearchRange old = m_range; ++ resetSearch(); ++ Lock lock(s_mutex); ++ m_next = next; ++ m_range = s_range; ++ // Reset data if range changed ++ if (old != s_range || m_what != text) { ++ m_currentContact.clear(); ++ m_currentSession.clear(); ++ m_currentSessionFull = false; ++ m_currentContactFull = false; ++ } ++ m_what = text; ++ m_startSearch = true; ++} ++ ++void CASearchThread::run() ++{ ++ Debug(ClientDriver::self(),DebugAll,"%s start running",currentName()); ++ while (true) { ++ if (exiting()) ++ break; ++ Lock lock(s_mutex); ++ if (!(m_what && m_startSearch)) { ++ lock.drop(); ++ Thread::yield(); ++ continue; ++ } ++ String what = m_what; ++ m_startSearch = false; ++ lock.drop(); ++ enableSearch(false); ++ m_searching = true; ++ switch (m_range) { ++ case CASearchRangeSession: ++ s_logic.setSearchHistory(what,m_next); ++ break; ++ case CASearchRangeContact: ++ searchCurrentContact(what); ++ break; ++ case CASearchRangeAll: ++ searchAll(what); ++ break; ++ default: ++ Debug(DebugStub,"%s range %d not implemented",currentName(),m_range); ++ } ++ m_searching = false; ++ enableSearch(true); ++ } ++ Debug(ClientDriver::self(),DebugAll,"%s stop running",currentName()); ++}; ++ ++void CASearchThread::resetSearch() ++{ ++ m_range = CASearchRangeInvalid; ++ while (m_searching) ++ Thread::yield(); ++} ++ ++// Search all archive ++void CASearchThread::searchAll(const String& what) ++{ ++ bool changed = false; ++ ObjList items; ++ Window* w = getWindow(); ++ if (w) { ++ NamedList p(""); ++ Client::self()->getOptions(s_logList,&p,w); ++ unsigned int n = p.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = p.getParam(i); ++ if (ns) ++ items.append(new String(ns->name())); ++ } ++ } ++ if (m_currentContact && !items.find(m_currentContact)) { ++ changed = true; ++ m_currentContact.clear(); ++ m_currentSession.clear(); ++ m_currentSessionFull = false; ++ m_currentContactFull = false; ++ } ++ if (!m_currentContact) { ++ changed = true; ++ m_currentSession.clear(); ++ m_currentSessionFull = false; ++ m_currentContactFull = false; ++ ObjList* o = m_next ? items.skipNull() : getListLastItem(items); ++ if (o) ++ m_currentContact = o->get()->toString(); ++ else ++ return; ++ } ++ bool found = false; ++ String start = m_currentContact; ++ while (!found) { ++ ChatFile* f = 0; ++ while (!f) { ++ if (m_currentContactFull) { ++ m_currentContactFull = false; ++ if (exiting() || m_range == CASearchRangeInvalid) ++ break; ++ ObjList* o = 0; ++ if (m_next) { ++ o = items.find(m_currentContact); ++ if (o) ++ o = o->skipNext(); ++ } ++ else ++ o = getListPrevItem(items,m_currentContact); ++ if (!o) { ++ if (m_next) ++ o = items.skipNull(); ++ else ++ o = getListLastItem(items); ++ } ++ if (!o || o->get()->toString() == start) ++ break; ++ m_currentContact = o->get()->toString(); ++ m_currentSession.clear(); ++ changed = true; ++ } ++ f = s_chatArchive.getChatFile(m_currentContact); ++ } ++ if (!f) ++ break; ++ // Retrieve the starting session if don't have one ++ if (!m_currentSession) { ++ changed = true; ++ m_currentSessionFull = false; ++ ObjList* o = m_next ? f->sessions().skipNull() : getListLastItem(f->sessions()); ++ if (o) ++ m_currentSession = o->get()->toString(); ++ } ++ if (m_currentSession) ++ found = searchContact(f,what,changed); ++ TelEngine::destruct(f); ++ if (found) ++ break; ++ m_currentSession.clear(); ++ m_currentContactFull = true; ++ } ++ if (!found) { ++ m_currentContact.clear(); ++ m_currentSession.clear(); ++ m_currentSessionFull = true; ++ m_currentContactFull = true; ++ } ++} ++ ++// Search in the current contact ++void CASearchThread::searchCurrentContact(const String& what) ++{ ++ ChatFile* f = 0; ++ bool changed = false; ++ if (m_currentSession) { ++ f = s_chatArchive.getChatFileBySession(m_currentSession); ++ if (f) { ++ String tmp = m_currentSession; ++ Window* w = getWindow(); ++ if (w) ++ Client::self()->getSelect(s_sessList,tmp,w); ++ changed = (tmp != m_currentSession); ++ } ++ else ++ m_currentSession.clear(); ++ } ++ if (!m_currentSession) { ++ changed = true; ++ m_currentSessionFull = false; ++ Window* w = getWindow(); ++ if (w) { ++ Client::self()->getSelect(s_sessList,m_currentSession,w); ++ // Select the first or last session if any ++ if (!m_currentSession) { ++ NamedList p(""); ++ Client::self()->getOptions(s_sessList,&p,w); ++ unsigned int n = p.length(); ++ NamedString* ns = 0; ++ for (unsigned int i = 0; i < n; i++) { ++ ns = p.getParam(i); ++ if (ns && m_next) ++ break; ++ } ++ if (ns) ++ m_currentSession = ns->name(); ++ } ++ } ++ f = s_chatArchive.getChatFileBySession(m_currentSession); ++ } ++ if (!f) ++ return; ++ searchContact(f,what,changed); ++ TelEngine::destruct(f); ++} ++ ++// Search in given contact contact ++bool CASearchThread::searchContact(ChatFile* f, const String& what, bool changed) ++{ ++ if (!f) ++ return false; ++ QString* search = new QString; ++ *search = QtClient::setUtf8(what); ++ f->lock(); ++ bool found = false; ++ String start = m_currentSession; ++ while (true) { ++ if (m_currentSessionFull) { ++ if (exiting() || m_range == CASearchRangeInvalid) ++ break; ++ ObjList* o = 0; ++ if (m_next) { ++ o = f->sessions().find(m_currentSession); ++ if (o) ++ o = o->skipNext(); ++ } ++ else ++ o = getListPrevItem(f->sessions(),m_currentSession); ++ if (!o && m_range == CASearchRangeContact) { ++ if (m_next) ++ o = f->sessions().skipNull(); ++ else ++ o = getListLastItem(f->sessions()); ++ } ++ if (!o || o->get()->toString() == start) { ++ m_currentContactFull = true; ++ break; ++ } ++ m_currentSession = o->get()->toString(); ++ m_currentSessionFull = false; ++ changed = true; ++ } ++ if (exiting() || m_range == CASearchRangeInvalid) ++ break; ++ ObjList list; ++ found = f->loadSession(m_currentSession,list,0,search); ++ if (exiting() || m_range == CASearchRangeInvalid) { ++ found = false; ++ break; ++ } ++ if (found) { ++ f->unlock(); ++ found = s_logic.setSearch(changed,f->toString(),m_currentSession,what,m_next); ++ f->lock(); ++ if (found) { ++ m_currentSessionFull = s_highlightAll; ++ break; ++ } ++ } ++ m_currentSessionFull = true; ++ } ++ f->unlock(); ++ if (!found) { ++ m_currentSession.clear(); ++ m_currentSessionFull = false; ++ } ++ if (search) ++ delete search; ++ return found; ++} ++ ++ ++/* ++ * CARefreshThread ++ */ ++CARefreshThread::CARefreshThread() ++ : Thread("CARefreshThread") ++{ ++} ++ ++CARefreshThread::~CARefreshThread() ++{ ++ s_logic.refreshTerminated(); ++} ++ ++void CARefreshThread::run() ++{ ++ Debug(ClientDriver::self(),DebugAll,"%s start running",currentName()); ++ s_chatArchive.refresh(); ++ Debug(ClientDriver::self(),DebugAll,"%s stop running",currentName()); ++} ++ ++} // namespace anonymous ++ ++#include "clientarchive.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/clientarchive.h b/modules/qt5/clientarchive.h +new file mode 100644 +index 0000000..9c5ac32 +--- /dev/null ++++ b/modules/qt5/clientarchive.h +@@ -0,0 +1,33 @@ ++/** ++ * clientarchive.h ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * Client archive management and UI logic ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __CLIENTARCHIVE_H ++#define __CLIENTARCHIVE_H ++ ++#include ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++}; // anonymous namespace ++ ++#endif // __CLIENTARCHIVE_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtable.cpp b/modules/qt5/customtable.cpp +new file mode 100644 +index 0000000..d4cb5bf +--- /dev/null ++++ b/modules/qt5/customtable.cpp +@@ -0,0 +1,710 @@ ++/** ++ * customtable.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom table implementation ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2010-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "customtable.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++// The factory ++class CustomTableFactory : public UIFactory ++{ ++public: ++ inline CustomTableFactory(const char* name = "CustomTableFactory") ++ : UIFactory(name) ++ { m_types.append(new String("CustomTable")); } ++ virtual void* create(const String& type, const char* name, NamedList* params = 0); ++}; ++ ++// Utility class used to disable/enable a table sorting and widget update flag ++class SafeWidget ++{ ++public: ++ SafeWidget(QTableWidget* table) ++ : m_widget(table), m_table(0) { ++ if (m_widget) ++ m_widget->setUpdatesEnabled(false); ++ if (table && table->isSortingEnabled()) { ++ m_table = table; ++ m_table->setSortingEnabled(false); ++ } ++ } ++ ~SafeWidget() ++ { drop(); } ++ inline void drop() { ++ if (m_table) ++ m_table->setSortingEnabled(true); ++ if (m_widget) ++ m_widget->setUpdatesEnabled(true); ++ m_widget = 0; ++ m_table = 0; ++ } ++private: ++ QWidget* m_widget; ++ QTableWidget* m_table; ++}; ++ ++static CustomTableFactory s_factory; ++ ++static inline const String& objListItem(ObjList* list, int index) ++{ ++ GenObject* gen = list ? (*list)[index] : 0; ++ return gen ? gen->toString() : String::empty(); ++} ++ ++ ++/* ++ * CustomTable ++ */ ++// Constructor for a custom table ++CustomTable::CustomTable(const char *name, const NamedList& params, QWidget* parent) ++ : QtTable(name,parent), ++ m_rowHeight(0), m_horzHeader(true), ++ m_notifyItemChanged(false), m_notifySelChgOnRClick(true), ++ m_contextMenu(0), m_changing(false) ++{ ++ // Build properties ++ QtClient::buildProps(this,params["buildprops"]); ++ // Set horizontal header ++ QHeaderView* h = horizontalHeader(); ++ if (h) ++ h->setHighlightSections(false); ++ ObjList* cols = params["hheader_columns"].split(',',false); ++ ObjList* title = params["hheader_columns_title"].split(',',true); ++ ObjList* check = params["hheader_columns_check"].split(',',false); ++ ObjList* size = params["hheader_columns_size"].split(',',true); ++ ObjList* resize = params["hheader_columns_resize"].split(',',true); ++ ObjList* emptyTitle = params["hheader_columns_allowemptytitle"].split(',',true); ++ int n = cols->count(); ++ setColumnCount(n); ++ for (int i = 0; i < n; i++) { ++ String id = objListItem(cols,i); ++ String text = objListItem(title,i); ++ if (!text) { ++ String tmp = id; ++ if (!emptyTitle->find(tmp.toLower())) ++ text = id; ++ } ++ QTableWidgetItem* it = new QTableWidgetItem(QtClient::setUtf8(text)); ++ id.toLower(); ++ it->setData(ColumnId,QVariant(QtClient::setUtf8(id))); ++ if (check->find(id)) ++ it->setData(ColumnItemCheckable,QVariant(true)); ++ setHorizontalHeaderItem(i,it); ++ if (!h) ++ continue; ++ // Set column width ++ int w = objListItem(size,i).toInteger(); ++ if (w > 0) ++ h->resizeSection(i,w); ++ // Set column resize mode ++ const String& resizeMode = objListItem(resize,i); ++ if (resizeMode == "fixed") ++ h->setSectionResizeMode(i,QHeaderView::Fixed); ++ else if (resizeMode == "stretch") ++ h->setSectionResizeMode(i,QHeaderView::Stretch); ++ else if (resizeMode == "contents") ++ h->setSectionResizeMode(i,QHeaderView::ResizeToContents); ++ else ++ h->setSectionResizeMode(i,QHeaderView::Interactive); ++ } ++ TelEngine::destruct(cols); ++ TelEngine::destruct(title); ++ TelEngine::destruct(check); ++ TelEngine::destruct(size); ++ TelEngine::destruct(resize); ++ TelEngine::destruct(emptyTitle); ++ // Init properties ++ m_saveProps << "_yate_col_widths"; ++ m_saveProps << "_yate_sorting"; ++ setSelectionMode(QAbstractItemView::SingleSelection); ++ setSelectionBehavior(QAbstractItemView::SelectRows); ++ setEditTriggers(QAbstractItemView::NoEditTriggers); ++ setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); ++ // Connect signals ++ QtClient::connectObjects(this,SIGNAL(cellChanged(int,int)),this,SLOT(itemCellChanged(int,int))); ++ // Apply parameters ++ setParams(params); ++} ++ ++CustomTable::~CustomTable() ++{ ++} ++ ++bool CustomTable::setParams(const NamedList& params) ++{ ++ SafeWidget tbl(this); ++ QtUIWidget::setParams(params); ++ unsigned int n = params.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* param = params.getParam(i); ++ if (!param) ++ continue; ++ if (param->name() == "filtervalue") ++ setFilter(*param); ++ else if (param->name() == "dynamiccellclicked") ++ setProperty("dynamicCellClicked",QVariant(QString(*param))); ++ else if (param->name() == "dynamicnoitemselchanged") ++ setProperty("dynamicNoItemSelChanged",QVariant(QString(*param))); ++ else if (param->name().startsWith("property:")) { ++ String prop = param->name().substr(9); ++ QWidget* target = this; ++ if (prop.startSkip("hheader:",false)) ++ target = horizontalHeader(); ++ if (target) ++ QtClient::setProperty(target,prop,*param); ++ } ++ else if (param->name() == "menu") { ++ // Re-build the context menu ++ if (m_contextMenu) { ++ QtClient::deleteLater(m_contextMenu); ++ m_contextMenu = 0; ++ } ++ NamedList* menu = static_cast(param->getObject(YATOM("NamedList"))); ++ if (menu) { ++ // Get parent window receiving menu events ++ QtWindow* wnd = static_cast(window()); ++ if (wnd) ++ m_contextMenu = QtClient::buildMenu(*menu,*menu,wnd,SLOT(action()), ++ SLOT(toggled(bool)),this); ++ } ++ } ++ else if (param->name() == "notifyselchgonrightclick") ++ m_notifySelChgOnRClick = param->toBoolean(m_notifySelChgOnRClick); ++ else if (param->name() == "filterby") { ++ setFilter(); ++ m_filterBy.clear(); ++ ObjList* list = param->split(',',false); ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ String* s = static_cast(o->get()); ++ m_filterBy.append(QtClient::setUtf8(s->toLower())); ++ } ++ TelEngine::destruct(list); ++ } ++ } ++ tbl.drop(); ++ return true; ++} ++ ++bool CustomTable::getOptions(NamedList& items) ++{ ++ int n = rowCount(); ++ for (int i = 0; i < n; i++) { ++ String id; ++ if (getId(id,i) && id) ++ items.addParam(id,""); ++ } ++ return true; ++} ++ ++bool CustomTable::addTableRow(const String& item, const NamedList* data, bool atStart) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::addTableRow(%s,%p,%u)", ++ name().c_str(),item.c_str(),data,atStart); ++ SafeWidget tbl(this); ++ int row = atStart ? 0 : rowCount(); ++ insertRow(row); ++ if (setRow(row,data,item)) ++ return true; ++ removeRow(row); ++ return false; ++} ++ ++// Add or set one or more table row(s). Screen update is locked while changing the table. ++// Each data list element is a NamedPointer carrying a NamedList with item parameters. ++// The name of an element is the item to update. ++// Set element's value to boolean value 'true' to add a new item if not found ++// or 'false' to set an existing one. Set it to empty string to delete the item ++bool CustomTable::updateTableRows(const NamedList* data, bool atStart) ++{ ++ if (!data) ++ return true; ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::updateTableRows(%p,%u)", ++ name().c_str(),data,atStart); ++ // Remember selected item ++ bool ok = true; ++ SafeWidget tbl(this); ++ unsigned int n = data->length(); ++ ObjList add; ++ // Delete and update rows ++ for (unsigned int i = 0; i < n; i++) { ++ if (Client::exiting()) ++ break; ++ // Get item and the list of parameters ++ NamedString* ns = data->getParam(i); ++ if (!ns) ++ continue; ++ // Delete ? ++ if (ns->null()) { ++ int row = getRow(ns->name()); ++ if (row >= 0) ++ removeRow(row); ++ else ++ ok = false; ++ continue; ++ } ++ // Set item or postpone add ++ int row = getRow(ns->name()); ++ if (row >= 0) ++ setRow(row,YOBJECT(NamedList,ns)); ++ else if (ns->toBoolean()) ++ add.append(ns)->setDelete(false); ++ else ++ ok = false; ++ } ++ n = add.count(); ++ if (n) { ++ int row = rowCount(); ++ if (row < 0) ++ row = 0; ++ // Append if not requested to insert at start or table is empty ++ if (!(atStart && row)) ++ setRowCount(row + n); ++ else { ++ for (unsigned int i = 0; i < n; i++) ++ insertRow(0); ++ } ++ for (ObjList* o = add.skipNull(); o; row++, o = o->skipNext()) { ++ NamedString* ns = static_cast(o->get()); ++ if (!setRow(row,YOBJECT(NamedList,ns),ns->name())) ++ ok = false; ++ } ++ } ++ return ok; ++} ++ ++bool CustomTable::delTableRow(const String& item) ++{ ++ SafeWidget tbl(this); ++ int row = getRow(item); ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::delTableRow(%s) found=%d", ++ name().c_str(),item.c_str(),row); ++ if (row < 0) ++ return false; ++ removeRow(row); ++ return true; ++} ++ ++bool CustomTable::setTableRow(const String& item, const NamedList* data) ++{ ++ SafeWidget tbl(this); ++ int row = getRow(item); ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setTableRow(%s,%p) found=%d", ++ name().c_str(),item.c_str(),data,row); ++ if (row < 0) ++ return false; ++ return setRow(row,data); ++} ++ ++bool CustomTable::getTableRow(const String& item, NamedList* data) ++{ ++ int row = getRow(item); ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::getTableRow(%s,%p) found=%d", ++ name().c_str(),item.c_str(),data,row); ++ if (row < 0) ++ return false; ++ if (!data) ++ return true; ++ int n = columnCount(); ++ for (int i = 1; i < n; i++) { ++ String name; ++ bool checkable = false; ++ QTableWidgetItem* h = getColumnId(name,checkable,i); ++ if (!(h && name)) ++ continue; ++ QTableWidgetItem* it = QTableWidget::item(row,i); ++ if (!it) ++ continue; ++ NamedString* ns = new NamedString(name); ++ QtClient::getUtf8(*ns,it->text()); ++ data->setParam(ns); ++ if (checkable) ++ data->setParam("check:" + name,String::boolText(it->checkState() == Qt::Checked)); ++ } ++ return true; ++} ++ ++bool CustomTable::clearTable() ++{ ++ setRowCount(0); ++ return true; ++} ++ ++// Set the selected entry ++bool CustomTable::setSelect(const String& item) ++{ ++ if (!item) ++ return true; ++ int row = getRow(item); ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setSelect(%s) found=%d", ++ name().c_str(),item.c_str(),row); ++ if (row < 0) ++ return false; ++ setCurrentCell(row,1); ++ return true; ++} ++ ++bool CustomTable::getSelect(String& item) ++{ ++ int row = currentRow(); ++ QTableWidgetItem* it = 0; ++ if (row >= 0) { ++ it = QTableWidget::item(row,0); ++ if (it) ++ QtClient::getUtf8(item,it->text()); ++ } ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::getSelect() found=(%d,%s)", ++ name().c_str(),row,item.c_str()); ++ return it != 0; ++} ++ ++// Find a table row by its item id ++int CustomTable::getRow(const String& item) ++{ ++ const QString tmp = QtClient::setUtf8(item); ++ for (int i = 0; i < rowCount(); i++) { ++ QTableWidgetItem* it = this->item(i,0); ++ if (it && it->text() == tmp) ++ return i; ++ } ++ return -1; ++} ++ ++// Find a table row id by its row index ++bool CustomTable::getId(String& item, int row) ++{ ++ QTableWidgetItem* it = this->item(row,0); ++ if (it) ++ QtClient::getUtf8(item,it->text()); ++ return it != 0; ++} ++ ++// Find a column by its label. Return -1 if not found ++QTableWidgetItem* CustomTable::getColumnId(String& id, bool& checkable, int col) ++{ ++ QTableWidgetItem* it = horizontalHeaderItem(col); ++ if (!it) ++ return 0; ++ QVariant var = it->data(ColumnId); ++ if (var.type() == QVariant::String) ++ QtClient::getUtf8(id,var.toString()); ++ else { ++ QtClient::getUtf8(id,it->text()); ++ id.toLower(); ++ } ++ var = it->data(ColumnItemCheckable); ++ checkable = var.toBool(); ++ return it; ++} ++ ++// Find a column by its label. Return -1 if not found ++int CustomTable::getColumn(const QString& name, bool hidden, bool caseInsensitive) ++{ ++ static QString ht("hidden:"); ++ QString what = name; ++ if (hidden) ++ what.insert(0,ht); ++ Qt::CaseSensitivity cs = caseInsensitive ? Qt::CaseInsensitive : Qt::CaseSensitive; ++ int n = columnCount(); ++ for (int i = 0; i < n; i++) { ++ QTableWidgetItem* it = horizontalHeaderItem(i); ++ if (!it) ++ continue; ++ QVariant var = it->data(ColumnId); ++ if (var.type() == QVariant::String) { ++ if (0 == var.toString().compare(what,cs)) ++ return i; ++ } ++ else if (0 == it->text().compare(what,cs)) ++ return i; ++ } ++ return -1; ++} ++ ++// (de)activate enter key press action ++void CustomTable::setEnterPressNotify(bool value) ++{ ++ QAction* act = findChild(m_enterKeyActionName); ++ if (act) { ++ if (!value) { ++ QWidget::removeAction(act); ++ QtClient::deleteLater(act); ++ } ++ return; ++ } ++ if (!value) ++ return; ++ act = new QAction("",this); ++ act->setObjectName(m_enterKeyActionName); ++ act->setShortcut(QKeySequence(Qt::Key_Return)); ++ act->setShortcutContext(Qt::WidgetShortcut); ++ act->setProperty("_yate_autoconnect",QVariant(false)); ++ QWidget::addAction(act); ++ QtClient::connectObjects(act,SIGNAL(triggered()),this,SLOT(actionTriggered())); ++} ++ ++// Retrieve table columns widths ++QString CustomTable::getColWidths() ++{ ++ String widths; ++ int n = columnCount(); ++ for (int i = 0; i < n; i++) ++ widths.append(String(columnWidth(i)),",",true); ++ return QtClient::setUtf8(widths); ++} ++ ++// Set the table columns widths string ++void CustomTable::setColWidths(QString value) ++{ ++ QHeaderView* hdr = horizontalHeader(); ++ bool skipLast = hdr && hdr->stretchLastSection(); ++ QStringList list = value.split(','); ++ for (int i = 0; i < list.size(); i++) { ++ if (skipLast && i == columnCount() - 1) ++ break; ++ bool ok = true; ++ int w = list[i].toInt(&ok); ++ if (ok && w >= 0) ++ setColumnWidth(i,w); ++ } ++} ++ ++// Retrieve table sorting ++QString CustomTable::getSorting() ++{ ++ String sorting; ++ if (isSortingEnabled()) { ++ QHeaderView* h = horizontalHeader(); ++ int col = h ? h->sortIndicatorSection() : -1; ++ if (col >= 0) ++ sorting << col << "," << ++ String::boolText(Qt::AscendingOrder == h->sortIndicatorOrder()); ++ } ++ return QtClient::setUtf8(sorting); ++} ++ ++// Set the table sorting ++void CustomTable::setSorting(QString value) ++{ ++ QStringList list = value.split(','); ++ if (list.size() < 2) ++ return; ++ bool ok = true; ++ int col = list[0].toInt(&ok); ++ if (ok && col >= 0 && col < columnCount()) { ++ String tmp; ++ QtClient::getUtf8(tmp,list[1]); ++ sortItems(col,tmp.toBoolean(true) ? Qt::AscendingOrder : Qt::DescendingOrder); ++ } ++} ++ ++// Setup a row ++bool CustomTable::setRow(int row, const NamedList* data, const String& item) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setRow(%d,%p,%s)", ++ name().c_str(),row,data,item.c_str()); ++ m_changing = true; ++ int n = columnCount(); ++ // First init ++ if (item) { ++ // Set row id ++ setItem(row,0,new QTableWidgetItem(QtClient::setUtf8(item))); ++ // Set row height ++ if (m_rowHeight > 0) ++ QTableWidget::setRowHeight(row,m_rowHeight); ++ // Set checkable columns ++ for (int i = 1; i < n; i++) { ++ String name; ++ bool checkable = false; ++ getColumnId(name,checkable,i); ++ if (!checkable) ++ continue; ++ QTableWidgetItem* it = QTableWidget::item(row,i); ++ if (!it) { ++ it = new QTableWidgetItem; ++ setItem(row,i,it); ++ } ++ it->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ++ it->setCheckState(Qt::Unchecked); ++ } ++ } ++ if (!data) { ++ m_changing = false; ++ return true; ++ } ++ for (int i = 1; i < n; i++) { ++ String name; ++ bool checkable = false; ++ getColumnId(name,checkable,i); ++ if (!name) ++ continue; ++ String* text = data->getParam(name); ++ String* img = data->getParam(name + "_image"); ++ String* check = checkable ? data->getParam("check:" + name) : 0; ++ if (!(text || img || check)) ++ continue; ++ QTableWidgetItem* it = QTableWidget::item(row,i); ++ if (!it) { ++ it = new QTableWidgetItem; ++ setItem(row,i,it); ++ if (!checkable) ++ it->setFlags(it->flags() & ~Qt::ItemFlags(Qt::ItemIsUserCheckable)); ++ else { ++ it->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); ++ it->setCheckState(Qt::Unchecked); ++ } ++ } ++ if (text) ++ it->setText(QtClient::setUtf8(*text)); ++ if (check) ++ it->setCheckState(check->toBoolean() ? Qt::Checked : Qt::Unchecked); ++ if (img) ++ it->setIcon(QIcon(QtClient::setUtf8(*img))); ++ } ++ m_changing = false; ++ return true; ++} ++ ++// Handle item cell content changes ++void CustomTable::onCellChanged(int row, int col) ++{ ++ if (m_changing || row < 0 || !m_notifyItemChanged) ++ return; ++ String item; ++ getId(item,row); ++ if (item) ++ triggerAction(item,"listitemchanged",this); ++} ++ ++void CustomTable::contextMenuEvent(QContextMenuEvent* e) ++{ ++ int yMax = rowCount() * rowHeight(0); ++ if (yMax < e->y()) ++ return; ++ if (m_contextMenu) ++ m_contextMenu->exec(e->globalPos()); ++} ++ ++// Catch a mouse press event ++// Disable selection change signal on right button events ++void CustomTable::mousePressEvent(QMouseEvent* event) ++{ ++ if (event->button() == Qt::RightButton && !m_notifySelChgOnRClick) { ++ int row = rowAt(event->y()); ++ if (row >= 0 && row != currentRow()) { ++ // Disconnect and re-connect only if connected ++ QtWindow* wnd = 0; ++ QVariant var = property("dynamicNoItemSelChanged"); ++ if (!var.toBool()) ++ wnd = QtClient::parentWindow(this); ++ if (wnd) ++ disconnect(this,SIGNAL(itemSelectionChanged()), ++ wnd,SLOT(selectionChanged())); ++ setCurrentCell(row,1); ++ if (wnd) ++ QtClient::connectObjects(this,SIGNAL(itemSelectionChanged()), ++ wnd,SLOT(selectionChanged())); ++ event->accept(); ++ } ++ return; ++ } ++ QTableWidget::mousePressEvent(event); ++} ++ ++// Slot for triggered signals received from actions added to the table ++void CustomTable::actionTriggered() ++{ ++ if (!sender() || currentRow() < 0) ++ return; ++ if (sender()->objectName() == m_enterKeyActionName) ++ onAction(this); ++} ++ ++// Set filter (hide not matching items) ++void CustomTable::setFilter(const String& value) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setFilter(%s)", ++ name().c_str(),value.c_str()); ++ SafeWidget tbl(this); ++ QString tmp = QtClient::setUtf8(value); ++ if (tmp == m_filterValue) ++ return; ++ m_filterValue = tmp; ++ // Match rows and show or hide them ++ int rows = rowCount(); ++ int cols = columnCount(); ++ for (int row = 0; row < rows; row++) ++ for (int col = 0; col < cols; col++) ++ if (updateFilter(row,col)) ++ break; ++} ++ ++// Check if the current filter matches a row. Show it if matched, hide it otherwise. ++bool CustomTable::updateFilter(int row, int col) ++{ ++ bool hide = !rowFilterMatch(row,col); ++ if (hide == isRowHidden(row)) ++ return false; ++ setRowHidden(row,hide); ++ return true; ++} ++ ++// Check if the current filter matches a row ++bool CustomTable::rowFilterMatch(int row, int col) ++{ ++ for (int i = m_filterBy.size() - 1; i >= 0; i--) { ++ QTableWidgetItem* hdr = horizontalHeaderItem(col); ++ if (!hdr || hdr->text() != m_filterBy[i]) ++ continue; ++ QTableWidgetItem* it = item(row,col); ++ if (it && it->text().contains(m_filterValue,Qt::CaseInsensitive)) ++ return true; ++ } ++ return false; ++} ++ ++ ++/* ++ * CustomTableFactory ++ */ ++// Build CustomTable ++void* CustomTableFactory::create(const String& type, const char* name, NamedList* params) ++{ ++ if (!params) ++ return 0; ++ QWidget* parentWidget = 0; ++ String* wndname = params->getParam("parentwindow"); ++ if (!TelEngine::null(wndname)) { ++ String* wName = params->getParam("parentwidget"); ++ QtWindow* wnd = static_cast(Client::self()->getWindow(*wndname)); ++ if (wnd && !TelEngine::null(wName)) ++ parentWidget = wnd->findChild(QtClient::setUtf8(*wName)); ++ } ++ if (type == "CustomTable") ++ return new CustomTable(name,*params,parentWidget); ++ return 0; ++} ++ ++}; // anonymous namespace ++ ++#include "customtable.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtable.h b/modules/qt5/customtable.h +new file mode 100644 +index 0000000..7d800f1 +--- /dev/null ++++ b/modules/qt5/customtable.h +@@ -0,0 +1,396 @@ ++/** ++ * customtable.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * A custom table ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __CUSTOMTABLE_H ++#define __CUSTOMTABLE_H ++ ++#include ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++class CustomTable : public QtTable ++{ ++ YCLASS(CustomTable,QtTable) ++ Q_CLASSINFO("CustomTable","Yate") ++ Q_OBJECT ++ Q_PROPERTY(QStringList _yate_save_props READ saveProps WRITE setSaveProps(QStringList)) ++ Q_PROPERTY(bool _yate_notifyitemchanged READ getNotifyItemChanged WRITE setNotifyItemChanged(bool)) ++ Q_PROPERTY(bool _yate_horizontalheader READ getHHeader WRITE setHHeader(bool)) ++ Q_PROPERTY(bool _yate_notifyonenterpressed READ enterPressNotify WRITE setEnterPressNotify(bool)) ++ Q_PROPERTY(int _yate_rowheight READ getRowHeight WRITE setRowHeight(int)) ++ Q_PROPERTY(QString _yate_col_widths READ getColWidths WRITE setColWidths(QString)) ++ Q_PROPERTY(QString _yate_sorting READ getSorting WRITE setSorting(QString)) ++public: ++ /** ++ * Table item data roles ++ */ ++ enum CustomRoles { ++ ColumnId = Qt::UserRole + 1, // Column id ++ ColumnItemCheckable = Qt::UserRole + 2, // Column items are checkable ++ }; ++ ++ /** ++ * Constructor ++ * @param name The name of the table ++ * @param params Parameters for building the table ++ * @param parent Optional parent ++ */ ++ CustomTable(const char* name, const NamedList& params, QWidget* parent = 0); ++ ++ /** ++ * Destructor ++ */ ++ ~CustomTable(); ++ ++ /** ++ * Check if the table has a filter set ++ * @return True if a filter is set ++ */ ++ inline bool hasFilter() const ++ { return 0 != m_filterBy.count() && m_filterValue.length(); } ++ ++ /** ++ * Function for setting the properties of the table ++ * @param params List that contains the properties to be set and their values ++ * @return True if it has succeeded, false if it hasn't ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Obtain all the entries that the table contains ++ * @param items List to be filled with all the entries the table contains ++ * @return True if there are elements, false if the table is empty ++ */ ++ virtual bool getOptions(NamedList& items); ++ ++ /** ++ * Add a new entry to the table ++ * @param item The new entry's object name ++ * @param data The parameters for building the new entry ++ * @param asStart True if the entry is to be inserted at the start of ++ * the table, false if it is to be appended ++ * @return True if the entry has been added, false otherwise ++ */ ++ virtual bool addTableRow(const String& item, const NamedList* data = 0, ++ bool atStart = false); ++ ++ /** ++ * Add or set one or more table row(s). Screen update is locked while changing the table. ++ * Each data list element is a NamedPointer carrying a NamedList with item parameters. ++ * The name of an element is the item to update. ++ * Set element's value to boolean value 'true' to add a new item if not found ++ * or 'false' to set an existing one. Set it to empty string to delete the item ++ * @param data The list of items to add/set/delete ++ * @param atStart True to add new items at start, false to add them to the end ++ * @return True if the operation was successfull ++ */ ++ virtual bool updateTableRows(const NamedList* data, bool atStart = false); ++ ++ /** ++ * Delete an entry from the table ++ * @param item Name of the object to be deleted ++ * @return True if the entry has been deleted, false otherwise ++ */ ++ virtual bool delTableRow(const String& item); ++ ++ /** ++ * Set/change the properties of a table entry ++ * @param item Name of the entry for which the properties will be set ++ * @param data List of properties to be set and their values ++ * @return True if the entry has been found and set, false if the entry hasn't been found ++ */ ++ virtual bool setTableRow(const String& item, const NamedList* data); ++ ++ /** Get the values of requested properties for an entry ++ * @param item Name of the searched entry ++ * @param data List of the properties for which the value is requested. ++ * It will be filled wiht the properties' values ++ * @return True if the entry is found and the list filled, ++ * false if the entry is not found ++ */ ++ virtual bool getTableRow(const String& item, NamedList* data = 0); ++ ++ /** ++ * Delete all table content ++ * @return True if it succeeds ++ */ ++ virtual bool clearTable(); ++ ++ /** ++ * Set the selected entry ++ * @param item String containing the new selection ++ * @return True if the operation was successfull ++ */ ++ virtual bool setSelect(const String& item); ++ ++ /** ++ * Obtain the selected entry ++ * @param item String in which the selected entry name is to be returned ++ * @return True if something is selected, false otherwise ++ */ ++ virtual bool getSelect(String& item); ++ ++ /** ++ * Retrieve the 0 based index of the current item ++ * @return The index of the current item (-1 on error or container empty) ++ */ ++ virtual int currentItemIndex() ++ { return QTableWidget::currentRow(); } ++ ++ /** ++ * Retrieve the number of items in container ++ * @return The number of items in container (-1 on error) ++ */ ++ virtual int itemCount() ++ { return QTableWidget::rowCount(); } ++ ++ /** ++ * Find a table row by its item id ++ * @param item Item name to find ++ * @return The row or -1 if not found ++ */ ++ int getRow(const String& item); ++ ++ /** ++ * Find a table row id by its row index ++ * @param item Item id to fill ++ * @param row Table row ++ * @return True if the row item was found ++ */ ++ bool getId(String& item, int row); ++ ++ /** ++ * Find a table column id by its column index ++ * @param id Column id to fill ++ * @param checkable Column checkable flag ++ * @param row Table row ++ * @return QTableWidgetItem pointer or 0 if not found ++ */ ++ QTableWidgetItem* getColumnId(String& id, bool& checkable, int col); ++ ++ /** ++ * Find a column by its label. Return -1 if not found ++ * @param text Column label text to find ++ * @param hidden True to find a hidden column (search by 'hidden:' prefix) ++ * @param caseInsensitive True to make a case insensitive comparison ++ * @return The column index or -1 if not found ++ */ ++ int getColumn(const QString& text, bool hidden = false, bool caseInsensitive = true); ++ ++ /** ++ * Find a column by its label. Return -1 if not found ++ * @param text Column label text to find ++ * @param hidden True to find a hidden column (search by 'hidden:' prefix) ++ * @param caseInsensitive True to make a case insensitive comparison ++ * @return The column index or -1 if not found ++ */ ++ inline int getColumn(const char* text, bool hidden = false, bool caseInsensitive = true) ++ { return getColumn(QtClient::setUtf8(text),hidden,caseInsensitive); } ++ ++ /** ++ * Check if this table is notifying item changed ++ * @return True if this table is notifying item changed ++ */ ++ bool getNotifyItemChanged() ++ { return m_notifyItemChanged; } ++ ++ /** ++ * Set/reset item changed notification flag ++ * @param on True to notify item changes, false to disable the notification ++ */ ++ void setNotifyItemChanged(bool on) ++ { m_notifyItemChanged = on; } ++ ++ /** ++ * Check if the horizontal header should be visible ++ * @return True if the horizontal header should be visible ++ */ ++ bool getHHeader() ++ { return m_horzHeader; } ++ ++ /** ++ * Show/hide the horizontal header ++ * @param on True to show the horizontal header, false to hide it ++ */ ++ void setHHeader(bool on) { ++ m_horzHeader = on; ++ QHeaderView* h = horizontalHeader(); ++ if (h) ++ h->setVisible(on); ++ } ++ ++ /** ++ * Check if enter key press action is active. Does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ * @return False ++ */ ++ bool enterPressNotify() ++ { return false; } ++ ++ /** ++ * (de)activate enter key press action ++ * @param value True to activate the enter key press action, false to disable it ++ */ ++ void setEnterPressNotify(bool value); ++ ++ /** ++ * Retrieve the table's default row height ++ * @return Table's default row height ++ */ ++ int getRowHeight() ++ { return m_rowHeight; } ++ ++ /** ++ * Set the table's default row height ++ * @param value Table's new default row height ++ */ ++ void setRowHeight(int value) ++ { m_rowHeight = value; } ++ ++ /** ++ * Retrieve table columns widths ++ * @return Comma separated list of columns widths ++ */ ++ QString getColWidths(); ++ ++ /** ++ * Set the table columns widths string ++ * @param value Comma separated list of columns widths ++ */ ++ void setColWidths(QString value); ++ ++ /** ++ * Retrieve table sorting ++ * @return Table sorting string ++ */ ++ QString getSorting(); ++ ++ /** ++ * Set the table sorting ++ * @param value Table sorting value ++ */ ++ void setSorting(QString value); ++ ++protected: ++ /** ++ * Setup a row ++ * @param row An existing row index ++ * @param data Row parameters ++ * @param item Set the row's id if not empty ++ * @return True on success ++ */ ++ virtual bool setRow(int row, const NamedList* data, ++ const String& item = String::empty()); ++ ++ /** ++ * Handle item cell content changes ++ * @param row Item row ++ * @param col Item column ++ */ ++ virtual void onCellChanged(int row, int col); ++ ++ /** ++ * Catch a context menu event and show the context menu ++ * @param e Context menu event ++ */ ++ virtual void contextMenuEvent(QContextMenuEvent* e); ++ ++ /** ++ * Catch a mouse press event ++ * Disable selection change signal on right button events ++ * @param event Mouse press event ++ */ ++ virtual void mousePressEvent(QMouseEvent* event); ++ ++protected slots: ++ /** ++ * Handle item children actions ++ */ ++ void itemChildAction() ++ { onAction(sender()); } ++ ++ /** ++ * Handle item children toggles ++ */ ++ void itemChildToggle(bool on) ++ { onToggle(sender(),on); } ++ ++ /** ++ * Handle item children select ++ */ ++ void itemChildSelect() ++ { onSelect(sender()); } ++ ++ /** ++ * Handle item cell changed ++ */ ++ void itemCellChanged(int row, int col) ++ { onCellChanged(row,col); } ++ ++ /** ++ * Slot for triggered signals received from actions added to the table ++ */ ++ void actionTriggered(); ++ ++private: ++ /** ++ * Set filter (hide not matching items) ++ * @param value Filter value ++ */ ++ void setFilter(const String& value = String::empty()); ++ ++ /** ++ * Check if the current filter matches a row. Show it if matched, hide it otherwise. ++ * @param row The row to check ++ * @param col The column containing the widget to check ++ * @return True if the row visibility changed ++ */ ++ bool updateFilter(int row, int col); ++ ++ /** ++ * Check if the current filter matches a row ++ * @param row The row to check ++ * @param col The column containing the widget to check ++ * @return True if match ++ */ ++ bool rowFilterMatch(int row, int col); ++ ++ int m_rowHeight; ++ bool m_horzHeader; // Show/hide the horizontal header ++ bool m_notifyItemChanged; // Notify 'listitemchanged' action ++ bool m_notifySelChgOnRClick; // Notify selection changed on mouse right button click ++ QMenu* m_contextMenu; ++ QString m_enterKeyActionName; // The name of the Enter key pressed action ++ // Filter ++ QStringList m_filterBy; // List of cell widget children name whose text is used to filter ++ // the table rows ++ QString m_filterValue; // The filter value ++ // Notifications ++ bool m_changing; // Content is changing from client (not from user): ++ // avoid notifications ++}; ++ ++}; // anonymous namespace ++ ++#endif // __CUSTOMTABLE_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtext.cpp b/modules/qt5/customtext.cpp +new file mode 100644 +index 0000000..d106ba8 +--- /dev/null ++++ b/modules/qt5/customtext.cpp +@@ -0,0 +1,611 @@ ++/** ++ * customtext.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom text edit objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2010-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "customtext.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++// The factory ++class CustomTextFactory : public UIFactory ++{ ++public: ++ inline CustomTextFactory(const char* name = "CustomFactory") ++ : UIFactory(name) ++ { m_types.append(new String("CustomTextEdit")); } ++ virtual void* create(const String& type, const char* name, NamedList* params = 0); ++}; ++ ++// Scroll an area to the end if has a vertical scroll bar ++class ScrollToEnd ++{ ++public: ++ inline ScrollToEnd(QAbstractScrollArea* area) ++ : m_area(area) ++ {} ++ inline ~ScrollToEnd() { ++ QScrollBar* bar = m_area ? m_area->verticalScrollBar() : 0; ++ if (bar) ++ bar->setSliderPosition(bar->maximum()); ++ } ++private: ++ QAbstractScrollArea* m_area; ++}; ++ ++static CustomTextFactory s_factory; ++// Global list of URL handlers ++static NamedList s_urlHandlers(""); ++ ++ ++// Check if a char is a word break one (including NULL) ++static inline bool isWordBreak(char c) ++{ ++ return (c == ' ' || c == '\t' || c == '\r' || c == '\n' || !c); ++} ++ ++// Check if a char should be ignored from URL end (including NULL) ++static inline bool isIgnoreUrlEnd(char c) ++{ ++ return (c == '.' || c == ';' || c == ':' || c == '?' || c == '!'); ++} ++ ++// Move a cursor at document start/end. ++// Adjust position by 'blocks' count ++// Select if required and blocks is not 0 ++static void moveCursor(QTextCursor& c, bool atStart, int blocks = 0, ++ bool select = false) ++{ ++ c.movePosition(!atStart ? QTextCursor::End : QTextCursor::Start); ++ if (!blocks) ++ return; ++ c.movePosition(!atStart ? QTextCursor::PreviousBlock : QTextCursor::NextBlock, ++ select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor, ++ blocks > 0 ? blocks : -blocks); ++} ++ ++/* ++ * CustomTextFormat ++ */ ++// Constructor. Build a Block type ++CustomTextFormat::CustomTextFormat(const String& id, const char* color, const char* bgcolor) ++ : NamedString(id), ++ m_type(Block), m_blockFormat(0), m_charFormat(0) ++{ ++ m_blockFormat = new QTextBlockFormat; ++ if (bgcolor) ++ m_blockFormat->setBackground(QColor(bgcolor)); ++ m_charFormat = new QTextCharFormat; ++ if (color) ++ m_charFormat->setForeground(QColor(color)); ++} ++ ++// Constructor. Build a Html/Plain type ++CustomTextFormat::CustomTextFormat(const String& id, const char* value, bool html) ++ : NamedString(id,value), ++ m_type(html ? Html : Plain), m_blockFormat(0), m_charFormat(0) ++{ ++} ++ ++CustomTextFormat::~CustomTextFormat() ++{ ++ if (m_blockFormat) ++ delete m_blockFormat; ++ if (m_charFormat) ++ delete m_charFormat; ++} ++ ++// Add/insert text into an edit widget ++int CustomTextFormat::insertText(QTextEdit* edit, const String& text, bool atStart, int blocks) ++{ ++ QTextDocument* doc = edit ? edit->document() : 0; ++ if (!doc) ++ return 0; ++ QTextCursor c(doc); ++ moveCursor(c,atStart,blocks,false); ++ int oldBlocks = doc->blockCount(); ++ c.insertBlock(); ++ c.movePosition(QTextCursor::PreviousBlock,QTextCursor::MoveAnchor); ++ // Insert text ++ if (type() == Html) ++ c.insertHtml(QtClient::setUtf8(text)); ++ else { ++ if (m_blockFormat) ++ c.setBlockFormat(*m_blockFormat); ++ if (m_charFormat) ++ c.setCharFormat(*m_charFormat); ++ c.insertText(QtClient::setUtf8(text)); ++ } ++ return doc->blockCount() - oldBlocks; ++} ++ ++// Set text from value. Replace text parameters if not empty ++void CustomTextFormat::buildText(String& text, const NamedList* params, ++ CustomTextEdit* owner, bool lineBrBefore) ++{ ++ if (null()) ++ return; ++ if (lineBrBefore) ++ text = ((type() == Html) ? "
" : "\r\n"); ++ text << *this; ++ NamedList dummy(""); ++ const NamedList* repl = &dummy; ++ if (params) { ++ // Escape or replace HTML markups. ++ // Make a copy of the input list if we are going to change it ++ if (type() == Html) { ++ dummy = *params; ++ unsigned int n = dummy.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ String* s = dummy.getParam(i); ++ if (!TelEngine::null(s)) { ++ Client::plain2html(*s); ++ if (owner) ++ owner->replace(*s); ++ } ++ } ++ } ++ else ++ repl = params; ++ } ++ repl->replaceParams(text); ++} ++ ++ ++/* ++ * TextFragmentList ++ */ ++// Restore this list in the document ++void TextFragmentList::restore(QTextDocument* doc) ++{ ++ if (doc) { ++ for (int i = 0; i < m_list.size(); i++) { ++ QTextCursor c(doc); ++ c.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor, ++ m_list[i].m_docPos); ++ c.movePosition(QTextCursor::NextCharacter,QTextCursor::KeepAnchor, ++ m_list[i].toPlainText().length()); ++ c.removeSelectedText(); ++ c.insertHtml(m_list[i].toHtml()); ++ } ++ } ++ m_list.clear(); ++}; ++ ++ ++/* ++ * CustomTextEdit ++ */ ++// Constructor ++CustomTextEdit::CustomTextEdit(const char* name, const NamedList& params, QWidget* parent) ++ : QtCustomWidget(name,parent), ++ m_edit(0), ++ m_debug(false), ++ m_items(""), ++ m_defItem(String::empty(),"",false), ++ m_followUrl(true), ++ m_urlHandlers(""), ++ m_tempItemCount(0), ++ m_tempItemReplace(true), ++ m_lastFoundPos(-1) ++{ ++ // Build properties ++ QtClient::buildProps(this,params["buildprops"]); ++ m_edit = new QTextBrowser(this); ++ m_edit->setObjectName(params.getValue("textedit_name",this->name() + "_textedit")); ++ m_edit->setOpenLinks(false); ++ m_edit->setOpenExternalLinks(false); ++ m_edit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); ++ QtClient::setWidget(this,m_edit); ++ m_searchFoundFormat.setBackground(QBrush(QColor("darkgreen"))); ++ m_searchFoundFormat.setForeground(QBrush(QColor("white"))); ++ m_debug = params.getBoolValue("_yate_debug_widget"); ++ if (m_debug) { ++ m_items.addParam(new CustomTextFormat(String(-1),"white")); // Output() or client set status ++ m_items.addParam(new CustomTextFormat(String(0),"yellow","red")); // DebugFail - blinking yellow on red ++ m_items.addParam(new CustomTextFormat(String(1),"yellow","red")); // Unnamed - yellow on red ++ m_items.addParam(new CustomTextFormat(String(2),"white","red")); // DebugCrit - white on red ++ m_items.addParam(new CustomTextFormat(String(3),"lightgrey","red")); // DebugConf - gray on red ++ m_items.addParam(new CustomTextFormat(String(4),"red")); // DebugStub - red on black ++ m_items.addParam(new CustomTextFormat(String(5),"orangered")); // DebugWarn - light red on black ++ m_items.addParam(new CustomTextFormat(String(6),"yellow")); // DebugMild - yellow on black ++ m_items.addParam(new CustomTextFormat(String(7),"lightgreen")); // DebugNote - light green on black ++ m_items.addParam(new CustomTextFormat(String(8),"white")); // DebugCall - white on black ++ m_items.addParam(new CustomTextFormat(String(9),"cyan")); // DebugInfo - light cyan on black ++ m_items.addParam(new CustomTextFormat(String(10),"teal")); // DebugAll - cyan on black ++ } ++ setParams(params); ++ // Connect signals ++ QtClient::connectObjects(m_edit,SIGNAL(anchorClicked(const QUrl&)),this,SLOT(urlTrigerred(const QUrl&))); ++} ++ ++// Set parameters ++bool CustomTextEdit::setParams(const NamedList& params) ++{ ++ static const String s_setRichItem = "set_richtext_item"; ++ static const String s_setPlainItem = "set_plaintext_item"; ++ static const String s_search = "search"; ++ unsigned int n = params.length(); ++ bool ok = true; ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ if (!(ns && ns->name())) ++ continue; ++ if (ns->name() == s_setRichItem) ++ setItem(*ns,true); ++ else if (ns->name() == s_setPlainItem) ++ setItem(*ns,false); ++ else if (ns->name() == s_search) ++ ok = setSearchHighlight(ns->toBoolean(),YOBJECT(NamedList,ns)) && ok; ++ else { ++ // Prefixed parameters ++ String tmp(ns->name()); ++ if (tmp.startSkip("set_url_handler:",false)) { ++ // Set handler from prefix[{scheme}]=formatting_template ++ if (!tmp) ++ continue; ++ if (!m_urlHandlers.c_str()) ++ m_urlHandlers.assign(s_urlHandlers.c_str()); ++ // Check for optional scheme ++ int pos = tmp.find('{'); ++ if (pos <= 0 || tmp[tmp.length() - 1] != '}') ++ m_urlHandlers.setParam(new CustomTextEditUrl(tmp,*ns)); ++ else ++ m_urlHandlers.setParam(new CustomTextEditUrl(tmp.substr(0,pos),*ns, ++ tmp.substr(pos + 1,tmp.length() - pos - 2))); ++ } ++ else if (tmp.startSkip("property:",false)) { ++ QObject* target = m_edit; ++ if (tmp.startSkip(name() + ":",false)) ++ target = this; ++ if (!QtClient::setProperty(target,tmp,*ns)) ++ ok = false; ++ } ++ } ++ } ++ return ok; ++} ++ ++// Append or insert text lines to this widget ++bool CustomTextEdit::addLines(const NamedList& lines, unsigned int max, bool atStart) ++{ ++ unsigned int n = lines.length(); ++ if (!n) ++ return true; ++ ScrollToEnd scroll(m_edit); ++ // Remove the temporary item(s) ++ if (m_tempItemCount && m_tempItemReplace) { ++ removeBlocks(m_tempItemCount); ++ m_tempItemCount = 0; ++ } ++ if (!m_debug) { ++ String text; ++ CustomTextFormat* last = 0; ++ // Line format: item= ++ // Each parameter may contain an optional list of parameters to be replaced in item ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = lines.getParam(i); ++ if (!ns) ++ continue; ++ CustomTextFormat* crt = find(ns->name()); ++ if (!crt) ++ crt = &m_defItem; ++ if (last && last->type() != crt->type() && text) { ++ // Format changed: insert text now and reset it ++ insert(*last,text,atStart); ++ text.clear(); ++ } ++ last = crt; ++ if (last != &m_defItem) { ++ String tmp; ++ last->buildText(tmp,YOBJECT(NamedList,ns),this,!text.null()); ++ text << tmp; ++ } ++ else ++ text << ns->name(); ++ } ++ if (last && text) ++ insert(*last,text,atStart); ++ } ++ else { ++ // Handle 'max' ++ QTextDocument* doc = m_edit->document(); ++ if (doc) ++ doc->setMaximumBlockCount((int)max); ++ // Line format: text=debuglevel ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = lines.getParam(i); ++ if (!ns) ++ continue; ++ CustomTextFormat* f = find(*ns); ++ // Use default output if not found ++ if (!f) ++ f = find(String("-1")); ++ if (f) { ++ // Ignore CR, LF or CR/LF at text end: we are adding a block ++ unsigned int n = 0; ++ if (ns->name().endsWith("\r\n")) ++ n = 2; ++ else if (ns->name().length()) { ++ int pos = ns->name().length() - 1; ++ if (ns->name()[pos] == '\r' || ns->name()[pos] == '\n') ++ n = 1; ++ } ++ if (n) ++ insert(*f,ns->name().substr(0,ns->name().length() - n),atStart); ++ else ++ insert(*f,ns->name(),atStart); ++ } ++ } ++ } ++ return true; ++} ++ ++// Set the displayed text of this widget ++bool CustomTextEdit::setText(const String& text, bool richText) ++{ ++ ScrollToEnd scroll(m_edit); ++ m_edit->clear(); ++ if (richText) ++ m_edit->insertHtml(QtClient::setUtf8(text)); ++ else ++ m_edit->insertPlainText(QtClient::setUtf8(text)); ++ return true; ++} ++ ++// Retrieve the displayed text of this widget ++bool CustomTextEdit::getText(String& text, bool richText) ++{ ++ if (richText) ++ QtClient::getUtf8(text,m_edit->toHtml()); ++ else ++ QtClient::getUtf8(text,m_edit->toPlainText()); ++ return true; ++} ++ ++// Add/change/clear a pre-formatted item (item must be name[:[value]) ++void CustomTextEdit::setItem(const String& value, bool html) ++{ ++ if (!value) ++ return; ++ int pos = value.find(':'); ++ if (pos > 0 && pos != (int)value.length() - 1) { ++ String id = value.substr(0,pos); ++ String val = value.substr(pos + 1); ++ CustomTextFormat* f = find(id); ++ // Remove existing if format changes ++ if (f && ((html && f->type() != CustomTextFormat::Html) || ++ (!html && f->type() == CustomTextFormat::Plain))) { ++ m_items.clearParam(f); ++ f = 0; ++ } ++ if (!f) ++ m_items.addParam(new CustomTextFormat(id,val,html)); ++ else ++ f->assign(val); ++ } ++ else if (pos < 0) ++ m_items.clearParam(value); ++ else if (pos > 0) ++ m_items.clearParam(value.substr(0,pos)); ++} ++ ++// Set/reset text highlight ++bool CustomTextEdit::setSearchHighlight(bool on, NamedList* params) ++{ ++ if (!on) { ++ m_lastFoundPos = -1; ++ if (params && params->getBoolValue("reset",true)) ++ m_searchFound.restore(m_edit->document()); ++ else ++ m_searchFound.m_list.clear(); ++ return true; ++ } ++ if (!params) ++ return false; ++ QTextDocument* doc = m_edit->document(); ++ if (!doc) ++ return false; ++ QString find = QtClient::setUtf8(params->getValue("find")); ++ if (!find.length()) ++ return false; ++ Qt::CaseSensitivity cs = params->getBoolValue("matchcase") ? ++ Qt::CaseSensitive : Qt::CaseInsensitive; ++ bool found = false; ++ QString text = doc->toPlainText(); ++ if (params->getBoolValue("all")) { ++ m_lastFoundPos = -1; ++ m_searchFound.restore(doc); ++ int pos = -1; ++ do { ++ pos = text.indexOf(find,pos + 1,cs); ++ if (pos >= 0) ++ handleFound(pos,find.length()); ++ } ++ while (pos >= 0); ++ if (m_searchFound.m_list.size()) { ++ found = true; ++ ensureCharVisible(m_searchFound.m_list[0].m_docPos); ++ } ++ } ++ else { ++ if (params->getBoolValue("next")) ++ m_lastFoundPos = text.indexOf(find,m_lastFoundPos >= 0 ? m_lastFoundPos + 1 : 0,cs); ++ else if (m_lastFoundPos < 0) ++ m_lastFoundPos = text.lastIndexOf(find,-1,cs); ++ else if (m_lastFoundPos) ++ m_lastFoundPos = text.lastIndexOf(find,m_lastFoundPos - 1,cs); ++ if (m_lastFoundPos >= 0) { ++ found = true; ++ m_searchFound.restore(doc); ++ handleFound(m_lastFoundPos,find.length()); ++ ensureCharVisible(m_lastFoundPos); ++ } ++ } ++ return found; ++} ++ ++// Ensure the character at a given position is visible ++void CustomTextEdit::ensureCharVisible(int pos) ++{ ++ QTextCursor show(m_edit->document()); ++ show.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,pos); ++ m_edit->setTextCursor(show); ++ m_edit->ensureCursorVisible(); ++} ++ ++// Replace string sequences with formatted text ++void CustomTextEdit::replace(String& text) ++{ ++ if (!text) ++ return; ++ // Replace URLs ? ++ if (m_followUrl) { ++ const NamedList& urls = m_urlHandlers.c_str() ? m_urlHandlers : s_urlHandlers; ++ unsigned int n = urls.length(); ++ for (int start = 0; start < (int)text.length();) { ++ int len = 1; ++ for (unsigned int i = 0; i < n; i++) { ++ const CustomTextEditUrl* ns = static_cast(urls.getParam(i)); ++ // Parameter name is the URL prefix ++ if (!(ns && ns->name())) ++ continue; ++ if (ns->name().length() >= text.length() - start) ++ continue; ++ // Get html template from parameter value or list name ++ const char* templ = *ns ? ns->c_str() : urls.c_str(); ++ if (TelEngine::null(templ)) ++ continue; ++ // Check for prefix match ++ if (::strncmp(text.c_str() + start,ns->name().c_str(),ns->name().length())) ++ continue; ++ // Detect url end ++ int end = start + (int)ns->name().length(); ++ while (!isWordBreak(text[end])) ++ end++; ++ // Go back 1 char if the last one should be ignored ++ if ((end > start + (int)ns->name().length()) && isIgnoreUrlEnd(text[end - 1])) ++ end--; ++ len = end - start; ++ // Replace the URL if have something after prefix ++ if (len <= (int)ns->name().length()) { ++ len++; ++ break; ++ } ++ // Check if we have a scheme to prepend for this one ++ String url = text.substr(start,len); ++ NamedList p(""); ++ p.addParam("url-display",url); ++ p.addParam("url",ns->m_scheme ? (ns->m_scheme + url) : url); ++ String u = templ; ++ p.replaceParams(u); ++ text = text.substr(0,start) + u + text.substr(end); ++ len = (int)u.length(); ++ break; ++ } ++ start += len; ++ } ++ } ++} ++ ++// Insert text using a given format. Update temporary item length if appropriate ++void CustomTextEdit::insert(CustomTextFormat& fmt, const String& text, bool atStart) ++{ ++ int n = fmt.insertText(m_edit,text,atStart,m_tempItemReplace ? 0 : m_tempItemCount); ++ if (m_tempItemName != fmt.toString()) { ++ // Reset counter if temporary item was replaced ++ if (m_tempItemReplace) ++ m_tempItemCount = 0; ++ } ++ else ++ m_tempItemCount = !atStart ? n : -n; ++} ++ ++// Remove blocks from edit widget ++void CustomTextEdit::removeBlocks(int blocks) ++{ ++ if (!blocks) ++ return; ++ QTextDocument* doc = m_edit->document(); ++ if (!doc) ++ return; ++ QTextCursor c(doc); ++ moveCursor(c,blocks < 0,blocks,true); ++ c.removeSelectedText(); ++} ++ ++// URL clicked notification ++void CustomTextEdit::urlTrigerred(const QUrl& url) ++{ ++ if (!(m_followUrl && Client::valid())) ++ return; ++ String tmp; ++ QtClient::getUtf8(tmp,url.toString()); ++ XDebug(ClientDriver::self(),DebugAll,"CustomTextEdit(%s)::urlTrigerred(%s)", ++ name().c_str(),tmp.c_str()); ++ Client::self()->openUrl(tmp); ++} ++ ++// Handle found item. Add data to found items. Set formatting ++void CustomTextEdit::handleFound(int pos, int len) ++{ ++ QTextCursor c(m_edit->document()); ++ c.movePosition(QTextCursor::NextCharacter,QTextCursor::MoveAnchor,pos); ++ c.movePosition(QTextCursor::NextCharacter,QTextCursor::KeepAnchor,len); ++ m_searchFound.add(c); ++ QString sel = c.selectedText(); ++ c.removeSelectedText(); ++ c.insertText(sel,m_searchFoundFormat); ++} ++ ++ ++/* ++ * CustomTextFactory ++ */ ++// Build objects ++void* CustomTextFactory::create(const String& type, const char* name, NamedList* params) ++{ ++ // Init URL handlers ++ if (!s_urlHandlers.c_str()) { ++ s_urlHandlers.assign("${url-display}"); ++ s_urlHandlers.addParam(new CustomTextEditUrl("http://")); ++ s_urlHandlers.addParam(new CustomTextEditUrl("https://")); ++ s_urlHandlers.addParam(new CustomTextEditUrl("www.","","http://")); ++ } ++ if (!params) ++ return 0; ++ QWidget* parentWidget = 0; ++ String* wndname = params->getParam("parentwindow"); ++ if (!TelEngine::null(wndname)) { ++ String* wName = params->getParam("parentwidget"); ++ QtWindow* wnd = static_cast(Client::self()->getWindow(*wndname)); ++ if (wnd && !TelEngine::null(wName)) ++ parentWidget = wnd->findChild(QtClient::setUtf8(*wName)); ++ } ++ if (type == "CustomTextEdit") ++ return new CustomTextEdit(name,*params,parentWidget); ++ return 0; ++} ++ ++}; // anonymous namespace ++ ++#include "customtext.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtext.h b/modules/qt5/customtext.h +new file mode 100644 +index 0000000..5580bf7 +--- /dev/null ++++ b/modules/qt5/customtext.h +@@ -0,0 +1,386 @@ ++/** ++ * customtext.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom text edit objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __CUSTOMTEXT_H ++#define __CUSTOMTEXT_H ++ ++#include "qt5client.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++class CustomTextFormat; // Custom QTextEdit format entry ++class CustomTextEditUrl; // Custom text edit url ++class TextFragment; // A formatted text document fragment ++class TextFragmentList; // A text fragment container ++class CustomTextEdit; // Custom QTextEdit ++ ++/** ++ * Implements interfaces used to add/insert text into a CustomTextEdit widget ++ * The value of the NamedString may contain a template used to replace parameters ++ * @short A custom QTextEdit format entry ++ */ ++class CustomTextFormat : public NamedString ++{ ++ YCLASS(CustomTextFormat,NamedString) ++public: ++ /** ++ * Text format type enumeration ++ */ ++ enum Type { ++ Html, // HTML formatted text ++ Plain, // Plain text ++ Block, // Use QT format class(es) ++ }; ++ ++ /** ++ * Constructor. Build a Block type ++ */ ++ CustomTextFormat(const String& id, const char* color, const char* bgcolor = 0); ++ ++ /** ++ * Constructor. Build a Html/Plain type ++ */ ++ CustomTextFormat(const String& id, const char* value, bool html); ++ ++ /** ++ * Destructor ++ */ ++ virtual ~CustomTextFormat(); ++ ++ /** ++ * Retrieve this object's type ++ */ ++ inline Type type() const ++ { return m_type; } ++ ++ /** ++ * Add/insert text into an edit widget ++ * @param edit Edit widget ++ * @param text Text buffer ++ * @param atStart True to insert at start, false to append ++ * @param blocks The number of blocks to skip if inserted at start or insert before if added ++ * @return The number of blocks added ++ */ ++ int insertText(QTextEdit* edit, const String& text, bool atStart, int blocks); ++ ++ /** ++ * Set text from value. Replace text parameters if not empty ++ * @param text Text buffer ++ * @param params Parameters to replace ++ * @param owner Text edit owner ++ * @param lineBrBefore True to append a libe break before it ++ */ ++ void buildText(String& text, const NamedList* params, CustomTextEdit* owner, ++ bool lineBrBefore); ++ ++private: ++ Type m_type; ++ QTextBlockFormat* m_blockFormat; ++ QTextCharFormat* m_charFormat; ++}; ++ ++/** ++ * This class holds an url definition with an optional scheme ++ * NamedString's value may contain optional formatting template ++ * @short Custom text edit url ++ */ ++class CustomTextEditUrl : public NamedString ++{ ++public: ++ inline CustomTextEditUrl(const char* name, const char* value = 0, const char* scheme = 0) ++ : NamedString(name,value), ++ m_scheme(scheme) ++ {} ++ String m_scheme; ++}; ++ ++/** ++ * This class keeps a formatted text document fragment along ++ * with document position ++ * @short A formatted text document fragment ++ */ ++class TextFragment : public QTextDocumentFragment ++{ ++public: ++ /** ++ * Constructor. Build a text fragment from a cursor's selection ++ * @param c The cursor ++ */ ++ inline TextFragment(const QTextCursor& c) ++ : QTextDocumentFragment(c), ++ m_docPos(c.selectionStart()) ++ {} ++ ++ /** ++ * Copy constructor ++ * @param other Source text fragment ++ */ ++ inline TextFragment(const TextFragment& other) ++ : QTextDocumentFragment(other), m_docPos(other.m_docPos) ++ {} ++ ++ /** ++ * The position of this fragment in the document ++ */ ++ int m_docPos; ++ ++private: ++ TextFragment() {}; ++}; ++ ++/** ++ * This class implements a TextFragment container ++ * @short A text fragment container ++ */ ++class TextFragmentList ++{ ++public: ++ /** ++ * Restore all fragments in the document. Clear the list ++ * @param doc The document ++ */ ++ void restore(QTextDocument* doc); ++ ++ /** ++ * Build and append a text fragment from a cursor's selection ++ * @param c The cursor ++ */ ++ inline void add(QTextCursor& c) ++ { m_list.append(TextFragment(c)); } ++ ++ /** ++ * The fragments owned by this container ++ */ ++ QList m_list; ++}; ++ ++/** ++ * This class holds custom text edit widget with abilities to add pre-formated ++ * parameterized text ++ * @short A custom text edit widget ++ */ ++class CustomTextEdit : public QtCustomWidget ++{ ++ YCLASS(CustomTextEdit,QtCustomWidget) ++ Q_CLASSINFO("CustomTextEdit","Yate") ++ Q_OBJECT ++ Q_PROPERTY(bool _yate_followurl READ followUrl WRITE setFollowUrl(bool)) ++ Q_PROPERTY(QString _yate_tempitemname READ tempItemName WRITE setTempItemName(QString)) ++ Q_PROPERTY(int _yate_tempitemcount READ tempItemCount WRITE setTempItemCount(int)) ++ Q_PROPERTY(bool _yate_tempitemreplace READ tempItemReplace WRITE setTempItemReplace(bool)) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ * @param params Object parameters ++ * @param parent Optional parent ++ */ ++ CustomTextEdit(const char* name, const NamedList& params, QWidget* parent); ++ ++ /** ++ * Set parameters. Add text ++ * @param params Parameter list ++ * @return True on success ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Clear the edit widget ++ * @return True ++ */ ++ virtual bool clearTable() { ++ m_edit->clear(); ++ return true; ++ } ++ ++ /** ++ * Append or insert text lines to this widget ++ * @param name The name of the widget ++ * @param lines List containing the lines ++ * @param max The maximum number of lines allowed to be displayed. Set to 0 to ignore ++ * @param atStart True to insert, false to append ++ * @return True on success ++ */ ++ virtual bool addLines(const NamedList& lines, unsigned int max, bool atStart = false); ++ ++ /** ++ * Set the displayed text of this widget ++ * @param text Text value to set ++ * @param richText True if the text contains format data ++ * @return True on success ++ */ ++ virtual bool setText(const String& text, bool richText = false); ++ ++ /** ++ * Retrieve the displayed text of this widget ++ * @param text Text value ++ * @param richText True to retrieve formatted data ++ * @return True on success ++ */ ++ virtual bool getText(String& text, bool richText = false); ++ ++ /** ++ * Add/change/clear a pre-formatted item (item must be name[:[value]) ++ * @param value Formatted item to set or clear ++ * @param html True to add rich text, false to add plain text ++ */ ++ void setItem(const String& value, bool html); ++ ++ /** ++ * Set/reset search text highlight ++ * @param on True to set, false to reset ++ * @param params Parameters. Ignored it reset ++ * @return True if reset or a match was found. False otherwise ++ */ ++ bool setSearchHighlight(bool on, NamedList* params); ++ ++ /** ++ * Ensure the character at a given position is visible ++ * @param pos The position in the document ++ */ ++ void ensureCharVisible(int pos); ++ ++ /** ++ * Replace string sequences with formatted text ++ * @param text Text buffer ++ */ ++ void replace(String& text); ++ ++ /** ++ * Insert text using a given format. Update temporary item length if appropriate ++ * @param fmt Format to use ++ * @param text Text to insert ++ * @param atStart Insert at start or append ++ */ ++ void insert(CustomTextFormat& fmt, const String& text, bool atStart); ++ ++ /** ++ * Remove blocks from edit widget ++ * @param blocks The number of blocks to remove, negative to remove from start ++ */ ++ void removeBlocks(int blocks); ++ ++ /** ++ * Retrieve the value of _yate_followurl property ++ * @return The value of _yate_followurl property ++ */ ++ bool followUrl() ++ { return m_followUrl; } ++ ++ /** ++ * Set the value of _yate_followurl property ++ * @param value The new value of _yate_followurl property ++ */ ++ void setFollowUrl(bool value) ++ { m_followUrl = value; } ++ ++ /** ++ * Retrieve the value of _yate_tempitemname property ++ * @return The value of _yate_tempitemname property ++ */ ++ QString tempItemName() ++ { return QtClient::setUtf8(m_tempItemName); } ++ ++ /** ++ * Set the value of _yate_tempitemname property ++ * @param value The new value of _yate_tempitemname property ++ */ ++ void setTempItemName(QString value) ++ { QtClient::getUtf8(m_tempItemName,value); } ++ ++ /** ++ * Retrieve the value of _yate_tempitemcount property ++ * @return The value of _yate_tempitemcount property ++ */ ++ int tempItemCount() ++ { return m_tempItemCount; } ++ ++ /** ++ * Set the value of _yate_tempitemcount property ++ * @param value The new value of _yate_tempitemcount property ++ */ ++ void setTempItemCount(int value) { ++ if (!value && m_tempItemCount) ++ removeBlocks(m_tempItemCount); ++ m_tempItemCount = value; ++ } ++ ++ /** ++ * Retrieve the value of _yate_tempitemreplace property ++ * @return The value of _yate_tempitemreplace property ++ */ ++ bool tempItemReplace() ++ { return m_tempItemReplace; } ++ ++ /** ++ * Set the value of _yate_tempitemreplace property ++ * @param value The new value of _yate_tempitemreplace property ++ */ ++ void setTempItemReplace(bool value) ++ { m_tempItemReplace = value; } ++ ++public slots: ++ /** ++ * URL clicked notification ++ * Use this slot instead of QT open external links: ++ * displayed text will be cleared if the link is not handled ++ */ ++ void urlTrigerred(const QUrl& url); ++ ++protected: ++ /** ++ * Handle found item. Add data to found items. Set formatting ++ * @param pos The position in document ++ * @param len Found text length ++ */ ++ void handleFound(int pos, int len); ++ ++ /** ++ * Retrieve a custom text format object ++ * @param name Item name ++ * @return CustomTextFormat pointer or 0 if not found ++ */ ++ inline CustomTextFormat* find(const String& name) ++ { return YOBJECT(CustomTextFormat,m_items.getParam(name)); } ++ ++ QTextBrowser* m_edit; // The edit widget ++ bool m_debug; // This is a debug widget ++ NamedList m_items; // Formatted items ++ CustomTextFormat m_defItem; // Default text format used to add plain text ++ // when an item is not found ++ bool m_followUrl; // Follow URLs ++ NamedList m_urlHandlers; // List specific URL handlers ++ String m_tempItemName; // Temporary last item name ++ int m_tempItemCount; // Temporary last item count ++ // negative: start, positive: end, 0: none ++ bool m_tempItemReplace; // Replace (delete) temporary item(s) ++ // Search ++ TextFragmentList m_searchFound; // Last found data: restore it on request ++ QTextCharFormat m_searchFoundFormat; // Found item(s) formatting ++ int m_lastFoundPos; // Last found position in document ++}; ++ ++}; // anonymous namespace ++ ++#endif // __CUSTOMTEXT_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtree.cpp b/modules/qt5/customtree.cpp +new file mode 100644 +index 0000000..966f345 +--- /dev/null ++++ b/modules/qt5/customtree.cpp +@@ -0,0 +1,4059 @@ ++/** ++ * customtree.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom QtTree based objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2010-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "customtree.h" ++ ++#ifndef _WINDOWS ++#include ++#include ++#endif ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++// The factory ++class CustomTreeFactory : public UIFactory ++{ ++public: ++ inline CustomTreeFactory(const char* name = "CustomTreeFactory") ++ : UIFactory(name) { ++ m_types.append(new String("ContactList")); ++ m_types.append(new String("QtCustomTree")); ++ m_types.append(new String("FileListTree")); ++ } ++ virtual void* create(const String& type, const char* name, NamedList* params = 0); ++}; ++ ++// Utility class used to disable/enable a tree sorting flag ++// Disable tree sorting upon creation and enable it on destruction ++// Objects of this class should be created in methods changing ++// tree content ++class SafeTree ++{ ++public: ++ inline SafeTree(QTreeWidget* tree) ++ : m_tree(tree), m_sorting(false) { ++ if (!tree) ++ return; ++ m_tree->setUpdatesEnabled(false); ++ if (tree->isSortingEnabled()) { ++ m_sorting = tree->isSortingEnabled(); ++ m_tree->setSortingEnabled(false); ++ } ++ } ++ inline ~SafeTree() { ++ if (!m_tree) ++ return; ++ if (m_sorting) ++ m_tree->setSortingEnabled(true); ++ m_tree->setUpdatesEnabled(true); ++ } ++private: ++ QTreeWidget* m_tree; ++ bool m_sorting; ++}; ++ ++// Inc/dec an integer value ++class SafeInt ++{ ++public: ++ inline SafeInt(int* value) ++ : m_value(value) { ++ if (m_value) ++ (*m_value)++; ++ } ++ inline ~SafeInt() { ++ if (m_value) ++ (*m_value)--; ++ } ++protected: ++ int* m_value; ++}; ++ ++// Utility class used to restore selection ++class TreeRestoreSel ++{ ++public: ++ inline TreeRestoreSel(QtCustomTree* tree, const String& check = String::empty()) ++ : m_tree(tree) { ++ if (!tree) ++ return; ++ tree->getSelect(m_sel); ++ if (m_sel && check && m_sel != check) ++ m_sel.clear(); ++ } ++ inline ~TreeRestoreSel() { ++ if (m_tree && m_sel) ++ m_tree->setSelect(m_sel); ++ } ++private: ++ QtCustomTree* m_tree; ++ String m_sel; ++}; ++ ++ ++static CustomTreeFactory s_factory; ++static const String s_noGroupId(MD5("Yate").hexDigest() + "_NOGROUP"); ++static const String s_offline("offline"); ++static NamedList s_delegateCommon(""); ++ ++// Set size from string ++static inline void setSize(QSize& size, const String& s) ++{ ++ if (!s) ++ return; ++ int pos = s.find(','); ++ if (pos < 0) { ++ int val = s.toInteger(0,0,0); ++ size = QSize(val,val); ++ } ++ else ++ size = QSize(s.substr(0,pos).toInteger(0,0,0),s.substr(pos + 1).toInteger(0,0,0)); ++} ++ ++// Utility: compare strings ++// return -1 if s1 < s2, 0 if s1 == s2, 1 if s1 > s2 ++static inline int compareStr(const QString& s1, const QString& s2, ++ Qt::CaseSensitivity cs) ++{ ++ if (cs == Qt::CaseSensitive) { ++ if (s1 == s2) ++ return 0; ++ return (s1 < s2) ? -1 : 1; ++ } ++ return s1.compare(s2,cs); ++} ++ ++// Utility: compare a single key item ++static bool caseInsensitiveLessThan(const QtTreeItemKey& left, ++ const QtTreeItemKey& right) ++{ ++ return compareStr(left.second,right.second,Qt::CaseInsensitive) < 0; ++} ++ ++// Utility: compare a single key item ++static bool caseInsensitiveGreaterThan(const QtTreeItemKey& left, ++ const QtTreeItemKey& right) ++{ ++ return compareStr(left.second,right.second,Qt::CaseInsensitive) > 0; ++} ++ ++// Utility: compare a single key item ++static bool caseSensitiveLessThan(const QtTreeItemKey& left, ++ const QtTreeItemKey& right) ++{ ++ return compareStr(left.second,right.second,Qt::CaseSensitive) < 0; ++} ++ ++// Utility: compare a single key item ++static bool caseSensitiveGreaterThan(const QtTreeItemKey& left, ++ const QtTreeItemKey& right) ++{ ++ return compareStr(left.second,right.second,Qt::CaseSensitive) > 0; ++} ++ ++// Utility: sort ++static inline void stableSort(QVector& v, ++ Qt::SortOrder order, Qt::CaseSensitivity cs) ++{ ++ if (order == Qt::AscendingOrder) { ++ if (cs == Qt::CaseInsensitive) ++ std::stable_sort(v.begin(),v.end(),caseInsensitiveLessThan); ++ else ++ std::stable_sort(v.begin(),v.end(),caseSensitiveLessThan); ++ } ++ else if (cs == Qt::CaseInsensitive) ++ std::stable_sort(v.begin(),v.end(),caseInsensitiveGreaterThan); ++ else ++ std::stable_sort(v.begin(),v.end(),caseSensitiveGreaterThan); ++} ++ ++// Utility: sort ++ ++// Retrieve a string from a list ++static inline const String& objListItem(ObjList* list, int index) ++{ ++ GenObject* gen = list ? (*list)[index] : 0; ++ return gen ? gen->toString() : String::empty(); ++} ++ ++int replaceHtmlParams(String& str, const NamedList& list, bool spaceEol = false) ++{ ++ int p1 = 0; ++ int cnt = 0; ++ while ((p1 = str.find("${",p1)) >= 0) { ++ int p2 = str.find('}',p1 + 2); ++ if (p2 <= 0) ++ return -1; ++ String param = str.substr(p1 + 2,p2 - p1 - 2); ++ param.trimBlanks(); ++ int defValPos = param.find('$'); ++ if (defValPos < 0) ++ param = list.getValue(param); ++ else { ++ // param is in ${$} format ++ String def = param.substr(defValPos + 1); ++ param = list.getValue(param.substr(0,defValPos).trimBlanks()); ++ if (!param && def) ++ param = list.getValue(def.trimBlanks()); ++ } ++ if (param) ++ Client::plain2html(param,spaceEol); ++ str = str.substr(0,p1) + param + str.substr(p2 + 1); ++ // advance search offset past the string we just replaced ++ p1 += param.length(); ++ cnt++; ++ } ++ return cnt; ++} ++ ++ ++/* ++ * QtCellGridDraw ++ */ ++// Set draw pen ++void QtCellGridDraw::setPen(Position pos, QPen pen) ++{ ++#define QtCellGridSetPen(val,p) \ ++ if (0 != (pos & val)) { \ ++ p = pen; \ ++ m_flags |= val; \ ++ } ++ QtCellGridSetPen(Left,m_left); ++ QtCellGridSetPen(Top,m_top); ++ QtCellGridSetPen(Right,m_right); ++ QtCellGridSetPen(Bottom,m_bottom); ++} ++ ++// Set draw pens from a list of parameters ++void QtCellGridDraw::setPen(const NamedList& params) ++{ ++ setPen(Left,params); ++ setPen(Top,params); ++ setPen(Right,params); ++ setPen(Bottom,params); ++} ++ ++// Set pen from parameters list ++void QtCellGridDraw::setPen(Position pos, const NamedList& params) ++{ ++ String prefix("griddraw_"); ++ if (pos == Left) ++ prefix << "left"; ++ else if (pos == Top) ++ prefix << "top"; ++ else if (pos == Right) ++ prefix << "right"; ++ else if (pos == Bottom) ++ prefix << "bottom"; ++ else ++ return; ++ QPen pen; ++ bool ok = false; ++ const String& color = params[prefix + "_color"]; ++ if (color) { ++ ok = true; ++ if (color[0] == '#') ++ pen.setColor(QColor(color.substr(1).toInteger(0,16))); ++ else ++ pen.setColor(QColor(color)); ++ } ++ if (ok) ++ setPen(pos,pen); ++} ++ ++// Draw the borders ++void QtCellGridDraw::draw(QPainter* p, QRect& rect, bool isFirstRow, bool isFirstColumn, ++ bool isLastRow, bool isLastColumn) const ++{ ++ if (!(p && flag(Pos))) ++ return; ++ if (0 != (m_flags & Left) && (!isFirstColumn || flag(DrawStart))) { ++ p->setPen(m_left); ++ p->drawLine(rect.topLeft(),rect.bottomLeft()); ++ } ++ if (0 != (m_flags & Top) && (!isFirstRow || flag(DrawStart))) { ++ p->setPen(m_top); ++ p->drawLine(rect.topLeft(),rect.topRight()); ++ } ++ if (0 != (m_flags & Right) && (!isLastColumn || flag(DrawEnd))) { ++ p->setPen(m_right); ++ p->drawLine(rect.topRight(),rect.bottomRight()); ++ } ++ if (0 != (m_flags & Bottom) && (!isLastRow || flag(DrawEnd))) { ++ p->setPen(m_bottom); ++ p->drawLine(rect.bottomLeft(),rect.bottomRight()); ++ } ++} ++ ++ ++// ++// QtTreeDrag ++// ++QtTreeDrag::QtTreeDrag(QObject* parent, const NamedList* params) ++ : QObject(parent), ++ m_urlBuilder(0) ++{ ++ if (!params) ++ return; ++ const String& fmt = (*params)[YSTRING("_yate_drag_url_template")]; ++ if (fmt) ++ setUrlBuilder(fmt,(*params)[YSTRING("_yate_drag_url_queryparams")]); ++} ++ ++// Set the URL builder, set to NULL if fmt is empty ++void QtTreeDrag::setUrlBuilder(const String& fmt, const String& queryParams) ++{ ++ if (m_urlBuilder) ++ QtClient::deleteLater(m_urlBuilder); ++ if (fmt) ++ m_urlBuilder = new QtUrlBuilder(this,fmt,queryParams); ++ else ++ m_urlBuilder = 0; ++} ++ ++// Build MIME data for a list of items ++QMimeData* QtTreeDrag::mimeData(const QList items) const ++{ ++ if (!m_urlBuilder) ++ return 0; ++ int n = items.size(); ++ if (n < 1) ++ return 0; ++ QList urls; ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* it = static_cast(items[i]); ++ QUrl url = m_urlBuilder->build(*it); ++ if (!url.isEmpty()) ++ urls.append(url); ++ } ++ QMimeData* data = new QMimeData; ++ if (urls.size() > 0) ++ data->setUrls(urls); ++ return data; ++} ++ ++ ++// ++// QtTreeItemProps ++// ++// Set a button's action, create if it not found ++bool QtTreeItemProps::setPaintButtonAction(const String& name, const String& action) ++{ ++ QtPaintButtonDesc* b = QtPaintButtonDesc::find(m_paintItemsDesc,name); ++ if (b) ++ b->m_params.assign(action); ++ return b != 0; ++} ++ ++// Set a button's parameter, create it if not found ++bool QtTreeItemProps::setPaintButtonParam(const String& name, const String& param, ++ const String& value) ++{ ++ if (!(name && param)) ++ return false; ++ QtPaintButtonDesc* b = QtPaintButtonDesc::find(m_paintItemsDesc,name); ++ if (!b) ++ return false; ++ if (param == YSTRING("_yate_iconsize")) ++ setSize(b->m_iconSize,value); ++ else if (param == YSTRING("_yate_size")) ++ setSize(b->m_size,value); ++ else ++ b->m_params.setParam(param,value); ++ return true; ++} ++ ++ ++// ++// QtTreeItem ++// ++QtTreeItem::QtTreeItem(const char* id, int type, const char* text, bool storeExp) ++ : QTreeWidgetItem(type), ++ NamedList(id), ++ m_storeExp(storeExp), ++ m_heightDelta(0), ++ m_filtered(true), ++ m_extraPaintRight(0) ++{ ++ if (!TelEngine::null(text)) ++ QTreeWidgetItem::setText(0,QtClient::setUtf8(text)); ++ XDebug(ClientDriver::self(),DebugAll,"QtTreeItem(%s) type=%d [%p]",id,type,this); ++} ++ ++QtTreeItem::~QtTreeItem() ++{ ++ TelEngine::destruct(m_extraPaintRight); ++ XDebug(ClientDriver::self(),DebugAll,"~QtTreeItem(%s) type=%d [%p]",c_str(),type(),this); ++} ++ ++// Set a column's icon from a list of parameter cname_image ++void QtTreeItem::setImage(int col, const String& cname, const NamedList& list, int role) ++{ ++ String* s = cname ? list.getParam(cname + "_image") : 0; ++ if (!s) ++ return; ++ if (role <= Qt::UserRole) ++ QTreeWidgetItem::setIcon(col,QIcon(QtClient::setUtf8(*s))); ++ else ++ setData(col,role,QtClient::setUtf8(*s)); ++} ++ ++// Update item filtered flag ++bool QtTreeItem::setFilter(const NamedList* filter) ++{ ++ if (!filter) { ++ m_filtered = true; ++ return true; ++ } ++ int params = 0; ++ m_filtered = false; ++ NamedIterator iter(*this); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get()); params++) { ++ if (!*ns) ++ continue; ++ const String& match = (*filter)[ns->name()]; ++ if (ns->find(match) >= 0) { ++ m_filtered = true; ++ break; ++ } ++ } ++ if (!params) ++ m_filtered = true; ++ return m_filtered; ++} ++ ++// Set extra data to paint on right side of the item ++void QtTreeItem::setExtraPaintRight(QtPaintItems* obj) ++{ ++ TelEngine::destruct(m_extraPaintRight); ++ m_extraPaintRight = obj; ++ QVariant var; ++ if (m_extraPaintRight) ++ var = QtRefObjectHolder::setVariant(m_extraPaintRight); ++ setData(0,QtCustomTree::RoleQtDrawItems,var); ++} ++ ++// Set extra paint buttons on right side of the item ++void QtTreeItem::setExtraPaintRightButtons(const String& list, QtTreeItemProps* props) ++{ ++ if (!list) { ++ setExtraPaintRight(0); ++ return; ++ } ++ QtPaintItems* items = new QtPaintItems(list); ++ if (props) { ++ ObjList* pList = list.split(','); ++ for (ObjList* o = pList->skipNull(); o; o = o->skipNext()) { ++ String* s = static_cast(o->get()); ++ QtPaintButtonDesc* b = QtPaintButtonDesc::find(props->m_paintItemsDesc,*s,false); ++ if (b) ++ items->append(*b); ++ } ++ TelEngine::destruct(pList); ++ } ++ items->itemsAdded(); ++ setExtraPaintRight(items); ++} ++ ++ ++/* ++ * QtCustomTree ++ */ ++QtCustomTree::QtCustomTree(const char* name, const NamedList& params, QWidget* parent, ++ bool applyParams) ++ : QtTree(name,parent), ++ m_notifyItemChanged(false), ++ m_hasCheckableCols(false), ++ m_menu(0), ++ m_autoExpand(false), ++ m_rowHeight(-1), ++ m_changing(0), ++ m_filter(0), ++ m_haveWidgets(false), ++ m_haveDrawQtItems(false), ++ m_setCurrentColumn(-1), ++ m_drop(0), ++ m_acceptDropOnEmpty(QtDrop::Ask), ++ m_drag(0), ++ m_drawBranches(false), ++ m_timerTriggerSelect(0), ++ m_lastItemDrawHover(0) ++{ ++ setIndentation(0); ++ setUniformRowHeights(false); ++ setFrameShape(QFrame::NoFrame); ++ setRootIsDecorated(false); ++ // Add item props translation ++ addItemType(QTreeWidgetItem::Type,"default"); ++ NamedIterator iter(params); ++ int typeN = 0; ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == YSTRING("buildprops")) ++ // Build properties ++ QtClient::buildProps(this,*ns); ++ else if (ns->name() == YSTRING("_yate_tree_additemtype")) { ++ // Add item types ++ if (*ns) ++ addItemType(TypeCount + typeN++,*ns); ++ } ++ else if (ns->name() == YSTRING("vertical_scroll_policy")) { ++ // Vertical scroll policy ++ if (*ns == YSTRING("item")) ++ QTreeWidget::setVerticalScrollMode(QAbstractItemView::ScrollPerItem); ++ else if (*ns == YSTRING("pixel")) ++ QTreeWidget::setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); ++ } ++ else if (ns->name() == YSTRING("_yate_set_draganddrop")) { ++ // Drag & Drop ++ bool drag = false; ++ bool drop = false; ++ QtDragAndDrop::checkEnable(*ns,drag,drop); ++ if (drag) { ++ m_drag = new QtTreeDrag(this,¶ms); ++ setDragEnabled(true); ++ } ++ if (drop) { ++ m_drop = new QtListDrop(this,¶ms); ++ setAcceptDrops(true); ++ } ++ } ++ else if (ns->name() == YSTRING("_yate_widgetattributes")) ++ QtClient::setWidgetAttributes(this,*ns); ++ else if (ns->name() == YSTRING("_yate_set_currentcolumn")) ++ // Current column to set when index changes ++ m_setCurrentColumn = getColumnNo(*ns); ++ else if (ns->name() == YSTRING("_yate_busywidget")) ++ QtClient::buildBusy(this,this,*ns,params); ++ else if (ns->name() == YSTRING("property:rootIsDecorated")) ++ m_drawBranches = true; ++ } ++ QTreeWidgetItem* hdr = headerItem(); ++ if (hdr) { ++ String* columns = params.getParam("columns"); ++ if (TelEngine::null(columns)) ++ hdr->setHidden(true); ++ else { ++ QHeaderView* header = QTreeView::header(); ++ ObjList* id = columns->split(',',false); ++ ObjList* title = params["columns.title"].split(',',true); ++ ObjList* width = params["columns.width"].split(',',true); ++ ObjList* sizeMode = params["columns.resize"].split(',',true); ++ ObjList* check = params["columns.check"].split(',',false); ++ ObjList* emptyTitle = params["columns.allowemptytitle"].split(',',false); ++ setColumnCount(id->count()); ++ int n = 0; ++ for (ObjList* o = id->skipNull(); o; o = o->skipNext(), n++) { ++ String* name = static_cast(o->get()); ++ String caption = objListItem(title,n); ++ if (!caption) { ++ String tmp = *name; ++ if (!emptyTitle->find(tmp.toLower())) ++ caption = *name; ++ } ++ hdr->setText(n,QtClient::setUtf8(caption)); ++ hdr->setData(n,RoleId,QtClient::setUtf8(name->toLower())); ++ int ww = objListItem(width,n).toInteger(-1); ++ if (ww > 0) ++ setColumnWidth(n,ww); ++ if (check->find(*name)) { ++ hdr->setData(n,RoleCheckable,QVariant(true)); ++ m_hasCheckableCols = true; ++ } ++ // Header ++ if (!header) ++ continue; ++ const String& szMode = header ? objListItem(sizeMode,n) : String::empty(); ++ if (szMode == "fixed") ++ header->setSectionResizeMode(n,QHeaderView::Fixed); ++ else if (szMode == "stretch") ++ header->setSectionResizeMode(n,QHeaderView::Stretch); ++ else if (szMode == "contents") ++ header->setSectionResizeMode(n,QHeaderView::ResizeToContents); ++ else ++ header->setSectionResizeMode(n,QHeaderView::Interactive); ++ } ++ TelEngine::destruct(id); ++ TelEngine::destruct(title); ++ TelEngine::destruct(width); ++ TelEngine::destruct(sizeMode); ++ TelEngine::destruct(check); ++ TelEngine::destruct(emptyTitle); ++ } ++ } ++ // Create item delegates ++ if (!s_delegateCommon) { ++ s_delegateCommon.assign(" "); ++ s_delegateCommon.addParam("role_display",String(RoleHtmlDelegate)); ++ s_delegateCommon.addParam("role_image",String(RoleImage)); ++ s_delegateCommon.addParam("role_background",String(RoleBackground)); ++ s_delegateCommon.addParam("role_margins",String(RoleMargins)); ++ s_delegateCommon.addParam("role_qtdrawitems",String(RoleQtDrawItems)); ++ } ++ QList dlgs = QtItemDelegate::buildDelegates(this,params,&s_delegateCommon); ++ QStringList cNames; ++ for (int i = 0; i < dlgs.size(); i++) { ++ QtItemDelegate* dlg = qobject_cast(dlgs[i]); ++ if (!dlg) { ++ delete dlgs[i]; ++ continue; ++ } ++ if (cNames.size() < 1) ++ cNames = columnIDs(); ++ dlg->updateColumns(cNames); ++ QList& cols = dlg->columns(); ++ for (int i = 0; i < cols.size(); i++) ++ setItemDelegateForColumn(cols[i],dlg); ++ if (cols.size() < 1) ++ setItemDelegate(dlg); ++ } ++ if (hdr && !dlgs.size()) { ++ String* htmlDlg = params.getParam("htmldelegate"); ++ if (!TelEngine::null(htmlDlg)) { ++ ObjList* l = htmlDlg->split(',',false); ++ for (ObjList* o = l->skipNull(); o; o = o->skipNext()) { ++ String* s = static_cast(o->get()); ++ int col = s->toInteger(-1); ++ if (col < 0) ++ col = getColumn(*s); ++ if (col < 0 || col >= columnCount()) ++ continue; ++ hdr->setData(col,RoleHtmlDelegate,true); ++ String prefix; ++ prefix << name << ".htmldelegate." << col; ++ NamedList pp(prefix); ++ pp.copySubParams(params,String("delegateparam.") + *s + "."); ++ pp.setParam(prefix + ".role_display",String(RoleHtmlDelegate)); ++ pp.setParam(prefix + ".role_image",String(RoleImage)); ++ pp.setParam(prefix + ".role_background",String(RoleBackground)); ++ pp.setParam(prefix + ".role_margins",String(RoleMargins)); ++ pp.setParam(prefix + ".role_qtdrawitems",String(RoleQtDrawItems)); ++ QtHtmlItemDelegate* dlg = new QtHtmlItemDelegate(this,pp); ++ XDebug(ClientDriver::self(),DebugNote, ++ "QtCustomTree(%s) setting html item delegate (%p,%s) for column %d [%p]", ++ name,dlg,dlg->toString().c_str(),col,this); ++ setItemDelegateForColumn(col,dlg); ++ } ++ TelEngine::destruct(l); ++ } ++ } ++ // Grid ++ m_gridDraw.setPen(params); ++ // Connect signals ++ QtClient::connectObjects(this,SIGNAL(itemSelectionChanged()), ++ this,SLOT(itemSelChangedSlot())); ++ QtClient::connectObjects(this,SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), ++ this,SLOT(itemDoubleClickedSlot(QTreeWidgetItem*,int))); ++ QtClient::connectObjects(this,SIGNAL(itemActivated(QTreeWidgetItem*,int)), ++ this,SLOT(itemDoubleClickedSlot(QTreeWidgetItem*,int))); ++ QtClient::connectObjects(this,SIGNAL(itemExpanded(QTreeWidgetItem*)), ++ this,SLOT(itemExpandedSlot(QTreeWidgetItem*))); ++ QtClient::connectObjects(this,SIGNAL(itemCollapsed(QTreeWidgetItem*)), ++ this,SLOT(itemCollapsedSlot(QTreeWidgetItem*))); ++ QtClient::connectObjects(this,SIGNAL(itemChanged(QTreeWidgetItem*,int)), ++ this,SLOT(itemChangedSlot(QTreeWidgetItem*,int))); ++ // Set params ++ applyItemViewProps(params); ++ if (applyParams) ++ setParams(params); ++} ++ ++// Destructor ++QtCustomTree::~QtCustomTree() ++{ ++ TelEngine::destruct(m_filter); ++} ++ ++// Method re-implemented from QTreeWidget. ++// Draw item grid if set ++void QtCustomTree::drawRow(QPainter* p, const QStyleOptionViewItem& opt, ++ const QModelIndex& idx) const ++{ ++ QTreeWidget::drawRow(p,opt,idx); ++ if (m_gridDraw.flag(QtCellGridDraw::Pos)) { ++ p->save(); ++ int row = idx.row(); ++ int lastCol = columnCount() - 1; ++ for (int i = 0; i <= lastCol; i++) { ++ QModelIndex s = idx.sibling(row,i); ++ if (s.isValid()) { ++ QRect r = visualRect(s); ++ m_gridDraw.draw(p,r,!row,!i,false,i == lastCol); ++ } ++ } ++ p->restore(); ++ } ++} ++ ++// Retrieve item type definition from [type:]value. Create if not found ++QtUIWidgetItemProps* QtCustomTree::getItemProps(QString& in, String& value) ++{ ++ String type; ++ int pos = in.indexOf(':'); ++ if (pos >= 0) { ++ QtClient::getUtf8(type,in.left(pos)); ++ QtClient::getUtf8(value,in.right(in.length() - pos - 1)); ++ } ++ else ++ QtClient::getUtf8(value,in); ++ if (!type) ++ type = itemPropsName(QTreeWidgetItem::Type); ++ QtUIWidgetItemProps* p = QtUIWidget::getItemProps(type); ++ if (!p) { ++ p = new QtTreeItemProps(type); ++ m_itemProps.append(p); ++ } ++ return p; ++} ++ ++// Set params ++bool QtCustomTree::setParams(const NamedList& params) ++{ ++ SafeInt safeChg(&m_changing); ++ bool ok = QtUIWidget::setParams(params); ++ ok = QtUIWidget::setParams(this,params) && ok; ++ buildMenu(m_menu,params.getParam(YSTRING("menu"))); ++ NamedString* filter = params.getParam(YSTRING("filter")); ++ if (filter) { ++ TelEngine::destruct(m_filter); ++ NamedList* p = YOBJECT(NamedList,filter); ++ if (p && p->count()) ++ m_filter = new NamedList(*p); ++ checkItemFilter(); ++ } ++ return ok; ++} ++ ++// Retrieve an item ++bool QtCustomTree::getTableRow(const String& item, NamedList* data) ++{ ++ SafeInt safeChg(&m_changing); ++ QtTreeItem* it = find(item); ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::getTableRow(%s) found=%p [%p]", ++ name().c_str(),item.c_str(),it,this); ++ if (!it) ++ return false; ++ if (data) { ++ data->copyParams(*it); ++ // Get checked items ++ if (m_hasCheckableCols) { ++ QTreeWidgetItem* hdr = headerItem(); ++ int n = hdr ? columnCount() : 0; ++ for (int i = 0; i < n; i++) { ++ if (!hdr->data(i,RoleCheckable).toBool()) ++ continue; ++ String id; ++ getItemData(id,*hdr,i); ++ if (!id) ++ continue; ++ bool checked = it->checkState(i) != Qt::Unchecked; ++ data->setParam("check:" + id,String::boolText(checked)); ++ } ++ } ++ QWidget* w = itemWidget(it,0); ++ if (w) ++ getParams(w,*data); ++ } ++ return true; ++} ++ ++bool QtCustomTree::setTableRow(const String& item, const NamedList* data) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::setTableRow(%s,%p) [%p]", ++ name().c_str(),item.c_str(),data,this); ++ QtTreeItem* it = find(item); ++ if (!it) ++ return false; ++ if (!data) ++ return true; ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ return updateItem(*it,*data); ++} ++ ++// Add a new account or contact ++bool QtCustomTree::addTableRow(const String& item, const NamedList* data, bool atStart) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::addTableRow(%s,%p,%u) [%p]", ++ name().c_str(),item.c_str(),data,atStart,this); ++ if (!data) ++ return false; ++ if (find(item)) ++ return false; ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ QtTreeItem* parent = 0; ++ int type = QTreeWidgetItem::Type; ++ if (data) { ++ type = itemType((*data)["item_type"]); ++ const String& pName = (*data)["parent"]; ++ if (pName) { ++ parent = find(pName); ++ if (!parent) { ++ Debug(ClientDriver::self(),DebugAll, ++ "QtCustomTree(%s)::addTableRow(%s,%p,%u) parent '%s' not found [%p]", ++ name().c_str(),item.c_str(),data,atStart,pName.c_str(),this); ++ return false; ++ } ++ } ++ } ++ QtTreeItem* it = new QtTreeItem(item,type); ++ if (data) ++ it->copyParams(*data); ++ if (addChild(it,atStart,parent)) ++ return !data || updateItem(*it,*data); ++ TelEngine::destruct(it); ++ return false; ++} ++ ++// Remove an item from tree ++bool QtCustomTree::delTableRow(const String& item) ++{ ++ if (!item) ++ return false; ++ SafeInt safeChg(&m_changing); ++ QtTreeItem* it = find(item); ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::delTableRow(%s) found=%p [%p]", ++ name().c_str(),item.c_str(),it,this); ++ if (!it) ++ return false; ++ removeItem(it); ++ return true; ++} ++ ++// Add, set or remove one or more items. ++// Each data list element is a NamedPointer carrying a NamedList with item parameters. ++// The name of an element is the item to update. ++// Set element's value to boolean value 'true' to add a new item if not found ++// or 'false' to set an existing one. Set it to empty string to delete the item ++bool QtCustomTree::updateTableRows(const NamedList* data, bool atStart) ++{ ++ if (!data) ++ return true; ++ SafeInt safeChg(&m_changing); ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::updateTableRows() [%p]", ++ name().c_str(),this); ++ SafeTree tree(this); ++ scheduleDelayedItemsLayout(); ++ QList removed; ++ bool ok = false; ++ NamedIterator iter(*data); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (!ns->name()) ++ continue; ++ QtTreeItem* item = find(ns->name()); ++ if (!ns->null()) { ++ NamedList* params = YOBJECT(NamedList,ns); ++ if (!params) { ++ ok = (0 != item) || ok; ++ continue; ++ } ++ if (item) ++ ok = updateItem(*item,*params) || ok; ++ else if (ns->toBoolean()) ++ ok = addTableRow(ns->name(),params,atStart) || ok; ++ } ++ else if (item) { ++ removed.append(item); ++ ok = true; ++ } ++ } ++ removeItems(removed); ++ executeDelayedItemsLayout(); ++ return ok; ++} ++ ++// Retrieve the current selection ++bool QtCustomTree::setSelect(const String& item) ++{ ++ QtTreeItem* it = item ? find(item) : 0; ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::setSelect(%s) found=%p [%p]", ++ name().c_str(),item.c_str(),it,this); ++ if (it) ++ setCurrentItem(it); ++ else if (item) ++ setCurrentItem(0); ++ return it || !item; ++} ++ ++// Retrieve the current selection ++bool QtCustomTree::getSelect(String& item) ++{ ++ QList list = selectedItems(); ++ bool ok = list.size() > 0 && list[0]; ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::getSelect(%s) found=%u [%p]", ++ name().c_str(),item.c_str(),ok,this); ++ if (ok) ++ item = (static_cast(list[0]))->id(); ++ return ok; ++} ++ ++// Retrieve multiple selection ++bool QtCustomTree::getSelect(NamedList& items) ++{ ++ QList sel = selectedItems(); ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::getSelect(%p) found=%u [%p]", ++ name().c_str(),&items,sel.size(),this); ++ addItems(items,sel); ++ return 0 != sel.size(); ++} ++ ++// Remove all items from tree ++bool QtCustomTree::clearTable() ++{ ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::clearTable() [%p]", ++ name().c_str(),this); ++ SafeInt safeChg(&m_changing); ++ QTreeWidget::clear(); ++ return true; ++} ++ ++// Catch item selection changed signal ++void QtCustomTree::itemSelChangedSlot() ++{ ++ stopSelectTriggerTimer(); ++ QList sel = selectedItems(); ++ int nSel = sel.size(); ++ DDebug(ClientDriver::self(),DebugAll, ++ "QtCustomTree(%s)::itemSelChangedSlot() sel=%d [%p]", ++ name().c_str(),nSel,this); ++ if (m_haveWidgets) { ++ for (int i = 0; i < nSel; i++) ++ applyStyleSheet(static_cast(sel[i]),true); ++ } ++ if (nSel <= 0) ++ onSelect(this,&(String::empty())); ++ else if (nSel == 1) ++ onSelect(this,&(static_cast(sel[0])->toString())); ++ else { ++ NamedList list(""); ++ addItems(list,sel); ++ onSelectMultiple(this,&list); ++ } ++} ++ ++// Re-implemented from QTreeWidget ++void QtCustomTree::timerEvent(QTimerEvent* ev) ++{ ++ if (m_timerTriggerSelect && ev->timerId() == m_timerTriggerSelect) { ++ stopSelectTriggerTimer(); ++ itemSelChangedSlot(); ++ return; ++ } ++ QtTree::timerEvent(ev); ++} ++ ++// Re-implemented from QTreeWidget ++void QtCustomTree::drawBranches(QPainter* painter, const QRect& rect, ++ const QModelIndex& index) const ++{ ++ if (m_drawBranches) ++ QtTree::drawBranches(painter,rect,index); ++} ++ ++// Re-implemented from QTreeWidget ++QMimeData* QtCustomTree::mimeData(const QList items) const ++{ ++ QMimeData* data = m_drag ? m_drag->mimeData(items) : 0; ++ return data ? data : QtTree::mimeData(items); ++} ++ ++// Re-implemented from QAbstractItemView ++void QtCustomTree::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) ++{ ++ QTreeWidget::selectionChanged(selected,deselected); ++ QList unsel; ++ QModelIndexList unselIndexes = deselected.indexes(); ++ if (unselIndexes.size() > 0) ++ unsel = findItems(unselIndexes); ++ DDebug(ClientDriver::self(),DebugAll, ++ "QtCustomTree(%s)::onSelChanged() desel=%d [%p]", ++ name().c_str(),unsel.size(),this); ++ if (m_haveWidgets) ++ for (int i = 0; i < unsel.size(); i++) ++ applyStyleSheet(unsel[i],false); ++} ++ ++// Re-implemented from QAbstractItemView ++void QtCustomTree::currentChanged(const QModelIndex& current, const QModelIndex& previous) ++{ ++ QtTree::currentChanged(current,previous); ++ if (m_setCurrentColumn >= 0 && m_setCurrentColumn != current.column() && ++ m_setCurrentColumn < columnCount()) { ++ QTreeWidgetItem* it = itemFromIndex(current); ++ if (it) { ++ QModelIndex idx = indexFromItem(it,m_setCurrentColumn); ++ if (idx.isValid()) ++ setCurrentIndex(idx); ++ } ++ } ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::dragEnterEvent(QDragEnterEvent* e) ++{ ++ if (m_drop) ++ handleDropEvent(e); ++#ifdef XDEBUG ++ String tmp = " "; ++ QtClient::dumpMime(tmp,e->mimeData()); ++ Debug(ClientDriver::self(),DebugAll,"QtCustomTree(%s) DRAG ENTER MIME: [%p]%s", ++ name().c_str(),this,tmp.safe()); ++#endif ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::dropEvent(QDropEvent* e) ++{ ++ if (m_drop && m_drop->started()) ++ handleDropEvent(e); ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::dragMoveEvent(QDragMoveEvent* e) ++{ ++ if (m_drop && m_drop->started()) ++ handleDropEvent(e); ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::dragLeaveEvent(QDragLeaveEvent* e) ++{ ++ if (m_drop && m_drop->started()) ++ m_drop->reset(); ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::mouseMoveEvent(QMouseEvent* e) ++{ ++ QtTree::mouseMoveEvent(e); ++ if (m_haveDrawQtItems) { ++ QtTreeItem* it = static_cast(itemAt(e->pos())); ++ if (m_lastItemDrawHover && m_lastItemDrawHover != it && ++ m_lastItemDrawHover->extraPaintRight() && ++ m_lastItemDrawHover->extraPaintRight()->setHover(false)) { ++ m_lastItemDrawHover->extraPaintRight()->setPressed(false); ++ QtTree::repaint(m_lastItemDrawHover->extraPaintRight()->displayRect()); ++ } ++ if (it && it->extraPaintRight()) { ++ if (it->extraPaintRight()->displayRect().contains(e->pos())) { ++ if (it->extraPaintRight()->setHover(e->pos())) ++ QtTree::repaint(it->extraPaintRight()->displayRect()); ++ } ++ else if (it->extraPaintRight()->setHover(false)) ++ QtTree::repaint(it->extraPaintRight()->displayRect()); ++ } ++ m_lastItemDrawHover = it; ++ } ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::mousePressEvent(QMouseEvent* e) ++{ ++ QtTree::mousePressEvent(e); ++ if (e->button() == Qt::LeftButton && ++ m_lastItemDrawHover && m_lastItemDrawHover->extraPaintRight() && ++ m_lastItemDrawHover->extraPaintRight()->displayRect().contains(e->pos()) && ++ m_lastItemDrawHover->extraPaintRight()->mousePressed(true,e->pos())) ++ QtTree::repaint(m_lastItemDrawHover->extraPaintRight()->displayRect()); ++} ++ ++// Re-implemented from QWidget ++void QtCustomTree::mouseReleaseEvent(QMouseEvent* e) ++{ ++ QtTree::mouseReleaseEvent(e); ++ if (e->button() == Qt::LeftButton && ++ m_lastItemDrawHover && m_lastItemDrawHover->extraPaintRight()) { ++ String action; ++ if (m_lastItemDrawHover->extraPaintRight()->mousePressed(false,e->pos(),&action)) { ++ if (action) ++ triggerAction(m_lastItemDrawHover->id(),action,this); ++ QtTree::repaint(m_lastItemDrawHover->extraPaintRight()->displayRect()); ++ } ++ } ++} ++ ++// Re-implemented from QTreeView ++void QtCustomTree::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) ++{ ++ if (m_lastItemDrawHover) { ++ QModelIndex idx = indexFromItem(m_lastItemDrawHover); ++ if (idx.isValid() && idx.row() >= start && idx.row() <= end) ++ m_lastItemDrawHover = 0; ++ } ++ QtTree::rowsAboutToBeRemoved(parent,start,end); ++} ++ ++// Retrieve tree sorting ++QString QtCustomTree::getSorting() ++{ ++ String t; ++ QHeaderView* h = isSortingEnabled() ? QTreeView::header() : 0; ++ if (h) { ++ int col = h->sortIndicatorSection(); ++ int sort = h->sortIndicatorOrder(); ++ if (col >= 0 && col < columnCount()) { ++ String id; ++ QTreeWidgetItem* hdr = headerItem(); ++ if (hdr) ++ getItemData(id,*hdr,col); ++ t << (id ? id : String(col)) << "," << String::boolText(sort == Qt::AscendingOrder); ++ } ++ } ++ return QtClient::setUtf8(t); ++} ++ ++// Set tree sorting ++void QtCustomTree::updateSorting(const String& key, Qt::SortOrder sort) ++{ ++ SafeInt safeChg(&m_changing); ++ QHeaderView* h = QTreeView::header(); ++ if (!h) ++ return; ++ int col = key.toInteger(-1); ++ if (col < 0) ++ col = getColumn(key); ++ if (col >= 0 && col < columnCount()) ++ h->setSortIndicator(col,sort); ++} ++ ++// Build a tree context menu ++bool QtCustomTree::buildMenu(QMenu*& menu, NamedString* ns) ++{ ++ if (!ns) ++ return false; ++ NamedList* p = YOBJECT(NamedList,ns); ++ if (!p) ++ return false; ++ if (menu) ++ QtClient::deleteLater(menu); ++ // Check if we are part of a widget list container item ++ QtUIWidget* container = QtUIWidget::container(this); ++ if (!container) ++ menu = QtClient::buildMenu(*p,0,0,0,0,this); ++ else ++ menu = container->buildWidgetItemMenu(this,p,String::empty(),false); ++ return true; ++} ++ ++// Retrieve all items' id ++bool QtCustomTree::getOptions(NamedList& items) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::getOptions() [%p]", ++ name().c_str(),this); ++ findItems(items); ++ return true; ++} ++ ++// Retrieve a QObject list containing container items ++QList QtCustomTree::getContainerItems() ++{ ++ QList list; ++ QList items = findItems(); ++ for (int i = 0; i < items.size(); i++) { ++ QWidget* w = itemWidget(items[i],0); ++ if (w) ++ list.append(static_cast(w)); ++ } ++ return list; ++} ++ ++// Retrieve model index for a given item ++QModelIndex QtCustomTree::modelIndex(const String& item, const String* what) ++{ ++ int col = TelEngine::null(what) ? 0 : getColumn(*what); ++ if (col < 0) ++ return QModelIndex(); ++ QtTreeItem* it = find(item); ++ if (it) ++ return indexFromItem(it,col); ++ return QModelIndex(); ++} ++ ++// Find a tree item ++QtTreeItem* QtCustomTree::find(const String& id, QtTreeItem* start, bool includeStart, ++ bool recursive) ++{ ++ if (start && includeStart && id == start->id()) ++ return start; ++ QTreeWidgetItem* root = start ? static_cast(start) : invisibleRootItem(); ++ if (!root) ++ return 0; ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ if (id == item->id() || ++ (recursive && 0 != (item = find(id,item,false,true)))) ++ return item; ++ } ++ return 0; ++} ++ ++// Find all tree items ++QList QtCustomTree::findItems(bool recursive, QtTreeItem* start) ++{ ++ QList list; ++ QTreeWidgetItem* root = start ? static_cast(start) : invisibleRootItem(); ++ if (!root) ++ return list; ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ list.append(item); ++ if (recursive) { ++ QList tmp = findItems(true,item); ++ list += tmp; ++ } ++ } ++ return list; ++} ++ ++// Find all tree items having a given id ++QList QtCustomTree::findItems(const String& id, QtTreeItem* start, ++ bool includeStart, bool recursive) ++{ ++ QList list; ++ if (start && includeStart && id == start->id()) ++ list.append(start); ++ QTreeWidgetItem* root = start ? static_cast(start) : invisibleRootItem(); ++ if (!root) ++ return list; ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ if (id == item->id()) ++ list.append(item); ++ if (recursive) { ++ QList tmp = findItems(id,item,false,true); ++ list += tmp; ++ } ++ } ++ return list; ++} ++ ++// Find all tree items having a given type ++QList QtCustomTree::findItems(int type, QtTreeItem* start, ++ bool includeStart, bool recursive) ++{ ++ QList list; ++ if (start && includeStart && type == start->type()) ++ list.append(start); ++ QTreeWidgetItem* root = start ? static_cast(start) : invisibleRootItem(); ++ if (!root) ++ return list; ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ if (type == item->type()) ++ list.append(item); ++ if (recursive) { ++ QList tmp = findItems(type,item,false,true); ++ list += tmp; ++ } ++ } ++ return list; ++} ++ ++// Find all tree items from model ++QList QtCustomTree::findItems(QModelIndexList list) ++{ ++ QList l; ++ QTreeWidgetItem* root = invisibleRootItem(); ++ if (!root) ++ return l; ++ for (int i = 0; i < list.size(); i++) { ++ QModelIndex& idx = list[i]; ++ if (!idx.isValid()) ++ continue; ++ QtTreeItem* it = static_cast(itemFromIndex(idx)); ++ if (it && !l.contains(it)) ++ l.append(it); ++ } ++ return l; ++} ++ ++// Find al tree items ++void QtCustomTree::findItems(NamedList& list, QtTreeItem* start, bool includeStart, ++ bool recursive) ++{ ++ if (start && includeStart) ++ list.setParam(start->id(),""); ++ QTreeWidgetItem* root = start ? static_cast(start) : invisibleRootItem(); ++ if (!root) ++ return; ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ list.setParam(item->id(),""); ++ if (recursive) ++ findItems(list,item,false,true); ++ } ++} ++ ++// Add a child to a given item ++QtTreeItem* QtCustomTree::addChild(QtTreeItem* child, int pos, QtTreeItem* parent) ++{ ++ if (!child) ++ return 0; ++ SafeInt safeChg(&m_changing); ++ QTreeWidgetItem* root = parent ? static_cast(parent) : invisibleRootItem(); ++ if (!root) ++ return 0; ++ DDebug(ClientDriver::self(),DebugAll, ++ "QtCustomTree(%s) adding child '%s' type=%d parent=%p pos=%d", ++ name().c_str(),child->id().c_str(),child->type(),parent,pos); ++ setItemRowHeight(child); ++ if (pos < 0 || pos >= root->childCount()) ++ root->addChild(child); ++ else ++ root->insertChild(pos,child); ++ setupItem(child); ++ itemAdded(*child,parent); ++ return child; ++} ++ ++// Add a list of children to a given item ++void QtCustomTree::addChildren(QList list, int pos, QtTreeItem* parent) ++{ ++ SafeInt safeChg(&m_changing); ++ QTreeWidgetItem* root = parent ? static_cast(parent) : invisibleRootItem(); ++ if (!root) ++ return; ++ for (int i = 0; i < list.size(); i++) ++ setItemRowHeight(list[i]); ++ if (pos < 0 || pos >= root->childCount()) ++ root->addChildren(list); ++ else ++ root->insertChildren(pos,list); ++ for (int i = 0; i < list.size(); i++) { ++ QtTreeItem* item = static_cast(list[i]); ++ if (!item) ++ continue; ++ setupItem(item); ++ itemAdded(*item,parent); ++ } ++} ++ ++// Setup an item. Load its widget if not found ++void QtCustomTree::setupItem(QtTreeItem* item) ++{ ++ if (!item) ++ return; ++ SafeInt safeChg(&m_changing); ++ // Set widget ++ QWidget* w = itemWidget(item,0); ++ if (!w) { ++ w = loadWidgetType(this,item->id(),itemPropsName(item->type())); ++ if (w) { ++ m_haveWidgets = true; ++ w->setAutoFillBackground(true); ++ XDebug(ClientDriver::self(),DebugAll, ++ "QtCustomTree(%s) set widget (%p,%s) for child '%s' [%p]", ++ name().c_str(),w,YQT_OBJECT_NAME(w),item->id().c_str(),this); ++ // Adjust widget to row height if configured, ++ // or row height to widget otherwise ++ QSize sz = item->sizeHint(0); ++ int h = getItemRowHeight(item->type()); ++ if (h > 0) ++ w->setFixedHeight(std::max(h,sz.height()) + item->m_heightDelta); ++ else { ++ sz.setHeight(w->height()); ++ // We have no particular width constraint but must ensure that ++ // the size we give as a hint is valid because otherwise it ++ // will be ignored, at least with Qt 5.15.2 ++ sz.setWidth(std::max(0,sz.width())); ++ item->setSizeHint(0,sz); ++ } ++ setItemWidget(item,0,w); ++ applyStyleSheet(item,item->isSelected()); ++ } ++ } ++ // Set checkable columns ++ uncheckItem(*item); ++} ++ ++// Set and item's row height hint ++void QtCustomTree::setItemRowHeight(QTreeWidgetItem* item) ++{ ++ if (!item) ++ return; ++ int h = getItemRowHeight(item->type()); ++ if (h <= 0) ++ return; ++ QSize sz = item->sizeHint(0); ++ sz.setHeight(h + (static_cast(item))->m_heightDelta); ++ // We have no particular width constraint but must ensure that the size we ++ // give as a hint is valid because otherwise it will be ignored, at least ++ // with Qt 5.15.2 ++ sz.setWidth(std::max(0,sz.width())); ++ item->setSizeHint(0,sz); ++ QWidget* w = itemWidget(item,0); ++ if (w) ++ w->setFixedHeight(sz.height()); ++} ++ ++// Retrieve a list with column IDs ++QStringList QtCustomTree::columnIDs() ++{ ++ QStringList tmp; ++ QTreeWidgetItem* hdr = headerItem(); ++ int n = hdr ? columnCount() : 0; ++ for (int i = 0; i < n; i++) ++ tmp.append(hdr->data(i,RoleId).toString()); ++ return tmp; ++} ++ ++// Retrieve a column name ++bool QtCustomTree::getColumnName(String& buf, int col) ++{ ++ QTreeWidgetItem* hdr = 0; ++ if (col >= 0 && col < columnCount()) ++ hdr = headerItem(); ++ if (!hdr) ++ return false; ++ getItemData(buf,*hdr,col); ++ return true; ++} ++ ++// Retrieve a column by it's id ++int QtCustomTree::getColumn(const String& id) ++{ ++ QTreeWidgetItem* hdr = headerItem(); ++ int n = hdr ? columnCount() : 0; ++ for (int i = 0; i < n; i++) { ++ String tmp; ++ getItemData(tmp,*hdr,i); ++ if (tmp == id) ++ return i; ++ } ++ return -1; ++} ++ ++// Show or hide empty children. ++void QtCustomTree::showEmptyChildren(bool show, QtTreeItem* parent) ++{ ++ QTreeWidgetItem* root = parent ? static_cast(parent) : invisibleRootItem(); ++ if (!root) ++ return; ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ int n = root->childCount(); ++ for (int i = 0; i < n; i++) { ++ QtTreeItem* item = static_cast(root->child(i)); ++ if (!item) ++ continue; ++ if (show) { ++ showItem(*item,true); ++ continue; ++ } ++ // Find a displayed child. Hide the item if not found ++ QTreeWidgetItem* child = 0; ++ int nc = item->childCount(); ++ for (int j = 0; j < nc; j++, child = 0) { ++ child = item->child(j); ++ if (child && !child->isHidden()) ++ break; ++ } ++ showItem(*item,child != 0); ++ } ++} ++ ++// Set the expanded/collapsed image of an item ++void QtCustomTree::setStateImage(QtTreeItem& item, QtTreeItemProps* p) ++{ ++ if (!p) ++ p = treeItemProps(item.type()); ++ if (!(p && p->m_stateWidget)) ++ return; ++ SafeInt safeChg(&m_changing); ++ NamedList tmp(""); ++ const String& img = item.isExpanded() ? p->m_stateExpandedImg : p->m_stateCollapsedImg; ++ tmp.addParam("image:" + p->m_stateWidget,img); ++ tmp.addParam(p->m_stateWidget + "_image",img); ++ updateItem(item,tmp); ++} ++ ++// Set an item props ui ++void QtCustomTree::setItemUi(QString value) ++{ ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_ui = tmp; ++} ++ ++// Set an item props style sheet ++void QtCustomTree::setItemStyle(QString value) ++{ ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_styleSheet = tmp; ++} ++ ++// Set an item props selected style sheet ++void QtCustomTree::setItemSelectedStyle(QString value) ++{ ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_selStyleSheet = tmp; ++} ++ ++// Set an item props accept drop ++void QtCustomTree::setItemAcceptDrop(QString value) ++{ ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_acceptDrop = QtDrop::acceptDropType(tmp,QtDrop::None); ++} ++ ++// Set an item props state widget name ++void QtCustomTree::setItemStateWidget(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_stateWidget = tmp; ++} ++ ++// Set an item's expanded image ++void QtCustomTree::setExpandedImage(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_stateExpandedImg = Client::s_skinPath + tmp; ++} ++ ++// Set an item's collapsed image ++void QtCustomTree::setItemCollapsedImage(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_stateCollapsedImg = Client::s_skinPath + tmp; ++} ++ ++// Set an item's tooltip template ++void QtCustomTree::setItemTooltip(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_toolTip = tmp; ++} ++ ++// Set an item's statistics widget name ++void QtCustomTree::setItemStatsWidget(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_statsWidget = tmp; ++} ++ ++// Set an item's statistics template ++void QtCustomTree::setItemStatsTemplate(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_statsTemplate = tmp; ++} ++ ++// Set an item props height ++void QtCustomTree::setItemHeight(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_height = tmp.toInteger(-1); ++} ++ ++// Set an item props background ++void QtCustomTree::setItemBg(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (!p) ++ return; ++ if (tmp) { ++ if (tmp[0] == '#') ++ p->m_bg = QBrush(QColor(tmp.substr(1).toInteger(0,16))); ++ else if (tmp.startSkip("color:",false)) ++ p->m_bg = QBrush(QColor(tmp.c_str())); ++ else ++ p->m_bg = QBrush(); ++ } ++ else ++ p->m_bg = QBrush(); ++} ++ ++// Set an item props margins ++// Order: left,top,right,bottom ++void QtCustomTree::setItemMargins(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (!p) ++ return; ++ p->m_margins = QRect(); ++ if (!tmp) ++ return; ++ ObjList* list = tmp.split(','); ++ int i = 0; ++ for (ObjList* o = list; o; o = o->next(), i++) { ++ int val = o->get() ? o->get()->toString().toInteger() : 0; ++ if (i == 0) ++ p->m_margins.setLeft(val); ++ else if (i == 1) ++ p->m_margins.setTop(val); ++ else if (i == 2) ++ p->m_margins.setRight(val); ++ else if (i == 3) ++ p->m_margins.setBottom(val); ++ } ++ TelEngine::destruct(list); ++} ++ ++// Set an item props editable ++void QtCustomTree::setItemEditable(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (p) ++ p->m_editable = tmp.toBoolean(); ++} ++ ++// Set an item's paint button and action ++// Format [type:][button_name:]action_name ++void QtCustomTree::setItemPaintButton(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (!p) ++ return; ++ if (!tmp) ++ return; ++ int pos = tmp.find(':'); ++ if (pos < 0) { ++ p->setPaintButtonAction(tmp,tmp); ++ return; ++ } ++ String name = tmp.substr(0,pos); ++ if (name) ++ p->setPaintButtonAction(name,tmp.substr(pos + 1)); ++} ++ ++// Set an item's paint button parameter ++// Format [type:]button_name:param_name[:param_value] ++void QtCustomTree::setItemPaintButtonParam(QString value) ++{ ++ String tmp; ++ QtTreeItemProps* p = YOBJECT(QtTreeItemProps,getItemProps(value,tmp)); ++ if (!p) ++ return; ++ int pos = tmp.find(':'); ++ if (pos < 1) ++ return; ++ String name = tmp.substr(0,pos); ++ tmp = tmp.substr(pos + 1); ++ if (!tmp) ++ return; ++ pos = tmp.find(':'); ++ if (!pos) ++ return; ++ if (pos > 0) ++ p->setPaintButtonParam(name,tmp.substr(0,pos),tmp.substr(pos + 1)); ++ else ++ p->setPaintButtonParam(name,tmp); ++} ++ ++// Retrieve a comma separated list with column widths ++QString QtCustomTree::colWidths() ++{ ++ if (!columnCount()) ++ return QString(); ++ String t; ++ int cols = columnCount(); ++ for (int i = 0; i < cols; i++) ++ t.append(String(columnWidth(i)),","); ++ return QtClient::setUtf8(t); ++} ++ ++// Set column widths ++void QtCustomTree::setColWidths(QString widths) ++{ ++ if (!columnCount()) ++ return; ++ QStringList list = widths.split(","); ++ for (int i = 0; i < list.size(); i++) { ++ if (!list[i].length()) ++ continue; ++ int width = list[i].toInt(); ++ if (width > 0) ++ setColumnWidth(i,width); ++ } ++} ++ ++// Set sorting (column and order) ++void QtCustomTree::setSorting(QString s) ++{ ++ SafeInt safeChg(&m_changing); ++ if (!s.length()) { ++ updateSorting(String::empty(),Qt::AscendingOrder); ++ return; ++ } ++ String key; ++ String order; ++ int pos = s.indexOf(QChar(',')); ++ if (pos >= 0) { ++ QtClient::getUtf8(key,s.left(pos)); ++ QtClient::getUtf8(order,s.right(s.length() - pos - 1)); ++ } ++ else ++ QtClient::getUtf8(key,s); ++ updateSorting(key,order.toBoolean(true) ? Qt::AscendingOrder : Qt::DescendingOrder); ++} ++ ++// Retrieve items expanded status value ++QString QtCustomTree::itemsExpStatus() ++{ ++ String tmp; ++ for (int i = 0; i < m_expStatus.size(); i++) { ++ String val; ++ val << m_expStatus[i].first.uriEscape(',') << "=" << ++ String::boolText(m_expStatus[i].second > 0); ++ tmp.append(val,","); ++ } ++ return QtClient::setUtf8(tmp); ++} ++ ++// Set items expanded status value ++void QtCustomTree::setItemsExpStatus(QString s) ++{ ++ m_expStatus.clear(); ++ QStringList list = s.split(",",Qt::SkipEmptyParts); ++ for (int i = 0; i < list.size(); i++) { ++ String id; ++ String value; ++ int pos = list[i].lastIndexOf('='); ++ if (pos > 0) { ++ QtClient::getUtf8(id,list[i].left(pos)); ++ int n = list[i].size() - pos - 1; ++ if (n) ++ QtClient::getUtf8(value,list[i].right(n)); ++ } ++ else ++ QtClient::getUtf8(id,list[i]); ++ if (id) { ++ id = id.uriUnescape(); ++ m_expStatus.append(QtTokenDict(id,value.toBoolean(m_autoExpand) ? 1 : 0)); ++ } ++ } ++} ++ ++// Add items as list parameter ++void QtCustomTree::addItems(NamedList& dest, QList items) ++{ ++ for (int i = 0; i < items.size(); i++) ++ dest.addParam(static_cast(items[i])->toString(),""); ++} ++ ++// Apply item widget style sheet ++void QtCustomTree::applyStyleSheet(QtTreeItem* item, bool selected) ++{ ++ if (!item) ++ return; ++ QWidget* w = itemWidget(item,0); ++ if (!w) ++ return; ++ QtUIWidgetItemProps* p = QtUIWidget::getItemProps(itemPropsName(item->type())); ++ if (p) ++ applyWidgetStyle(w,selected ? p->m_selStyleSheet : p->m_styleSheet); ++} ++ ++// Process item double click ++void QtCustomTree::onItemDoubleClicked(QtTreeItem* item, int column) ++{ ++ if (item && Client::self()) ++ onAction(this); ++} ++ ++// Item expanded/collapsed notification ++void QtCustomTree::onItemExpandedChanged(QtTreeItem* item) ++{ ++ if (!item) ++ return; ++ if (item->m_storeExp) ++ setStoreExpStatus(item->id(),item->isExpanded()); ++ QtTreeItemProps* props = treeItemProps(item); ++ if (props) { ++ setStateImage(*item,props); ++ applyItemStatistics(*item,props); ++ } ++} ++ ++// Process item changed signal ++void QtCustomTree::onItemChanged(QtTreeItem* item, int column) ++{ ++ if (m_changing || !m_notifyItemChanged || !item) ++ return; ++ NamedList p(""); ++ QString s = item->text(column); ++ if (s.size() > 0) { ++ String col; ++ getColumnName(col,column); ++ if (col) ++ QtClient::getUtf8(p,"text." + col,s); ++ } ++ closePersistentEditor(static_cast(item),column); ++ triggerAction(item->id(),"listitemchanged",this,&p); ++} ++ ++// Catch a context menu event and show the context menu ++void QtCustomTree::contextMenuEvent(QContextMenuEvent* e) ++{ ++ QtTreeItem* it = static_cast(itemAt(e->pos())); ++ QMenu* menu = contextMenu(it); ++ if (!menu) ++ menu = m_menu; ++ if (!menu) ++ return; ++ menu->exec(e->globalPos()); ++} ++ ++// Update a tree item ++bool QtCustomTree::updateItem(QtTreeItem& item, const NamedList& params) ++{ ++ SafeInt safeChg(&m_changing); ++ SafeTree safeTree(this); ++ DDebug(ClientDriver::self(),DebugAll,"QtCustomTree(%s)::updateItem(%p,%s) [%p]", ++ name().c_str(),&item,item.id().c_str(),this); ++ bool all = (¶ms == &item); ++ if (!all) ++ item.copyParams(params); ++ const NamedList& p = all ? (const NamedList&)item : params; ++ QTreeWidgetItem* hdr = headerItem(); ++ QtTreeItemProps* props = treeItemProps(item.type()); ++ int n = columnCount(); ++ QModelIndex idx; ++ for (int col = 0; col < n; col++) { ++ if (!col) { ++ String* hp = params.getParam(YSTRING("_yate_itemheight_delta")); ++ if (hp) { ++ item.m_heightDelta = hp->toInteger(); ++ setItemRowHeight(&item); ++ doItemsLayout(); ++ } ++ if (props) { ++ String* showActions = params.getParam(YSTRING("_yate_showactions")); ++ QtPaintItems* pItems = item.extraPaintRight(); ++ if (showActions && ++ ((!pItems && *showActions) || (pItems && *showActions != pItems->name()))) { ++ item.setExtraPaintRightButtons(*showActions,props); ++ m_haveDrawQtItems = true; ++ setMouseTracking(true); ++ } ++ } ++ } ++ QWidget* w = itemWidget(&item,col); ++ if (w) { ++ QtUIWidget::setParams(w,p); ++ continue; ++ } ++ if (!hdr) ++ continue; ++ String id; ++ getItemData(id,*hdr,col); ++ item.setText(col,id,p); ++ item.setCheckState(col,id,p); ++ int imageRole = Qt::UserRole; ++ if (props) { ++ // Set brush ++ if (props->m_bg != QBrush()) ++ item.setData(col,RoleBackground,props->m_bg); ++ if (idx.isValid()) ++ idx = idx.sibling(idx.row(),col); ++ else ++ idx = indexFromItem(&item,col); ++ QtHtmlItemDelegate* html = 0; ++ if (idx.isValid()) ++ html = qobject_cast(itemDelegate(idx)); ++ if (html) { ++ // HTML delegate ++ imageRole = html->roleImage(); ++ if (html->roleDisplayText() == RoleHtmlDelegate) { ++ QStringList qList; ++ String s = props->m_styleSheet; ++ if (s) ++ replaceHtmlParams(s,item,true); ++ qList.append(QtClient::setUtf8(s)); ++ s = props->m_selStyleSheet; ++ if (s) { ++ replaceHtmlParams(s,item); ++ qList.append(QtClient::setUtf8(s)); ++ } ++ item.setData(col,RoleHtmlDelegate,qList); ++ } ++ } ++ } ++ item.setImage(col,id,p,imageRole); ++ } ++ applyItemTooltip(item); ++ checkItemFilter(&item,false); ++ return true; ++} ++ ++// Get the context menu associated with a given item ++QMenu* QtCustomTree::contextMenu(QtTreeItem* item) ++{ ++ return 0; ++} ++ ++// Item added notification ++void QtCustomTree::itemAdded(QtTreeItem& item, QtTreeItem* parent) ++{ ++ SafeInt safeChg(&m_changing); ++ checkItemFilter(&item,false); ++ bool on = m_autoExpand; ++ if (item.m_storeExp) { ++ int n = getStoreExpStatus(item.id()); ++ if (n >= 0) ++ on = (n > 0); ++ else ++ setStoreExpStatus(item.id(),on); ++ } ++ QtTreeItemProps* props = treeItemProps(item); ++ bool editable = false; ++ item.setExpanded(on); ++ if (props) { ++ setStateImage(item,props); ++ applyItemTooltip(item,props); ++ applyItemStatistics(item,props); ++ applyItemMargins(item,true,props); ++ editable = props->m_editable; ++ } ++ if (editable) ++ item.setFlags(item.flags() | Qt::ItemIsEditable); ++ else ++ item.setFlags(item.flags() & ~Qt::ItemIsEditable); ++ if (parent) ++ applyItemStatistics(*parent); ++} ++ ++// Handle item visiblity changes ++void QtCustomTree::itemVisibleChanged(QtTreeItem& item) ++{ ++ SafeInt safeChg(&m_changing); ++ // Uncheck columns for invisible item ++ if (item.isHidden()) ++ uncheckItem(item); ++} ++ ++// Check item filter ++void QtCustomTree::checkItemFilter(QtTreeItem* item, bool recursive) ++{ ++ QTreeWidgetItem* root = 0; ++ if (item) { ++ item->setFilter(m_filter); ++ itemFilterChanged(*item); ++ if (recursive) ++ root = static_cast(item); ++ } ++ else if (recursive) ++ root = invisibleRootItem(); ++ int nc = root ? root->childCount() : 0; ++ for (int i = 0; i < nc; i++) { ++ QtTreeItem* it = static_cast(root->child(i)); ++ checkItemFilter(it,true); ++ } ++} ++ ++// Handle item filter changes ++void QtCustomTree::itemFilterChanged(QtTreeItem& item) ++{ ++ showItem(item,item.filterMatched()); ++} ++ ++// Uncheck all checkable columns in a given item ++void QtCustomTree::uncheckItem(QtTreeItem& item) ++{ ++ if (!m_hasCheckableCols) ++ return; ++ SafeInt safeChg(&m_changing); ++ QTreeWidgetItem* hdr = headerItem(); ++ int n = hdr ? columnCount() : 0; ++ for (int i = 0; i < n; i++) ++ if (hdr->data(i,RoleCheckable).toBool()) ++ item.setCheckState(i,false); ++} ++ ++// Remove an item ++void QtCustomTree::removeItem(QtTreeItem* it, bool* setSelTimer) ++{ ++ if (!it) ++ return; ++ bool sel = shouldSetSelTimer(*it); ++ QTreeWidgetItem* parent = it->parent(); ++ if (parent && parent != invisibleRootItem()) { ++ parent->removeChild(it); ++ applyItemStatistics(*static_cast(parent)); ++ } ++ TelEngine::destruct(it); ++ if (setSelTimer) ++ *setSelTimer = sel; ++ else if (sel) ++ startSelectTriggerTimer(); ++} ++ ++// Remove a list of items ++void QtCustomTree::removeItems(QList items) ++{ ++ bool setSelTimer = false; ++ for (int i = 0; i < items.size(); i++) { ++ bool sel = false; ++ removeItem(static_cast(items[i]),&sel); ++ setSelTimer = setSelTimer || sel; ++ } ++ if (setSelTimer) ++ startSelectTriggerTimer(); ++} ++ ++// Update a tree item's tooltip ++void QtCustomTree::applyItemTooltip(QtTreeItem& item, QtTreeItemProps* p) ++{ ++ if (!p) ++ p = treeItemProps(item); ++ if (!(p && p->m_toolTip)) ++ return; ++ String tooltip = p->m_toolTip; ++ item.replaceParams(tooltip); ++ for (int n = columnCount() - 1; n >= 0; n--) ++ item.setToolTip(n,QtClient::setUtf8(tooltip)); ++} ++ ++// Fill a list with item statistics. ++void QtCustomTree::fillItemStatistics(QtTreeItem& item, NamedList& list) ++{ ++ list.addParam("count",String(item.childCount())); ++} ++ ++// Update a tree item's statistics ++void QtCustomTree::applyItemStatistics(QtTreeItem& item, QtTreeItemProps* p) ++{ ++ if (!p) ++ p = treeItemProps(item); ++ if (!(p && p->m_statsTemplate)) ++ return; ++ SafeInt safeChg(&m_changing); ++ String text; ++ if (!item.isExpanded()) { ++ text = p->m_statsTemplate; ++ NamedList list(""); ++ fillItemStatistics(item,list); ++ list.replaceParams(text); ++ } ++ NamedList params(""); ++ if (p->m_statsWidget) ++ params.addParam(p->m_statsWidget,text); ++ else ++ params.addParam("statistics",text); ++ updateItem(item,params); ++} ++ ++// Update a tree item's margins ++void QtCustomTree::applyItemMargins(QtTreeItem& item, bool set, QtTreeItemProps* p) ++{ ++ if (!p) ++ p = treeItemProps(item); ++ if (!p) ++ return; ++ for (int n = columnCount() - 1; n >= 0; n--) ++ item.setData(n,RoleMargins,set ? p->m_margins : QRect()); ++} ++ ++// Store (update) to or remove from item expanded status storage an item ++void QtCustomTree::setStoreExpStatus(const String& id, bool on, bool store) ++{ ++ if (!id) ++ return; ++ for (int i = 0; i < m_expStatus.size(); i++) ++ if (m_expStatus[i].first == id) { ++ m_expStatus[i].second = on ? 1 : 0; ++ return; ++ } ++ m_expStatus.append(QtTokenDict(id,on ? 1 : 0)); ++} ++ ++// Retrieve the expanded status of an item from storage ++int QtCustomTree::getStoreExpStatus(const String& id) ++{ ++ if (!id) ++ return -1; ++ for (int i = 0; i < m_expStatus.size(); i++) ++ if (m_expStatus[i].first == id) ++ return m_expStatus[i].second; ++ return -1; ++} ++ ++// Handle drop events ++bool QtCustomTree::handleDropEvent(QDropEvent* e) ++{ ++ if (!m_drop) ++ return false; ++ QDragMoveEvent* move = 0; ++ QDragEnterEvent* enter = 0; ++ if (e->type() == QEvent::DragMove) { ++ if (!m_drop->started()) ++ return false; ++ move = static_cast(e); ++ } ++ else if (e->type() == QEvent::DragEnter) { ++ if (!m_drop->start(*static_cast(e))) ++ return false; ++ // Init drop accept params ++ String always; ++ String none; ++ String ask; ++ for (ObjList* o = m_itemPropsType.skipNull(); o ; o = o->skipNext()) { ++ NamedInt* ni = static_cast(o->get()); ++ QtUIWidgetItemProps* p = QtUIWidget::getItemProps(*ni); ++ if (!p) ++ continue; ++ if (p->m_acceptDrop == QtDrop::Always) ++ always.append(*ni,","); ++ else if (p->m_acceptDrop == QtDrop::None) ++ none.append(*ni,","); ++ else if (p->m_acceptDrop == QtDrop::Ask) ++ ask.append(*ni,","); ++ } ++ m_drop->setAcceptOnEmpty(m_acceptDropOnEmpty); ++ m_drop->updateAcceptType(always,QtDrop::Always); ++ m_drop->updateAcceptType(none,QtDrop::None); ++ m_drop->updateAcceptType(ask,QtDrop::Ask); ++ enter = static_cast(e); ++ move = static_cast(e); ++ } ++ else if (e->type() == QEvent::Drop) { ++ if (!m_drop->started()) ++ return false; ++ } ++ else ++ return false; ++ int acceptDrop = QtDrop::None; ++ QtTreeItem* it = static_cast(itemAt(e->pos())); ++ if (it) ++ acceptDrop = m_drop->getAcceptType(itemPropsName(it->type())); ++ else ++ acceptDrop = m_drop->acceptOnEmpty(); ++ // Done if drop event ++ if (e->type() == QEvent::Drop) { ++ bool ok = false; ++ // Notify ? ++ if (acceptDrop != QtDrop::None) { ++ if (it) { ++ m_drop->params().setParam(YSTRING("item"),it->toString()); ++ m_drop->params().setParam(YSTRING("item_type"),itemPropsName(it->type())); ++ } ++ ok = triggerAction(QtDrop::s_notifyClientDrop,m_drop->params(),this); ++ } ++ m_drop->reset(); ++ if (ok) ++ e->accept(); ++ else ++ e->ignore(); ++ return ok; ++ } ++ if (acceptDrop == QtDrop::Ask) { ++ if (it) { ++ m_drop->params().setParam(YSTRING("item"),it->toString()); ++ m_drop->params().setParam(YSTRING("item_type"),itemPropsName(it->type())); ++ } ++ if (triggerAction(QtDrop::s_askClientAcceptDrop,m_drop->params(),this)) { ++ if (enter && !m_drop->params().getBoolValue(YSTRING("_yate_accept_drop"),true)) { ++ m_drop->reset(); ++ enter->ignore(rect()); ++ return false; ++ } ++ // Update allowed item types and empty space ++ m_drop->updateAccept(m_drop->params()); ++ if (it) ++ acceptDrop = m_drop->getAcceptType(itemPropsName(it->type())); ++ else ++ acceptDrop = m_drop->acceptOnEmpty(); ++ } ++ } ++ if (it && move) { ++ if (acceptDrop != QtDrop::None) ++ move->accept(QTreeWidget::visualItemRect(it)); ++ else ++ move->ignore(QTreeWidget::visualItemRect(it)); ++ } ++ else if (acceptDrop != QtDrop::None) ++ e->accept(); ++ else ++ e->ignore(); ++ if (enter) ++ enter->acceptProposedAction(); ++ return true; ++} ++ ++// Check if an item has any selected child ++bool QtCustomTree::hasSelectedChild(QtTreeItem& item) ++{ ++ for (int i = item.childCount() - 1; i >= 0; i--) { ++ QtTreeItem* ch = static_cast(item.child(i)); ++ if (ch && (ch->isSelected() || hasSelectedChild(*ch))) ++ return true; ++ } ++ return false; ++} ++ ++ ++/* ++ * ContactList ++ */ ++ContactList::ContactList(const char* name, const NamedList& params, QWidget* parent) ++ : QtCustomTree(name,params,parent,false), ++ m_flatList(true), ++ m_showOffline(true), ++ m_hideEmptyGroups(true), ++ m_expStatusGrp(true), ++ m_menuContact(0), ++ m_menuChatRoom(0), ++ m_sortOrder(Qt::AscendingOrder), ++ m_compareNameCs(Qt::CaseSensitive) ++{ ++ XDebug(ClientDriver::self(),DebugAll,"ContactList(%s) [%p]",name,this); ++ // Add item props translation ++ addItemType(TypeContact,"contact"); ++ addItemType(TypeChatRoom,"chatroom"); ++ addItemType(TypeGroup,"group"); ++ m_savedIndent = indentation(); ++ m_noGroupText = "None"; ++ setParams(params); ++} ++ ++// Set params ++bool ContactList::setParams(const NamedList& params) ++{ ++ SafeInt safeChg(&m_changing); ++ bool ok = QtCustomTree::setParams(params); ++ buildMenu(m_menuContact,params.getParam("contactmenu")); ++ buildMenu(m_menuChatRoom,params.getParam("chatroommenu")); ++ return ok; ++} ++ ++// Update a contact ++bool ContactList::setTableRow(const String& item, const NamedList* data) ++{ ++ SafeInt safeChg(&m_changing); ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::setTableRow(%s,%p)", ++ name().c_str(),item.c_str(),data); ++ ContactItem* c = findContact(item); ++ if (!c) ++ return false; ++ if (!data) ++ return true; ++ SafeTree tree(this); ++ bool changed = c->updateName(*data,m_compareNameCs); ++ if (!changed && !m_flatList) ++ changed = c->groupsWouldChange(*data); ++ if (!changed) ++ updateContact(item,*data); ++ else ++ replaceContact(*c,*data); ++ listChanged(); ++ return true; ++} ++ ++// Add a new account or contact ++bool ContactList::addTableRow(const String& item, const NamedList* data, bool atStart) ++{ ++ SafeInt safeChg(&m_changing); ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::addTableRow(%s,%p,%u)", ++ name().c_str(),item.c_str(),data,atStart); ++ if (!data) ++ return false; ++ if (find(item)) ++ return false; ++ SafeTree tree(this); ++ addContact(item,*data); ++ listChanged(); ++ return true; ++} ++ ++// Remove an item from tree ++bool ContactList::delTableRow(const String& item) ++{ ++ SafeInt safeChg(&m_changing); ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::delTableRow(%s)", ++ name().c_str(),item.c_str()); ++ if (!item) ++ return false; ++ SafeTree tree(this); ++ bool ok = removeContact(item); ++ listChanged(); ++ return ok; ++} ++ ++// Add, set or remove one or more contacts. ++// Each data list element is a NamedPointer carrying a NamedList with item parameters. ++// The name of an element is the item to update. ++// Set element's value to boolean value 'true' to add a new item if not found ++// or 'false' to set an existing one. Set it to empty string to delete the item ++bool ContactList::updateTableRows(const NamedList* data, bool atStart) ++{ ++ if (!data) ++ return true; ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::updateTableRows()", ++ name().c_str()); ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ bool ok = false; ++ QList list; ++ QTreeWidgetItem* root = invisibleRootItem(); ++ bool empty = root && !root->childCount(); ++ NamedIterator iter(*data); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (!ns->name()) ++ continue; ++ if (!ns->null()) { ++ NamedList* params = YOBJECT(NamedList,ns); ++ if (!empty) { ++ if (!params) ++ ok = (0 != find(ns->name())) || ok; ++ else if (ns->toBoolean() || find(ns->name())) ++ ok = updateContact(ns->name(),*params) || ok; ++ } ++ else if (params) ++ list.append(createContact(ns->name(),*params)); ++ } ++ else ++ ok = removeContact(ns->name()) || ok; ++ } ++ if (!empty) ++ listChanged(); ++ else { ++ setContacts(list); ++ ok = true; ++ } ++ return ok; ++} ++ ++// Count online/total contacts in a group. ++void ContactList::countContacts(QtTreeItem* grp, int& total, int& online) ++{ ++ QList c = findItems(TypeContact,grp,true,false); ++ QList r = findItems(TypeChatRoom,grp,true,false); ++ total = c.size() + r.size(); ++ online = 0; ++ for (int i = 0; i < c.size(); i++) ++ if (!(static_cast(c[i]))->offline()) ++ online++; ++ for (int j = 0; j < r.size(); j++) ++ if (!(static_cast(r[j]))->offline()) ++ online++; ++} ++ ++// Contact list changed notification ++void ContactList::listChanged() ++{ ++ // Hide empty groups ++ if (!m_flatList) ++ showEmptyChildren(!m_hideEmptyGroups); ++ // Update contact count in groups ++ if (!m_flatList) { ++ QList grps = findItems(TypeGroup,0,true,false); ++ for (int i = 0; i < grps.size(); i++) { ++ if (!grps[i]) ++ continue; ++ applyItemStatistics(*(grps[i])); ++ } ++ } ++} ++ ++// Find a contact ++ContactItem* ContactList::findContact(const String& id, QList* list) ++{ ++ QList local; ++ if (!list) ++ list = &local; ++ *list = findItems(id); ++ for (int i = 0; i < list->size(); i++) { ++ QtTreeItem* it = static_cast((*list)[i]); ++ if (isContactType(it->type()) && it->id() == id) ++ return static_cast(it); ++ } ++ return 0; ++} ++ ++// Set '_yate_nogroup_caption' property ++void ContactList::setNoGroupCaption(QString value) ++{ ++ SafeInt safeChg(&m_changing); ++ QtClient::getUtf8(m_noGroupText,value); ++} ++ ++// Set contact grouping ++void ContactList::setFlatList(bool flat) ++{ ++ if (flat == m_flatList) ++ return; ++ QTreeWidgetItem* root = invisibleRootItem(); ++ if (!root) ++ return; ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ TreeRestoreSel sel(this); ++ setCurrentItem(0); ++ // Retrieve (take) contacts ++ QList c = root->takeChildren(); ++ // Shown by group: remove groups and contact duplicates ++ if (!m_flatList) { ++ for (int i = 0; i < c.size(); i++) { ++ c << c[i]->takeChildren(); ++ if (c[i]->type() == TypeGroup) { ++ delete c[i]; ++ c[i] = 0; ++ } ++ } ++ for (int i = 0; i < c.size(); i++) { ++ if (!c[i]) ++ continue; ++ for (int j = i + 1; j < c.size(); j++) { ++ QtTreeItem* cc = static_cast(c[j]); ++ if (cc && cc->id() == (static_cast(c[i]))->id()) { ++ delete c[j]; ++ c[j] = 0; ++ } ++ } ++ } ++ // Make sure the list contains valid pointers ++ for (int i = 0; i < c.size();) ++ if (c[i]) ++ i++; ++ else ++ c.removeAt(i); ++ } ++ // Set new grouping ++ m_flatList = flat; ++ // Save/restore indendation ++ if (!m_flatList) ++ setIndentation(m_savedIndent); ++ else { ++ m_savedIndent = indentation(); ++ setIndentation(0); ++ } ++ setContacts(c); ++} ++ ++// Show or hide offline contacts ++void ContactList::setShowOffline(bool value) ++{ ++ if (m_showOffline == value) ++ return; ++ m_showOffline = value; ++ QTreeWidgetItem* root = invisibleRootItem(); ++ if (!root) ++ return; ++ SafeTree tree(this); ++ SafeInt safeChg(&m_changing); ++ String sel; ++ getSelect(sel); ++ setCurrentItem(0); ++ QList list = findItems(TypeContact); ++ for (int i = 0; i < list.size(); i++) { ++ ContactItem* c = static_cast(list[i]); ++ if (!c) ++ continue; ++ if (c->offline()) ++ showItem(*c,m_showOffline); ++ } ++ listChanged(); ++ // Avoid selecting a hidden item ++ QtTreeItem* it = sel ? find(sel) : 0; ++ if (it && !it->isHidden()) ++ setCurrentItem(it); ++} ++ ++// Retrieve tree sorting ++QString ContactList::getSorting() ++{ ++ if (!m_sortKey) ++ return QtCustomTree::getSorting(); ++ String tmp = m_sortKey; ++ tmp << "," << String::boolText(m_sortOrder == Qt::AscendingOrder); ++ return QtClient::setUtf8(tmp); ++} ++ ++// Set tree sorting ++void ContactList::updateSorting(const String& key, Qt::SortOrder sort) ++{ ++ SafeInt safeChg(&m_changing); ++ if (!isSortingEnabled()) { ++ m_sortKey = key; ++ m_sortOrder = sort; ++ } ++ else ++ QtCustomTree::updateSorting(key,sort); ++} ++ ++// Optimized add. Set the whole tree ++void ContactList::setContacts(QList& list) ++{ ++ SafeInt safeChg(&m_changing); ++ // Add contacts to tree ++ if (m_flatList) { ++ sortContacts(list); ++ addChildren(list,-1,0); ++ } ++ else { ++ ContactItemList cil; ++ for (int i = 0; i < list.size(); i++) ++ createContactTree(static_cast(list[i]),cil); ++ if (cil.m_groups.size()) { ++ addChildren(cil.m_groups); ++ for (int i = 0; i < cil.m_groups.size(); i++) { ++ sortContacts(cil.m_contacts[i]); ++ QtTreeItem* grp = static_cast(cil.m_groups[i]); ++ addChildren(cil.m_contacts[i],-1,grp); ++ } ++ } ++ } ++ listChanged(); ++} ++ ++// Create a contact ++ContactItem* ContactList::createContact(const String& id, const NamedList& params) ++{ ++ ContactItem* c = new ContactItem(id,params); ++ c->copyParams(params); ++ c->updateName(params,m_compareNameCs); ++ return c; ++} ++ ++// Add or update a contact ++bool ContactList::updateContact(const String& id, const NamedList& params) ++{ ++ if (TelEngine::null(id)) ++ return false; ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::updateContact(%s)", ++ name().c_str(),id.c_str()); ++ SafeInt safeChg(&m_changing); ++ QList list; ++ ContactItem* c = findContact(id,&list); ++ if (!c) { ++ addContact(id,params); ++ return true; ++ } ++ bool changed = c->updateName(params,m_compareNameCs); ++ if (!changed && !m_flatList) ++ changed = c->groupsWouldChange(params); ++ if (!changed) { ++ for (int i = 0; i < list.size(); i++) ++ if (isContactType(list[i]->type()) && list[i]->id() == id) ++ updateContact(*(static_cast(list[i])),params); ++ } ++ else ++ replaceContact(*c,params); ++ return true; ++} ++ ++// Remove a contact from tree ++bool ContactList::removeContact(const String& id) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::removeContact(%s)", ++ name().c_str(),id.c_str()); ++ SafeInt safeChg(&m_changing); ++ if (m_flatList) { ++ QtTreeItem* it = find(id,0,false,false); ++ if (it) ++ delete it; ++ return it != 0; ++ } ++ // Remove from each group ++ QTreeWidgetItem* root = QTreeWidget::invisibleRootItem(); ++ if (!root) ++ return false; ++ bool ok = false; ++ while (true) { ++ int start = 0; ++ int n = root->childCount(); ++ for (; start < n; start++) { ++ QtTreeItem* it = static_cast(root->child(start)); ++ if (!it) ++ continue; ++ QtTreeItem* c = find(id,it,false,false); ++ if (!c) ++ continue; ++ ok = true; ++ delete c; ++ // Remove empty group and restart ++ if (!it->childCount()) { ++ delete it; ++ if (start < n - 1) ++ break; ++ } ++ } ++ if (start == n) ++ break; ++ } ++ return ok; ++} ++ ++// Update a contact ++bool ContactList::updateContact(ContactItem& c, const NamedList& params, bool all) ++{ ++#ifdef DEBUG ++ String tmp; ++ params.dump(tmp," "); ++ Debug(ClientDriver::self(),DebugAll,"ContactList(%s)::updateContact(%p,%s) all=%u %s", ++ name().c_str(),&c,c.id().c_str(),all,tmp.safe()); ++#endif ++ QtCustomTree::updateItem(c,params); ++ // Show/hide ++ if (c.type() == TypeContact && !m_showOffline) ++ showItem(c,!c.offline()); ++ return true; ++} ++ ++// Update a contact ++bool ContactList::updateItem(QtTreeItem& item, const NamedList& params) ++{ ++ if (isContactType(item.type())) ++ return updateContact(*static_cast(&item),params); ++ return QtCustomTree::updateItem(item,params); ++} ++ ++// Get the context menu associated with a given item ++QMenu* ContactList::contextMenu(QtTreeItem* item) ++{ ++ if (!item) ++ return QtCustomTree::contextMenu(0); ++ if (item->type() == TypeContact) { ++ if (m_menuContact) ++ return m_menuContact; ++ } ++ if (item->type() == TypeChatRoom) { ++ if (m_menuChatRoom) ++ return m_menuChatRoom; ++ } ++ else if (item->type() == TypeGroup) ++ return m_menu; ++ return QtCustomTree::contextMenu(item); ++} ++ ++// Item added notification ++void ContactList::itemAdded(QtTreeItem& item, QtTreeItem* parent) ++{ ++ SafeInt safeChg(&m_changing); ++ QtCustomTree::itemAdded(item,parent); ++ DDebug(ClientDriver::self(),DebugAll,"ContactList(%s)::itemAdded(%p,%p) type=%d id=%s", ++ name().c_str(),&item,parent,item.type(),item.id().c_str()); ++ if (isContactType(item.type())) { ++ ContactItem* c = static_cast(&item); ++ updateContact(*c,*c); ++ return; ++ } ++ if (item.type() != TypeGroup) ++ return; ++ // Set group name ++ QWidget* w = itemWidget(&item,0); ++ if (!w) { ++ QtCustomTree::updateItem(item,item); ++ return; ++ } ++ QtWindow* wnd = QtClient::parentWindow(this); ++ if (!wnd) ++ return; ++ String text; ++ QtClient::getUtf8(text,item.text(0)); ++ String n; ++ QtClient::getUtf8(n,w->objectName()); ++ String buf; ++ wnd->setText(buildChildName(buf,n,"group"),text,false); ++} ++ ++// Fill a list with item statistics ++void ContactList::fillItemStatistics(QtTreeItem& item, NamedList& list) ++{ ++ if (item.type() != TypeGroup) ++ return; ++ int total = 0; ++ int online = 0; ++ countContacts(&item,total,online); ++ list.addParam("total",String(total)); ++ list.addParam("online",String(online)); ++} ++ ++// Update a tree item's margins ++void ContactList::applyItemMargins(QtTreeItem& item, bool set, QtTreeItemProps* p) ++{ ++ set = !m_flatList && item.type() != TypeGroup; ++ QtCustomTree::applyItemMargins(item,set,p); ++} ++ ++// Retrieve a group item from root or create a new one ++QtTreeItem* ContactList::getGroup(const String& name, bool create) ++{ ++ const String& grp = name ? name : s_noGroupId; ++ if (!grp) ++ return 0; ++ // Check if the group already exists ++ QList list = findItems(grp,0,false,false); ++ for (int i = 0; i < list.size(); i++) { ++ if (list[i]->id() == grp && list[i]->type() == TypeGroup) ++ return list[i]; ++ } ++ if (!create) ++ return 0; ++ QTreeWidgetItem* root = invisibleRootItem(); ++ if (!root) ++ return 0; ++ const String& gText = name ? name : m_noGroupText; ++ XDebug(ClientDriver::self(),DebugAll,"ContactList(%s) creating group id=%s text='%s'", ++ this->name().c_str(),grp.c_str(),gText.c_str()); ++ // Always keep 'no group' the last one ++ // Insert any other group before it ++ int pos = -1; ++ if (grp != s_noGroupId) { ++ QtTreeItem* noGrp = getGroup(s_noGroupId,false); ++ if (noGrp) ++ pos = root->indexOfChild(noGrp); ++ } ++ QtTreeItem* g = createGroup(grp,gText,m_expStatusGrp); ++ if (!addChild(g,pos)) ++ TelEngine::destruct(g); ++ return g; ++} ++ ++// Add a contact ++void ContactList::addContact(const String& id, const NamedList& params) ++{ ++ SafeInt safeChg(&m_changing); ++ ContactItem* c = createContact(id,params); ++ if (m_flatList) { ++ addContact(c); ++ return; ++ } ++ ContactItemList cil; ++ createContactTree(c,cil); ++ for (int i = 0; i < cil.m_groups.size(); i++) { ++ QtTreeItem* cg = static_cast(cil.m_groups[i]); ++ if (cil.m_contacts[i].size()) { ++ ContactItem* item = static_cast((cil.m_contacts[i])[0]); ++ QtTreeItem* grp = getGroup(cg->id() != s_noGroupId ? cg->id() : String::empty()); ++ if (grp) ++ addContact(item,grp); ++ else ++ TelEngine::destruct(item); ++ } ++ TelEngine::destruct(cg); ++ } ++} ++ ++// Add a contact to a specified parent ++void ContactList::addContact(ContactItem* c, QtTreeItem* parent) ++{ ++ if (!c) ++ return; ++ SafeInt safeChg(&m_changing); ++ int pos = -1; ++ if (m_sortKey == "name") { ++ bool asc = (m_sortOrder == Qt::AscendingOrder); ++ QTreeWidgetItem* p = parent ? (QTreeWidgetItem*)parent : invisibleRootItem(); ++ int n = p ? p->childCount() : 0; ++ for (int i = 0; i < n; i++) { ++ ContactItem* item = static_cast(p->child(i)); ++ int comp = compareStr(c->m_name,item->m_name,m_compareNameCs); ++ if (comp && (asc == (comp < 0))) { ++ pos = i; ++ break; ++ } ++ } ++ } ++ QtCustomTree::addChild(c,pos,parent); ++} ++ ++// Replace an existing contact. Remove it and add it again ++void ContactList::replaceContact(ContactItem& c, const NamedList& params) ++{ ++ if (!c) ++ return; ++ TreeRestoreSel sel(this,c.id()); ++ SafeInt safeChg(&m_changing); ++ String id = c.id(); ++ NamedList p(c); ++ p.copyParams(params); ++ removeContact(id); ++ addContact(id,p); ++} ++ ++// Create contact structure (groups and lists) ++void ContactList::createContactTree(ContactItem* c, ContactItemList& cil) ++{ ++ if (!c) ++ return; ++ SafeInt safeChg(&m_changing); ++ bool noGrp = true; ++ ObjList* grps = c->groups(); ++ for (ObjList* o = grps->skipNull(); o; o = o->skipNext()) { ++ String* grp = static_cast(o->get()); ++ if (grp->null()) ++ continue; ++ noGrp = false; ++ int index = cil.getGroupIndex(*grp,*grp,m_expStatusGrp); ++ if (o->skipNext()) ++ cil.m_contacts[index].append(createContact(c->id(),*c)); ++ else ++ cil.m_contacts[index].append(c); ++ } ++ TelEngine::destruct(grps); ++ if (noGrp) { ++ int index = cil.getGroupIndex(s_noGroupId,m_noGroupText,m_expStatusGrp); ++ cil.m_contacts[index].append(c); ++ } ++} ++ ++// Sort contacts ++void ContactList::sortContacts(QList& list) ++{ ++ if (!list.size()) ++ return; ++ SafeInt safeChg(&m_changing); ++ if (m_sortKey == "name") { ++ QVector v(list.size()); ++ for (int i = 0; i < list.size(); i++) { ++ v[i].first = list[i]; ++ v[i].second = (static_cast(list[i]))->m_name; ++ } ++ stableSort(v,m_sortOrder,m_compareNameCs); ++ for (int i = 0; i < list.size(); i++) ++ list[i] = v[i].first; ++ } ++} ++ ++ ++/* ++ * ContactItem ++ */ ++// Update name. Return true if changed ++bool ContactItem::updateName(const NamedList& params, Qt::CaseSensitivity cs) ++{ ++ const String* name = params.getParam("name"); ++ if (!name) ++ return false; ++ QString s = QtClient::setUtf8(*name); ++ if (!compareStr(m_name,s,cs)) ++ return false; ++ m_name = s; ++ return true; ++} ++ ++// Check if groups would change ++bool ContactItem::groupsWouldChange(const NamedList& params) ++{ ++ String* grps = params.getParam("groups"); ++ if (!grps) ++ return false; ++ bool changed = false; ++ ObjList* cgroups = groups(); ++ ObjList* newList = Client::splitUnescape(*grps); ++ ObjList* o = 0; ++ for (o = newList->skipNull(); o && !changed; o = o->skipNext()) ++ changed = !cgroups->find(o->get()->toString()); ++ for (o = cgroups->skipNull(); o && !changed; o = o->skipNext()) ++ changed = !newList->find(o->get()->toString()); ++ TelEngine::destruct(newList); ++ TelEngine::destruct(cgroups); ++ return changed; ++} ++ ++// Check if the contact status is 'offline' ++bool ContactItem::offline() ++{ ++ String* status = getParam("status"); ++ return status && *status == s_offline; ++} ++ ++ ++/* ++ * ContactItemList ++ */ ++int ContactItemList::getGroupIndex(const String& id, const String& text, bool expStat) ++{ ++ for (int i = 0; i < m_groups.size(); i++) { ++ QtTreeItem* item = static_cast(m_groups[i]); ++ if (item->id() == id) ++ return i; ++ } ++ int pos = m_groups.size(); ++ if (pos && id != s_noGroupId && ++ (static_cast(m_groups[pos - 1]))->id() == s_noGroupId) ++ pos--; ++ m_groups.insert(pos,ContactList::createGroup(id,text,expStat)); ++ m_contacts.insert(pos,QtTreeItemList()); ++ return pos; ++} ++ ++// ++// FileItem ++// ++FileItem::FileItem(int type, const char* name, const String& path, ++ QFileIconProvider* prov) ++ : String(name), ++ m_type(type), m_icon(0) ++{ ++ FileListTree::buildFileFullName(m_fullName,path,name); ++ if (prov) ++ m_icon = new QIcon(FileListTree::fileIcon(type,m_fullName,prov)); ++} ++ ++FileItem::FileItem(const String& path, QFileIconProvider* prov) ++ : String(FileListTree::s_upDir), ++ m_type(FileListTree::TypeDir), m_icon(0) ++{ ++ Client::removeLastNameInPath(m_fullName,path); ++ if (prov) ++ m_icon = new QIcon(FileListTree::fileIcon(m_type,m_fullName,prov)); ++} ++ ++FileItem::~FileItem() ++{ ++ if (m_icon) ++ delete m_icon; ++} ++ ++ ++// ++// DirListThread ++// ++// Skip special directories (. or ..) ++static inline bool skipSpecial(const char* s) ++{ ++ return *s && *s == '.' && (!s[1] || (s[1] == '.' && !s[2])); ++} ++ ++void DirListThread::run() ++{ ++ ObjList* dirs = m_listDirs ? &m_dirs : 0; ++ ObjList* files = m_listFiles ? &m_files : 0; ++ XDebug(QtDriver::self(),DebugAll,"DirListThread(%s) starting [%p]", ++ m_dir.c_str(),this); ++#ifdef _WINDOWS ++ String name(m_dir); ++ if (!name.endsWith("\\")) ++ name << "\\"; ++ name << "*"; ++ // Init find ++ WIN32_FIND_DATAA d; ++ HANDLE hFind = ::FindFirstFileA(name,&d); ++ if (hFind == INVALID_HANDLE_VALUE) { ++ m_error = ::GetLastError(); ++ if (m_error == ERROR_NO_MORE_FILES) ++ m_error = 0; ++ runTerminated(); ++ return; ++ } ++ // Enumerate content ++ ::SetLastError(0); ++ do { ++ if (isFinished()) ++ break; ++ if (d.dwFileAttributes & FILE_ATTRIBUTE_DEVICE || ++ skipSpecial(d.cFileName)) ++ continue; ++ if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { ++ if (dirs) ++ dirs = addItem(FileListTree::TypeDir,d.cFileName,m_dirs,dirs); ++ } ++ else if (files) ++ files = addItem(FileListTree::TypeFile,d.cFileName,m_files,files); ++ } ++ while (::FindNextFileA(hFind,&d)); ++ if (isRunning()) { ++ m_error = ::GetLastError(); ++ if (m_error == ERROR_NO_MORE_FILES) ++ m_error = 0; ++ } ++ else ++ m_error = ERROR_CANCELLED; ++ ::FindClose(hFind); ++#else ++ errno = 0; ++ DIR* dir = ::opendir(m_dir); ++ if (!dir) { ++ m_error = errno; ++ runTerminated(); ++ return; ++ } ++ struct dirent* entry; ++ while ((entry = ::readdir(dir)) != 0) { ++ if (isFinished()) ++ break; ++ if (skipSpecial(entry->d_name)) ++ continue; ++#ifdef _DIRENT_HAVE_D_TYPE ++ if (entry->d_type == DT_DIR) { ++ if (dirs) ++ dirs = addItem(FileListTree::TypeDir,entry->d_name,m_dirs,dirs); ++ } ++ else if (entry->d_type == DT_REG && files) ++ files = addItem(FileListTree::TypeFile,entry->d_name,m_files,files); ++#else ++ struct stat stat_buf; ++ String p; ++ p << m_dir << "/" << entry->d_name; ++ if (::stat(p,&stat_buf)) ++ break; ++ if (S_ISDIR(stat_buf.st_mode)) { ++ if (dirs) ++ dirs = addItem(FileListTree::TypeDir,entry->d_name,m_dirs,dirs); ++ } ++ else if (S_ISREG(stat_buf.st_mode) && files) ++ files = addItem(FileListTree::TypeFile,entry->d_name,m_files,files); ++#endif // _DIRENT_HAVE_D_TYPE ++ } ++ if (isRunning()) ++ m_error = errno; ++ else ++ m_error = ECANCELED; ++ ::closedir(dir); ++#endif // _WINDOWS ++ runTerminated(); ++} ++ ++ObjList* DirListThread::addItemSort(ObjList& list, FileItem* it) ++{ ++ if (!it) ++ return 0; ++ ObjList* o = list.skipNull(); ++ bool asc = (m_sort == QtClient::SortAsc); ++ for (; o; o = o->skipNext()) { ++ FileItem* crt = static_cast(o->get()); ++ int cmp = m_caseSensitive ? ::strcmp(*it,*crt) : ::strcasecmp(*it,*crt); ++ if (!cmp) ++ continue; ++ // Ascending ? ++ if (asc) { ++ if (cmp > 0) ++ continue; ++ } ++ else if (cmp < 0) ++ continue; ++ return o->insert(it); ++ } ++ if (o) ++ return o->append(it); ++ return list.append(it); ++} ++ ++// Called when terminated from run() ++void DirListThread::runTerminated() ++{ ++ XDebug(QtDriver::self(),DebugAll,"DirListThread(%s) finished error=%d [%p]", ++ m_dir.c_str(),m_error,this); ++ if (m_error) ++ return; ++ // Add up dir ++ if (m_listDirs && m_listUpDir) ++ m_dirs.insert(new FileItem(m_dir,m_iconProvider)); ++} ++ ++ ++// ++// FileListTree ++// ++const String FileListTree::s_upDir = ".."; ++ ++static inline void setRootPath(String& path) ++{ ++#ifdef _WINDOWS ++ path = ""; ++#else ++ path = "/"; ++#endif ++} ++ ++static inline int getPathType(const String& s, int defVal) ++{ ++ if (s == YSTRING("upthenhome")) ++ return FileListTree::PathUpThenHome; ++ if (s == YSTRING("home")) ++ return FileListTree::PathHome; ++ if (s == YSTRING("root")) ++ return FileListTree::PathRoot; ++ if (s == YSTRING("none")) ++ return FileListTree::PathNone; ++ return defVal; ++} ++ ++static inline void removePathSepEnd(String& s) ++{ ++ Client::removeEndsWithPathSep(s,s); ++} ++ ++// Constructor ++FileListTree::FileListTree(const char* name, const NamedList& params, QWidget* parent) ++ : QtCustomTree(name,params,parent,false), ++ m_fileSystemList(false), ++ m_autoChangeDir(true), ++ m_listFiles(false), ++ m_sort(QtClient::SortNone), ++ m_listOnFailure(PathUpThenHome), ++ m_iconProvider(0), ++ m_dirListThread(0) ++{ ++ XDebug(ClientDriver::self(),DebugAll,"FileListTree(%s) [%p]",name,this); ++ // Add item props translation ++ addItemType(TypeDir,"dir"); ++ addItemType(TypeFile,"file"); ++ addItemType(TypeDrive,"drive"); ++ // Set some defaults ++ if (params.getBoolValue(YSTRING("filelist_default_itemstyle"))) { ++ setItemStyle("dir:

${name}

"); ++ setItemStyle("file:

${name}

"); ++ setItemStyle("drive:

${name}

"); ++ } ++ String* ihs = params.getParam(YSTRING("filelist_default_itemheight")); ++ int ih = 0; ++ if (ihs) { ++ if (ihs->toBoolean()) ++ ih = 16; ++ else ++ ih = ihs->toInteger(); ++ } ++ if (ih > 0) { ++ String tmp(ih); ++ setItemHeight(QtClient::setUtf8("dir:" + tmp)); ++ setItemHeight(QtClient::setUtf8("file:" + tmp)); ++ setItemHeight(QtClient::setUtf8("drive:" + tmp)); ++ setUniformRowHeights(true); ++ } ++ // Display contents from file system ++ m_fileSystemList = params.getBoolValue(YSTRING("filelist_filesystemlist")); ++ if (m_fileSystemList) { ++ m_sort = QtClient::SortAsc; ++ m_nameParam = params.getValue(YSTRING("filelist_filesystemlist_name_column"),"name"); ++ m_autoChangeDir = params.getBoolValue(YSTRING("filelist_filesystemlist_autochangedir"),true); ++ m_listFiles = params.getBoolValue(YSTRING("filelist_filesystemlist_listfiles"),true); ++ if (params.getBoolValue(YSTRING("filelist_filesystemlist_showicons"))) ++ m_iconProvider = new QFileIconProvider; ++ m_listOnFailure = getPathType(params[YSTRING("filelist_filesystemlist_listonfailure")], ++ PathUpThenHome); ++ } ++ setParams(params); ++ if (m_fileSystemList) { ++ String* s = params.getParam(YSTRING("filelist_filesystemlist_startpath")); ++ if (s) { ++ int t = getPathType(*s,PathRoot); ++ if (t == PathHome) ++ setFsPath(QDir::homePath()); ++ else if (t != PathNone) ++ setFsPath(); ++ } ++ } ++} ++ ++// Destructor ++FileListTree::~FileListTree() ++{ ++ setDirListThread(false); ++ if (m_iconProvider) ++ delete m_iconProvider; ++} ++ ++// Set _yate_filesystem_path property ++void FileListTree::setFsPath(QString path) ++{ ++ if (!m_fileSystemList) ++ return; ++ String tmp; ++ QtClient::getUtf8(tmp,QDir::toNativeSeparators(path)); ++ setFsPath(tmp); ++} ++ ++// Set _yate_refresh property ++void FileListTree::setRefresh(QString val) ++{ ++ if (m_fileSystemList) ++ setFsPath(m_fsPath); ++} ++ ++// Change file system path, refresh data ++void FileListTree::setFsPath(const String& path, bool force) ++{ ++ if (!m_fileSystemList) ++ return; ++ if (!force && m_fsPath == path) ++ return; ++ String old = m_fsPath; ++ m_fsPath = path; ++ removePathSepEnd(m_fsPath); ++ if (!m_fsPath) { ++ setRootPath(m_fsPath); ++#ifdef _WINDOWS ++ ObjList tmp; ++ QFileInfoList l = QDir::drives(); ++ for (int i = 0; i < l.size(); i++) { ++ QFileInfo fi = l[i]; ++ String n; ++ QtClient::getUtf8(n,QDir::toNativeSeparators(fi.absoluteFilePath())); ++ removePathSepEnd(n); ++ if (n) ++ tmp.append(new FileItem(TypeDrive,n,String::empty(),m_iconProvider)); ++ } ++ refresh(0,0,&tmp); ++ m_acceptDropOnEmpty = QtDrop::None; ++ return; ++#endif ++ } ++ if (setDirListThread(true)) { ++ m_acceptDropOnEmpty = QtDrop::Always; ++ clearTable(); ++ } ++ else { ++ m_acceptDropOnEmpty = QtDrop::None; ++ m_fsPath = old; ++ } ++} ++ ++static void addFileItems(QList& items, ObjList* list, ++ const String& nameParam, int iconCol) ++{ ++ if (!list) ++ return; ++ for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { ++ FileItem* f = static_cast(o->get()); ++ QtTreeItem* it = new QtTreeItem(f->m_fullName,f->m_type); ++ it->addParam(nameParam,*f); ++ if (iconCol >= 0 && f->m_icon) ++ it->setIcon(iconCol,*(f->m_icon)); ++ items.append(it); ++ } ++} ++ ++// Directory listing thread finished notification ++void FileListTree::refresh(ObjList* dirs, ObjList* files, ObjList* drives) ++{ ++ clearTable(); ++ SafeInt safeChg(&m_changing); ++ QList list; ++ int col = getColumnNo(m_nameParam); ++ addFileItems(list,drives,m_nameParam,col); ++ addFileItems(list,dirs,m_nameParam,col); ++ addFileItems(list,files,m_nameParam,col); ++ addChildren(list); ++ for (int i = 0; i < list.size(); i++) { ++ QtTreeItem* it = static_cast(list[i]); ++ updateItem(*it,*it); ++ } ++} ++ ++// Sort a list of items ++void FileListTree::sortItems(QList& list, int type) ++{ ++ if (type != TypeDir && type != TypeFile) ++ return; ++ QVector v(list.size()); ++ for (int i = 0; i < list.size(); i++) { ++ v[i].first = list[i]; ++ v[i].second = QtClient::setUtf8(static_cast(list[i])->getValue(m_nameParam)); ++ } ++ stableSort(v,Qt::AscendingOrder,Qt::CaseInsensitive); ++ for (int i = 0; i < list.size(); i++) ++ list[i] = v[i].first; ++} ++ ++// Retrieve the icon for a given item ++QIcon FileListTree::icon(QtTreeItem& item) ++{ ++ return fileIcon(item.type(),item.toString(),m_iconProvider); ++} ++ ++// Retrieve the icon for a given item type ++QIcon FileListTree::fileIcon(int type, const String& name, QFileIconProvider* provider) ++{ ++ if (!provider) ++ return QIcon(); ++ if (type == TypeDir || type == TypeFile) { ++ QFileInfo fi(QtClient::setUtf8(name)); ++ return provider->icon(fi); ++ } ++ if (type == TypeDrive) ++ return provider->icon(QFileIconProvider::Drive); ++ return QIcon(); ++} ++ ++// Catch dir list thread terminate signal ++void FileListTree::onDirThreadTerminate() ++{ ++ if (!m_dirListThread) ++ return; ++ QThread* th = qobject_cast(sender()); ++ if (th != m_dirListThread) ++ return; ++ DirListThread* t = static_cast(th); ++ bool ok = !t->m_error; ++ if (ok) ++ refresh(t->m_listDirs ? &t->m_dirs : 0,t->m_listFiles ? &t->m_files : 0); ++ else if (QtDriver::self() && QtDriver::self()->debugAt(DebugNote)) { ++ String s; ++ Thread::errorString(s,t->m_error); ++ Debug(QtDriver::self(),DebugNote,"FileListTree(%s) failed to list '%s': %d '%s' [%p]", ++ name().c_str(),m_fsPath.c_str(),t->m_error,s.c_str(),this); ++ } ++ QtBusyWidget::showBusyChild(this,false); ++ resetThread(); ++ if (ok) ++ return; ++ if (m_listOnFailure == PathUpThenHome) { ++ // Up dir, then home ++ if (!isHomePath()) { ++ if (isRootPath(m_fsPath)) { ++ setFsPath(QDir::homePath()); ++ return; ++ } ++ int pos = m_fsPath.rfind(*Engine::pathSeparator()); ++ if (pos >= 0) ++ setFsPath(m_fsPath.substr(0,pos)); ++ else ++ setFsPath(); ++ return; ++ } ++ } ++ else if (m_listOnFailure == PathRoot) { ++ // Try root path ++ if (!isRootPath(m_fsPath)) { ++ setFsPath(); ++ return; ++ } ++ } ++ // Always try home path if something set ++ if (m_listOnFailure != PathNone && !isHomePath()) { ++ setFsPath(QDir::homePath()); ++ return; ++ } ++ m_fsPath = ""; ++ m_acceptDropOnEmpty = QtDrop::None; ++ refresh(0,0); ++} ++ ++// Start/stop dir list thread ++bool FileListTree::setDirListThread(bool on) ++{ ++ QtBusyWidget::showBusyChild(this,false); ++ resetThread(); ++ if (!on) ++ return true; ++ DirListThread* t = new DirListThread(0,m_fsPath,true,m_listFiles); ++ if (!QtClient::connectObjects(t,SIGNAL(finished()), ++ this,SLOT(onDirThreadTerminate()))) { ++ t->deleteLater(); ++ return false; ++ } ++ t->m_iconProvider = m_iconProvider; ++ t->m_listUpDir = !isRootPath(m_fsPath); ++ t->m_sort = m_sort; ++ QtBusyWidget::showBusyChild(this,true); ++ m_dirListThread = t; ++ m_dirListThread->start(); ++ return true; ++} ++ ++// Process item double click ++void FileListTree::onItemDoubleClicked(QtTreeItem* item, int column) ++{ ++ if (item && item->type() != TypeFile && m_fileSystemList && m_autoChangeDir) ++ setFsPath(item->toString()); ++ else ++ QtCustomTree::onItemDoubleClicked(item,column); ++} ++ ++void FileListTree::resetThread() ++{ ++ if (!m_dirListThread) ++ return; ++ QThread* t = m_dirListThread; ++ m_dirListThread = 0; ++ t->disconnect(); ++ t->exit(); ++ t->deleteLater(); ++} ++ ++ ++// ++// QtPaintItemDesc ++// ++QtPaintButtonDesc* QtPaintItemDesc::button() ++{ ++ return 0; ++} ++ ++ ++// ++// QtPaintButtonDesc ++// ++QtPaintButtonDesc* QtPaintButtonDesc::button() ++{ ++ return this; ++} ++ ++// Find a button in a list ++QtPaintButtonDesc* QtPaintButtonDesc::find(ObjList& list, const String& name, ++ bool create) ++{ ++ if (!name) ++ return 0; ++ ObjList* o = list.find(name); ++ if (!o && create) ++ o = list.append(new QtPaintButtonDesc(name)); ++ return o ? static_cast(o->get())->button() : 0; ++} ++ ++ ++// ++// QtPaintItem ++// ++// Set hover state ++bool QtPaintItem::setHover(bool on) ++{ ++ if (m_hover == on) ++ return false; ++ m_hover = on; ++ return true; ++} ++ ++// Set pressed state ++bool QtPaintItem::setPressed(bool on) ++{ ++ if (m_pressed == on) ++ return false; ++ m_pressed = on; ++ return true; ++} ++ ++// Retrieve the item name ++const String& QtPaintItem::toString() const ++{ ++ return name(); ++} ++ ++ ++// ++// QtPaintButton ++// ++QtPaintButton::QtPaintButton(QtPaintButtonDesc& desc) ++ : QtPaintItem(desc,desc.m_size), ++ m_image(0), ++ m_iconSize(desc.m_iconSize), ++ m_iconOffset(0,0) ++{ ++ if (m_iconSize.width() > m_size.width()) ++ m_iconSize.setWidth(m_size.width()); ++ if (m_iconSize.height() > m_size.height()) ++ m_iconSize.setHeight(m_size.height()); ++ m_iconOffset.setWidth((m_size.width() - m_iconSize.width()) / 2); ++ m_iconOffset.setHeight((m_size.height() - m_iconSize.height()) / 2); ++ m_action = desc.m_params; ++ m_image = &m_normalImage; ++ loadImages(desc.m_params); ++ updateOptState(); ++} ++ ++// Load button images ++void QtPaintButton::loadImages(const NamedList& params) ++{ ++ loadImage(m_normalImage,params,YSTRING("_yate_normal_icon")); ++ if (!loadImage(m_hoverImage,params,YSTRING("_yate_hover_icon"))) ++ m_hoverImage = m_normalImage; ++ if (!loadImage(m_pressedImage,params,YSTRING("_yate_pressed_icon"))) ++ m_pressedImage = m_normalImage; ++} ++ ++// Set hover state ++bool QtPaintButton::setHover(bool on) ++{ ++ if (!QtPaintItem::setHover(on)) ++ return false; ++ if (!on) ++ m_pressed = false; ++ updateOptState(); ++ return true; ++} ++ ++// Set pressed state ++bool QtPaintButton::setPressed(bool on) ++{ ++ if (!QtPaintItem::setPressed(on)) ++ return false; ++ updateOptState(); ++ return true; ++} ++ ++// Draw the button ++void QtPaintButton::draw(QPainter* painter, const QRect& rect) ++{ ++ m_displayRect = rect; ++ if (!(painter && m_image)) ++ return; ++ QPoint p(m_iconOffset.width() + rect.x(),m_iconOffset.height() + rect.y()); ++ painter->drawPixmap(p,*m_image); ++} ++ ++// Load an image, adjust its size ++bool QtPaintButton::loadImage(QPixmap& pixmap, const NamedList& params, const String& param) ++{ ++ if (!QtClient::getSkinPathPixmapFromCache(pixmap,params[param])) ++ return false; ++ // Adjust size ++ if (pixmap.size() != m_iconSize) ++ pixmap = pixmap.scaled(m_iconSize,Qt::KeepAspectRatio); ++ return true; ++} ++ ++// Update option state ++void QtPaintButton::updateOptState() ++{ ++ if (m_enabled) { ++ if (m_hover) { ++ if (!m_pressed) ++ m_image = &m_hoverImage; ++ else ++ m_image = &m_pressedImage; ++ } ++ else if (m_pressed) ++ m_image = &m_pressedImage; ++ else ++ m_image = &m_normalImage; ++ } ++ else ++ m_image = &m_normalImage; ++} ++ ++ ++// ++// QtPaintItems ++// ++// Add an item from description ++void QtPaintItems::append(QtPaintItemDesc& desc) ++{ ++ QtPaintButtonDesc* bDesc = desc.button(); ++ if (!bDesc) ++ return; ++ QtPaintButton* b = new QtPaintButton(*bDesc); ++ m_items.remove(b->toString()); ++ m_items.append(b); ++} ++ ++// Calculate area needed to paint ++void QtPaintItems::itemsAdded() ++{ ++ m_size.setHeight(0); ++ m_size.setWidth(0); ++ for (ObjList* o = m_items.skipNull(); o; o = o->skipNext()) { ++ QtPaintItem* item = static_cast(o->get()); ++ if (m_size.width()) ++ m_size.setWidth(m_size.width() + m_itemSpace); ++ m_size.setWidth(m_size.width() + item->size().width()); ++ if (m_size.height() < item->size().height()) ++ m_size.setHeight(item->size().height()); ++ } ++ if (m_size.width()) ++ m_size.setWidth(m_size.width() + m_margins.x() + m_margins.width()); ++ if (m_size.height()) ++ m_size.setHeight(m_size.height() + m_margins.y() + m_margins.height()); ++} ++ ++// Set hover. Update item at position ++bool QtPaintItems::setHover(const QPoint& pos) ++{ ++ bool chg = setHover(true); ++ if (m_lastItemHover) { ++ if (m_lastItemHover->displayRect().contains(pos)) ++ return chg; ++ m_lastItemHover->setHover(false); ++ m_lastItemHover = 0; ++ chg = true; ++ } ++ for (ObjList* o = m_items.skipNull(); !m_lastItemHover && o; o = o->skipNext()) { ++ QtPaintItem* it = static_cast(o->get()); ++ if (it->displayRect().contains(pos)) ++ m_lastItemHover = it; ++ } ++ if (m_lastItemHover) ++ chg = m_lastItemHover->setHover(true) || chg; ++ return chg; ++} ++ ++// Set hover state ++bool QtPaintItems::setHover(bool on) ++{ ++ if (!QtPaintItem::setHover(on)) ++ return false; ++ if (on) ++ return true; ++ if (m_lastItemHover) { ++ m_lastItemHover->setHover(false); ++ m_lastItemHover = 0; ++ } ++ return true; ++} ++ ++// Mouse pressed/released. Update item at position ++bool QtPaintItems::mousePressed(bool on, const QPoint& pos, String* action) ++{ ++ bool chg = false; ++ if (m_lastItemHover) { ++ if (m_lastItemHover->displayRect().contains(pos)) { ++ chg = m_lastItemHover->setPressed(on); ++ if (chg && !on && action) ++ *action = m_lastItemHover->action(); ++ } ++ else ++ chg = m_lastItemHover->setPressed(false); ++ } ++ return setPressed(on); ++} ++ ++// Set pressed state ++bool QtPaintItems::setPressed(bool on) ++{ ++ bool chg = !on && m_lastItemHover && m_lastItemHover->setPressed(false); ++ return QtPaintItem::setPressed(on) || chg; ++} ++ ++// Draw items. ++void QtPaintItems::draw(QPainter* painter, const QRect& rect) ++{ ++ m_displayRect = rect; ++ if (!painter) ++ return; ++ painter->save(); ++ int maxX = rect.x() + rect.width(); ++ int x = rect.x() + m_margins.x(); ++ int y = rect.y() + m_margins.y(); ++ for (ObjList* o = m_items.skipNull(); o && (x < maxX); o = o->skipNext()) { ++ QtPaintItem* item = static_cast(o->get()); ++ QRect r(x,y,item->size().width(),item->size().height()); ++ painter->setClipRect(r); ++ item->draw(painter,r); ++ x += item->size().width() + m_itemSpace; ++ } ++ painter->restore(); ++} ++ ++ ++// ++// QtItemDelegate ++// ++QtItemDelegate::QtItemDelegate(QObject* parent, const NamedList& params) ++ : QItemDelegate(parent), ++ String(params), ++ m_drawFocus(true), ++ m_roleDisplayText(Qt::DisplayRole), ++ m_roleImage(Qt::UserRole), ++ m_roleBackground(Qt::UserRole), ++ m_roleMargins(Qt::UserRole), ++ m_roleQtDrawItems(Qt::UserRole) ++{ ++ static const String s_drawfocus = "drawfocus"; ++ static const String s_columns = "columns"; ++ static const String s_editableCols = "editable_cols"; ++ static const String s_role_display = "role_display"; ++ static const String s_role_image = "role_image"; ++ static const String s_role_background = "role_background"; ++ static const String s_role_margins = "role_margins"; ++ static const String s_role_qtdrawitems = "role_qtdrawitems"; ++ static const String s_noRoles = "noroles"; ++ static const String s_noImageRole = "noimagerole"; ++ ++ String pref = params; ++ if (pref) ++ pref << "."; ++ NamedIterator iter(params); ++ bool noRoleImage = false; ++ bool noRoles = false; ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) { ++ if (ns->name() == s_drawfocus) ++ m_drawFocus = ns->toBoolean(); ++ else if (ns->name() == s_columns) ++ m_columnsStr = QtClient::setUtf8(*ns).split(',',Qt::SkipEmptyParts); ++ else if (ns->name() == s_editableCols) ++ m_editableColsStr = QtClient::setUtf8(*ns).split(',',Qt::SkipEmptyParts); ++ else if (ns->name() == s_noImageRole) ++ noRoleImage = ns->toBoolean(); ++ else if (ns->name() == s_noRoles) ++ noRoles = ns->toBoolean(); ++ else if (pref && ns->name().startsWith(pref,false)) { ++ // Handle parameters set from code (not configurable) ++ String tmp = ns->name().substr(pref.length()); ++ if (tmp == s_role_display) ++ m_roleDisplayText = ns->toInteger(Qt::DisplayRole); ++ else if (tmp == s_role_image) ++ m_roleImage = ns->toInteger(Qt::UserRole); ++ else if (tmp == s_role_background) ++ m_roleBackground = ns->toInteger(Qt::UserRole); ++ else if (tmp == s_role_margins) ++ m_roleMargins = ns->toInteger(Qt::UserRole); ++ else if (tmp == s_role_qtdrawitems) ++ m_roleQtDrawItems = ns->toInteger(Qt::UserRole); ++ } ++ } ++ // Disable role(s) ++ if (noRoles) { ++ m_roleDisplayText = Qt::DisplayRole; ++ m_roleImage = Qt::UserRole; ++ m_roleBackground = Qt::UserRole; ++ m_roleMargins = Qt::UserRole; ++ } ++ else { ++ if (noRoleImage) ++ m_roleImage = Qt::UserRole; ++ } ++#ifdef XDEBUG ++ String dump; ++ params.dump(dump," "); ++ Debug(DebugAll,"QtItemDelegate(%s) created: %s [%p]",c_str(),dump.c_str(),this); ++#endif ++} ++ ++// Utility: translate name to int value ++static void setIntListName(QList& dest, QStringList& values, QStringList& cNames, ++ bool unique = true) ++{ ++ dest.clear(); ++ for (int i = 0; i < values.size(); i++) { ++ bool ok = false; ++ int val = values[i].toInt(&ok); ++ if (!ok) ++ val = cNames.indexOf(values[i]); ++ if (val >= 0 && !(unique && dest.contains(val))) ++ dest.append(val); ++ } ++} ++ ++// Update column position from column names. ++// 'cNames' must be the column names in their order, starting from 0 ++void QtItemDelegate::updateColumns(QStringList& cNames) ++{ ++ setIntListName(m_columns,m_columnsStr,cNames); ++ setIntListName(m_editableCols,m_editableColsStr,cNames); ++} ++ ++void QtItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, ++ const QModelIndex& index) const ++{ ++ QStyleOptionViewItem opt = setOptions(index,option); ++ opt.features = option.features; ++ // Prepare painter ++ painter->save(); ++ // Retrieve check ++ QRect checkRect; ++ Qt::CheckState checkState = Qt::Unchecked; ++ QVariant checkVar = index.data(Qt::CheckStateRole); ++ if (checkVar.isValid()) { ++ checkState = static_cast(checkVar.toInt()); ++ checkRect = doCheck(opt,opt.rect,checkVar); ++ } ++ // Retrieve image (decoration) ++ QPixmap pixmap; ++ QRect decorationRect; ++ bool isStd = (m_roleImage <= Qt::UserRole); ++ QVariant pVar = index.data(isStd ? Qt::DecorationRole : m_roleImage); ++ if (pVar.isValid()) { ++ if (isStd) ++ pixmap = decoration(opt,pVar); ++ else { ++ QString file = pVar.toString(); ++ QtClient::getPixmapFromCache(pixmap,file); ++ // Resize the pixmap ++ if (!pixmap.isNull()) ++ pixmap = pixmap.scaled(opt.decorationSize.width(), ++ opt.decorationSize.height(),Qt::KeepAspectRatio); ++ } ++ decorationRect = QRect(QPoint(0,0),pixmap.size()); ++ } ++ // Retrieve text to display ++ QString text = getDisplayText(opt,index); ++ QRect displayRect = opt.rect; ++ displayRect.setWidth(INT_MAX/256); ++ displayRect = textRectangle(painter,displayRect,opt.font,text); ++ // Retrieve margins and apply them ++ QRect margins; ++ if (m_roleMargins != Qt::UserRole) { ++ pVar = index.data(m_roleMargins); ++ if (pVar.type() == QVariant::Rect) { ++ margins = pVar.toRect(); ++ applyMargins(opt.rect,margins,true); ++ } ++ } ++ QtPaintItems* extraPaint = 0; ++ if (m_roleQtDrawItems != Qt::UserRole) { ++ pVar = index.data(m_roleQtDrawItems); ++ if (pVar.type() == QVariant::UserType) { ++ QtRefObjectHolder holder = pVar.value(); ++ extraPaint = static_cast((RefObject*)holder.m_refObj); ++ } ++ } ++ // Calculate layout ++ doLayout(opt,&checkRect,&decorationRect,&displayRect,false); ++ // Draw the item ++ if (m_roleMargins != Qt::UserRole) ++ applyMargins(opt.rect,margins,false); ++ drawBackground(painter,opt,index); ++ if (m_roleMargins != Qt::UserRole) ++ applyMargins(opt.rect,margins,true); ++ drawCheck(painter,opt,checkRect,checkState); ++ drawDecoration(painter,opt,decorationRect,pixmap); ++ if (extraPaint && extraPaint->size().width()) { ++ // Steal extra paint area from text display ++ int w = extraPaint->size().width(); ++ if (w < displayRect.width()) ++ displayRect.setWidth(displayRect.width() - w); ++ else { ++ w = displayRect.width(); ++ displayRect.setWidth(0); ++ } ++ int y = displayRect.y(); ++ int h = extraPaint->size().height(); ++ int delta = (displayRect.height() - h) / 2; ++ if (delta) { ++ if (delta > 0) ++ y += delta; ++ else ++ h += delta; ++ } ++ QRect rect(displayRect.x() + displayRect.width(),y,w,h); ++ extraPaint->draw(painter,rect); ++ } ++ drawDisplay(painter,opt,displayRect,text); ++ if (m_roleMargins != Qt::UserRole) ++ applyMargins(opt.rect,margins,false); ++ drawFocus(painter,opt,displayRect); ++ // Restore painter ++ painter->restore(); ++} ++ ++// Build a list of delegates. Return a list of QtItemDelegate ++QList QtItemDelegate::buildDelegates(QObject* parent, const NamedList& params, ++ const NamedList* common, const String& prefix) ++{ ++ QList list; ++ String pref = prefix; ++ for (int n = 0; true; n++) { ++ if (n) ++ pref << "." << n; ++ NamedString* ns = params.getParam(pref); ++ if (!ns) { ++ if (n) ++ break; ++ continue; ++ } ++ NamedList p(pref); ++ pref << "."; ++ p.copySubParams(params,pref); ++ if (common) { ++ NamedIterator iter(*common); ++ for (const NamedString* ns = 0; 0 != (ns = iter.get());) ++ p.addParam(pref + ns->name(),*ns); ++ } ++ QAbstractItemDelegate* dlg = build(parent,*ns,p); ++ if (dlg) ++ list.append(dlg); ++ } ++ return list; ++} ++ ++// Build a delegate ++QAbstractItemDelegate* QtItemDelegate::build(QObject* parent, const String& cls, ++ NamedList& params) ++{ ++ if (!cls || cls == YSTRING("QtItemDelegate")) ++ return new QtItemDelegate(parent,params); ++ if (cls == YSTRING("QtHtmlItemDelegate")) ++ return new QtHtmlItemDelegate(parent,params); ++ QObject* obj = (QObject*)UIFactory::build(cls,String::empty(),¶ms); ++ if (!obj) ++ return 0; ++ QAbstractItemDelegate* d = qobject_cast(obj); ++ if (d) ++ return d; ++ delete obj; ++ return 0; ++} ++ ++// Retrieve display text for a given index ++QString QtItemDelegate::getDisplayText(const QStyleOptionViewItem& opt, ++ const QModelIndex& index) const ++{ ++ QVariant var = index.data(m_roleDisplayText); ++ if (var.type() == QVariant::StringList) { ++ QStringList list = var.toStringList(); ++ if (!list.size()) ++ return QString(); ++ // 1 item or not selected: return the first string ++ if (list.size() == 1 || 0 == (opt.state & QStyle::State_Selected)) ++ return list[0]; ++ return list[1]; ++ } ++ if (var.canConvert(QVariant::String)) ++ return var.toString(); ++ return QString(); ++} ++ ++void QtItemDelegate::drawBackground(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QModelIndex& index) const ++{ ++ QVariant var; ++ if (m_roleBackground != Qt::UserRole) ++ var = index.data(m_roleBackground); ++ if (!var.isValid()) { ++ QItemDelegate::drawBackground(painter,opt,index); ++ return; ++ } ++ if (var.canConvert(QMetaType::QBrush)) { ++ QPointF oldBO = painter->brushOrigin(); ++ painter->setBrushOrigin(opt.rect.topLeft()); ++ painter->fillRect(opt.rect,qvariant_cast(var)); ++ painter->setBrushOrigin(oldBO); ++ } ++ else ++ Debug(DebugNote,"QtItemDelegate(%s) unhandled background variant type=%s", ++ c_str(),var.typeName()); ++} ++ ++void QtItemDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect, const QPixmap& pixmap) const ++{ ++ if (pixmap.isNull() || !rect.isValid()) ++ return; ++ QPoint p = QStyle::alignedRect(opt.direction,opt.decorationAlignment, ++ pixmap.size(),rect).topLeft(); ++ painter->drawPixmap(p,pixmap); ++} ++ ++void QtItemDelegate::drawFocus(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect) const ++{ ++ if (!m_drawFocus) ++ return; ++ QItemDelegate::drawFocus(painter,opt,rect); ++} ++ ++QWidget* QtItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, ++ const QModelIndex& index) const ++{ ++ if (m_editableCols.size() && !m_editableCols.contains(index.column())) ++ return 0; ++ return QItemDelegate::createEditor(parent,option,index); ++} ++ ++// Apply item margins ++void QtItemDelegate::applyMargins(QRect& dest, const QRect& src, bool inc) const ++{ ++ if (inc) { ++ dest.setLeft(dest.left() + src.left()); ++ dest.setTop(dest.top() + src.top()); ++ dest.setRight(dest.right() - src.right()); ++ dest.setBottom(dest.bottom() - src.bottom()); ++ } ++ else { ++ dest.setLeft(dest.left() - src.left()); ++ dest.setTop(dest.top() - src.top()); ++ dest.setRight(dest.right() + src.right()); ++ dest.setBottom(dest.bottom() + src.bottom()); ++ } ++} ++ ++ ++// ++// QtHtmlItemDelegate ++// ++void QtHtmlItemDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect, const QString& text) const ++{ ++ if (text.isEmpty()) ++ return; ++ QTextDocument doc; ++ doc.setHtml(text); ++ QAbstractTextDocumentLayout* layout = doc.documentLayout(); ++ if (!layout) ++ return; ++ QAbstractTextDocumentLayout::PaintContext context; ++ painter->save(); ++ painter->setClipRect(rect); ++ QSize sz(layout->documentSize().toSize()); ++ int y = rect.y(); ++ if (sz.height()) { ++ // Align vcenter and bottom (top is the default for document) ++ if (0 != (opt.displayAlignment & Qt::AlignVCenter)) ++ y += (rect.height() - sz.height()) / 2; ++ else if (0 != (opt.displayAlignment & Qt::AlignBottom)) ++ y += rect.height() - sz.height(); ++ } ++ painter->translate(rect.x(),y); ++ layout->draw(painter,context); ++ painter->restore(); ++} ++ ++ ++// ++// CustomTreeFactory ++// ++// Build objects ++void* CustomTreeFactory::create(const String& type, const char* name, NamedList* params) ++{ ++ if (!params) ++ return 0; ++ ++ QWidget* parentWidget = 0; ++ String* wndname = params->getParam("parentwindow"); ++ if (!TelEngine::null(wndname)) { ++ String* wName = params->getParam("parentwidget"); ++ QtWindow* wnd = static_cast(Client::self()->getWindow(*wndname)); ++ if (wnd && !TelEngine::null(wName)) ++ parentWidget = wnd->findChild(QtClient::setUtf8(*wName)); ++ } ++ if (type == "ContactList") ++ return new ContactList(name,*params,parentWidget); ++ if (type == "FileListTree") ++ return new FileListTree(name,*params,parentWidget); ++ if (type == "QtCustomTree") ++ return new QtCustomTree(name,*params,parentWidget); ++ return 0; ++} ++ ++}; // anonymous namespace ++ ++#include "customtree.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/customtree.h b/modules/qt5/customtree.h +new file mode 100644 +index 0000000..c4f6f46 +--- /dev/null ++++ b/modules/qt5/customtree.h +@@ -0,0 +1,2466 @@ ++/** ++ * customtree.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom QtTree based objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __CUSTOMTREE_H ++#define __CUSTOMTREE_H ++ ++#include "qt5client.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++class QtCellGridDraw; // Draw cell grid ++class QtTreeItemProps; // Tree widget container item properties ++class QtTreeDrag; // Drag data builder ++class QtTreeItem; // A tree widget item ++class QtCustomTree; // A custom tree widget ++class ContactList; // A contact list tree ++class ContactItem; // A contact list contact ++class ContactItemList; // Groups and contact items belonging to them ++class FileListTree; // Specialized tree showing directories and files ++class QtPaintItemDesc; // Generic item description (base class) ++class QtPaintButtonDesc; // Button description ++class QtPaintItem; // Custom painted item ++class QtPaintButton; // Custom painted button ++class QtPaintItems; // Holds items to paint ++class QtPaintImages; // Holds images to paint ++class QtItemDelegate; // Custom item delegate ++class QtHtmlItemDelegate; // Custom HTML item delegate ++ ++typedef QList QtTreeItemList; ++typedef QPair QtTreeItemKey; ++typedef QPair QtTokenDict; ++ ++ ++/** ++ * Utility used to draw a cell grid (borders) ++ * @short Draw cell grid (borders) ++ */ ++class QtCellGridDraw ++{ ++public: ++ /** ++ * Position and flags enumeration ++ */ ++ enum Position { ++ None = 0, ++ Left = 1, ++ Top = 2, ++ Right = 4, ++ Bottom = 8, ++ DrawStart = 16, ++ DrawEnd = 32, ++ // Masks ++ Pos = Left | Top | Right | Bottom, ++ }; ++ ++ /** ++ * Constructor ++ * @param flags Optional flags ++ */ ++ explicit inline QtCellGridDraw(int flags = 0) ++ : m_flags(flags) ++ {} ++ ++ /** ++ * Retrieve specific flags if set ++ * @param val Flags to retrieve ++ * @return Draw flags masked with given value ++ */ ++ inline int flag(int val) const ++ { return (m_flags & val); } ++ ++ /** ++ * Set draw pen ++ * @param pos Position to set pen (Left, Right, Top or Bottom) ++ * @param pen Pen to set ++ */ ++ void setPen(Position pos, QPen pen); ++ ++ /** ++ * Set draw pens from a list of parameters ++ * @param params Parameter list ++ */ ++ void setPen(const NamedList& params); ++ ++ /** ++ * Set pen from parameters list ++ * @param pos Position to set ++ * @param params Parameter list ++ */ ++ void setPen(Position pos, const NamedList& params); ++ ++ /** ++ * Draw the borders ++ * @param p The painter to use ++ * @param rect Cell rectangle ++ * @param isFirstRow True if drawing the first row ++ * @param isFirstColumn True if drawing the first column ++ * @param isLastRow True if drawing the last row ++ * @param isLastColumn True if drawing the last column ++ */ ++ void draw(QPainter* p, QRect& rect, bool isFirstRow, bool isFirstColumn, ++ bool isLastRow, bool isLastColumn) const; ++ ++protected: ++ int m_flags; ++ QPen m_left; ++ QPen m_top; ++ QPen m_right; ++ QPen m_bottom; ++}; ++ ++ ++/** ++ * This class holds data about a tree widget container item ++ * @short Tree widget container item properties ++ */ ++class QtTreeItemProps : public QtUIWidgetItemProps ++{ ++ YCLASS(QtTreeItemProps,QtUIWidgetItemProps) ++public: ++ /** ++ * Constructor ++ * @param type Item type ++ */ ++ explicit inline QtTreeItemProps(const String& type) ++ : QtUIWidgetItemProps(type), ++ m_height(-1), m_editable(false) ++ {} ++ ++ /** ++ * Set a button's action, create if it not found ++ * @param name Button name ++ * @param action Button action ++ * @return True on success, false if 'name' was found but is not a button ++ */ ++ bool setPaintButtonAction(const String& name, const String& action); ++ ++ /** ++ * Set a button's parameter, create it if not found ++ * @param name Button name ++ * @param param Parameter to set ++ * @param value Parameter value ++ * @return True on success, false if 'name' was found but is not a button ++ */ ++ bool setPaintButtonParam(const String& name, const String& param, ++ const String& value = String::empty()); ++ ++ int m_height; // Item height ++ String m_stateWidget; // Item widget or column showing the state ++ String m_stateExpandedImg; // Image to show when expanded ++ String m_stateCollapsedImg; // Image to show when collapsed ++ String m_toolTip; // Tooltip template ++ String m_statsWidget; // Item widget showing statistics while collapsed ++ String m_statsTemplate; // Statistics template (may include ${count} for children count) ++ QBrush m_bg; // Item background ++ QRect m_margins; // Item internal margins ++ bool m_editable; // Item is editable ++ ObjList m_paintItemsDesc; // Paint items description ++}; ++ ++ ++/** ++ * This class holds data used to build tree drag data ++ * @short Drag data builder ++ */ ++class QtTreeDrag : public QObject, public GenObject ++{ ++ YCLASS(QtTreeDrag,GenObject) ++ Q_CLASSINFO("QtTreeDrag","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param parent Object parent ++ * @param params Optional parameters ++ */ ++ QtTreeDrag(QObject* parent, const NamedList* params = 0); ++ ++ /** ++ * Set the URL builder, set to NULL if fmt is empty ++ * @param format Format to use when building base URL ++ * @param queryParams Query params to add to URL ++ */ ++ void setUrlBuilder(const String& fmt = String::empty(), ++ const String& queryParams = String::empty()); ++ ++ /** ++ * Build MIME data for a list of items ++ * @param item The list ++ * @return QMimeData pointer, 0 on failure ++ */ ++ QMimeData* mimeData(const QList items) const; ++ ++protected: ++ QtUrlBuilder* m_urlBuilder; ++}; ++ ++ ++/** ++ * This class holds a custom tree widget item ++ * @short A tree widget item ++ */ ++class QtTreeItem : public QTreeWidgetItem, public NamedList ++{ ++ YCLASS(QtTreeItem,NamedList) ++public: ++ /** ++ * Constructor ++ * @param id Item id ++ * @param type Item type ++ * @param text Optional text for item column 0 ++ * @param storeExp Set it to true to (re)store item expanded state ++ */ ++ QtTreeItem(const char* id, int type = Type, const char* text = 0, bool storeExp = false); ++ ++ /** ++ * Destructor ++ */ ++ ~QtTreeItem(); ++ ++ /** ++ * Set a column's text from a list of parameter cname ++ * @param col Column to set ++ * @param cname Column name ++ * @param list The list containing the parameter ++ */ ++ inline void setText(int col, const String& cname, const NamedList& list) { ++ String* s = cname ? list.getParam(cname) : 0; ++ if (s) ++ QTreeWidgetItem::setText(col,QtClient::setUtf8(*s)); ++ } ++ ++ /** ++ * Set a column's icon from a list of parameter cname_image ++ * @param col Column to set ++ * @param cname Column name ++ * @param list The list containing the parameter ++ * @param role Set image file path in this role if greater then Qt::UserRole ++ */ ++ void setImage(int col, const String& cname, const NamedList& list, ++ int role = Qt::UserRole); ++ ++ /** ++ * Set a column's check state from boolean value ++ * @param col Column to set ++ * @param check Check state ++ */ ++ inline void setCheckState(int col, bool check) ++ { QTreeWidgetItem::setCheckState(col,check ? Qt::Checked : Qt::Unchecked); } ++ ++ /** ++ * Set a column's check state from a list of parameter check:cname ++ * @param col Column to set ++ * @param cname Column name ++ * @param list The list containing the parameter ++ */ ++ inline void setCheckState(int col, const String& cname, const NamedList& list) { ++ String* s = cname ? list.getParam("check:" + cname) : 0; ++ if (s) ++ setCheckState(col,s->toBoolean()); ++ } ++ ++ /** ++ * Retrieve the item id ++ * @return Item id ++ */ ++ inline const String& id() const ++ { return toString(); } ++ ++ /** ++ * Check if the item is filtered (filter matched) ++ * @return True if the item is filtered ++ */ ++ inline bool filterMatched() const ++ { return m_filtered; } ++ ++ /** ++ * Update item filtered flag. Set it to true if the parameter list pointer is 0 ++ * @param filter Filter parameter list ++ * @return Filtered value ++ */ ++ bool setFilter(const NamedList* filter); ++ ++ /** ++ * Retrieve extra data to paint on right side of the item ++ * @return QtPaintItems pointer held by this item (may be 0) ++ */ ++ inline QtPaintItems* extraPaintRight() const ++ { return m_extraPaintRight; } ++ ++ /** ++ * Set extra data to paint on right side of the item ++ * @param obj Object to set ++ */ ++ void setExtraPaintRight(QtPaintItems* obj = 0); ++ ++ /** ++ * Set extra paint buttons on right side of the item ++ * @param list Buttons list ++ * @param props Item props containing the description ++ */ ++ void setExtraPaintRightButtons(const String& list, QtTreeItemProps* props); ++ ++ /** ++ * Save/restore item expanded status ++ */ ++ bool m_storeExp; ++ ++ /** ++ * Item height delta from global item size ++ */ ++ int m_heightDelta; ++ ++protected: ++ bool m_filtered; // Item filtered flag ++ QtPaintItems* m_extraPaintRight; // Extra items to paint on right side ++}; ++ ++ ++/** ++ * This class holds a custom tree widget ++ * @short QT based tree widget ++ */ ++class QtCustomTree : public QtTree ++{ ++ YCLASS(QtCustomTree,QtTree) ++ Q_CLASSINFO("QtCustomTree","Yate") ++ Q_OBJECT ++ Q_PROPERTY(QStringList _yate_save_props READ saveProps WRITE setSaveProps(QStringList)) ++ Q_PROPERTY(bool autoExpand READ autoExpand WRITE setAutoExpand(bool)) ++ Q_PROPERTY(int rowHeight READ rowHeight WRITE setRowHeight(int)) ++ Q_PROPERTY(bool _yate_horizontalheader READ getHHeader WRITE setHHeader(bool)) ++ Q_PROPERTY(bool _yate_notifyitemchanged READ getNotifyItemChanged WRITE setNotifyItemChanged(bool)) ++ Q_PROPERTY(QString _yate_itemui READ itemUi WRITE setItemUi(QString)) ++ Q_PROPERTY(QString _yate_itemstyle READ itemStyle WRITE setItemStyle(QString)) ++ Q_PROPERTY(QString _yate_itemselectedstyle READ itemSelectedStyle WRITE setItemSelectedStyle(QString)) ++ Q_PROPERTY(QString _yate_itemacceptdrop READ itemAcceptDrop WRITE setItemAcceptDrop(QString)) ++ Q_PROPERTY(QString _yate_itemacceptdroponempty READ itemAcceptDropOnEmpty WRITE setItemAcceptDropOnEmpty(QString)) ++ Q_PROPERTY(QString _yate_itemstatewidget READ itemStateWidget WRITE setItemStateWidget(QString)) ++ Q_PROPERTY(QString _yate_itemexpandedimage READ itemExpandedImage WRITE setExpandedImage(QString)) ++ Q_PROPERTY(QString _yate_itemcollapsedimage READ itemCollapsedImage WRITE setItemCollapsedImage(QString)) ++ Q_PROPERTY(QString _yate_itemtooltip READ itemTooltip WRITE setItemTooltip(QString)) ++ Q_PROPERTY(QString _yate_itemstatswidget READ itemStatsWidget WRITE setItemStatsWidget(QString)) ++ Q_PROPERTY(QString _yate_itemstatstemplate READ itemStatsTemplate WRITE setItemStatsTemplate(QString)) ++ Q_PROPERTY(QString _yate_itemheight READ itemHeight WRITE setItemHeight(QString)) ++ Q_PROPERTY(QString _yate_itembackground READ itemBg WRITE setItemBg(QString)) ++ Q_PROPERTY(QString _yate_itemmargins READ itemMargins WRITE setItemMargins(QString)) ++ Q_PROPERTY(QString _yate_itemeditable READ itemEditable WRITE setItemEditable(QString)) ++ Q_PROPERTY(QString _yate_itempaintbutton READ itemPaintButton WRITE setItemPaintButton(QString)) ++ Q_PROPERTY(QString _yate_itempaintbuttonparam READ itemPaintButtonParam WRITE setItemPaintButtonParam(QString)) ++ Q_PROPERTY(QString _yate_col_widths READ colWidths WRITE setColWidths(QString)) ++ Q_PROPERTY(QString _yate_sorting READ sorting WRITE setSorting(QString)) ++ Q_PROPERTY(QString _yate_itemsexpstatus READ itemsExpStatus WRITE setItemsExpStatus(QString)) ++public: ++ /** ++ * List item type enumeration ++ */ ++ enum ItemType { ++ TypeCount = QTreeWidgetItem::UserType ++ }; ++ ++ /** ++ * List item data role ++ */ ++ enum ItemDataRole { ++ RoleId = Qt::UserRole + 1, // Item id (used in headers) ++ RoleCheckable, // Column checkable (used in headers) ++ RoleHtmlDelegate, // Headers: true if a column has a custom html item delegate ++ // Rows: QStringList with data ++ RoleImage, // Role containing item image file name ++ RoleBackground, // Role containing item background color ++ RoleMargins, // Role containing item internal margins ++ RoleQtDrawItems, // Role containing extra display data (QObject descendent) ++ RoleCount ++ }; ++ ++ /** ++ * Constructor ++ * @param name Object name ++ * @param params Parameters ++ * @param parent Optional parent ++ * @param applyParams Apply parameters (call setParams()) ++ */ ++ QtCustomTree(const char* name, const NamedList& params, QWidget* parent = 0, ++ bool applyParams = true); ++ ++ /** ++ * Destructor ++ */ ++ virtual ~QtCustomTree(); ++ ++ /** ++ * Method re-implemented from QTreeWidget. ++ * Draw item grid if set ++ */ ++ virtual void drawRow(QPainter* p, const QStyleOptionViewItem& opt, ++ const QModelIndex& idx) const; ++ ++ /** ++ * Retrieve item type definition from [type:]value. Create it if not found ++ * @param in Input string ++ * @param value Item property value ++ * @return QtUIWidgetItemProps pointer or 0 ++ */ ++ virtual QtUIWidgetItemProps* getItemProps(QString& in, String& value); ++ ++ /** ++ * Set object parameters ++ * @param params Parameters list ++ * @return True on success ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Retrieve an item ++ * @param item Item id ++ * @param data Item parameters to fill ++ * @return True on success ++ */ ++ virtual bool getTableRow(const String& item, NamedList* data = 0); ++ ++ /** ++ * Update an existing item ++ * @param item Item id ++ * @param data Item parameters ++ * @return True on success ++ */ ++ virtual bool setTableRow(const String& item, const NamedList* data); ++ ++ /** ++ * Add a new entry (account or contact) to the tree ++ * @param item Item id ++ * @param data Item parameters ++ * @param asStart True if the entry is to be inserted at the start of ++ * the table, false if it is to be appended ++ * @return True if the entry has been added, false otherwise ++ */ ++ virtual bool addTableRow(const String& item, const NamedList* data = 0, ++ bool atStart = false); ++ ++ /** ++ * Remove an item from tree ++ * @param item Item id ++ * @return True on success ++ */ ++ virtual bool delTableRow(const String& item); ++ ++ /** ++ * Add, set or remove one or more items. ++ * Screen update is locked while changing the tree. ++ * Each data list element is a NamedPointer carrying a NamedList with item parameters. ++ * The name of an element is the item to update. ++ * Set element's value to boolean value 'true' to add a new item if not found ++ * or 'false' to set an existing one. Set it to empty string to delete the item ++ * @param data The list of items to add/set/delete ++ * @param atStart True to add new items at start, false to add them to the end ++ * @return True on success ++ */ ++ virtual bool updateTableRows(const NamedList* data, bool atStart = false); ++ ++ /** ++ * Set the widget's selection ++ * @param item String containing the new selection ++ * @return True if the operation was successfull ++ */ ++ virtual bool setSelect(const String& item); ++ ++ /** ++ * Retrieve the current selection ++ * @param item String to fill with selected item id ++ * @return True on success ++ */ ++ virtual bool getSelect(String& item); ++ ++ /** ++ * Retrieve multiple selection ++ * @param items List to be to filled with selection's contents ++ * @return True if the operation was successfull ++ */ ++ virtual bool getSelect(NamedList& items); ++ ++ /** ++ * Remove all items from tree ++ * @return True ++ */ ++ virtual bool clearTable(); ++ ++ /** ++ * Retrieve all items' id ++ * @param items List to fill with widget's items ++ * @return True ++ */ ++ virtual bool getOptions(NamedList& items); ++ ++ /** ++ * Retrieve a QObject list containing tree item widgets ++ * @return The list of container item widgets ++ */ ++ virtual QList getContainerItems(); ++ ++ /** ++ * Retrieve model index for a given item ++ * @param item Item to edit ++ * @param what Optional sub-item ++ * @return Model index for the item, can be invalid ++ */ ++ virtual QModelIndex modelIndex(const String& item, const String* what = 0); ++ ++ /** ++ * Update a tree item ++ * @param item Item to update ++ * @param params Item parameters ++ * @return True on success ++ */ ++ virtual bool updateItem(QtTreeItem& item, const NamedList& params); ++ ++ /** ++ * Find a tree item ++ * @param id Item id ++ * @param start Optional start item. Set it to 0 to start with root item ++ * @param includeStart Include start item in id check. ++ * Set it to false to check start children only ++ * @param recursive True to make a recursive search, ++ * false to check only start first level children ++ * @return QTreeItem pointer or 0 ++ */ ++ virtual QtTreeItem* find(const String& id, QtTreeItem* start = 0, ++ bool includeStart = true, bool recursive = true); ++ ++ /** ++ * Find all tree items ++ * @param recursive True to make a recursive search, false to add only direct children ++ * @param parent Optional parent item. Set it to 0 to use the root item ++ * @return The list of items ++ */ ++ QList findItems(bool recursive = true, QtTreeItem* parent = 0); ++ ++ /** ++ * Find all tree items having a given id ++ * @param id Item id ++ * @param start Optional start item. Set it to 0 to start with root item ++ * @param includeStart Include start item in id check. ++ * Set it to false to check start children only ++ * @param recursive True to make a recursive search, ++ * false to check only start first level children ++ * @return The list of items ++ */ ++ QList findItems(const String& id, QtTreeItem* start = 0, ++ bool includeStart = true, bool recursive = true); ++ ++ /** ++ * Find all tree items having a given type ++ * @param id Item type ++ * @param start Optional start item. Set it to 0 to start with root item ++ * @param includeStart Include start item in id check. ++ * Set it to false to check start children only ++ * @param recursive True to make a recursive search, ++ * false to check only start first level children ++ * @return The list of items ++ */ ++ QList findItems(int type, QtTreeItem* start = 0, ++ bool includeStart = true, bool recursive = true); ++ ++ /** ++ * Find all tree items from model ++ * @param list Model index list ++ * @return The list of items ++ */ ++ QList findItems(QModelIndexList list); ++ ++ /** ++ * Find all tree items ++ * @param list List to fill ++ * @param start Optional start item. Set it to 0 to start with root item ++ * @param includeStart Include start item in id check. ++ * Set it to false to check start children only ++ * @param recursive True to make a recursive search, ++ * false to check only start first level children ++ */ ++ void findItems(NamedList& list, QtTreeItem* start = 0, ++ bool includeStart = true, bool recursive = true); ++ ++ /** ++ * Add a child to a given item ++ * @param child Child to add ++ * @param pos Position to insert. Negative to add after the last child ++ * @param parent The parent item. Set it to 0 to add to the root ++ * @return QtTreeItem pointer on failure, 0 on success ++ */ ++ QtTreeItem* addChild(QtTreeItem* child, int pos = -1, QtTreeItem* parent = 0); ++ ++ /** ++ * Add a child to a given item ++ * @param child Child to add ++ * @param atStart True to insert at start, false to add aftr the last child ++ * @param parent The parent item. Set it to 0 to add to the root ++ * @return QtTreeItem pointer on failure, 0 on success ++ */ ++ inline QtTreeItem* addChild(QtTreeItem* child, bool atStart, QtTreeItem* parent = 0) ++ { return addChild(child,atStart ? 0 : -1,parent); } ++ ++ /** ++ * Add a list of children to a given item ++ * @param list Children to add ++ * @param pos Position to insert. Negative to add after the last child ++ * @param parent The parent item. Set it to 0 to add to the root ++ */ ++ void addChildren(QList list, int pos = -1, QtTreeItem* parent = 0); ++ ++ /** ++ * Setup an item. Load its widget if not found ++ * @param item Item to setup ++ */ ++ void setupItem(QtTreeItem* item); ++ ++ /** ++ * Retrieve and item's row height by type ++ * @param item Item to set ++ * @return Item row height ++ */ ++ inline int getItemRowHeight(int type) const { ++ QtTreeItemProps* p = treeItemProps(type); ++ return (p && p->m_height > 0) ? p->m_height : m_rowHeight; ++ } ++ ++ /** ++ * Set and item's row height hint ++ * @param item Item to set ++ */ ++ void setItemRowHeight(QTreeWidgetItem* item); ++ ++ /** ++ * Retrieve item properties associated with a given type ++ * @param type Item type ++ * @return QtTreeItemProps poinetr or 0 if not found ++ */ ++ inline QtTreeItemProps* treeItemProps(int type) const { ++ QtUIWidgetItemProps* pt = QtUIWidget::getItemProps(itemPropsName(type)); ++ return YOBJECT(QtTreeItemProps,pt); ++ } ++ ++ /** ++ * Retrieve item properties associated with a given item ++ * @param item Item address ++ * @return QtTreeItemProps poinetr or 0 if not found ++ */ ++ inline QtTreeItemProps* treeItemProps(QtTreeItem& item) const ++ { return treeItemProps(item.type()); } ++ ++ /** ++ * Retrieve item properties associated with a given item ++ * @param item Item pointer ++ * @return QtTreeItemProps poinetr or 0 if not found ++ */ ++ inline QtTreeItemProps* treeItemProps(QtTreeItem* item) const ++ { return item ? treeItemProps(item->type()) : 0; } ++ ++ /** ++ * Retrieve string data associated with a column ++ * @param buf Destination string ++ * @param item The tree item whose data to retreive ++ * @param column Column to retrieve ++ * @param role Data role to retrieve, defaults to id ++ */ ++ inline void getItemData(String& buf, QTreeWidgetItem& item, int column, ++ int role = RoleId) ++ { QtClient::getUtf8(buf,item.data(column,role).toString()); } ++ ++ /** ++ * Retrieve boolean data associated with a column ++ * @param column Column to retrieve ++ * @param role Data role to retrieve ++ * @param item Optional item, use tree header item if 0 ++ * @return The boolean value for the given column and role ++ */ ++ inline bool getBoolItemData(int column, int role, QTreeWidgetItem* item = 0) { ++ if (!item) ++ item = headerItem(); ++ return item && item->data(column,role).toBool(); ++ } ++ ++ /** ++ * Retrieve a list with column IDs ++ * @return QStringList containing column IDs ++ */ ++ QStringList columnIDs(); ++ ++ /** ++ * Retrieve a column id by column number ++ * @param buf Destination buffer ++ * @param col column number ++ * @return True if found ++ */ ++ bool getColumnName(String& buf, int col); ++ ++ /** ++ * Retrieve a column by it's id ++ * @param id The column id to find ++ * @return Column number, -1 if not found ++ */ ++ int getColumn(const String& id); ++ ++ /** ++ * Convert a value to int, retrieve a column index ++ * @param str Column number or name ++ * @return Column number, -1 if not found ++ */ ++ inline int getColumnNo(const String& str) { ++ int val = str.toInteger(-1); ++ return val >= 0 ? val : getColumn(str); ++ } ++ ++ /** ++ * Show or hide an item ++ * @param item The item ++ * @param show True to show, false to hide ++ */ ++ inline void showItem(QtTreeItem& item, bool show) { ++ if (item.isHidden() != show) ++ return; ++ item.setHidden(!show); ++ itemVisibleChanged(item); ++ } ++ ++ /** ++ * Show or hide empty children. ++ * An empty item is an item without children or with all children hidden ++ * @param show True to show, false to hide ++ * @param parent The parent item. Set it to 0 to add to the root ++ */ ++ void showEmptyChildren(bool show, QtTreeItem* parent = 0); ++ ++ /** ++ * Set the expanded/collapsed image of an item ++ * @param item The item to set ++ * @param props Optional pointer to item props, detect it if 0 ++ */ ++ void setStateImage(QtTreeItem& item, QtTreeItemProps* props = 0); ++ ++ /** ++ * Retrieve the auto expand property ++ * @return The value of the auto expand property ++ */ ++ bool autoExpand() ++ { return m_autoExpand; } ++ ++ /** ++ * Set the auto expand property ++ * @param autoExpand The new value of the auto expand property ++ */ ++ void setAutoExpand(bool autoExpand) ++ { m_autoExpand = autoExpand; } ++ ++ /** ++ * Retrieve the row height ++ * @return The row height ++ */ ++ int rowHeight() ++ { return m_rowHeight; } ++ ++ /** ++ * Set the row height ++ * @param h The new value of the row height ++ */ ++ void setRowHeight(int h) ++ { m_rowHeight = h; } ++ ++ /** ++ * Check if the horizontal header is visible ++ * @return True if the horizontal is visible ++ */ ++ bool getHHeader() { ++ QTreeWidgetItem* h = headerItem(); ++ return h && !h->isHidden(); ++ } ++ ++ /** ++ * Show/hide the horizontal header ++ * @param on True to show the horizontal header, false to hide it ++ */ ++ void setHHeader(bool on) { ++ QTreeWidgetItem* h = headerItem(); ++ if (h) ++ h->setHidden(!on); ++ } ++ ++ /** ++ * Check if this table is notifying item changed ++ * @return True if this table is notifying item changed ++ */ ++ bool getNotifyItemChanged() ++ { return m_notifyItemChanged; } ++ ++ /** ++ * Set/reset item changed notification flag ++ * @param on True to notify item changes, false to disable the notification ++ */ ++ void setNotifyItemChanged(bool on) ++ { m_notifyItemChanged = on; } ++ ++ /** ++ * Read _yate_itemui property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemUi() ++ { return QString(); } ++ ++ /** ++ * Set an item props ui ++ * @param value Item props ui. Format [type:]ui_name ++ */ ++ void setItemUi(QString value); ++ ++ /** ++ * Read _yate_itemstyle property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemStyle() ++ { return QString(); } ++ ++ /** ++ * Set an item props style sheet ++ * @param value Item props style sheet. Format [type:]stylesheet ++ */ ++ void setItemStyle(QString value); ++ ++ /** ++ * Read _yate_itemselectedstyle property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemSelectedStyle() ++ { return QString(); } ++ ++ /** ++ * Set an item props selected style sheet ++ * @param value Item props selected style sheet. Format [type:]stylesheet ++ */ ++ void setItemSelectedStyle(QString value); ++ ++ /** ++ * Read _yate_itemacceptdrop property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemAcceptDrop() ++ { return QString(); } ++ ++ /** ++ * Set an item props accept drop ++ * @param value Item props accept drop. Format [type:]acceptdrop ++ */ ++ void setItemAcceptDrop(QString value); ++ ++ /** ++ * Read _yate_itemacceptdroponempty property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemAcceptDropOnEmpty() ++ { return QString(); } ++ ++ /** ++ * Set accept drop on empty space ++ * @param value Accept drop on empty space ++ */ ++ void setItemAcceptDropOnEmpty(QString value) { ++ String tmp; ++ QtClient::getUtf8(tmp,value); ++ m_acceptDropOnEmpty = QtDrop::acceptDropType(tmp,QtDrop::None); ++ } ++ ++ /** ++ * Read _yate_itemstatewidget property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemStateWidget() ++ { return QString(); } ++ ++ /** ++ * Set an item props state widget name ++ * @param value Item props state widget name. Format [type:]widgetname ++ */ ++ void setItemStateWidget(QString value); ++ ++ /** ++ * Read _yate_itemexpandedimage property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemExpandedImage() ++ { return QString(); } ++ ++ /** ++ * Set an item's expanded image ++ * @param value Item props expanded image. Format [type:]imagefile ++ */ ++ void setExpandedImage(QString value); ++ ++ /** ++ * Read _yate_itemcollapsedimage property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemCollapsedImage() ++ { return QString(); } ++ ++ /** ++ * Set an item's collapsed image ++ * @param value Item props collapsed image. Format [type:]imagefile ++ */ ++ void setItemCollapsedImage(QString value); ++ ++ /** ++ * Read _yate_itemtooltip property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemTooltip() ++ { return QString(); } ++ ++ /** ++ * Set an item's tooltip template ++ * @param value Item props tooltip template. Format [type:]imagefile ++ */ ++ void setItemTooltip(QString value); ++ ++ /** ++ * Read _yate_itemstatswidget property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemStatsWidget() ++ { return QString(); } ++ ++ /** ++ * Set an item's statistics widget name ++ * @param value Item props statistics widget name. Format [type:]widget_name ++ */ ++ void setItemStatsWidget(QString value); ++ ++ /** ++ * Read _yate_itemstatstemplate property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemStatsTemplate() ++ { return QString(); } ++ ++ /** ++ * Set an item's statistics template ++ * @param value Item props statistics template. Format [type:]template ++ */ ++ void setItemStatsTemplate(QString value); ++ ++ /** ++ * Read _yate_itemheight property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemHeight() ++ { return QString(); } ++ ++ /** ++ * Set an item props height ++ * @param value Item props height. Format [type:]height ++ */ ++ void setItemHeight(QString value); ++ ++ /** ++ * Read _yate_itembackground property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemBg() ++ { return QString(); } ++ ++ /** ++ * Set an item props background ++ * @param value Item props background. Format [type:]background ++ */ ++ void setItemBg(QString value); ++ ++ /** ++ * Read _yate_itemmargins property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemMargins() ++ { return QString(); } ++ ++ /** ++ * Set an item props margins ++ * @param value Item props margins. Format [type:]margins where ++ * margins is a comma separated list of item internal margins left,top,right,bottom ++ */ ++ void setItemMargins(QString value); ++ ++ /** ++ * Read _yate_itemeditable property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemEditable() ++ { return QString(); } ++ ++ /** ++ * Set an item props editable ++ * @param value Item props margins. Format [type:]editable ++ */ ++ void setItemEditable(QString value); ++ ++ /** ++ * Read _yate_itempaintbutton property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemPaintButton() ++ { return QString(); } ++ ++ /** ++ * Set an item's paint button and action ++ * @param value Item paint button action. Format [type:][button_name:]action_name ++ */ ++ void setItemPaintButton(QString value); ++ ++ /** ++ * Read _yate_itempaintbuttonparam property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemPaintButtonParam() ++ { return QString(); } ++ ++ /** ++ * Set an item's paint button parameter ++ * @param value Item paint button parameter. Format [type:]button_name:param_name[:param_value] ++ */ ++ void setItemPaintButtonParam(QString value); ++ ++ /** ++ * Retrieve a comma separated list with column widths ++ * @return Comma separated list containing column widths ++ */ ++ QString colWidths(); ++ ++ /** ++ * Set column widths ++ * @param witdhs Comma separated list containing column widths ++ */ ++ void setColWidths(QString widths); ++ ++ /** ++ * Retrieve tree sorting string (column and order) ++ * @return Sorting string ++ */ ++ QString sorting() ++ { return getSorting(); } ++ ++ /** ++ * Set sorting (column and order) ++ * @param s Sorting string ++ */ ++ void setSorting(QString s); ++ ++ /** ++ * Retrieve items expanded status value ++ * @return Items expanded status value ++ */ ++ QString itemsExpStatus(); ++ ++ /** ++ * Set items expanded status value ++ * param s Items expanded status value ++ */ ++ void setItemsExpStatus(QString s); ++ ++ /** ++ * Add items as list parameter ++ * @param Parameter list ++ * *param items Items list ++ */ ++ static void addItems(NamedList& dest, QList items); ++ ++protected slots: ++ /** ++ * Handle item children actions ++ */ ++ void itemChildAction() ++ { onAction(sender()); } ++ ++ /** ++ * Handle item children toggles ++ */ ++ void itemChildToggle(bool on) ++ { onToggle(sender(),on); } ++ ++ /** ++ * Handle item children select ++ */ ++ void itemChildSelect() ++ { onSelect(sender()); } ++ ++ /** ++ * Catch item double click ++ * @param item The item ++ * @param column Clicked column ++ */ ++ void itemDoubleClickedSlot(QTreeWidgetItem* item, int column) ++ { onItemDoubleClicked(static_cast(item),column); } ++ ++ /** ++ * Catch item expanded signal ++ * @param item The item ++ */ ++ void itemExpandedSlot(QTreeWidgetItem* item) ++ { onItemExpandedChanged(static_cast(item)); } ++ ++ /** ++ * Catch item collapsed signal ++ * @param item The item ++ */ ++ void itemCollapsedSlot(QTreeWidgetItem* item) ++ { onItemExpandedChanged(static_cast(item)); } ++ ++ /** ++ * Catch item changed signal ++ */ ++ void itemChangedSlot(QTreeWidgetItem* item, int column) ++ { onItemChanged(static_cast(item),column); } ++ ++ /** ++ * Catch item selection changed signal ++ */ ++ void itemSelChangedSlot(); ++ ++protected: ++ /** ++ * Re-implemented from QTreeWidget ++ */ ++ virtual void timerEvent(QTimerEvent* e); ++ ++ /** ++ * Re-implemented from QTreeWidget ++ */ ++ virtual void drawBranches(QPainter* painter, const QRect& rect, ++ const QModelIndex& index) const; ++ ++ /** ++ * Re-implemented from QTreeWidget ++ */ ++ virtual QMimeData* mimeData(const QList items) const; ++ ++ /** ++ * Re-implemented from QAbstractItemView ++ */ ++ virtual void selectionChanged(const QItemSelection& selected, ++ const QItemSelection& deselected); ++ ++ /** ++ * Re-implemented from QAbstractItemView ++ */ ++ virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void dragEnterEvent(QDragEnterEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void dropEvent(QDropEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void dragMoveEvent(QDragMoveEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void dragLeaveEvent(QDragLeaveEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void mouseMoveEvent(QMouseEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void mousePressEvent(QMouseEvent* e); ++ ++ /** ++ * Re-implemented from QWidget ++ */ ++ virtual void mouseReleaseEvent(QMouseEvent* e); ++ ++ /** ++ * Re-implemented from QTreeView ++ */ ++ virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); ++ ++ /** ++ * Retrieve the item props name associated with tree item type ++ * @param type Item type ++ * @return Item props name or empty if not found ++ */ ++ inline const String& itemPropsName(int type) const ++ { return NamedInt::lookupName(m_itemPropsType,type); } ++ ++ /** ++ * Retrieve the item type integer value from associated string (name) ++ * @param name Item type name ++ * @return Associated item type integer value. QTreeWidgetItem::Type if not found ++ */ ++ inline int itemType(const String& name) const ++ { return NamedInt::lookup(m_itemPropsType,name,QTreeWidgetItem::Type); } ++ ++ /** ++ * Add item prop to name translation ++ * @param type Item type ++ * @param name Type name ++ */ ++ inline void addItemType(int type, const char* name) ++ { NamedInt::addToListUniqueName(m_itemPropsType,new NamedInt(name,type)); } ++ ++ /** ++ * Retrieve tree sorting ++ * @return Sorting string ++ */ ++ virtual QString getSorting(); ++ ++ /** ++ * Set tree sorting ++ * @param key Sorting key ++ * @param sort Sort order ++ */ ++ virtual void updateSorting(const String& key, Qt::SortOrder sort); ++ ++ /** ++ * Build a tree context menu ++ * @param menu Menu to replace on success ++ * @param ns Pointer to received parameter ++ * @return True on success ++ */ ++ bool buildMenu(QMenu*& menu, NamedString* ns); ++ ++ /** ++ * Apply item widget style sheet ++ * @param item Target item ++ * @param selected True to apply selected item style ++ */ ++ void applyStyleSheet(QtTreeItem* item, bool selected); ++ ++ /** ++ * Process item double click ++ * @param item The item ++ * @param column Clicked column ++ */ ++ virtual void onItemDoubleClicked(QtTreeItem* item, int column); ++ ++ /** ++ * Item expanded/collapsed notification ++ * @param item The item ++ */ ++ virtual void onItemExpandedChanged(QtTreeItem* item); ++ ++ /** ++ * Process item changed signal ++ */ ++ virtual void onItemChanged(QtTreeItem* item, int column); ++ ++ /** ++ * Catch a context menu event and show the context menu ++ * @param e Context menu event ++ */ ++ virtual void contextMenuEvent(QContextMenuEvent* e); ++ ++ /** ++ * Get the context menu associated with a given item ++ * @param item The item (can be 0) ++ * @return QMenu pointer or 0 ++ */ ++ virtual QMenu* contextMenu(QtTreeItem* item); ++ ++ /** ++ * Item added notification ++ * @param item Added item ++ * @param parent The parent of the added tree item. 0 if added to the root ++ */ ++ virtual void itemAdded(QtTreeItem& item, QtTreeItem* parent); ++ ++ /** ++ * Handle item visiblity changes ++ * @param item Changed item ++ */ ++ virtual void itemVisibleChanged(QtTreeItem& item); ++ ++ /** ++ * Check item filter ++ * @param item Optional item. Check root if 0 ++ * @param recursive True to check recursive (check children's children also) ++ */ ++ void checkItemFilter(QtTreeItem* item = 0, bool recursive = true); ++ ++ /** ++ * Handle item filter changes ++ * @param item The item ++ */ ++ virtual void itemFilterChanged(QtTreeItem& item); ++ ++ /** ++ * Uncheck all checkable columns in a given item ++ * @param item The item ++ */ ++ virtual void uncheckItem(QtTreeItem& item); ++ ++ /** ++ * Remove an item ++ * @param item Item to remove ++ * @param setSelTimer Optional boolean to be set if select trigger timer should be started, ++ * set it to 0 to let this method handle the timer ++ */ ++ virtual void removeItem(QtTreeItem* item, bool* setSelTimer = 0); ++ ++ /** ++ * Remove a list of items ++ * @param items Items to remove ++ */ ++ virtual void removeItems(QList items); ++ ++ /** ++ * Update a tree item's tooltip ++ * @param item Item to update ++ * @param props Optional pointer to item props, detect it if 0 ++ */ ++ virtual void applyItemTooltip(QtTreeItem& item, QtTreeItemProps* props = 0); ++ ++ /** ++ * Fill a list with item statistics. ++ * The default implementation fills a 'count' parameter with the number of item children ++ * @param item The tree item ++ * @param list The list to fill ++ */ ++ virtual void fillItemStatistics(QtTreeItem& item, NamedList& list); ++ ++ /** ++ * Update a tree item's statistics ++ * @param item Item to update ++ * @param props Optional pointer to item props, detect it if 0 ++ */ ++ void applyItemStatistics(QtTreeItem& item, QtTreeItemProps* props = 0); ++ ++ /** ++ * Update a tree item's margins ++ * @param item Item to update ++ * @param set True to set from item props, false to set an empty rect ++ * @param props Optional pointer to item props, detect it if 0 ++ */ ++ virtual void applyItemMargins(QtTreeItem& item, bool set = true, ++ QtTreeItemProps* props = 0); ++ ++ /** ++ * Store (update) to or remove from item expanded status storage an item ++ * @param id Item id ++ * @param on Expanded status ++ * @param store True to store, false to remove ++ */ ++ void setStoreExpStatus(const String& id, bool on, bool store = true); ++ ++ /** ++ * Retrieve the expanded status of an item from storage ++ * @param id Item id ++ * @return 1 if expanded, 0 if collapsed, -1 if not found ++ */ ++ int getStoreExpStatus(const String& id); ++ ++ /** ++ * Handle drop events ++ * @param e The event ++ * @return True if accepted ++ */ ++ bool handleDropEvent(QDropEvent* e); ++ ++ /** ++ * Check if an item has any selected child ++ * @param item The item to check ++ * @return True if it has at least 1 selected child ++ */ ++ bool hasSelectedChild(QtTreeItem& item); ++ ++ /** ++ * Check if select trigger timer should be started ++ * @param item The item to check ++ * @return True if select trigger timer should be started ++ */ ++ inline bool shouldSetSelTimer(QtTreeItem& item) ++ { return !item.isSelected() && hasSelectedChild(item); } ++ ++ /** ++ * Stop select trigger timer ++ */ ++ inline void stopSelectTriggerTimer() { ++ if (!m_timerTriggerSelect) ++ return; ++ killTimer(m_timerTriggerSelect); ++ m_timerTriggerSelect = 0; ++ } ++ ++ /** ++ * Start select trigger timer ++ */ ++ inline void startSelectTriggerTimer() { ++ stopSelectTriggerTimer(); ++ m_timerTriggerSelect = startTimer(500); ++ } ++ ++ bool m_notifyItemChanged; // Notify 'listitemchanged' action ++ bool m_hasCheckableCols; // True if we have checkable columns ++ QMenu* m_menu; // Tree context menu ++ bool m_autoExpand; // Items are expanded when added ++ int m_rowHeight; // Tree row height ++ ObjList m_itemPropsType; // Tree item type to item props translation ++ QList m_expStatus; // List of stored item IDs and expanded status ++ QtCellGridDraw m_gridDraw; ++ int m_changing; // Content is changing from client (not from user): ++ // avoid notifications ++ NamedList* m_filter; // Item filter ++ bool m_haveWidgets; // True if we loaded any widget ++ bool m_haveDrawQtItems; // True if we have any custom drawn data in items ++ int m_setCurrentColumn; // Column to set when current index changed ++ QtListDrop* m_drop; // Drop handler ++ int m_acceptDropOnEmpty; // Accept drop on widget surface not occupied by any item ++ QtTreeDrag* m_drag; // Drag data builder ++ bool m_drawBranches; // Allow parent to draw branches ++ int m_timerTriggerSelect; // Trigger select timer id ++ QtTreeItem* m_lastItemDrawHover; // Last item we used to update custom drawn hover ++}; ++ ++ ++/** ++ * This class holds a contact list tree ++ * @short A contact list tree ++ */ ++class ContactList : public QtCustomTree ++{ ++ YCLASS(ContactList,QtCustomTree) ++ Q_CLASSINFO("ContactList","Yate") ++ Q_OBJECT ++ Q_PROPERTY(QString _yate_nogroup_caption READ noGroupCaption WRITE setNoGroupCaption(QString)) ++ Q_PROPERTY(bool _yate_flatlist READ flatList WRITE setFlatList(bool)) ++ Q_PROPERTY(bool _yate_showofflinecontacts READ showOffline WRITE setShowOffline(bool)) ++ Q_PROPERTY(bool _yate_hideemptygroups READ hideEmptyGroups WRITE setHideEmptyGroups(bool)) ++ Q_PROPERTY(bool _yate_comparecase READ cmpNameCs WRITE setCmpNameCs(bool)) ++public: ++ /** ++ * List item type enumeration ++ */ ++ enum ItemType { ++ TypeContact = QtCustomTree::TypeCount, ++ TypeChatRoom = QtCustomTree::TypeCount + 1, ++ TypeGroup, ++ }; ++ ++ /** ++ * Constructor ++ * @param name The name of the object ++ * @param params List parameters ++ * @param parent List parent ++ */ ++ ContactList(const char* name, const NamedList& params, QWidget* parent); ++ ++ /** ++ * Set list parameters ++ * @param params Parameter list ++ * @return True on success ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Update an existing item ++ * @param item Item id ++ * @param data Item parameters ++ * @return True on success ++ */ ++ virtual bool setTableRow(const String& item, const NamedList* data); ++ ++ /** ++ * Add a new entry (account or contact) to the tree ++ * @param item Item id ++ * @param data Item parameters ++ * @param asStart True if the entry is to be inserted at the start of ++ * the table, false if it is to be appended ++ * @return True if the entry has been added, false otherwise ++ */ ++ virtual bool addTableRow(const String& item, const NamedList* data = 0, ++ bool atStart = false); ++ ++ /** ++ * Remove an item from tree ++ * @param item Item id ++ * @return True on success ++ */ ++ virtual bool delTableRow(const String& item); ++ ++ /** ++ * Add, set or remove one or more contacts. ++ * Screen update is locked while changing the tree. ++ * Each data list element is a NamedPointer carrying a NamedList with item parameters. ++ * The name of an element is the item to update. ++ * Set element's value to boolean value 'true' to add a new item if not found ++ * or 'false' to set an existing one. Set it to empty string to delete the item ++ * @param data The list of items to add/set/delete ++ * @param atStart True to add new items at start, false to add them to the end ++ * @return True on success ++ */ ++ virtual bool updateTableRows(const NamedList* data, bool atStart = false); ++ ++ /** ++ * Count online/total contacts in a group. ++ * @param grp The group item ++ * @param total The number of contacts in the group ++ * @param online The number of online contacts in the group ++ */ ++ virtual void countContacts(QtTreeItem* grp, int& total, int& online); ++ ++ /** ++ * Contact list changed notification ++ * This method is called each time a contact is added, removed or changed or ++ * properties affecting display are changed ++ */ ++ virtual void listChanged(); ++ ++ /** ++ * Update a tree item ++ * @param item Item to update ++ * @param params Item parameters ++ * @return True on success ++ */ ++ virtual bool updateItem(QtTreeItem& item, const NamedList& params); ++ ++ /** ++ * Find a contact ++ * @param id Contact id ++ * @param list Optional list to be filled with items having the given id ++ * @return ContactItem pointer or 0 if not found ++ */ ++ ContactItem* findContact(const String& id, QList* list = 0); ++ ++ /** ++ * Retrieve the value of '_yate_nogroup_caption' property ++ * @return The value of '_yate_nogroup_caption' property ++ */ ++ QString noGroupCaption() ++ { return QtClient::setUtf8(m_noGroupText); } ++ ++ /** ++ * Set '_yate_nogroup_caption' property ++ * @param value The new value for '_yate_nogroup_caption' property ++ */ ++ void setNoGroupCaption(QString value); ++ ++ /** ++ * Check if the list is flat ++ * @return True if contacts are not grouped ++ */ ++ bool flatList() ++ { return m_flatList; } ++ ++ /** ++ * Set the flat list property ++ * @param flat The new value of the flat list property ++ */ ++ void setFlatList(bool flat); ++ ++ /** ++ * Check if offline contacts are shown ++ * @return True if the list displaying offline contacts ++ */ ++ bool showOffline() ++ { return m_showOffline; } ++ ++ /** ++ * Show or hide offline contacts ++ * @param value True to show, false to hide offline contacts ++ */ ++ void setShowOffline(bool value); ++ ++ /** ++ * Check if empty groups are hidden ++ * @return True if empty groups are hidden ++ */ ++ bool hideEmptyGroups() ++ { return m_hideEmptyGroups; } ++ ++ /** ++ * Show or hide empty groups ++ * @param value True to hide, false to show empty groups ++ */ ++ void setHideEmptyGroups(bool value) { ++ if (m_hideEmptyGroups == value) ++ return; ++ m_hideEmptyGroups = value; ++ if (!m_flatList) ++ showEmptyChildren(!m_hideEmptyGroups); ++ } ++ ++ /** ++ * Retrieve contact name comparison ++ * @return True if contact names are compared case sensitive ++ */ ++ bool cmpNameCs() ++ { return m_compareNameCs == Qt::CaseSensitive; } ++ ++ /** ++ * Set contact name comparison ++ * @return True to compare contact names case sensitive ++ */ ++ void setCmpNameCs(bool value) ++ { m_compareNameCs = (value ? Qt::CaseSensitive : Qt::CaseInsensitive); } ++ ++ /** ++ * Check if a given type is a contact or chat room ++ * @param type Type to check ++ * @return True if the type is contact or chat room ++ */ ++ static inline bool isContactType(int type) ++ { return type == TypeContact || type == TypeChatRoom; } ++ ++ /** ++ * Get contact type from a string value ++ * @param val The string ++ * @return Contact type value ++ */ ++ static inline int contactType(const String& val) { ++ if (!val || val != "chatroom") ++ return TypeContact; ++ return TypeChatRoom; ++ } ++ ++ /** ++ * Create a group item ++ * @param id Group id ++ * @param name Group name ++ * @param expStat Expanded state (re)store indicator ++ * @return Valid QtTreeItem pointer ++ */ ++ static inline QtTreeItem* createGroup(const String& id, const String& name, bool expStat) { ++ QtTreeItem* g = new QtTreeItem(id,TypeGroup,name,expStat); ++ g->addParam("name",name); ++ return g; ++ } ++ ++protected: ++ /** ++ * Retrieve tree sorting ++ * @return Sorting string ++ */ ++ virtual QString getSorting(); ++ ++ /** ++ * Set tree sorting ++ * @param key Sorting key ++ * @param sort Sort order ++ */ ++ virtual void updateSorting(const String& key, Qt::SortOrder sort); ++ ++ /** ++ * Optimized add. Set the whole tree ++ * @param list The list of contacts to set ++ */ ++ void setContacts(QList& list); ++ ++ /** ++ * Create a contact ++ * @param id Contact id ++ * @param params Contact parameters ++ * @return ContactItem pointer ++ */ ++ ContactItem* createContact(const String& id, const NamedList& params); ++ ++ // Update contact count in a group ++ void updateGroupCountContacts(QtTreeItem& item); ++ ++ // Add or update a contact ++ bool updateContact(const String& id, const NamedList& params); ++ ++ // Update a contact ++ bool updateContact(ContactItem& c, const NamedList& params, bool all = true); ++ ++ // Remove a contact from tree ++ bool removeContact(const String& id); ++ ++ /** ++ * Get the context menu associated with a given item ++ * @param item The item (can be 0) ++ * @return QMenu pointer or 0 ++ */ ++ virtual QMenu* contextMenu(QtTreeItem* item); ++ ++ /** ++ * Item added notification ++ * @param item Added item ++ * @param parent The parent of the added tree item. 0 if added to the root ++ */ ++ virtual void itemAdded(QtTreeItem& item, QtTreeItem* parent); ++ ++ /** ++ * Fill a list with item statistics. ++ * The default implementation fills a 'count' parameter with the number of item children ++ * @param item The tree item ++ * @param list The list to fill ++ */ ++ virtual void fillItemStatistics(QtTreeItem& item, NamedList& list); ++ ++ /** ++ * Update a tree item's margins ++ * @param item Item to update ++ * @param set True to set from item props, false to set an empty rect ++ * @param props Optional pointer to item props, detect it if 0 ++ */ ++ virtual void applyItemMargins(QtTreeItem& item, bool set = true, ++ QtTreeItemProps* props = 0); ++ ++ /** ++ * Retrieve a group item from root or create a new one ++ * @param name Group name or empry to use the empty group ++ * @param create True to create if not found ++ * @return QtTreeItem pointer or 0 ++ */ ++ QtTreeItem* getGroup(const String& name = String::empty(), bool create = true); ++ ++ /** ++ * Add a contact to the list ++ * @param id Contact id ++ * @param params Contact parameters ++ */ ++ void addContact(const String& id, const NamedList& params); ++ ++ /** ++ * Add a contact to a specified parent ++ * @param c The contact to add ++ * @param grp Optional parent ++ */ ++ void addContact(ContactItem* c, QtTreeItem* parent = 0); ++ ++ /** ++ * Replace an existing contact. Remove it and add it again ++ * @param c The contact item ++ * @param params Contact parameters ++ */ ++ void replaceContact(ContactItem& c, const NamedList& params); ++ ++ /** ++ * Create contact structure (groups and lists) ++ * @param c The contact to add ++ * @param cil Contact structure ++ */ ++ void createContactTree(ContactItem* c, ContactItemList& cil); ++ ++ /** ++ * Compare two contacts's name ++ * @param c1 First contact ++ * @param c2 Second contact ++ * @return -1 if c1 < c2, 0 if c1 == c2, 1 if c1 > c2 ++ */ ++ int compareContactName(ContactItem* c1, ContactItem* c2); ++ ++ /** ++ * Sort contacts ++ * @param list The list of contacts to sort ++ */ ++ void sortContacts(QList& list); ++ ++private: ++ int m_savedIndent; ++ bool m_flatList; // Flat list ++ bool m_showOffline; // Show or hide offline contacts ++ bool m_hideEmptyGroups; // Show or hide empty groups ++ bool m_expStatusGrp; // Save/restore groups expanded status ++ String m_noGroupText; // Group text to show for contacts not belonging to any group ++ QMap m_statusOrder; // Status order (names are mapped to status icons) ++ QMenu* m_menuContact; ++ QMenu* m_menuChatRoom; ++ // Sorting ++ String m_sortKey; // Sorting key ++ Qt::SortOrder m_sortOrder; // Sort order ++ Qt::CaseSensitivity m_compareNameCs; // Contact name case comparison ++}; ++ ++ ++/** ++ * This class holds a contact list contact tree item ++ * @short A contact list contact ++ */ ++class ContactItem : public QtTreeItem ++{ ++ YCLASS(ContactItem,QtTreeItem) ++public: ++ inline ContactItem(const char* id, const NamedList& p = NamedList::empty(), ++ bool contact = true) ++ : QtTreeItem(id,ContactList::contactType(p["type"])) ++ {} ++ // Build and return a list of groups ++ inline ObjList* groups() const ++ { return Client::splitUnescape((*this)["groups"]); } ++ // Update name. Return true if changed ++ bool updateName(const NamedList& params, Qt::CaseSensitivity cs); ++ // Check if groups would change ++ bool groupsWouldChange(const NamedList& params); ++ // Check if the contact status is 'offline' ++ bool offline(); ++ ++ QString m_name; ++}; ++ ++ ++/** ++ * Utility class used to hold contact groups along with contacts ++ * @short Groups and contact items belonging to them ++ */ ++class ContactItemList ++{ ++public: ++ /** ++ * Retrieve a group. Create it if not found. Create contact list entry when a group is created ++ * @param id Group id ++ * @param text Group text ++ * @param expStat Expanded state (re)store indicator for created item ++ * @return Valid groups index ++ */ ++ int getGroupIndex(const String& id, const String& text, bool expStat); ++ ++ QList m_groups; ++ QList m_contacts; ++}; ++ ++ ++/** ++ * File list item description. The String holds the file name ++ * @short A file list item ++ */ ++class FileItem : public String ++{ ++public: ++ /** ++ * Constructor ++ * @param type File item type ++ * @param name File name ++ * @param path File path ++ * @param prov Optional file icon provider ++ */ ++ FileItem(int type, const char* name, const String& path, ++ QFileIconProvider* prov = 0); ++ ++ /** ++ * Constructor. Build a FileListTree up directory ++ * @param path The path ++ * @param prov Optional file icon provider ++ */ ++ FileItem(const String& path, QFileIconProvider* prov = 0); ++ ++ /** ++ * Destructor ++ */ ++ ~FileItem(); ++ ++ int m_type; ++ String m_fullName; ++ QIcon* m_icon; ++}; ++ ++ ++/** ++ * Load local directory content ++ * @short Thread used to load local directory content ++ */ ++class DirListThread : public QThread ++{ ++ Q_CLASSINFO("DirListThread","Yate") ++ Q_OBJECT ++public: ++ inline DirListThread(QObject* parent, const String& dir, bool dirs = true, ++ bool files = true) ++ : QThread(parent), ++ m_dir(dir), m_error(0), m_listDirs(dirs), m_listUpDir(false), ++ m_listFiles(files), m_iconProvider(0), m_sort(QtClient::SortNone), ++ m_caseSensitive(false) ++ {} ++ virtual void run(); ++ ++ String m_dir; ++ int m_error; ++ bool m_listDirs; ++ bool m_listUpDir; ++ bool m_listFiles; ++ ObjList m_dirs; ++ ObjList m_files; ++ QFileIconProvider* m_iconProvider; ++ int m_sort; ++ bool m_caseSensitive; ++ ++protected: ++ inline ObjList* addItem(int type, const char* name, ObjList& list, ObjList* last) { ++ FileItem* it = new FileItem(type,name,m_dir,m_iconProvider); ++ if (m_sort == QtClient::SortNone) ++ return last->append(it); ++ return addItemSort(list,it); ++ } ++ ObjList* addItemSort(ObjList& list, FileItem* it); ++ // Called when terminated from run() ++ void runTerminated(); ++}; ++ ++ ++/** ++ * This class holds a file list tree ++ * @short Specialized tree showing directories and files ++ */ ++class FileListTree : public QtCustomTree ++{ ++ YCLASS(FileListTree,QtCustomTree) ++ Q_CLASSINFO("FileListTree","Yate") ++ Q_OBJECT ++ Q_PROPERTY(QString _yate_filesystem_path READ fsPath WRITE setFsPath(QString)) ++ Q_PROPERTY(QString _yate_refresh READ refresh WRITE setRefresh(QString)) ++public: ++ enum FileListPathType { ++ PathNone = 0, ++ PathRoot, ++ PathHome, ++ PathUpThenHome, ++ }; ++ ++ /** ++ * List item type enumeration ++ */ ++ enum ItemType { ++ TypeDir = QtCustomTree::TypeCount, ++ TypeFile = QtCustomTree::TypeCount + 1, ++ TypeDrive = QtCustomTree::TypeCount + 2, ++ }; ++ ++ /** ++ * Constructor ++ * @param name The name of the object ++ * @param params List parameters ++ * @param parent List parent ++ */ ++ FileListTree(const char* name, const NamedList& params, QWidget* parent); ++ ++ /** ++ * Destructor ++ */ ++ ~FileListTree(); ++ ++ /** ++ * Retrieve _yate_filesystem_path property ++ */ ++ QString fsPath() ++ { return QtClient::setUtf8(m_fsPath); } ++ ++ /** ++ * Set _yate_filesystem_path property ++ */ ++ void setFsPath(QString path); ++ ++ /** ++ * Read _yate_refresh property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString refresh() ++ { return QString(); } ++ ++ /** ++ * Set _yate_refresh property ++ */ ++ void setRefresh(QString val); ++ ++ /** ++ * Change file system path, refresh data ++ * @param path New path ++ * @param force Force updating even if path didn't changed ++ */ ++ void setFsPath(const String& path = String::empty(), bool force = true); ++ ++ /** ++ * Check if current path is home path ++ * @return True if current path is the home one ++ */ ++ inline bool isHomePath() ++ { return fsPath() == QDir::toNativeSeparators(QDir::homePath()); } ++ ++ /** ++ * Refresh data ++ * @param dir List of directory children ++ * @param files List of files children ++ * @param drives List of drives children ++ */ ++ void refresh(ObjList* dirs, ObjList* files, ObjList* drives = 0); ++ ++ /** ++ * Sort a list of items ++ * @param list Items to sort ++ * @param type Item type ++ */ ++ virtual void sortItems(QList& list, int type); ++ ++ /** ++ * Retrieve the icon for a given item ++ * @param item The item ++ * @return The item icon ++ */ ++ virtual QIcon icon(QtTreeItem& item); ++ ++ /** ++ * Retrieve the icon for a given item type ++ * @param type Item type ++ * @param name Item name ++ * @param provider The icon provider ++ * @return The item icon ++ */ ++ static QIcon fileIcon(int type, const String& name, QFileIconProvider* provider); ++ ++ /** ++ * Build file full name ++ * @param buf Destination buffer ++ * @param path File path ++ * @param name File name ++ */ ++ static inline void buildFileFullName(String& buf, const char* path, const char* name) { ++ buf = path; ++ if (!isRootPath(buf)) ++ buf << Engine::pathSeparator(); ++#ifndef _WINDOWS ++ else if (!buf) ++ buf << Engine::pathSeparator(); ++#endif ++ buf << name; ++ } ++ ++ /** ++ * Check if a path root ++ * @param path Path to check ++ * @return True if the given path is root ++ */ ++ static inline bool isRootPath(const String& path) { ++#ifdef _WINDOWS ++ return path.null(); ++#else ++ return path.null() || (path.at(0) == '/' && !path.at(1)); ++#endif ++ } ++ ++ static const String s_upDir; ++ ++protected slots: ++ ++ /** ++ * Catch dir list thread terminate signal ++ */ ++ void onDirThreadTerminate(); ++ ++protected: ++ /** ++ * Start/stop dir list thread ++ */ ++ bool setDirListThread(bool on); ++ ++ /** ++ * Process item double click ++ * @param item The item ++ * @param column Clicked column ++ */ ++ virtual void onItemDoubleClicked(QtTreeItem* item, int column); ++ ++ /** ++ * Reset the thread ++ */ ++ void resetThread(); ++ ++ bool m_fileSystemList; // Show file system dir content ++ bool m_autoChangeDir; // Auto change directory ++ bool m_listFiles; // List files in current directory ++ int m_sort; // Sort files ++ int m_listOnFailure; // What to list when fails current directory ++ QFileIconProvider* m_iconProvider; // The icon provider ++ String m_nameParam; // Item name column ++ String m_fsPath; // Current path ++ QThread* m_dirListThread; // Dir list thread ++}; ++ ++ ++/** ++ * This class implements a generic item description ++ * @short Generic item description (base class) ++ */ ++class QtPaintItemDesc : public String ++{ ++ YCLASS(QtPaintItemDesc,String) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ */ ++ inline QtPaintItemDesc(const char* name = 0) ++ : String(name) ++ {} ++ ++ /** ++ * Get a QtPaintButtonDesc from this object ++ * @return QtPaintButtonDesc pointer or 0 ++ */ ++ virtual QtPaintButtonDesc* button(); ++ ++ QSize m_size; ++}; ++ ++ ++/** ++ * This class implements a generic item description ++ * @short Generic item description (base class) ++ */ ++class QtPaintButtonDesc : public QtPaintItemDesc ++{ ++ YCLASS(QtPaintButtonDesc,QtPaintItemDesc) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ */ ++ inline QtPaintButtonDesc(const char* name = 0) ++ : QtPaintItemDesc(name), m_params(""), m_iconSize(16,16) ++ { m_size = QSize(16,16); } ++ ++ /** ++ * Get a QtPaintButtonDesc from this object ++ * @return QtPaintButtonDesc pointer ++ */ ++ virtual QtPaintButtonDesc* button(); ++ ++ /** ++ * Find a button in a list ++ * @param list List to search in ++ * @param name Button name ++ * @param create True (default) to create if not found ++ * @return QtPaintButtonDesc pointer or 0 if found and not a button ++ */ ++ static QtPaintButtonDesc* find(ObjList& list, const String& name, ++ bool create = true); ++ ++ NamedList m_params; ++ QSize m_iconSize; ++}; ++ ++ ++/** ++ * This class implements an item to be painted ++ * @short An item to be painted ++ */ ++class QtPaintItem : public RefObject ++{ ++ YCLASS(QtPaintItem,GenObject) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ * @param size Object size ++ */ ++ inline QtPaintItem(const char* name, QSize size) ++ : m_enabled(true), m_hover(false), m_pressed(false), ++ m_size(size), m_name(name) ++ {} ++ ++ /** ++ * Retrieve the item name ++ * @return Item name ++ */ ++ inline const String& name() const ++ { return m_name; } ++ ++ /** ++ * Retrieve item pressed state ++ * @return Item pressed state ++ */ ++ inline bool pressed() const ++ { return m_pressed; } ++ ++ /** ++ * Retrieve item size ++ * @return Item size ++ */ ++ inline const QSize& size() const ++ { return m_size; } ++ ++ /** ++ * Retrieve the rectangle this item is drawn in ++ * @return Item display rectangle ++ */ ++ inline const QRect& displayRect() const ++ { return m_displayRect; } ++ ++ /** ++ * Retrieve the item action ++ * @return Item action ++ */ ++ inline const String& action() const ++ { return m_action; } ++ ++ /** ++ * Set hover state ++ * @param on Hover state ++ * @return True if hover state changed ++ */ ++ virtual bool setHover(bool on); ++ ++ /** ++ * Set pressed state ++ * @param on Pressed state ++ * @return True if pressed state changed ++ */ ++ virtual bool setPressed(bool on); ++ ++ /** ++ * Draw the item ++ * @param painter Painter used to draw ++ * @param rect Rectangle to paint in ++ */ ++ virtual void draw(QPainter* painter, const QRect& rect) = 0; ++ ++ /** ++ * Retrieve the item name ++ * @return Item name ++ */ ++ virtual const String& toString() const; ++ ++protected: ++ bool m_enabled; ++ bool m_hover; ++ bool m_pressed; ++ String m_action; ++ QSize m_size; ++ QRect m_displayRect; ++ ++private: ++ String m_name; ++}; ++ ++ ++/** ++ * This class implements an item to be painted ++ * @short An item to be painted ++ */ ++class QtPaintButton : public QtPaintItem ++{ ++ YCLASS(QtPaintButton,QtPaintItem) ++public: ++ /** ++ * Constructor ++ * @param desc Button description ++ */ ++ QtPaintButton(QtPaintButtonDesc& desc); ++ ++ /** ++ * Load button images ++ * @param params Parameters list ++ */ ++ void loadImages(const NamedList& params); ++ ++ /** ++ * Set hover state ++ * @param on Hover state ++ * @True if hover state changed ++ */ ++ virtual bool setHover(bool on); ++ ++ /** ++ * Set pressed state ++ * @param on Pressed state ++ * @return True if pressed state changed ++ */ ++ virtual bool setPressed(bool on); ++ ++ /** ++ * Draw the item ++ * @param painter Painter used to draw ++ * @param rect Rectangle to paint in ++ */ ++ virtual void draw(QPainter* painter, const QRect& rect); ++ ++protected: ++ // Load an image, adjust its size ++ bool loadImage(QPixmap& pixmap, const NamedList& params, const String& param); ++ // Update state options ++ void updateOptState(); ++ ++ QPixmap m_normalImage; ++ QPixmap m_hoverImage; ++ QPixmap m_pressedImage; ++ QPixmap* m_image; ++ QSize m_iconSize; ++ QSize m_iconOffset; // Draw icon offset ++}; ++ ++ ++/** ++ * This class implements a list of items to be painted ++ * @short Custom item delegate ++ */ ++class QtPaintItems : public QtPaintItem ++{ ++ YCLASS(QtPaintItems,QtPaintItem) ++public: ++ /** ++ * Constructor ++ * @param name Object name ++ */ ++ inline QtPaintItems(const char* name = 0) ++ : QtPaintItem(name,QSize(0,0)), ++ m_margins(10,0,6,0), m_itemSpace(4), ++ m_lastItemHover(0) ++ {} ++ ++ /** ++ * Add an item from description ++ * @param desc Item description ++ */ ++ void append(QtPaintItemDesc& desc); ++ ++ /** ++ * Calculate area needed to paint. ++ * This method should be called after all items are set ++ */ ++ void itemsAdded(); ++ ++ /** ++ * Set hover. Update item at position ++ * @param pos Position to check ++ * @return True if state changed (needs repaint) ++ */ ++ bool setHover(const QPoint& pos); ++ ++ /** ++ * Set hover state ++ * @param on Hover state ++ * @return True if hover state changed ++ */ ++ virtual bool setHover(bool on); ++ ++ /** ++ * Mouse pressed/released. Update item at position ++ * @param on Pressed state ++ * @param pos Position to check ++ * @param action Pointer to action to be set on mouse release ++ * @return True if state changed (needs repaint) ++ */ ++ bool mousePressed(bool on, const QPoint& pos, String* action = 0); ++ ++ /** ++ * Set pressed state ++ * @param on Pressed state ++ * @return True if pressed state changed ++ */ ++ virtual bool setPressed(bool on); ++ ++ /** ++ * Draw items ++ * @param painter Painter used to draw ++ * @param rect Rect to paint in ++ */ ++ virtual void draw(QPainter* painter, const QRect& rect); ++ ++protected: ++ ObjList m_items; ++ QRect m_margins; ++ int m_itemSpace; ++ QtPaintItem* m_lastItemHover; // Last item we handle mouse hover for ++}; ++ ++ ++/** ++ * This class implements a custom item delegate ++ * @short Custom item delegate ++ */ ++class QtItemDelegate : public QItemDelegate, public String ++{ ++ YCLASS(QtItemDelegate,String) ++ Q_CLASSINFO("QtItemDelegate","Yate") ++ Q_OBJECT ++public: ++ QtItemDelegate(QObject* parent, const NamedList& params = NamedList::empty()); ++ virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, ++ const QModelIndex& index) const; ++ inline QList& columns() ++ { return m_columns; } ++ inline int roleDisplayText() const ++ { return m_roleDisplayText; } ++ inline int roleImage() const ++ { return m_roleImage; } ++ // Update column position from column names. ++ // 'cNames' must be the column names in their order, starting from 0 ++ void updateColumns(QStringList& cNames); ++ // Build a list of delegates ++ static QList buildDelegates(QObject* parent, const NamedList& params, ++ const NamedList* common = 0, const String& prefix = "itemdelegate"); ++ // Build a delegate ++ static QAbstractItemDelegate* build(QObject* parent, const String& cls, NamedList& params); ++protected: ++ // Retrieve display text for a given index ++ virtual QString getDisplayText(const QStyleOptionViewItem& opt, ++ const QModelIndex& index) const; ++ // Inherited methods ++ virtual void drawBackground(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QModelIndex& index) const; ++ virtual void drawDecoration(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect, const QPixmap& pixmap) const; ++ virtual void drawFocus(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect) const; ++ virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, ++ const QModelIndex& index) const; ++ // Apply item margins ++ void applyMargins(QRect& dest, const QRect& src, bool inc) const; ++ ++ bool m_drawFocus; // Draw focus ++ int m_roleDisplayText; // Item display role to handle ++ int m_roleImage; // Item role containing image file name ++ int m_roleBackground; // Item background role to handle ++ int m_roleMargins; // Item internal margins role to handle ++ int m_roleQtDrawItems; // Item draw extra role to handle ++ QStringList m_columnsStr; // Column names this delegate should be set for ++ QStringList m_editableColsStr; // List of editable column names ++ QList m_editableCols; // List of editable columns ++ QList m_columns; // List of editable columns ++}; ++ ++ ++/** ++ * This class implements a custom item delegate used to display HTML texts ++ * @short Custom HTML item delegate ++ */ ++class QtHtmlItemDelegate : public QtItemDelegate ++{ ++ YCLASS(QtHtmlItemDelegate,QtItemDelegate) ++ Q_CLASSINFO("QtHtmlItemDelegate","Yate") ++ Q_OBJECT ++public: ++ QtHtmlItemDelegate(QObject* parent, const NamedList& params = NamedList::empty()) ++ : QtItemDelegate(parent,params) ++ {} ++protected: ++ virtual void drawDisplay(QPainter* painter, const QStyleOptionViewItem& opt, ++ const QRect& rect, const QString& text) const; ++}; ++ ++}; // anonymous namespace ++ ++#endif // __CUSTOMTREE_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/updater.cpp b/modules/qt5/updater.cpp +new file mode 100644 +index 0000000..9aa6ea0 +--- /dev/null ++++ b/modules/qt5/updater.cpp +@@ -0,0 +1,511 @@ ++/** ++ * updater.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Auto updater logic and downloader for Qt-5 clients. ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "updater.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MIN_SIZE 1024 ++#define MAX_SIZE (16*1024*1024) ++ ++#define TMP_EXT ".tmp" ++#ifdef _WINDOWS ++#define EXE_EXT ".exe" ++#else ++#define EXE_EXT ".bin" ++#endif ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++/** ++ * UI logic interaction ++ */ ++class UpdateLogic : public ClientLogic ++{ ++public: ++ enum Policy { ++ Invalid, ++ Never, ++ Check, ++ Download, ++ Install, ++ }; ++ inline UpdateLogic(const char* name) ++ : ClientLogic(name,100), ++ m_policy(Invalid), m_checking(false), ++ m_checked(false), m_install(false), ++ m_http(0), m_file(0), m_httpSlots(0), m_canUpdate(true) ++ { } ++ virtual ~UpdateLogic(); ++ inline Policy policy() const ++ { return static_cast(m_policy); } ++ virtual bool initializedClient(); ++ virtual void exitingClient(); ++ virtual bool action(Window* wnd, const String& name, NamedList* params); ++ virtual bool toggle(Window* wnd, const String& name, bool active); ++ void dataProgress(qint64 done, qint64 total); ++ void requestDone(); ++ void endHttp(bool error); ++protected: ++ void setPolicy(int policy, bool save); ++ void startChecking(bool start = true); ++ void finishedChecking(); ++ void startDownloading(bool start = true); ++ void finishedDownloading(); ++ void startInstalling(); ++private: ++ QString filePath(bool temp); ++ bool startHttp(const char* url, const QString& saveAs); ++ void stopHttp(); ++ void stopFile(); ++ int m_policy; ++ bool m_checking; ++ bool m_checked; ++ bool m_install; ++ String m_url; ++ QNetworkReply* m_http; ++ QFile* m_file; ++ QtUpdateHttp* m_httpSlots; ++ bool m_canUpdate; ++}; ++/** ++ * Plugin registration ++ */ ++class Updater : public Plugin ++{ ++public: ++ Updater(); ++ virtual ~Updater(); ++ virtual void initialize(); ++private: ++ UpdateLogic* m_logic; ++}; ++ ++static Updater s_plugin; ++ ++static const TokenDict s_policies[] = { ++ { "never", UpdateLogic::Never }, ++ { "check", UpdateLogic::Check }, ++ { "download", UpdateLogic::Download }, ++ { "install", UpdateLogic::Install }, ++ { 0, UpdateLogic::Invalid } ++}; ++ ++UpdateLogic::~UpdateLogic() ++{ ++} ++ ++bool UpdateLogic::initializedClient() ++{ ++ // Check if the current user can write to install dir ++ // Disable and uncheck all updater UI controls on failure ++ Configuration cfg(Engine::configFile("updater")); ++ m_canUpdate = !File::exists(cfg) || cfg.save(); ++ if (!m_canUpdate) { ++ Debug(toString(),DebugInfo,"Disabling updates: the current user can't write to '%s'", ++ Engine::configPath().c_str()); ++ NamedList p(""); ++ p.addParam("check:upd_automatic","false"); ++ p.addParam("active:upd_automatic","false"); ++ p.addParam("active:upd_install","false"); ++ p.addParam("active:upd_check","false"); ++ p.addParam("active:upd_download","false"); ++ for (int i = 0; s_policies[i].token; i++) ++ p.addParam("active:upd_policy_" + String(s_policies[i].token),"false"); ++ if (Client::self()) ++ Client::self()->setParams(&p); ++ return false; ++ } ++ ++ int policy = Engine::config().getIntValue("client",toString(),s_policies,Never); ++ policy = Client::s_settings.getIntValue(toString(),"policy",s_policies,policy); ++ setPolicy(policy,false); ++ if (QFile::exists(filePath(false))) { ++ m_install = Client::s_settings.getBoolValue(toString(),"install"); ++ if ((m_policy >= Install) && !m_install) { ++ Debug(toString(),DebugNote,"Deleting old updater file"); ++ QFile::remove(filePath(false)); ++ } ++ } ++ Client::self()->setActive("upd_install",m_install); ++ if (m_install && (m_policy >= Install)) ++ startInstalling(); ++ else if (m_policy >= Check) ++ startChecking(); ++ return false; ++} ++ ++void UpdateLogic::exitingClient() ++{ ++ startDownloading(false); ++ startChecking(false); ++ stopHttp(); ++ delete m_httpSlots; ++ m_httpSlots = 0; ++} ++ ++bool UpdateLogic::action(Window* wnd, const String& name, NamedList* params) ++{ ++ if (!m_canUpdate) ++ return false; ++ if (name == "upd_install") ++ startInstalling(); ++ else if (name == "upd_check") ++ startChecking(); ++ else if (name == "upd_download") ++ startDownloading(); ++ else ++ return false; ++ return true; ++} ++ ++bool UpdateLogic::toggle(Window* wnd, const String& name, bool active) ++{ ++ if (!m_canUpdate) ++ return false; ++ if (!name.startsWith("upd_")) ++ return false; ++ if (name == "upd_check") ++ startChecking(active); ++ else if (name == "upd_download") ++ startDownloading(active); ++ else if (name == "upd_automatic") ++ setPolicy(active ? Install : Never,true); ++ else if (active) { ++ String tmp = name; ++ if (tmp.startSkip("upd_policy_",false)) ++ setPolicy(lookup(tmp,s_policies,Invalid),true); ++ } ++ return true; ++} ++ ++void UpdateLogic::setPolicy(int policy, bool save) ++{ ++ if ((policy == Invalid) || (policy == m_policy)) ++ return; ++ const char* pol = lookup(policy,s_policies); ++ if (!pol) ++ return; ++ m_policy = policy; ++ if (save) { ++ Client::s_settings.setValue(toString(),"policy",pol); ++ Client::save(Client::s_settings); ++ } ++ if (!Client::self()) ++ return; ++ for (policy = Never; policy <= Install; policy++) { ++ String tmp = "upd_policy_"; ++ tmp += lookup(policy,s_policies); ++ Client::self()->setCheck(tmp,(policy == m_policy)); ++ } ++ Client::self()->setCheck("upd_automatic",(Install == m_policy)); ++} ++ ++void UpdateLogic::startChecking(bool start) ++{ ++ String url = Engine::config().getValue("client","updateurl"); ++ Engine::runParams().replaceParams(url); ++ if (url.trimBlanks().null()) { ++ start = false; ++ if (Client::self()) { ++ Client::self()->setActive("upd_check",false); ++ Client::self()->setActive("upd_download",false); ++ Client::self()->setActive("upd_install",false); ++ } ++ } ++ if (start) { ++ Debug(toString(),DebugNote,"Checking new version: %s",url.c_str()); ++ m_checked = false; ++ m_checking = true; ++ start = startHttp(url,""); ++ if (Client::self()) { ++ Client::self()->setActive("upd_download",false); ++ Client::self()->setSelect("upd_progress","0"); ++ Client::self()->setText("upd_version",""); ++ } ++ } ++ else ++ stopHttp(); ++ if (Client::self()) ++ Client::self()->setCheck("upd_check",start); ++} ++ ++void UpdateLogic::startDownloading(bool start) ++{ ++ m_checking = false; ++ if (start && m_install) { ++ m_install = false; ++ Client::s_settings.setValue(toString(),"install",String::boolText(false)); ++ Client::save(Client::s_settings); ++ } ++ if (start) { ++ Debug(toString(),DebugNote,"Downloading from: %s",m_url.c_str()); ++ start = startHttp(m_url,filePath(true)); ++ } ++ else { ++ stopHttp(); ++ QFile::remove(filePath(true)); ++ } ++ if (Client::self()) { ++ Client::self()->setActive("upd_check",!start); ++ Client::self()->setActive("upd_install",m_install); ++ Client::self()->setCheck("upd_download",start); ++ Client::self()->setSelect("upd_progress","0"); ++ } ++} ++ ++void UpdateLogic::startInstalling() ++{ ++ if (!QFile::exists(filePath(false))) ++ return; ++ QString cmd = Engine::config().getValue("client","updatecmd"); ++ if (!cmd.isEmpty()) { ++ String tmp = cmd.toUtf8().constData(); ++ NamedList params(Engine::runParams()); ++ params.setParam("filename",filePath(false).toUtf8().constData()); ++ params.replaceParams(tmp); ++ if (tmp.trimBlanks().null()) ++ return; ++ cmd = QString::fromUtf8(tmp.c_str()); ++ } ++ else ++ cmd = filePath(false); ++ if (QProcess::startDetached(cmd)) { ++ Debug(toString(),DebugNote,"Executing: %s",cmd.toUtf8().constData()); ++ Client::s_settings.setValue(toString(),"install",String::boolText(false)); ++ Client::save(Client::s_settings); ++ Engine::halt(0); ++ return; ++ } ++ Debug(toString(),DebugWarn,"Failed to execute: %s",cmd.toUtf8().constData()); ++} ++ ++void UpdateLogic::finishedChecking() ++{ ++ if (Client::self()) { ++ Client::self()->setCheck("upd_check",false); ++ Client::self()->setActive("upd_download",m_checked); ++ Client::self()->setSelect("upd_progress","0"); ++ } ++ if (m_checked && (m_policy >= Download)) ++ startDownloading(); ++} ++ ++void UpdateLogic::finishedDownloading() ++{ ++ if (Client::self()) { ++ Client::self()->setCheck("upd_download",false); ++ Client::self()->setActive("upd_check",true); ++ Client::self()->setActive("upd_install",m_install); ++ if (!m_install) ++ Client::self()->setSelect("upd_progress","0"); ++ } ++ Client::s_settings.setValue(toString(),"install",String::boolText(m_install)); ++ Client::save(Client::s_settings); ++} ++ ++QString UpdateLogic::filePath(bool temp) ++{ ++ return QString::fromUtf8((Engine::configPath(true) + Engine::pathSeparator() + toString() + ++ (temp ? TMP_EXT : EXE_EXT))); ++} ++ ++bool UpdateLogic::startHttp(const char* url, const QString& saveAs) ++{ ++ stopHttp(); ++ QUrl qurl(QString::fromUtf8(url)); ++ if (!qurl.isValid()) ++ return false; ++ QFile* file = 0; ++ if (!saveAs.isEmpty()) { ++ QFile::remove(saveAs); ++ file = new QFile(saveAs); ++ if (!(file->open(QIODevice::WriteOnly) && ++ file->setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ExeOwner))) { ++ file->remove(); ++ delete file; ++ return false; ++ } ++ m_file = file; ++ } ++ if (!m_httpSlots) ++ m_httpSlots = new QtUpdateHttp(this); ++ const char* proxy = Client::s_settings.getValue(toString(),"proxy_host"); ++ if (proxy) { ++ QNetworkProxy proxyCfg(QNetworkProxy::DefaultProxy, ++ proxy, ++ Client::s_settings.getIntValue(toString(),"proxy_port",8080), ++ Client::s_settings.getValue(toString(),"proxy_user"), ++ Client::s_settings.getValue(toString(),"proxy_pass")); ++ m_httpSlots->setProxy(proxyCfg); ++ } ++ QNetworkRequest req(qurl); ++ m_http = m_httpSlots->get(req); ++ return true; ++} ++ ++void UpdateLogic::stopHttp() ++{ ++ QNetworkReply* http = m_http; ++ m_http = 0; ++ if (http) { ++ http->abort(); ++ delete http; ++ } ++ stopFile(); ++} ++ ++void UpdateLogic::stopFile() ++{ ++ QFile* file = m_file; ++ m_file = 0; ++ delete file; ++} ++ ++void UpdateLogic::endHttp(bool error) ++{ ++ stopFile(); ++ if (!m_http) ++ return; ++ if (m_checking) { ++ if (!error) { ++ QByteArray data = m_http->readAll(); ++ if (data.size() <= 1024) { ++ String str(data.constData()); ++ // 1st row is the URL, everything else description ++ int nl = str.find('\n'); ++ if (nl > 0) { ++ int len = (str.at(nl - 1) == '\r') ? (nl - 1) : nl; ++ URI url(str.substr(0,len)); ++ url.trimBlanks(); ++ if (url.getProtocol() == "http") { ++ m_checked = true; ++ m_url = url; ++ if (Client::self()) ++ Client::self()->setText("upd_version",str.substr(nl+1)); ++ } ++ } ++ } ++ } ++ finishedChecking(); ++ } ++ else { ++ if (!error) { ++ QFileInfo info(filePath(true)); ++ if ((info.size() >= MIN_SIZE) && (info.size() <= MAX_SIZE)) { ++ QFile::remove(filePath(false)); ++ m_install = QFile::rename(filePath(true),filePath(false)); ++ } ++ } ++ QFile::remove(filePath(true)); ++ finishedDownloading(); ++ } ++} ++ ++void UpdateLogic::dataProgress(qint64 done, qint64 total) ++{ ++ if (m_http && m_file) { ++ qint64 ready = m_http->bytesAvailable(); ++ while (ready >= 1024) { ++ char buf[1024]; ++ qint64 got = m_http->read(buf,std::min(ready,sizeof(buf))); ++ if (got <= 0) ++ break; ++ m_file->write(buf,got); ++ ready -= got; ++ } ++ } ++ int percent = 0; ++ if (done) ++ percent = (done <= total) ? (int)((done * 100) / total) : 50; ++ if (!Client::self()) ++ return; ++ Client::self()->setSelect("upd_progress",String(percent)); ++} ++ ++void UpdateLogic::requestDone() ++{ ++ if (m_http && m_file) { ++ QByteArray buf = m_http->readAll(); ++ m_file->write(buf); ++ } ++ endHttp(m_http ? (QNetworkReply::NoError != m_http->error()) : true); ++} ++ ++ ++QNetworkReply* QtUpdateHttp::get(const QNetworkRequest& request) ++{ ++ QNetworkReply* reply = QNetworkAccessManager::get(request); ++ if (reply) { ++ connect(reply,&QNetworkReply::downloadProgress,this,&QtUpdateHttp::dataProgress); ++ connect(reply,&QNetworkReply::finished,this,&QtUpdateHttp::requestDone); ++ } ++ return reply; ++} ++ ++void QtUpdateHttp::dataProgress(qint64 done, qint64 total) ++{ ++ if (m_logic) ++ m_logic->dataProgress(done, total); ++} ++ ++void QtUpdateHttp::requestDone() ++{ ++ if (m_logic) ++ m_logic->requestDone(); ++} ++ ++ ++Updater::Updater() ++ : Plugin("updater",true), m_logic(0) ++{ ++ Output("Loaded module Updater"); ++} ++ ++Updater::~Updater() ++{ ++ Output("Unloading module Updater"); ++ TelEngine::destruct(m_logic); ++} ++ ++void Updater::initialize() ++{ ++ Output("Initializing module Updater"); ++ if (m_logic) ++ return; ++ m_logic = new UpdateLogic("updater"); ++} ++ ++}; // anonymous namespace ++ ++#include "updater.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/updater.h b/modules/qt5/updater.h +new file mode 100644 +index 0000000..388569e +--- /dev/null ++++ b/modules/qt5/updater.h +@@ -0,0 +1,79 @@ ++/** ++ * updater.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Auto updater logic and downloader for Qt-5 clients. ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __UPDATER_H ++#define __UPDATER_H ++ ++#include ++ ++#undef open ++#undef read ++#undef close ++#undef write ++#undef mkdir ++ ++#define QT_NO_DEBUG ++#define QT_DLL ++#define QT_GUI_LIB ++#define QT_CORE_LIB ++#define QT_THREAD_SUPPORT ++ ++#include ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++class UpdateLogic; ++ ++/** ++ * Proxy object so HTTP notification slots are created in the GUI thread ++ */ ++class QtUpdateHttp : public QNetworkAccessManager ++{ ++ Q_CLASSINFO("QtUpdateHttp","Yate") ++ Q_OBJECT ++public: ++ /** ++ * Constructor ++ * @param logic Qt update logic owning this object ++ */ ++ inline QtUpdateHttp(UpdateLogic* logic) ++ : QNetworkAccessManager(), ++ m_logic(logic) ++ { } ++ /** ++ * Start an HTTP request and create a corresponding QNetworkReply object ++ * with its signals attached to this object. ++ * @return New QNetworkReply object attached to this object's slots ++ */ ++ QNetworkReply* get(const QNetworkRequest& request); ++private slots: ++ void dataProgress(qint64 done, qint64 total); ++ void requestDone(); ++private: ++ UpdateLogic* m_logic; ++}; ++ ++}; // anonymous namespace ++ ++#endif /* __UPDATER_H */ ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/widgetlist.cpp b/modules/qt5/widgetlist.cpp +new file mode 100644 +index 0000000..5e9a3d3 +--- /dev/null ++++ b/modules/qt5/widgetlist.cpp +@@ -0,0 +1,691 @@ ++/** ++ * widgetlist.cpp ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom widget list objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2010-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#include "widgetlist.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++// The factory ++class WidgetListFactory : public UIFactory ++{ ++public: ++ inline WidgetListFactory(const char* name = "WidgetListFactory") ++ : UIFactory(name) ++ { m_types.append(new String("WidgetList")); } ++ virtual void* create(const String& type, const char* name, NamedList* params = 0); ++}; ++ ++static const TokenDict s_delItemDict[] = { ++ {"global", WidgetList::DelItemGlobal}, ++ {"single", WidgetList::DelItemSingle}, ++ {"native", WidgetList::DelItemNative}, ++ {0,0} ++}; ++ ++static WidgetListFactory s_factory; ++ ++ ++/* ++ * WidgetListTabWidget ++ */ ++WidgetListTabWidget::WidgetListTabWidget(WidgetList* parent, const NamedList& params) ++ : QTabWidget(parent) ++{ ++ // Configure delete item button ++#if QT_VERSION >= 0x040500 ++ if (parent->m_delItemType == WidgetList::DelItemSingle || ++ parent->m_delItemType == WidgetList::DelItemNative) { ++ // Set closable tabs ++ bool closable = parent->m_delItemType == WidgetList::DelItemNative; ++ setTabsClosable(closable); ++ // Connect close signal if native close is used ++ if (tabsClosable()) ++ QtClient::connectObjects(this,SIGNAL(tabCloseRequested(int)),parent,SLOT(closeItem(int))); ++ } ++#else ++ // Override settings: we don't support close button on tab page ++ if (parent->m_delItemType != WidgetList::DelItemNone) ++ parent->setDelItemType(WidgetList::DelItemGlobal); ++#endif ++ if (parent->m_delItemType == WidgetList::DelItemGlobal) ++ setCloseButton(); ++} ++ ++// Build and set a close button for a given tab or a global close if index is negative ++void WidgetListTabWidget::setCloseButton(int index) ++{ ++ WidgetList* list = static_cast(parent()); ++ if (!list) ++ return; ++ // Check if we can set a close button ++#if QT_VERSION >= 0x040500 ++ if (index >= 0) { ++ if (list->m_delItemType != WidgetList::DelItemSingle || tabsClosable() || !tabBar()) ++ return; ++ } ++ else if (list->m_delItemType != WidgetList::DelItemGlobal) ++ return; ++#else ++ if (index >= 0 || list->m_delItemType != WidgetList::DelItemGlobal) ++ return; ++#endif ++ // Build the button ++ QToolButton* b = new QToolButton(this); ++ b->setProperty("_yate_noautoconnect",QVariant(true)); ++ if (index >= 0) { ++#if QT_VERSION >= 0x040500 ++ QWidget* w = widget(index); ++ String item; ++ QtUIWidget::getListItemIdProp(w,item); ++ QtUIWidget::setListItemProp(b,QtClient::setUtf8(item)); ++ tabBar()->setTabButton(index,QTabBar::RightSide,b); ++#else ++ delete b; ++ return; ++#endif ++ } ++ else ++ setCornerWidget(b,Qt::TopRightCorner); ++ list->applyDelItemProps(b); ++ QtClient::connectObjects(b,SIGNAL(clicked()),list,SLOT(closeItem())); ++} ++ ++// Set tab close button if needed ++void WidgetListTabWidget::tabInserted(int index) ++{ ++#if QT_VERSION >= 0x040500 ++ if (!tabsClosable()) ++ setCloseButton(index); ++#endif ++ QTabWidget::tabInserted(index); ++} ++ ++// Tab removed. Notify the parent ++void WidgetListTabWidget::tabRemoved(int index) ++{ ++ WidgetList* list = static_cast(parent()); ++ if (list) ++ list->itemRemoved(index); ++} ++ ++/* ++ * WidgetListStackedWidget ++ */ ++WidgetListStackedWidget::WidgetListStackedWidget(WidgetList* parent, const NamedList& params) ++ : QStackedWidget(parent) ++{ ++} ++ ++/* ++ * WidgetList ++ */ ++// Constructor ++WidgetList::WidgetList(const char* name, const NamedList& params, QWidget* parent) ++ : QtCustomWidget(name,parent), ++ m_hideWndWhenEmpty(false), ++ m_tab(0), ++ m_pages(0), ++ m_delItemType(DelItemNone), ++ m_delItemProps("") ++{ ++ // Build properties ++ QtClient::buildProps(this,params["buildprops"]); ++ // Retrieve the delete item props ++ updateDelItemProps(params,true); ++ const String& type = params["type"]; ++ XDebug(ClientDriver::self(),DebugAll,"WidgetList(%s) type=%s",name,type.c_str()); ++ QString wName = buildQChildName(params.getValue("widgetname","widget")); ++ if (type == "tabs") { ++ m_tab = new WidgetListTabWidget(this,params); ++ m_tab->setObjectName(wName); ++ QtClient::setWidget(this,m_tab); ++ QtClient::connectObjects(m_tab,SIGNAL(currentChanged(int)), ++ this,SLOT(currentChanged(int))); ++ } ++ else if (type == "pages") { ++ QWidget* hdr = 0; ++ const String& header = params["header"]; ++ if (header) ++ hdr = QtWindow::loadUI(Client::s_skinPath + header,this,header); ++ if (hdr) ++ hdr->setObjectName(QtClient::setUtf8("pages_header")); ++ m_pages = new WidgetListStackedWidget(this,params); ++ m_pages->setObjectName(wName); ++ QVBoxLayout* newLayout = new QVBoxLayout; ++ newLayout->setSpacing(0); ++ newLayout->setContentsMargins(0,0,0,0); ++ if (hdr) ++ newLayout->addWidget(hdr); ++ newLayout->addWidget(m_pages); ++ QLayout* l = layout(); ++ if (l) ++ delete l; ++ setLayout(newLayout); ++ QtClient::connectObjects(m_pages,SIGNAL(currentChanged(int)), ++ this,SLOT(currentChanged(int))); ++ QtClient::connectObjects(m_pages,SIGNAL(widgetRemoved(int)), ++ this,SLOT(itemRemoved(int))); ++ } ++ // Set navigation ++ QtUIWidget::initNavigation(params); ++ setParams(params); ++} ++ ++// Find an item widget by index ++QWidget* WidgetList::findItemByIndex(int index) ++{ ++ QWidget* w = 0; ++ if (m_tab) ++ w = m_tab->widget(index); ++ else if (m_pages) ++ w = m_pages->widget(index); ++ return w; ++} ++ ++// Set widget parameters ++bool WidgetList::setParams(const NamedList& params) ++{ ++ bool ok = QtUIWidget::setParams(params); ++ ok = QtUIWidget::setParams(this,params) && ok; ++ updateDelItemProps(params); ++ return ok; ++} ++ ++// Get widget's items ++bool WidgetList::getOptions(NamedList& items) ++{ ++ QList list = getContainerItems(); ++ for (int i = 0; i < list.size(); i++) ++ if (list[i]->isWidgetType()) { ++ String id; ++ getListItemIdProp(list[i],id); ++ items.addParam(id,""); ++ } ++ return true; ++} ++ ++// Retrieve item parameters ++bool WidgetList::getTableRow(const String& item, NamedList* data) ++{ ++ QWidget* w = findItem(item); ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::getTableRow(%s,%p) found=%p", ++ name().c_str(),item.c_str(),data,w); ++ if (!w) ++ return false; ++ if (data) ++ getParams(w,*data); ++ return true; ++} ++ ++// Add an item ++bool WidgetList::addTableRow(const String& item, const NamedList* data, bool atStart) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::addTableRow(%s,%p,%u)", ++ name().c_str(),item.c_str(),data,atStart); ++ QWidget* parent = 0; ++ if (item) { ++ if (m_tab) ++ parent = m_tab; ++ else ++ parent = m_pages; ++ } ++ if (!parent) ++ return false; ++ const String& type = data ? (*data)["item_type"] : String::empty(); ++ QWidget* w = loadWidgetType(parent,item,type); ++ if (!w) ++ return false; ++ QtUIWidgetItemProps* p = QtUIWidget::getItemProps(type); ++ if (p && p->m_styleSheet) ++ applyWidgetStyle(w,p->m_styleSheet); ++ if (addItem(w,atStart)) ++ setTableRow(item,data); ++ return w != 0; ++} ++ ++// Add or set one or more table row(s) ++bool WidgetList::updateTableRows(const NamedList* data, bool atStart) ++{ ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::updateTableRows(%p,%u)", ++ name().c_str(),data,atStart); ++ // Save the hide empty window flag ++ bool oldHide = m_hideWndWhenEmpty; ++ String oldWHide = m_hideWidgetWhenEmpty; ++ m_hideWndWhenEmpty = false; ++ m_hideWidgetWhenEmpty = ""; ++ bool ok = true; ++ unsigned int n = data->length(); ++ for (unsigned int i = 0; i < n; i++) { ++ if (Client::exiting()) ++ break; ++ // Get item and the list of parameters ++ NamedString* ns = data->getParam(i); ++ if (!ns) ++ continue; ++ // Delete ? ++ if (ns->null()) { ++ ok = delTableRow(ns->name()) && ok; ++ continue; ++ } ++ // Set existing item or add a new one ++ if (getTableRow(ns->name())) ++ ok = setTableRow(ns->name(),YOBJECT(NamedList,ns)) && ok; ++ else if (ns->toBoolean()) ++ ok = addTableRow(ns->name(),YOBJECT(NamedList,ns),atStart) && ok; ++ else ++ ok = false; ++ } ++ m_hideWndWhenEmpty = oldHide; ++ m_hideWidgetWhenEmpty = oldWHide; ++ QtUIWidget::updateNavigation(); ++ hideEmpty(); ++ return ok; ++} ++ ++// Delete an item ++bool WidgetList::delTableRow(const String& item) ++{ ++ QWidget* w = findItem(item); ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::delTableRow(%s) found=%p", ++ name().c_str(),item.c_str(),w); ++ if (!w) ++ return false; ++ QtClient::deleteLater(w); ++ QtUIWidget::updateNavigation(); ++ hideEmpty(); ++ return true; ++} ++ ++// Set existing item parameters ++bool WidgetList::setTableRow(const String& item, const NamedList* data) ++{ ++ QWidget* w = findItem(item); ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::setTableRow(%s,%p) wid=%p", ++ name().c_str(),item.c_str(),data,w); ++ if (!w) ++ return false; ++ if (data) { ++ if (m_tab) { ++ // Hook some parameters to set them in tab ++ String* name = m_itemTextParam ? data->getParam(m_itemTextParam) : 0; ++ if (name) ++ m_tab->setTabText(m_tab->indexOf(w),QtClient::setUtf8(*name)); ++ if (m_itemImgParam) { ++ String* tmp = data->getParam("image:" + m_itemImgParam); ++ if (tmp) ++ m_tab->setTabIcon(m_tab->indexOf(w),QIcon(QtClient::setUtf8(*tmp))); ++ } ++ } ++ QtUIWidget::setParams(w,*data); ++ } ++ return true; ++} ++ ++// Delete all items ++bool WidgetList::clearTable() ++{ ++ if (m_tab || m_pages) { ++ QList list = getContainerItems(); ++ for (int i = 0; i < list.size(); i++) ++ if (list[i]->isWidgetType()) ++ QtClient::deleteLater(list[i]); ++ } ++ else ++ return false; ++ QtUIWidget::updateNavigation(); ++ hideEmpty(); ++ return true; ++} ++ ++// Select (set active) an item ++bool WidgetList::setSelect(const String& item) ++{ ++ QWidget* w = findItem(item); ++ if (!w) ++ return false; ++ if (m_tab) ++ m_tab->setCurrentWidget(w); ++ else if (m_pages) ++ m_pages->setCurrentWidget(w); ++ else ++ return false; ++ QtUIWidget::updateNavigation(); ++ return true; ++} ++ ++// Retrieve the selected (active) item ++bool WidgetList::getSelect(String& item) ++{ ++ QWidget* w = selectedItem(); ++ if (w) ++ QtUIWidget::getListItemIdProp(w,item); ++ DDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::getSelect() '%s' wid=%p", ++ name().c_str(),item.c_str(),w); ++ return w != 0; ++} ++ ++// Retrieve a QObject list containing container items ++QList WidgetList::getContainerItems() ++{ ++ QList list; ++ if (m_tab) { ++ int n = m_tab->count(); ++ for (int i = 0; i < n; i++) { ++ QWidget* w = m_tab->widget(i); ++ if (w) ++ list.append(static_cast(w)); ++ } ++ } ++ else if (m_pages) { ++ int n = m_pages->count(); ++ for (int i = 0; i < n; i++) { ++ QWidget* w = m_pages->widget(i); ++ if (w) ++ list.append(static_cast(w)); ++ } ++ } ++ return list; ++} ++ ++// Select an item by its index ++bool WidgetList::setSelectIndex(int index) ++{ ++ if (index < 0 || index >= itemCount()) ++ return false; ++ QWidget* w = findItemByIndex(index); ++ String item; ++ if (w) ++ QtUIWidget::getListItemIdProp(w,item); ++ return item ? setSelect(item) : false; ++} ++ ++// Retrieve the 0 based index of the current item ++int WidgetList::currentItemIndex() ++{ ++ if (m_tab) ++ return m_tab->currentIndex(); ++ if (m_pages) ++ return m_pages->currentIndex(); ++ return -1; ++} ++ ++// Retrieve the number of items in container ++int WidgetList::itemCount() ++{ ++ if (m_tab) ++ return m_tab->count(); ++ if (m_pages) ++ return m_pages->count(); ++ return -1; ++} ++ ++// Set _yate_hidewndwhenempty property value. Apply it if changed ++void WidgetList::setHideWndWhenEmpty(bool value) ++{ ++ if (m_hideWndWhenEmpty == value) ++ return; ++ m_hideWndWhenEmpty = value; ++ hideEmpty(); ++} ++ ++// Set _yate_hidewidgetwhenempty property value. Apply it if changed ++void WidgetList::setHideWidgetWhenEmpty(QString value) ++{ ++ String s; ++ QtClient::getUtf8(s,value); ++ if (m_hideWidgetWhenEmpty == s) ++ return; ++ m_hideWidgetWhenEmpty = s; ++ hideEmpty(); ++} ++ ++// Start/stop item flash ++void WidgetList::setFlashItem(QString value) ++{ ++ if (!m_tab) ++ return; ++ String on; ++ String item; ++ int pos = value.indexOf(':'); ++ if (pos > 0) { ++ QtClient::getUtf8(on,value.left(pos)); ++ QtClient::getUtf8(item,value.right(value.length() - pos - 1)); ++ } ++ else ++ return; ++ QWidget* w = findItem(item); ++ if (!w) ++ return; ++ int index = m_tab->indexOf(w); ++ if (on.toBoolean()) ++ m_tab->setTabTextColor(index,QColor("green")); ++ else ++ m_tab->setTabTextColor(index,QColor("black")); ++} ++ ++// Handle selection changes ++void WidgetList::currentChanged(int index) ++{ ++ String item; ++ if (index >= 0 && index < itemCount()) { ++ QWidget* w = findItemByIndex(index); ++ if (w) ++ QtUIWidget::getListItemIdProp(w,item); ++ // Avoid notifying no selection ++ if (!item) ++ return; ++ } ++ QtWindow* wnd = item ? QtClient::parentWindow(this) : 0; ++ if (wnd) ++ Client::self()->select(wnd,name(),item); ++} ++ ++// Item removed slot. Notify the client when empty ++void WidgetList::itemRemoved(int index) ++{ ++ if (itemCount()) ++ return; ++ QtWindow* wnd = QtClient::parentWindow(this); ++ if (wnd) ++ Client::self()->select(wnd,name(),String::empty()); ++} ++ ++// Handle current item close action ++void WidgetList::closeItem(int index) ++{ ++ if (!m_delItemActionPrefix) ++ return; ++ String item; ++ if (index < 0) { ++ if (m_delItemType == DelItemSingle) ++ QtUIWidget::getListItemProp(sender(),item); ++ else if (m_delItemType == DelItemGlobal) ++ getSelect(item); ++ } ++ else if (m_delItemType == DelItemNative) { ++ // Signalled by tab native close button ++ QWidget* w = findItemByIndex(index); ++ if (w) ++ QtUIWidget::getListItemIdProp(w,item); ++ } ++ XDebug(ClientDriver::self(),DebugAll, ++ "WidgetList(%s)::closeItem(%d) sender (%p,%s) found=%s", ++ name().c_str(),index,sender(),YQT_OBJECT_NAME(sender()),item.c_str()); ++ QtWindow* wnd = item ? QtClient::parentWindow(this) : 0; ++ if (wnd) ++ Client::self()->action(wnd,m_delItemActionPrefix + item); ++} ++ ++// Handle children events ++bool WidgetList::eventFilter(QObject* watched, QEvent* event) ++{ ++ if (!Client::valid()) ++ return QtCustomWidget::eventFilter(watched,event); ++ if (event->type() == QEvent::KeyPress) { ++ if (m_wndEvHooked) { ++ QtWindow* wnd = qobject_cast(watched); ++ if (wnd && wnd == getWindow()) { ++ QString child; ++ QWidget* sel = selectedItem(); ++ if (sel && buildQChildNameProp(child,sel,"_yate_keypress_redirect") && ++ QtClient::sendEvent(*event,sel,child)) { ++ QWidget* wid = sel->findChild(child); ++ if (wid) ++ wid->setFocus(); ++ return true; ++ } ++ return QtCustomWidget::eventFilter(watched,event); ++ } ++ } ++ bool filter = false; ++ if (!filterKeyEvent(watched,static_cast(event),filter)) ++ return QtCustomWidget::eventFilter(watched,event); ++ return filter; ++ } ++ return QtCustomWidget::eventFilter(watched,event); ++} ++ ++// Hide the parent window if the container is empty ++void WidgetList::hideEmpty() ++{ ++ if (itemCount() || !Client::valid() || !(m_hideWndWhenEmpty || m_hideWidgetWhenEmpty)) ++ return; ++ QtWindow* wnd = QtClient::parentWindow(this); ++ if (!wnd) ++ return; ++ if (m_hideWndWhenEmpty) ++ Client::self()->setVisible(wnd->id(),false); ++ if (m_hideWidgetWhenEmpty) ++ wnd->setShow(m_hideWidgetWhenEmpty,false); ++} ++ ++// Insert/add a widget item ++bool WidgetList::addItem(QWidget*& w, bool atStart) ++{ ++ if (!w) ++ return false; ++ int index = atStart ? 0 : itemCount(); ++ if (m_tab) ++ m_tab->insertTab(index,w,QString()); ++ else if (m_pages) ++ m_pages->insertWidget(index,w); ++ else { ++ QtClient::deleteLater(w); ++ w = 0; ++ } ++ if (w) ++ QtUIWidget::updateNavigation(); ++ return w != 0; ++} ++ ++// Retrieve the selected item widget ++QWidget* WidgetList::selectedItem() ++{ ++ if (m_tab) ++ return m_tab->currentWidget(); ++ if (m_pages) ++ return m_pages->currentWidget(); ++ return 0; ++} ++ ++// Set delete item type ++void WidgetList::setDelItemType(int type) ++{ ++ if (type == m_delItemType) ++ return; ++ m_delItemType = type; ++ XDebug(ClientDriver::self(),DebugAll,"WidgetList(%s)::setDelItemType(%d = %s)", ++ name().c_str(),type,lookup(type,s_delItemDict)); ++} ++ ++// Retrieve delete item object properties ++void WidgetList::updateDelItemProps(const NamedList& params, bool first) ++{ ++ static const String s_delItemProp = "delete_item_property:"; ++ if (first) { ++ m_delItemActionPrefix = params.getValue("delete_item_action"); ++ if (m_delItemActionPrefix) { ++ setDelItemType(params.getIntValue("delete_item_type",s_delItemDict,DelItemNone)); ++ if (m_delItemType != DelItemNone) ++ m_delItemActionPrefix << ":" << this->name() << ":"; ++ else ++ m_delItemActionPrefix.clear(); ++ } ++ } ++ if (m_delItemType == DelItemNone) ++ return; ++ unsigned int n = params.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = params.getParam(i); ++ if (!(ns && ns->name().startsWith(s_delItemProp))) ++ continue; ++ String prop = ns->name().substr(s_delItemProp.length()); ++ if (!prop) ++ continue; ++ m_delItemProps.setParam(prop,*ns); ++ // TODO: Apply the property to all delete item objects if changed ++ } ++} ++ ++// Apply delete item object properties ++void WidgetList::applyDelItemProps(QObject* obj) ++{ ++ if (!obj) ++ return; ++ unsigned int n = m_delItemProps.length(); ++ for (unsigned int i = 0; i < n; i++) { ++ NamedString* ns = m_delItemProps.getParam(i); ++ if (!ns) ++ continue; ++ DDebug(ClientDriver::self(),DebugAll, ++ "WidgetList(%s)::applyDelItemProps() %s=%s", ++ name().c_str(),ns->name().c_str(),ns->c_str()); ++ QtClient::setProperty(obj,ns->name(),*ns); ++ } ++} ++ ++/* ++ * WidgetListFactory ++ */ ++// Build objects ++void* WidgetListFactory::create(const String& type, const char* name, NamedList* params) ++{ ++ if (!params) ++ return 0; ++ QWidget* parentWidget = 0; ++ String* wndname = params->getParam("parentwindow"); ++ if (!TelEngine::null(wndname)) { ++ String* wName = params->getParam("parentwidget"); ++ QtWindow* wnd = static_cast(Client::self()->getWindow(*wndname)); ++ if (wnd && !TelEngine::null(wName)) ++ parentWidget = wnd->findChild(QtClient::setUtf8(*wName)); ++ } ++ if (type == "WidgetList") ++ return new WidgetList(name,*params,parentWidget); ++ return 0; ++} ++ ++}; // anonymous namespace ++ ++#include "widgetlist.moc" ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/modules/qt5/widgetlist.h b/modules/qt5/widgetlist.h +new file mode 100644 +index 0000000..9d65b82 +--- /dev/null ++++ b/modules/qt5/widgetlist.h +@@ -0,0 +1,430 @@ ++/** ++ * widgetlist.h ++ * This file is part of the YATE Project http://YATE.null.ro ++ * ++ * Custom widget list objects ++ * ++ * Yet Another Telephony Engine - a fully featured software PBX and IVR ++ * Copyright (C) 2004-2020 Null Team ++ * ++ * This software is distributed under multiple licenses; ++ * see the COPYING file in the main directory for licensing ++ * information for this specific distribution. ++ * ++ * This use of this software may be subject to additional restrictions. ++ * See the LEGAL file in the main directory for details. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ++ */ ++ ++#ifndef __WIDGETLIST_H ++#define __WIDGETLIST_H ++ ++#include "qt5client.h" ++ ++using namespace TelEngine; ++namespace { // anonymous ++ ++class WidgetListTabWidget; // A tab widget client of a widget list ++class WidgetListStackedWidget; // A stacked widget client of a widget list ++class WidgetList; // A widget list ++ ++class WidgetListTabWidget : public QTabWidget ++{ ++public: ++ /** ++ * Constructor ++ * @param parent WidgetList parent ++ */ ++ WidgetListTabWidget(WidgetList* parent, const NamedList& params); ++ ++ /** ++ * Set tab text color ++ * @param index Tab index ++ * @param color Tab text color ++ */ ++ inline void setTabTextColor(int index, QColor color) { ++ QTabBar* bar = tabBar(); ++ if (bar) ++ bar->setTabTextColor(index,color); ++ } ++ ++ /** ++ * Retrieve the tab text color ++ * @param index Tab index ++ * @return Text color of the given index ++ */ ++ inline QColor tabTextColor(int index) { ++ QTabBar* bar = tabBar(); ++ return bar ? bar->tabTextColor(index) : QColor(); ++ } ++ ++protected: ++ /** ++ * Build and set a close button for a given tab or a global close if index is negative ++ * Connect the button to parent's slot. ++ * This method is called from tabInserted() with non negative index ++ */ ++ void setCloseButton(int index = -1); ++ ++ /** ++ * Tab inserted. Set tab close button if needed ++ */ ++ virtual void tabInserted(int index); ++ ++ /** ++ * Tab removed. Notify the parent ++ */ ++ virtual void tabRemoved(int index); ++}; ++ ++class WidgetListStackedWidget : public QStackedWidget ++{ ++public: ++ /** ++ * Constructor ++ * @param parent WidgetList parent ++ */ ++ WidgetListStackedWidget(WidgetList* parent, const NamedList& params); ++}; ++ ++/** ++ * This class holds a basic widget list container ++ * @short A widget list ++ */ ++class WidgetList : public QtCustomWidget ++{ ++ friend class WidgetListTabWidget; ++ YCLASS(WidgetList,QtCustomWidget) ++ Q_CLASSINFO("WidgetList","Yate") ++ Q_OBJECT ++ Q_PROPERTY(bool _yate_hidewndwhenempty READ hideWndWhenEmpty WRITE setHideWndWhenEmpty(bool)) ++ Q_PROPERTY(QString _yate_hidewidgetwhenempty READ hideWidgetWhenEmpty WRITE setHideWidgetWhenEmpty(QString)) ++ Q_PROPERTY(QString _yate_itemui READ itemUi WRITE setItemUi(QString)) ++ Q_PROPERTY(QString _yate_itemstyle READ itemStyle WRITE setItemStyle(QString)) ++ Q_PROPERTY(QString _yate_itemtextparam READ itemTextParam WRITE setItemTextParam(QString)) ++ Q_PROPERTY(QString _yate_itemimageparam READ itemImageParam WRITE setItemImageParam(QString)) ++ Q_PROPERTY(QString _yate_flashitem READ flashItem WRITE setFlashItem(QString)) ++public: ++ /** ++ * Delete item button type ++ */ ++ enum DelItem { ++ DelItemNone = 0, // No delete item button ++ DelItemGlobal, // Global (delete selected) button ++ DelItemSingle, // Delete button on each item ++ DelItemNative, // Delete button on each item: use native if available ++ }; ++ ++ /** ++ * Constructor ++ * @param name Object name ++ * @param params Object parameters ++ * @param parent Optional parent ++ */ ++ WidgetList(const char* name, const NamedList& params, QWidget* parent); ++ ++ /** ++ * Find an item widget by index ++ * @param index Item index ++ * @return QWidget pointer or 0 ++ */ ++ QWidget* findItemByIndex(int index); ++ ++ /** ++ * Set widget parameters ++ * @param params Parameter list ++ * @return True on success ++ */ ++ virtual bool setParams(const NamedList& params); ++ ++ /** ++ * Get widget's items ++ * @param items List to fill with widget's items ++ * @return True ++ */ ++ virtual bool getOptions(NamedList& items); ++ ++ /** ++ * Retrieve item parameters ++ * @param item Item id ++ * @param data List to be filled with parameters ++ * @return True on success ++ */ ++ virtual bool getTableRow(const String& item, NamedList* data = 0); ++ ++ /** ++ * Add a new item ++ * @param item Item id ++ * @param data Item parameters ++ * @param asStart True to insert at start, false to append ++ * @return True on success ++ */ ++ virtual bool addTableRow(const String& item, const NamedList* data = 0, ++ bool atStart = false); ++ ++ /** ++ * Add/set/delete one or more item(s) ++ * @param data The list of items to add/set/delete ++ * @param atStart True to add new items at start, false to add them to the end ++ * @return True if the operation was successfull ++ */ ++ virtual bool updateTableRows(const NamedList* data, bool atStart = false); ++ ++ /** ++ * Delete an item ++ * @param item Item id ++ * @return True on success ++ */ ++ virtual bool delTableRow(const String& item); ++ ++ /** ++ * Set existing item parameters ++ * @param item Item id ++ * @param data Item parameters ++ * @return True on success ++ */ ++ virtual bool setTableRow(const String& item, const NamedList* data); ++ ++ /** ++ * Delete all items ++ * @return True on success ++ */ ++ virtual bool clearTable(); ++ ++ /** ++ * Select (set active) an item ++ * @param item Item id ++ * @return True on success ++ */ ++ virtual bool setSelect(const String& item); ++ ++ /** ++ * Retrieve the selected (active) item ++ * @param item Item id ++ * @return True on success ++ */ ++ virtual bool getSelect(String& item); ++ ++ /** ++ * Retrieve a QObject list containing tree item widgets ++ * @return The list of container item widgets ++ */ ++ virtual QList getContainerItems(); ++ ++ /** ++ * Select an item by its index ++ * @param index Item index to select ++ * @return True on success ++ */ ++ virtual bool setSelectIndex(int index); ++ ++ /** ++ * Retrieve the 0 based index of the current item ++ * @return The index of the current item (-1 on error or container empty) ++ */ ++ virtual int currentItemIndex(); ++ ++ /** ++ * Retrieve the number of items in container ++ * @return The number of items in container (-1 on error) ++ */ ++ virtual int itemCount(); ++ ++ /** ++ * Retrieve _yate_hidewndwhenempty property value ++ * @return _yate_hidewndwhenempty property value ++ */ ++ bool hideWndWhenEmpty() ++ { return m_hideWndWhenEmpty; } ++ ++ /** ++ * Set _yate_hidewndwhenempty property value. Apply it if changed ++ * @param value The new value of _yate_hidewndwhenempty property ++ */ ++ void setHideWndWhenEmpty(bool value); ++ ++ /** ++ * Retrieve _yate_hidewidgetwhenempty property value ++ * @return _yate_hidewidgetwhenempty property value ++ */ ++ QString hideWidgetWhenEmpty() ++ { return QtClient::setUtf8(m_hideWidgetWhenEmpty); } ++ ++ /** ++ * Set _yate_hidewidgetwhenempty property value. Apply it if changed ++ * @param value The new value of _yate_hidewidgetwhenempty property ++ */ ++ void setHideWidgetWhenEmpty(QString value); ++ ++ /** ++ * Read _yate_itemui property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemUi() ++ { return QString(); } ++ ++ /** ++ * Set an item props ui ++ * @param value Item props ui. Format [type:]ui_name ++ */ ++ void setItemUi(QString value) { ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_ui = tmp; ++ } ++ ++ /** ++ * Read _yate_itemstyle property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString itemStyle() ++ { return QString(); } ++ ++ /** ++ * Set an item props style sheet ++ * @param value Item props style sheet. Format [type:]stylesheet ++ */ ++ void setItemStyle(QString value) { ++ String tmp; ++ QtUIWidgetItemProps* p = getItemProps(value,tmp); ++ p->m_styleSheet = tmp; ++ } ++ ++ /** ++ * Retrieve _yate_itemtextparam property value ++ * @return The value of _yate_itemtextparam property ++ */ ++ QString itemTextParam() ++ { return QtClient::setUtf8(m_itemTextParam); } ++ ++ /** ++ * Set _yate_itemtextparam property value ++ * @param value The new value of _yate_itemtextparam property ++ */ ++ void setItemTextParam(QString value) ++ { QtClient::getUtf8(m_itemTextParam,value); } ++ ++ /** ++ * Retrieve _yate_itemimageparam property value ++ * @return The value of _yate_itemimageparam property ++ */ ++ QString itemImageParam() ++ { return QtClient::setUtf8(m_itemImgParam); } ++ ++ /** ++ * Set _yate_itemimageparam property value ++ * @param value The new value of _yate_itemimageparam property ++ */ ++ void setItemImageParam(QString value) ++ { QtClient::getUtf8(m_itemImgParam,value); } ++ ++ /** ++ * Read _yate_flashitem property accessor: does nothing ++ * This method is here to stop MOC compiler complaining about missing READ accessor function ++ */ ++ QString flashItem() ++ { return QString(); } ++ ++ /** ++ * Start/stop item flash ++ * @param value Item value. Format bool_value:item_id ++ */ ++ void setFlashItem(QString value); ++ ++public slots: ++ /** ++ * Handle item children actions ++ */ ++ void itemChildAction() ++ { onAction(sender()); } ++ ++ /** ++ * Handle item children toggles ++ */ ++ void itemChildToggle(bool on) ++ { onToggle(sender(),on); } ++ ++ /** ++ * Handle selection changes ++ */ ++ void currentChanged(int index); ++ ++ /** ++ * Item removed slot. Notify the client when empty ++ */ ++ void itemRemoved(int index); ++ ++ /** ++ * Handle item children select ++ */ ++ void itemChildSelect() ++ { onSelect(sender()); } ++ ++ /** ++ * Handle item close action ++ */ ++ void closeItem(int index = -1); ++ ++protected: ++ /** ++ * Handle children events ++ */ ++ virtual bool eventFilter(QObject* watched, QEvent* event); ++ ++ /** ++ * Hide the parent window or widget if the container is empty ++ */ ++ void hideEmpty(); ++ ++ /** ++ * Insert/add a widget item ++ * @param w Widget to append or insert (it will be deleted and reset on failure) ++ * @param atStart True to insert, false to add ++ * @return True on success ++ */ ++ bool addItem(QWidget*& w, bool atStart); ++ ++ /** ++ * Retrieve the selected item widget ++ * @return QWidget pointer or 0 ++ */ ++ QWidget* selectedItem(); ++ ++ /** ++ * Set delete item type ++ * @param type The new delete item type ++ */ ++ void setDelItemType(int type); ++ ++ /** ++ * Retrieve delete item object properties ++ * @param params Parameter list ++ * @param first True if called from constructor: update delete item type also ++ */ ++ void updateDelItemProps(const NamedList& params, bool first = false); ++ ++ /** ++ * Apply delete item object properties ++ * @param obj The object ++ */ ++ void applyDelItemProps(QObject* obj); ++ ++ bool m_hideWndWhenEmpty; // Hide the parent window when the container is empty ++ String m_hideWidgetWhenEmpty; // Widget to hide when the container is empty ++ WidgetListTabWidget* m_tab; // Tab widget if used ++ WidgetListStackedWidget* m_pages; // Stacked widget if used ++ int m_delItemType; // Delete item type ++ NamedList m_delItemProps; // Delete item widget properties ++ String m_delItemActionPrefix; // Delete item action prefix ++ String m_itemTextParam; // Hook this parameter to set item text ++ String m_itemImgParam; // Hook this parameter to set item image ++}; ++ ++}; // anonymous namespace ++ ++#endif // __WIDGETLIST_H ++ ++/* vi: set ts=8 sw=4 sts=4 noet: */ +diff --git a/share/skins/default/qt5client.rc b/share/skins/default/qt5client.rc +new file mode 100644 +index 0000000..59811da +--- /dev/null ++++ b/share/skins/default/qt5client.rc +@@ -0,0 +1,170 @@ ++; If you want to create windows which have as a parent another window, in the section for each child window ++; add the parameter "parent=" with the name of the parent window. ++; ATTENTION! Keep in mind that the parent window should be created before the child window. ++; This means that here you should list the windows in a order similar to the example bellow: ++; ++; [parentwindow_1] ++; description=... ++; [childwindow_1] ++; description=... ++; parent=parentwindow_1 ++; [childwindow_2] ++; description=... ++; parent=parentwindow_1 ++; ++; [parentwindow_2] ++; description=... ++; [childwindow_3] ++; description=... ++; parent=parentwindow_2 ++ ++ ++[mainwindow] ++enabled=true ++visible=true ++mainwindow=true ++description=qt5client.ui ++ ++[settings] ++enabled=yes ++description=settings.ui ++ ++[events] ++enabled=true ++description=events.ui ++ ++[help] ++enabled=true ++description=help.ui ++ ++[confirm] ++enabled=true ++description=confirm.ui ++ ++[message] ++enabled=true ++description=message.ui ++ ++[input] ++enabled=false ++description=input.ui ++ ++[inputpwd] ++enabled=false ++save=false ++description=inputpwd.ui ++ ++[inputacccred] ++enabled=false ++save=false ++description=inputacccred.ui ++ ++[account] ++enabled=true ++description=account.ui ++ ++[accountlist] ++enabled=true ++description=accountlist.ui ++ ++[accountwizard] ++enabled=true ++description=accountwizard.ui ++ ++[addrbook] ++enabled=true ++description=addrbook.ui ++ ++[chat] ++enabled=no ++save=false ++description=chat.ui ++ ++[dockedchat] ++enabled=yes ++description=dockedchat.ui ++ ++[mucs] ++enabled=yes ++description=mucs.ui ++ ++[mucchat] ++enabled=no ++description=mucchat.ui ++ ++[mucinvite] ++enabled=yes ++visible=false ++description=mucinvite.ui ++ ++[mucprivchat] ++enabled=no ++description=mucprivchat.ui ++ ++[joinmucwizard] ++enabled=true ++savealias=false ++description=joinmucwizard.ui ++ ++[contactedit] ++enabled=no ++save=false ++description=contactedit.ui ++ ++[chatroomedit] ++enabled=no ++save=false ++description=chatroomedit.ui ++ ++[contactinfo] ++enabled=no ++save=false ++description=contactinfo.ui ++ ++[contactfs] ++enabled=no ++save=false ++description=contactfs.ui ++ ++[contactfsd] ++enabled=no ++save=false ++description=contactfsd.ui ++ ++[messages_header] ++enabled=no ++description=messages_header.ui ++ ++[messages_generic] ++enabled=no ++description=messages_generic.ui ++ ++[messages_okrejignore] ++enabled=no ++description=messages_okrejignore.ui ++ ++[messages_loginfail] ++enabled=no ++description=messages_loginfail.ui ++ ++[fileprogress] ++enabled=yes ++description=fileprogress.ui ++ ++[fileprogress_item] ++enabled=no ++description=fileprogress_item.ui ++ ++[archive] ++enabled=yes ++visible=false ++description=archive.ui ++ ++[busy] ++enabled=no ++description=busy.ui ++ ++[notification] ++enabled=yes ++save=false ++description=notification.ui +diff --git a/share/skins/default/qt5client.ui b/share/skins/default/qt5client.ui +new file mode 100644 +index 0000000..6c66c45 +--- /dev/null ++++ b/share/skins/default/qt5client.ui +@@ -0,0 +1,3184 @@ ++ ++ mainwindow ++ ++ ++ ++ 0 ++ 0 ++ 340 ++ 520 ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 300 ++ 520 ++ ++ ++ ++ Qt::StrongFocus ++ ++ ++ Yate Client ++ ++ ++ null_team-32.png ++ ++ ++ Yate Client - Telephony Client ++ ++ ++ QWidget#mainwindow { ++ background:#f7f5fd; ++} ++ ++ ++ ++ ++ true ++ ++ ++ true ++ ++ ++ ++ 0 ++ ++ ++ 2 ++ ++ ++ 0 ++ ++ ++ 2 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ &Yate ++ ++ ++ true ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ &Settings ++ ++ ++ true ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ S&tatus ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ &Friends ++ ++ ++ true ++ ++ ++ ++ Subscription ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 6 ++ ++ ++ ++ ++ 16777215 ++ 6 ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 200 ++ ++ ++ ++ QTabWidget::pane { ++ border: 0px solid #717fa0; ++ border-top: 1px solid #717fa0; ++} ++ ++ ++ ++ QTabWidget::Rounded ++ ++ ++ 1 ++ ++ ++ ++ 20 ++ 20 ++ ++ ++ ++ ++ Chat ++ ++ ++ chat_tab.png ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ QFrame#frame_chat_contacts { ++ border: 1px solid #717fa0; ++ border-top: 0px solid #717fa0; ++} ++ ++ ++ ++ QFrame::StyledPanel ++ ++ ++ true ++ ++ ++ chat_contacts ++ ++ ++ ContactList ++ ++ ++ ++ columns=name ++ htmldelegate=name ++ delegateparam.name.drawfocus=false ++ property:_yate_save_props=_yate_flatlist,_yate_showofflinecontacts,_yate_hideemptygroups,_yate_itemsexpstatus ++ property:_yate_flatlist=false ++ property:_yate_showofflinecontacts=true ++ property:_yate_hideemptygroups=true ++ property:_yate_sorting=name,true ++ property:_yate_horizontalheader=false ++ property:itemsExpandable=true ++ property:autoExpand=true ++ property:allColumnsShowFocus=false ++ property:indentation=0 ++ property:_yate_nogroup_caption=Not set ++ property:_yate_comparecase=false ++ property:_yate_itemheight=contact:40 ++ property:_yate_itemheight=group:22 ++ property:_yate_itemheight=chatroom:40 ++ property:_yate_itemstyle=contact:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:13px;">${name}</span><br>${status_text}</br></p></body></html> ++ property:_yate_itemstyle=group:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14px;">${name}${statistics}</span></p></body></html> ++ property:_yate_itemstyle=chatroom:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:13px;">${name}</span><br>${status_text}</br></p></body></html> ++ property:_yate_itemselectedstyle=contact:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" color:white; font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:13px;">${name}</span><br>${status_text}</br></p></body></html> ++ property:_yate_itemselectedstyle=group:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14px;">${name}${statistics}</span></p></body></html> ++ property:_yate_itemselectedstyle=chatroom:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" color:white; font-size:10px; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:13px;">${name}</span><br>${status_text}</br></p></body></html> ++ property:_yate_itembackground=group:color:white ++ property:_yate_itemstatewidget=group:name ++ property:_yate_itemexpandedimage=group:expanded.png ++ property:_yate_itemcollapsedimage=group:collapsed.png ++ property:_yate_itemstatstemplate=group: (${online}/${total}) ++ property:_yate_itemtooltip=contact:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">${name}</span></p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${status_text}</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${contact}</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Account: ${account}</p></body></html> ++ property:_yate_itemtooltip=group:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">${name}</span></p></body></html> ++ property:_yate_itemtooltip=chatroom:<html><head><meta name="qrichtext" content="1" /><style type="text/css">\np, li { white-space: pre-wrap; }\n</style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">${name}</span></p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${status_text}</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">${contact}</p><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Account: ${account}</p></body></html> ++ property:_yate_itemmargins=contact:20 ++ property:_yate_itemmargins=chatroom:20 ++ property:_yate_itempaintbutton=contact:share_file ++ property:_yate_itempaintbuttonparam=contact:share_file:_yate_size:20 ++ property:_yate_itempaintbuttonparam=contact:share_file:_yate_iconsize:20 ++ property:_yate_itempaintbuttonparam=contact:share_file:_yate_normal_icon:sharefile_20.png ++ property:_yate_itempaintbuttonparam=contact:share_file:_yate_pressed_icon:sharefile_pressed_20.png ++ property:_yate_itempaintbuttonparam=contact:share_file:_yate_hover_icon:sharefile_hover_20.png ++ property:_yate_itempaintbutton=contact:shared_file ++ property:_yate_itempaintbuttonparam=contact:shared_file:_yate_size:20 ++ property:_yate_itempaintbuttonparam=contact:shared_file:_yate_iconsize:20 ++ property:_yate_itempaintbuttonparam=contact:shared_file:_yate_normal_icon:sharedfile_20.png ++ property:_yate_itempaintbuttonparam=contact:shared_file:_yate_pressed_icon:sharedfile_pressed_20.png ++ property:_yate_itempaintbuttonparam=contact:shared_file:_yate_hover_icon:sharedfile_hover_20.png ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Telephony ++ ++ ++ phone_tab.png ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 50 ++ ++ ++ ++ ++ 16777215 ++ 50 ++ ++ ++ ++ QFrame#frameButtons { ++ border: 1px solid #717fa0; ++ border-top: 0px; ++ border-bottom: 0px; ++} ++ ++ ++ ++ 1 ++ ++ ++ 4 ++ ++ ++ 4 ++ ++ ++ 4 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 40 ++ 46 ++ ++ ++ ++ ++ 40 ++ 46 ++ ++ ++ ++ Calls ++ ++ ++ QToolButton { ++ border: 0px solid #717fa0; ++ border-radius: 0px; ++ font-size: 11px; ++ background: transparent; ++} ++QToolButton:checked { ++ padding-top: 0px; ++ padding-left: 0px; ++} ++ ++ ++ ++ Calls ++ ++ ++ calls_tab.png ++ ++ ++ ++ 26 ++ 26 ++ ++ ++ ++ true ++ ++ ++ true ++ ++ ++ true ++ ++ ++ Qt::ToolButtonTextUnderIcon ++ ++ ++ true ++ ++ ++ selectitem:framePages:PageCalls ++ ++ ++ calls_tab.png ++ ++ ++ calls_tab_pressed.png ++ ++ ++ calls_tab_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 46 ++ 46 ++ ++ ++ ++ ++ 46 ++ 46 ++ ++ ++ ++ History ++ ++ ++ QToolButton { ++ border: 0px solid #717fa0; ++ border-radius: 0px; ++ font-size: 11px; ++ background: transparent; ++} ++QToolButton:checked { ++ padding-top: 0px; ++ padding-left: 0px; ++} ++ ++ ++ ++ History ++ ++ ++ cdr_tab.png ++ ++ ++ ++ 26 ++ 26 ++ ++ ++ ++ true ++ ++ ++ false ++ ++ ++ true ++ ++ ++ Qt::ToolButtonTextUnderIcon ++ ++ ++ true ++ ++ ++ selectitem:framePages:PageCDR ++ ++ ++ cdr_tab.png ++ ++ ++ cdr_tab_pressed.png ++ ++ ++ cdr_tab_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 50 ++ 46 ++ ++ ++ ++ ++ 50 ++ 46 ++ ++ ++ ++ Address Book ++ ++ ++ QToolButton { ++ border: 0px solid #717fa0; ++ border-radius: 0px; ++ font-size: 11px; ++ background: transparent; ++} ++QToolButton:checked { ++ padding-top: 0px; ++ padding-left: 0px; ++} ++ ++ ++ ++ Contacts ++ ++ ++ contacts_tab.png ++ ++ ++ ++ 26 ++ 26 ++ ++ ++ ++ true ++ ++ ++ false ++ ++ ++ true ++ ++ ++ Qt::ToolButtonTextUnderIcon ++ ++ ++ true ++ ++ ++ selectitem:framePages:PageContacts ++ ++ ++ contacts_tab.png ++ ++ ++ contacts_tab_pressed.png ++ ++ ++ contacts_tab_hover.png ++ ++ ++ ++ ++ ++ ++ Qt::Horizontal ++ ++ ++ ++ 40 ++ 20 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ 0 ++ 200 ++ ++ ++ ++ 0 ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 12 ++ ++ ++ ++ ++ 16777215 ++ 12 ++ ++ ++ ++ border: 1px solid #717fa0; ++ border-bottom: 0px; ++ border-top: 0px; ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 15 ++ 8 ++ ++ ++ ++ ++ 15 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ border: 0px; ++ ++ ++ ++ ++ ++ pointer.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 8 ++ ++ ++ ++ ++ 16777215 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ QFrame#frame { ++ border: 1px solid #717fa0; ++ border-top: 0px; ++} ++ ++ ++ ++ 6 ++ ++ ++ 8 ++ ++ ++ 4 ++ ++ ++ 8 ++ ++ ++ 6 ++ ++ ++ ++ ++ 2 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 80 ++ 16 ++ ++ ++ ++ ++ 80 ++ 16777215 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 80 ++ 0 ++ ++ ++ ++ ++ 80 ++ 16777215 ++ ++ ++ ++ Protocol ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 80 ++ 25 ++ ++ ++ ++ ++ 80 ++ 25 ++ ++ ++ ++ VoIP protocol used to make a direct call ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ Account ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Account used to call through a server ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ ++ ++ ++ ++ 2 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 24 ++ 24 ++ ++ ++ ++ ++ 24 ++ 24 ++ ++ ++ ++ Show or hide the keypad ++ ++ ++ dialpad_20.png ++ ++ ++ ++ 20 ++ 20 ++ ++ ++ ++ true ++ ++ ++ dialpad_20.png ++ ++ ++ dialpad_20_hover.png ++ ++ ++ dialpad_20_pressed.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 24 ++ ++ ++ ++ ++ 16777215 ++ 24 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 24 ++ ++ ++ ++ ++ 16777215 ++ 24 ++ ++ ++ ++ Qt::WheelFocus ++ ++ ++ true ++ ++ ++ true ++ ++ ++ 20 ++ ++ ++ call ++ ++ ++ call ++ ++ ++ true ++ ++ ++ true ++ ++ ++ true ++ ++ ++ true ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 24 ++ ++ ++ ++ ++ 16777215 ++ 24 ++ ++ ++ ++ Make a new call ++ ++ ++ Call ++ ++ ++ call.png ++ ++ ++ ++ 20 ++ 20 ++ ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ call_hover.png ++ ++ ++ call.png ++ ++ ++ call_pressed.png ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ font-size: 10px; ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 100 ++ ++ ++ ++ ++ 16777215 ++ 100 ++ ++ ++ ++ ++ 2 ++ ++ ++ 2 ++ ++ ++ ++ ++ 1 ++ ++ ++ 1 ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit1.png);} ++QToolButton::hover {image:url(digit1_hover.png);} ++QToolButton::hover:pressed {image:url(digit1_pressed.png);} ++QToolButton:pressed {image:url(digit1.png);} ++QToolButton:!enabled {image:url(digit1.png);} ++ ++ ++ ++ digit:1 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit2.png);} ++QToolButton::hover {image:url(digit2_hover.png);} ++QToolButton::hover:pressed {image:url(digit2_pressed.png);} ++QToolButton:pressed {image:url(digit2.png);} ++QToolButton:!enabled {image:url(digit2.png);} ++ ++ ++ ++ digit:2 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit3.png);} ++QToolButton::hover {image:url(digit3_hover.png);} ++QToolButton::hover:pressed {image:url(digit3_pressed.png);} ++QToolButton:pressed {image:url(digit3.png);} ++QToolButton:!enabled {image:url(digit3.png);} ++ ++ ++ ++ digit:3 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit4.png);} ++QToolButton::hover {image:url(digit4_hover.png);} ++QToolButton::hover:pressed {image:url(digit4_pressed.png);} ++QToolButton:pressed {image:url(digit4.png);} ++QToolButton:!enabled {image:url(digit4.png);} ++ ++ ++ ++ digit:4 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit5.png);} ++QToolButton::hover {image:url(digit5_hover.png);} ++QToolButton::hover:pressed {image:url(digit5_pressed.png);} ++QToolButton:pressed {image:url(digit5.png);} ++QToolButton:!enabled {image:url(digit5.png);} ++ ++ ++ ++ digit:5 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit6.png);} ++QToolButton::hover {image:url(digit6_hover.png);} ++QToolButton::hover:pressed {image:url(digit6_pressed.png);} ++QToolButton:pressed {image:url(digit6.png);} ++QToolButton:!enabled {image:url(digit6.png);} ++ ++ ++ ++ digit:6 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit7.png);} ++QToolButton::hover {image:url(digit7_hover.png);} ++QToolButton::hover:pressed {image:url(digit7_pressed.png);} ++QToolButton:pressed {image:url(digit7.png);} ++QToolButton:!enabled {image:url(digit7.png);} ++ ++ ++ ++ digit:7 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit8.png);} ++QToolButton::hover {image:url(digit8_hover.png);} ++QToolButton::hover:pressed {image:url(digit8_pressed.png);} ++QToolButton:pressed {image:url(digit8.png);} ++QToolButton:!enabled {image:url(digit8.png);} ++ ++ ++ ++ digit:8 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit9.png);} ++QToolButton::hover {image:url(digit9_hover.png);} ++QToolButton::hover:pressed {image:url(digit9_pressed.png);} ++QToolButton:pressed {image:url(digit9.png);} ++QToolButton:!enabled {image:url(digit9.png);} ++ ++ ++ ++ digit:9 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digitstar.png);} ++QToolButton::hover {image:url(digitstar_hover.png);} ++QToolButton::hover:pressed {image:url(digitstar_pressed.png);} ++QToolButton:pressed {image:url(digitstar.png);} ++QToolButton:!enabled {image:url(digitstar.png);} ++ ++ ++ ++ digit:* ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digit0.png);} ++QToolButton::hover {image:url(digit0_hover.png);} ++QToolButton::hover:pressed {image:url(digit0_pressed.png);} ++QToolButton:pressed {image:url(digit0.png);} ++QToolButton:!enabled {image:url(digit0.png);} ++ ++ ++ ++ digit:0 ++ ++ ++ ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ ++ 42 ++ 23 ++ ++ ++ ++ QToolButton {border: 0px; image:url(digitpound.png);} ++QToolButton::hover {image:url(digitpound_hover.png);} ++QToolButton::hover:pressed {image:url(digitpound_pressed.png);} ++QToolButton:pressed {image:url(digitpound.png);} ++QToolButton:!enabled {image:url(digitpound.png);} ++ ++ ++ ++ digit:# ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 8 ++ ++ ++ ++ ++ 16777215 ++ 8 ++ ++ ++ ++ QFrame { ++ background:#f7f5fd; ++} ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ QFrame#frame_channels{ ++ border: 1px solid #717fa0; ++} ++ ++ ++ ++ true ++ ++ ++ channels ++ ++ ++ QtCustomTree ++ ++ ++ ++ vertical_scroll_policy=pixel ++ property:_yate_horizontalheader=false ++ property:itemsExpandable=false ++ property:rootIsDecorated=false ++ property:allColumnsShowFocus=false ++ property:styleSheet=QTreeWidget {background: #f3faff;} ++ property:_yate_itemui=channel_item.ui ++ property:_yate_itemstyle=:QWidget#${name}{background: #f3faff;} QFrame#${name}_frame{background: #ffffff; border: 1px solid #717fa0;} ++ property:_yate_itemheight=86 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ QFrame#frame_page_log { ++ border: 0px solid #717fa0; ++ border-top: 0px solid #717fa0; ++} ++ ++ ++ ++ QFrame::StyledPanel ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 12 ++ ++ ++ ++ ++ 16777215 ++ 12 ++ ++ ++ ++ border: 1px solid #717fa0; ++ border-bottom: 0px; ++ border-top: 0px; ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 60 ++ 8 ++ ++ ++ ++ ++ 60 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ border: 0px; ++ ++ ++ ++ ++ ++ pointer.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 8 ++ ++ ++ ++ ++ 16777215 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ QFrame#frame_log { ++ border: 1px solid #717fa0; ++ border-bottom: 0px solid #717fa0; ++} ++ ++ ++ ++ true ++ ++ ++ log ++ ++ ++ QtCustomTree ++ ++ ++ ++ property:_yate_save_props=_yate_sorting,_yate_col_widths ++ property:tabKeyNavigation=false ++ property:sortingEnabled=true ++ property:allColumnsShowFocus=true ++ property:_yate_horizontalheader=true ++ property:_yate_notifyonenterpressed=true ++ property:_yate_notifyitemchanged=true ++ property:_yate_itemheight=0 ++ property:_yate_sorting=time,false ++ property:_yate_itemheight=20 ++ columns=Enabled,Party,Time,Duration ++ columns.check=enabled ++ columns.width=30 ++ columns.resize=fixed ++ columns.allowemptytitle=enabled ++ griddraw_right_color=#e3e6e9 ++ griddraw_bottom_color=#a9c2c2 ++ ++ ++ ++ ++ ++ ++ ++ QFrame#frame_log_buttons { ++ border: 1px solid #717fa0; ++} ++ ++ ++ ++ QFrame::StyledPanel ++ ++ ++ ++ 4 ++ ++ ++ 4 ++ ++ ++ ++ ++ false ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Call back this number ++ ++ ++ Call ++ ++ ++ call.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ call.png ++ ++ ++ call_pressed.png ++ ++ ++ call_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Clear all calls log ++ ++ ++ Clear ++ ++ ++ clear.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ clear:log: ++ ++ ++ clear.png ++ ++ ++ clear_pressed.png ++ ++ ++ clear_hover.png ++ ++ ++ ++ ++ ++ ++ false ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Delete the selected call log ++ ++ ++ Delete ++ ++ ++ delete.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ deletecheckeditems:log: ++ ++ ++ delete.png ++ ++ ++ delete_pressed.png ++ ++ ++ delete_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Turn this number into a contact ++ ++ ++ Contact ++ ++ ++ add.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ add.png ++ ++ ++ add_pressed.png ++ ++ ++ add_hover.png ++ ++ ++ ++ ++ ++ ++ Qt::Horizontal ++ ++ ++ ++ 40 ++ 20 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ QFrame#frame_page_contacts { ++ border: 0px solid #717fa0; ++ border-left: 1px solid #717fa0; ++ border-right: 1px solid #717fa0; ++} ++ ++ ++ ++ QFrame::StyledPanel ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 12 ++ ++ ++ ++ ++ 16777215 ++ 12 ++ ++ ++ ++ border: 0px solid #717fa0; ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 106 ++ 8 ++ ++ ++ ++ ++ 106 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ ++ 18 ++ 8 ++ ++ ++ ++ border: 0px; ++ ++ ++ ++ ++ ++ pointer.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 8 ++ ++ ++ ++ ++ 16777215 ++ 8 ++ ++ ++ ++ border: 0px; ++border-bottom: 1px solid #717fa0; ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 4 ++ ++ ++ 6 ++ ++ ++ 6 ++ ++ ++ 6 ++ ++ ++ 6 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 25 ++ 25 ++ ++ ++ ++ ++ 25 ++ 25 ++ ++ ++ ++ Create new contact ++ ++ ++ New ++ ++ ++ add.png ++ ++ ++ Qt::ToolButtonIconOnly ++ ++ ++ add.png ++ ++ ++ add_pressed.png ++ ++ ++ add_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ 0 ++ ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ QLineEdit { ++ background-image: url(search.png); ++ background-repeat: no-repeat; ++ background-position: left; ++ padding-left: 20px; ++} ++ ++ ++ ++ true ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ QFrame#frame_contacts { ++ border: 0px; ++ border-top: 1px solid #717fa0; ++ border-bottom: 1px solid #717fa0; ++} ++ ++ ++ ++ true ++ ++ ++ contacts ++ ++ ++ QtCustomTree ++ ++ ++ ++ property:_yate_save_props=_yate_sorting,_yate_col_widths ++ property:tabKeyNavigation=false ++ property:sortingEnabled=true ++ property:allColumnsShowFocus=true ++ property:_yate_horizontalheader=true ++ property:_yate_notifyonenterpressed=true ++ property:_yate_notifyitemchanged=true ++ property:_yate_itemheight=0 ++ property:_yate_sorting=name,true ++ property:_yate_itemheight=20 ++ columns=Enabled,Name,Number/URI ++ columns.check=enabled ++ columns.width=30 ++ columns.resize=fixed ++ columns.allowemptytitle=enabled ++ griddraw_right_color=#e3e6e9 ++ griddraw_bottom_color=#a9c2c2 ++ ++ ++ ++ ++ ++ ++ ++ QFrame#frame_contacts_buttons { ++ border: 0px solid #717fa0; ++ border-bottom: 1px solid #717fa0; ++} ++ ++ ++ ++ QFrame::StyledPanel ++ ++ ++ ++ 4 ++ ++ ++ 4 ++ ++ ++ ++ ++ false ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Call this contact ++ ++ ++ Call ++ ++ ++ call.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ call.png ++ ++ ++ call_pressed.png ++ ++ ++ call_hover.png ++ ++ ++ ++ ++ ++ ++ false ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Delete contact(s) ++ ++ ++ Delete ++ ++ ++ delete.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ deletecheckeditems:contacts: ++ ++ ++ delete.png ++ ++ ++ delete_pressed.png ++ ++ ++ delete_hover.png ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ ++ 16777215 ++ 25 ++ ++ ++ ++ Edit this contact ++ ++ ++ Edit ++ ++ ++ edit.png ++ ++ ++ Qt::ToolButtonTextBesideIcon ++ ++ ++ edit.png ++ ++ ++ edit_pressed.png ++ ++ ++ edit_hover.png ++ ++ ++ ++ ++ ++ ++ Qt::Horizontal ++ ++ ++ ++ 40 ++ 20 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 110 ++ ++ ++ ++ ++ 16777215 ++ 110 ++ ++ ++ ++ QScrollBar { ++ border: 1px solid #717fa0; ++ background: white; ++} ++QScrollBar::handle { ++ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff, stop: 1 #f6e8e8); ++ border: 1px solid #717fa0; ++} ++QScrollBar:vertical { ++ width: 14px; ++ margin: 16px 0px 16px 0px; ++ border-top: 0px; ++ border-bottom: 0px; ++} ++QScrollBar:horizontal { ++ height: 14px; ++ margin: 0px 16px 0px 16px; ++ border-left: 0px; ++ border-right: 0px; ++} ++QScrollBar::add-line { ++ height: 14px; ++ width: 14px; ++ border: 1px solid #717fa0; ++ subcontrol-origin: margin; ++ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff, stop: 1 #f6e8e8); ++} ++QScrollBar::sub-line { ++ height: 14px; ++ width: 14px; ++ border: 1px solid #717fa0; ++ subcontrol-origin: margin; ++ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffffff, stop: 1 #f6e8e8); ++} ++QScrollBar::handle:vertical { border-left: 0px; border-right: 0px; } ++QScrollBar::handle:horizontal { border-top: 0px; border-bottom: 0px; } ++QScrollBar::add-line:vertical { subcontrol-position: bottom; image: url(scroll_down.png); height: 14px; } ++QScrollBar::sub-line:vertical { subcontrol-position: top; image: url(scroll_up.png); height: 14px; } ++QScrollBar::add-line:horizontal { subcontrol-position: right; image: url(scroll_right.png); width: 14px; } ++QScrollBar::sub-line:horizontal { subcontrol-position: left; image: url(scroll_left.png); width: 14px; } ++QScrollBar::up-arrow:pressed { border: 1px solid #717fa0; } ++QScrollBar::down-arrow:pressed { border: 1px solid #717fa0; } ++QScrollBar::left-arrow:pressed { border: 1px solid #717fa0; } ++QScrollBar::right-arrow:pressed { border: 1px solid #717fa0; } ++ ++ ++QLabel { ++ font-size: 11px; ++} ++QTextEdit { ++ border: 0px; ++ font-size: 11px; ++} ++ ++ ++ ++ true ++ ++ ++ messages ++ ++ ++ WidgetList ++ ++ ++ ++ type=pages ++ header=messages_header.ui ++ navigate_prev=messages_prev ++ navigate_next=messages_next ++ navigate_info=messages_counter ++ navigate_info_format=${index}/${count} ++ navigate_title=messages_title ++ delete_item_action=deleteitem ++ property:_yate_hidewndwhenempty=false ++ property:_yate_hidewidgetwhenempty=frame_messages ++ property:_yate_itemui=generic:messages_generic.ui ++ property:_yate_itemui=subscription:messages_okrejignore.ui ++ property:_yate_itemui=mucinvite:messages_okrejignore.ui ++ property:_yate_itemui=loginfail:messages_loginfail.ui ++ property:_yate_itemui=incomingcall:messages_okrejignore.ui ++ property:_yate_itemui=incomingfile:messages_okrejignore.ui ++ property:_yate_itemui=rosterreqfail:messages_generic.ui ++ property:_yate_itemui=noaudio:messages_generic.ui ++ property:_yate_itemui=contactupdatefail:messages_generic.ui ++ property:_yate_itemui=contactremovefail:messages_generic.ui ++ ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 16 ++ 32 ++ ++ ++ ++ ++ 16777215 ++ 32 ++ ++ ++ ++ ++ 4 ++ ++ ++ 2 ++ ++ ++ 4 ++ ++ ++ 2 ++ ++ ++ 4 ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 26 ++ 0 ++ ++ ++ ++ ++ 26 ++ 16777215 ++ ++ ++ ++ status_offline.png ++ ++ ++ QToolButton::InstantPopup ++ ++ ++ true ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 0 ++ 25 ++ ++ ++ ++ Yate ++ ++ ++ ++ ++ ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 26 ++ 0 ++ ++ ++ ++ ++ 26 ++ 16777215 ++ ++ ++ ++ Show or hide the Help window ++ ++ ++ quest.png ++ ++ ++ true ++ ++ ++ showwindow:help:0 ++ ++ ++ quest.png ++ ++ ++ quest_pressed.png ++ ++ ++ quest_hover.png ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ quit.png ++ ++ ++ Quit ++ ++ ++ QAction::QuitRole ++ ++ ++ ++ ++ addaccount_menu.png ++ ++ ++ Add account ++ ++ ++ ++ ++ user_menu.png ++ ++ ++ Accounts ++ ++ ++ ++ ++ configure_menu.png ++ ++ ++ Options ++ ++ ++ QAction::PreferencesRole ++ ++ ++ ++ ++ chat_menu.png ++ ++ ++ Chat ++ ++ ++ ++ ++ Request subscription ++ ++ ++ ++ ++ Request subscription removal ++ ++ ++ ++ ++ Remove subscription ++ ++ ++ ++ ++ phone_menu.png ++ ++ ++ Call ++ ++ ++ ++ ++ add_menu.png ++ ++ ++ Add ++ ++ ++ ++ ++ edit_menu.png ++ ++ ++ Edit ++ ++ ++ Edit contact ++ ++ ++ ++ ++ delete_menu.png ++ ++ ++ Remove ++ ++ ++ Remove contact ++ ++ ++ ++ ++ true ++ ++ ++ true ++ ++ ++ Show offline friends ++ ++ ++ setparams:property:chat_contacts:_yate_showofflinecontacts ++ ++ ++ ++ ++ true ++ ++ ++ Flat list ++ ++ ++ setparams:property:chat_contacts:_yate_flatlist ++ ++ ++ ++ ++ status_online_menu.png ++ ++ ++ Online ++ ++ ++ ++ ++ status_busy_menu.png ++ ++ ++ Busy ++ ++ ++ ++ ++ status_away_menu.png ++ ++ ++ Away ++ ++ ++ ++ ++ status_xa_menu.png ++ ++ ++ Extended away ++ ++ ++ ++ ++ status_dnd_menu.png ++ ++ ++ Do Not Disturb ++ ++ ++ ++ ++ status_offline_menu.png ++ ++ ++ Offline ++ ++ ++ ++ ++ muc_menu.png ++ ++ ++ Join chat room ++ ++ ++ ++ ++ addaccountwiz_menu.png ++ ++ ++ Add account wizard ++ ++ ++ ++ ++ notif_menu.png ++ ++ ++ Show notifications ++ ++ ++ true ++ ++ ++ ++ ++ info_menu.png ++ ++ ++ Info ++ ++ ++ ++ ++ true ++ ++ ++ events_menu.png ++ ++ ++ Debug window ++ ++ ++ showwindow:events ++ ++ ++ ++ ++ true ++ ++ ++ true ++ ++ ++ Hide empty groups ++ ++ ++ setparams:property:chat_contacts:_yate_hideemptygroups ++ ++ ++ ++ ++ true ++ ++ ++ true ++ ++ ++ Advanced mode ++ ++ ++ ++ ++ sendfile_menu.png ++ ++ ++ Send file ++ ++ ++ ++ ++ file_trans_menu.png ++ ++ ++ File transfer ++ ++ ++ ++ ++ archive_menu.png ++ ++ ++ Archive ++ ++ ++ ++ ++ archive.png ++ ++ ++ Show log ++ ++ ++ ++ ++ addchatroom_menu.png ++ ++ ++ Add chat room ++ ++ ++ ++ ++ sharefile_menu.png ++ ++ ++ Share Files ++ ++ ++ ++ ++ sharedfile_menu.png ++ ++ ++ Shared Files ++ ++ ++ ++ ++ ++ +diff --git a/yate-config.in b/yate-config.in +index ec66c69..7a82be6 100644 +--- a/yate-config.in ++++ b/yate-config.in +@@ -7,7 +7,7 @@ + # Take a look at the source file yate-config.sh instead. + # + # Yet Another Telephony Engine - a fully featured software PBX and IVR +-# Copyright (C) 2005-2014 Null Team ++# Copyright (C) 2005-2020 Null Team + # + # This software is distributed under multiple licenses; + # see the COPYING file in the main directory for licensing +@@ -134,29 +134,29 @@ while [ "$#" != 0 ]; do + --param=HAVE_MALLINFO) + echo "@HAVE_MALLINFO@" + ;; +- --param=QT4_STATIC_MODULES) +- echo "@QT4_STATIC_MODULES@" ++ --param=QT5_STATIC_MODULES) ++ echo "@QT5_STATIC_MODULES@" + ;; +- --param=QT4_VER) +- echo "@QT4_VER@" ++ --param=QT5_VER) ++ echo "@QT5_VER@" + ;; +- --param=QT4_MOC) +- echo "@QT4_MOC@" ++ --param=QT5_MOC) ++ echo "@QT5_MOC@" + ;; +- --param=QT4_LIB_NET) +- echo "@QT4_LIB_NET@" ++ --param=QT5_LIB_NET) ++ echo "@QT5_LIB_NET@" + ;; +- --param=QT4_INC_NET) +- echo "@QT4_INC_NET@" ++ --param=QT5_INC_NET) ++ echo "@QT5_INC_NET@" + ;; +- --param=QT4_LIB) +- echo "@QT4_LIB@" ++ --param=QT5_LIB) ++ echo "@QT5_LIB@" + ;; +- --param=QT4_INC) +- echo "@QT4_INC@" ++ --param=QT5_INC) ++ echo "@QT5_INC@" + ;; +- --param=HAVE_QT4) +- echo "@HAVE_QT4@" ++ --param=HAVE_QT5) ++ echo "@HAVE_QT5@" + ;; + --param=LIBUSB_LIB) + echo "@LIBUSB_LIB@" +-- +2.30.2 + diff --git a/net-voip/yate/patches/yate-6.4.0.patchset b/net-voip/yate/patches/yate-6.4.0.patchset new file mode 100644 index 000000000..c7e484267 --- /dev/null +++ b/net-voip/yate/patches/yate-6.4.0.patchset @@ -0,0 +1,106 @@ +From 8d56f7a3e1d87f52a62b0db57a8b7fba4dc921e1 Mon Sep 17 00:00:00 2001 +From: Gerasim Troeglazov <3dEyes@gmail.com> +Date: Wed, 22 Dec 2021 16:50:04 +0300 +Subject: Fix build on Haiku + + +diff --git a/configure.ac b/configure.ac +index 77e4287..6397879 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1803,7 +1803,7 @@ INSTALL_D="install -D" + CFLAGS=`echo "$CFLAGS" | sed 's/\(^\| \+\)-g[[0-9]]*//' | sed 's/[[[:space:]]]\{2,\}/ /g'` + MODULE_CFLAGS="-fno-exceptions -fPIC $HAVE_GCC_FORMAT_CHECK $HAVE_BLOCK_RETURN" + MODULE_CPPFLAGS="$HAVE_NO_OVERLOAD_VIRT_WARN $RTTI_OPT $MODULE_CFLAGS" +-MODULE_LDRELAX="-rdynamic -shared" ++MODULE_LDRELAX="-shared" + MODULE_SYMBOLS="-Wl,--retain-symbols-file,/dev/null" + SONAME_OPT="-shared -Wl,-soname=" + case "x$uname_os" in +diff --git a/engine/Engine.cpp b/engine/Engine.cpp +index da817a0..34ff80c 100644 +--- a/engine/Engine.cpp ++++ b/engine/Engine.cpp +@@ -44,6 +44,10 @@ __declspec(dllimport) BOOL WINAPI SHGetSpecialFolderPathA(HWND,LPSTR,INT,BOOL); + + #else // _WINDOWS + ++#ifdef __HAIKU__ ++typedef void (*sighandler_t)(int); ++#endif ++ + #include "yatepaths.h" + #include + #include +diff --git a/engine/Mutex.cpp b/engine/Mutex.cpp +index 40c5d95..2ecccd7 100644 +--- a/engine/Mutex.cpp ++++ b/engine/Mutex.cpp +@@ -32,7 +32,7 @@ typedef HANDLE HSEMAPHORE; + + #ifdef MUTEX_HACK + extern "C" { +-#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) ++#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined(__HAIKU__) + extern int pthread_mutexattr_settype(pthread_mutexattr_t *__attr, int __kind); + #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE + #else +diff --git a/engine/Socket.cpp b/engine/Socket.cpp +index 103a736..405b44f 100644 +--- a/engine/Socket.cpp ++++ b/engine/Socket.cpp +@@ -40,7 +40,7 @@ + #endif + + #undef HAS_AF_UNIX +- ++#undef HAVE_GHBN_R + #ifndef _WINDOWS + + #include +diff --git a/engine/Thread.cpp b/engine/Thread.cpp +index b62626c..80adb2f 100644 +--- a/engine/Thread.cpp ++++ b/engine/Thread.cpp +@@ -201,6 +201,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority + default: + break; + } ++#ifndef __HAIKU__ + int err = ::pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED); + if (!err) + err = ::pthread_attr_setschedpolicy(&attr,policy); +@@ -218,6 +219,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority + #ifdef XDEBUG + else + Debug(DebugInfo,"Successfully set high thread priority %d",prio); ++#endif + #endif + } + #endif /* _WINDOWS */ +@@ -250,7 +252,7 @@ ThreadPrivate* ThreadPrivate::create(Thread* t,const char* name,Thread::Priority + } + #else /* _WINDOWS */ + e = ::pthread_create(&p->thread,&attr,startFunc,p); +-#ifdef PTHREAD_INHERIT_SCHED ++#if defined(PTHREAD_INHERIT_SCHED) && !defined(__HAIKU__) + if ((0 == i) && (EPERM == e) && (prio > Thread::Normal)) { + Debug(DebugWarn,"Failed to create thread with priority %d, trying with inherited",prio); + ::pthread_attr_setinheritsched(&attr,PTHREAD_INHERIT_SCHED); +diff --git a/yatemath.h b/yatemath.h +index 2a42426..7207e3e 100644 +--- a/yatemath.h ++++ b/yatemath.h +@@ -25,6 +25,9 @@ + #include + #include + #include ++#ifdef __HAIKU__ ++#undef bzero ++#endif + + namespace TelEngine { + +-- +2.30.2 + diff --git a/net-voip/yate/yate-6.1.1~git.recipe b/net-voip/yate/yate-6.1.1~git.recipe deleted file mode 100644 index cab6dbae6..000000000 --- a/net-voip/yate/yate-6.1.1~git.recipe +++ /dev/null @@ -1,75 +0,0 @@ -SUMMARY="Universal telephony client" -DESCRIPTION="Yate stands for Yet Another Telephony Engine, and like the name \ -states it is mainly a telephony engine; while currently focused on Voice over \ -Internet Protocol (VoIP) and PSTN, its power lies in its ability to be easily \ -extended. Voice, video, data and instant messenging can all be unified under \ -Yate's flexible routing engine, maximizing communications efficiency and \ -minimizing infrastructure costs for businesses." -HOMEPAGE="http://www.yate.ro/" -COPYRIGHT="2010-2020 KDE Organisation" -LICENSE="GNU LGPL v2" -REVISION="1" -srcGitRev="8505d691922a3df3545d9b4196170d6084467e37" -SOURCE_URI="https://github.com/keepitsimpletech/yate/archive/$srcGitRev.zip" -CHECKSUM_SHA256="2891f8a128421acbf059a72b103c8b9ad10f65052bd0d1c8ed223250e8945b55" -SOURCE_DIR="yate-$srcGitRev" -PATCHES="yate-$portVersion.patchset" - -ARCHITECTURES="?all !x86_gcc2" -SECONDARY_ARCHITECTURES="?x86" - -PROVIDES=" - yate$secondaryArchSuffix = $portVersion - app:YateClient$secondaryArchSuffix = $portVersion - " -REQUIRES=" - haiku$secondaryArchSuffix - lib:libQt5Core$secondaryArchSuffix - lib:libQt5Gui$secondaryArchSuffix - lib:libQt5Network$secondaryArchSuffix - lib:libQt5Widgets$secondaryArchSuffix - lib:libQt5Xml$secondaryArchSuffix - " - -BUILD_REQUIRES=" - haiku${secondaryArchSuffix}_devel - devel:libQt5Core$secondaryArchSuffix - " -BUILD_PREREQUIRES=" - cmd:autoconf - cmd:g++$secondaryArchSuffix - cmd:make - cmd:qmake - " - -BUILD() -{ - ./autogen.sh - ./configure - make yatepaths.h - qmake -o Makefile.qmake Yate.pro - make $jobArgs -f Makefile.qmake -} - -INSTALL() -{ - mkdir -p $appsDir/Yate - cp build/* $appsDir/Yate - cp -r client-conf.d $appsDir/Yate - - local APP_SIGNATURE="application/x-vnd.qt5-yate" - local MAJOR="`echo "$portVersion" | cut -d. -f1`" - local MIDDLE="`echo "$portVersion" | cut -d. -f2`" - local MINOR="`echo "$portVersion" | cut -d. -f3`" - local LONG_INFO="$SUMMARY" - sed \ - -e "s|@APP_SIGNATURE@|$APP_SIGNATURE|" \ - -e "s|@MAJOR@|$MAJOR|" \ - -e "s|@MIDDLE@|$MIDDLE|" \ - -e "s|@MINOR@|$MINOR|" \ - -e "s|@LONG_INFO@|$LONG_INFO|" \ - $portDir/additional-files/yate.rdef.in > yate.rdef - - addResourcesToBinaries yate.rdef $appsDir/Yate/YateClient - addAppDeskbarSymlink $appsDir/Yate/YateClient -} diff --git a/net-voip/yate/yate-6.4.0.recipe b/net-voip/yate/yate-6.4.0.recipe new file mode 100644 index 000000000..e2945b3ad --- /dev/null +++ b/net-voip/yate/yate-6.4.0.recipe @@ -0,0 +1,166 @@ +SUMMARY="Yet Another Telephony Engine" +DESCRIPTION="Yate stands for Yet Another Telephony Engine, and like the name \ +states it is mainly a telephony engine; while currently focused on Voice over \ +Internet Protocol (VoIP) and PSTN, its power lies in its ability to be easily \ +extended. Voice, video, data and instant messenging can all be unified under \ +Yate's flexible routing engine, maximizing communications efficiency and \ +minimizing infrastructure costs for businesses." +HOMEPAGE="http://www.yate.ro/" +COPYRIGHT="2005-2021 Null Team" +LICENSE="GNU LGPL v2" +REVISION="1" +SOURCE_URI="http://voip.null.ro/tarballs/yate6/yate-6.4.0-1.tar.gz" +CHECKSUM_SHA256="8c23dc6bffbf8d478db3a85964b5019771c8f6c9acf5220f3465516a748a03b0" +SOURCE_DIR="yate" +PATCHES=" + yate-$portVersion.patchset + yate-$portVersion-qt5.patchset + " +ADDITIONAL_FILES="yate.rdef.in" +ARCHITECTURES="?all !x86_gcc2" +SECONDARY_ARCHITECTURES="?x86" + +GLOBAL_WRITABLE_FILES=" + settings/yate/accfile.conf keep-old + settings/yate/amrnbcodec.conf keep-old + settings/yate/analog.conf keep-old + settings/yate/cache.conf keep-old + settings/yate/callcounters.conf keep-old + settings/yate/callfork.conf keep-old + settings/yate/camel_map.conf keep-old + settings/yate/ccongestion.conf keep-old + settings/yate/cdrbuild.conf keep-old + settings/yate/cdrfile.conf keep-old + settings/yate/ciscosm.conf keep-old + settings/yate/clustering.conf keep-old + settings/yate/cpuload.conf keep-old + settings/yate/dbpbx.conf keep-old + settings/yate/dsoundchan.conf keep-old + settings/yate/dummyradio.conf keep-old + settings/yate/enumroute.conf keep-old + settings/yate/eventlogs.conf keep-old + settings/yate/extmodule.conf keep-old + settings/yate/fileinfo.conf keep-old + settings/yate/filetransfer.conf keep-old + settings/yate/gvoice.conf keep-old + settings/yate/h323chan.conf keep-old + settings/yate/heartbeat.conf keep-old + settings/yate/isupmangler.conf keep-old + settings/yate/jabberclient.conf keep-old + settings/yate/jabberserver.conf keep-old + settings/yate/javascript.conf keep-old + settings/yate/jbfeatures.conf keep-old + settings/yate/lateroute.conf keep-old + settings/yate/lksctp.conf keep-old + settings/yate/mgcpca.conf keep-old + settings/yate/mgcpgw.conf keep-old + settings/yate/moh.conf keep-old + settings/yate/monitoring.conf keep-old + settings/yate/mux.conf keep-old + settings/yate/mysqldb.conf keep-old + settings/yate/openssl.conf keep-old + settings/yate/pbxassist.conf keep-old + settings/yate/pgsqldb.conf keep-old + settings/yate/presence.conf keep-old + settings/yate/providers.conf keep-old + settings/yate/queues.conf keep-old + settings/yate/queuesnotify.conf keep-old + settings/yate/radiotest.conf keep-old + settings/yate/regexroute.conf keep-old + settings/yate/regfile.conf keep-old + settings/yate/register.conf keep-old + settings/yate/rmanager.conf keep-old + settings/yate/sigtransport.conf keep-old + settings/yate/sip_cnam_lnp.conf keep-old + settings/yate/sipfeatures.conf keep-old + settings/yate/sqlitedb.conf keep-old + settings/yate/ss7_lnp_ansi.conf keep-old + settings/yate/subscription.conf keep-old + settings/yate/tdmcard.conf keep-old + settings/yate/tonegen.conf keep-old + settings/yate/users.conf keep-old + settings/yate/wiresniff.conf keep-old + settings/yate/wpcard.conf keep-old + settings/yate/yate-qt4.conf keep-old + settings/yate/yate.conf keep-old + settings/yate/Yate.conf keep-old + settings/yate/ybladerf.conf keep-old + settings/yate/yiaxchan.conf keep-old + settings/yate/yjinglechan.conf keep-old + settings/yate/yradius.conf keep-old + settings/yate/yrtpchan.conf keep-old + settings/yate/ysigchan.conf keep-old + settings/yate/ysipchan.conf keep-old + settings/yate/ysnmpagent.conf keep-old + settings/yate/ysockschan.conf keep-old + settings/yate/ystunchan.conf keep-old + settings/yate/zapcard.conf keep-old + settings/yate/zlibcompress.conf keep-old + " + +PROVIDES=" + yate$secondaryArchSuffix = $portVersion + app:YateClient$secondaryArchSuffix = $portVersion + " +REQUIRES=" + haiku$secondaryArchSuffix + lib:libQt5Core$secondaryArchSuffix + lib:libQt5Gui$secondaryArchSuffix + lib:libQt5Network$secondaryArchSuffix + lib:libQt5Widgets$secondaryArchSuffix + lib:libQt5Xml$secondaryArchSuffix + " + +BUILD_REQUIRES=" + haiku${secondaryArchSuffix}_devel + devel:libQt5Core$secondaryArchSuffix + " +BUILD_PREREQUIRES=" + cmd:autoconf + cmd:g++$secondaryArchSuffix + cmd:make + cmd:qmake + cmd:which + " + +BUILD() +{ + ./autogen.sh + export LDFLAGS=-lnetwork + ./configure \ + --prefix $appsDir/Yate \ + --sysconfdir=$settingsDir \ + --libdir=$appsDir/Yate/lib \ + --sharedstatedir=$appsDir/Yate/data + + make $jobArgs +} + +INSTALL() +{ + make install + + mv $appsDir/Yate/bin/yate-qt5 $appsDir/Yate/Yate + mv $settingsDir/yate/yate.conf $settingsDir/yate/Yate.conf + mv $settingsDir/yate/yate-qt5.conf $settingsDir/yate/Yate-qt5.conf + + # FIXME: this is wrong, paths need to be configured propely + mv $appsDir/Yate/share/yate/* $appsDir/Yate/share + mv $appsDir/Yate/lib/yate/* $appsDir/Yate/lib + + local APP_SIGNATURE="application/x-vnd.qt5-yate" + local MAJOR="`echo "$portVersion" | cut -d. -f1`" + local MIDDLE="`echo "$portVersion" | cut -d. -f2`" + local MINOR="`echo "$portVersion" | cut -d. -f3`" + local LONG_INFO="$SUMMARY" + sed \ + -e "s|@APP_SIGNATURE@|$APP_SIGNATURE|" \ + -e "s|@MAJOR@|$MAJOR|" \ + -e "s|@MIDDLE@|$MIDDLE|" \ + -e "s|@MINOR@|$MINOR|" \ + -e "s|@LONG_INFO@|$LONG_INFO|" \ + $portDir/additional-files/yate.rdef.in > yate.rdef + + addResourcesToBinaries yate.rdef $appsDir/Yate/Yate + addAppDeskbarSymlink $appsDir/Yate/Yate +}