1 /**
2  * qt4client.cpp
3  * This file is part of the YATE Project http://YATE.null.ro
4  *
5  * A Qt-4 based universal telephony client
6  *
7  * Yet Another Telephony Engine - a fully featured software PBX and IVR
8  * Copyright (C) 2004-2014 Null Team
9  *
10  * This software is distributed under multiple licenses;
11  * see the COPYING file in the main directory for licensing
12  * information for this specific distribution.
13  *
14  * This use of this software may be subject to additional restrictions.
15  * See the LEGAL file in the main directory for details.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20  */
21 
22 #include "qt4client.h"
23 #include <QtUiTools>
24 
25 #ifdef _WINDOWS
26 #define DEFAULT_DEVICE "dsound/*"
27 #define PLATFORM_LOWERCASE_NAME "windows"
28 #elif defined(__APPLE__)
29 #define DEFAULT_DEVICE "coreaudio/*"
30 #define PLATFORM_LOWERCASE_NAME "apple"
31 #elif defined(__linux__)
32 #define DEFAULT_DEVICE "alsa/default"
33 #define PLATFORM_LOWERCASE_NAME "linux"
34 #else
35 #define DEFAULT_DEVICE "oss//dev/dsp"
36 #define PLATFORM_LOWERCASE_NAME "unknown"
37 #endif
38 
39 namespace TelEngine {
40 
41 static unsigned int s_allHiddenQuit = 0; // Quit on all hidden notification if this counter is 0
42 
43 // Factory used to create objects in client's thread
44 class Qt4ClientFactory : public UIFactory
45 {
46 public:
47     Qt4ClientFactory(const char* name = "Qt4ClientFactory");
48     virtual void* create(const String& type, const char* name, NamedList* params = 0);
49 };
50 
51 // Class used for temporary operations on QT widgets
52 // Keeps a pointer to a widget and its type
53 // NOTE: The methods of this class don't check the widget pointer
54 class QtWidget
55 {
56 public:
57     enum Type {
58 	PushButton     = 0,
59 	CheckBox       = 1,
60 	Table          = 2,
61 	ListBox        = 3,
62 	ComboBox       = 4,
63 	Tab            = 5,
64 	StackWidget    = 6,
65 	TextEdit       = 7,
66 	Label          = 8,
67 	LineEdit       = 9,
68 	AbstractButton = 10,
69 	Slider         = 11,
70 	ProgressBar    = 12,
71 	SpinBox        = 13,
72 	Calendar       = 14,
73 	Splitter       = 15,
74 	TextBrowser    = 16,
75 	Unknown,                         // Unknown type
76 	Action,                          // QAction descendant
77 	CustomTable,                     // QtTable descendant
78 	CustomTree,                      // QtTree descendant
79 	CustomWidget,                    // QtCustomWidget descendant
80 	CustomObject,                    // QtCustomObject descendant
81 	Missing                          // Invalid pointer
82     };
83     // Set widget from object
QtWidget(QObject * w)84     inline QtWidget(QObject* w)
85 	: m_widget(0), m_action(0), m_object(0), m_type(Missing) {
86 	    if (!w)
87 		return;
88 	    if (w->inherits("QWidget"))
89 		m_widget = static_cast<QWidget*>(w);
90 	    else if (w->inherits("QAction"))
91 		m_action = static_cast<QAction*>(w);
92 	    m_type = getType();
93 	}
94     // Set widget from object and type
QtWidget(QWidget * w,int t)95     inline QtWidget(QWidget* w, int t)
96 	: m_widget(w), m_action(0), m_object(0), m_type(t) {
97 	    if (!m_widget)
98 		m_type = Missing;
99 	}
100     // Set widget/action from object and name
QtWidget(QObject * parent,const String & name)101     inline QtWidget(QObject* parent, const String& name)
102 	: m_widget(0), m_action(0), m_object(0), m_type(Missing) {
103 	    QString what = QtClient::setUtf8(name);
104 	    m_widget = qFindChild<QWidget*>(parent,what);
105 	    if (!m_widget) {
106 		m_action = qFindChild<QAction*>(parent,what);
107 		if (!m_action)
108 		    m_object = qFindChild<QObject*>(parent,what);
109 	    }
110 	    m_type = getType();
111 	}
valid() const112     inline bool valid() const
113 	{ return type() != Missing; }
invalid() const114     inline bool invalid() const
115 	{ return type() == Missing; }
type() const116     inline int type() const
117 	{ return m_type; }
operator QWidget*()118     inline operator QWidget*()
119 	{ return m_widget; }
inherits(const char * classname)120     inline bool inherits(const char* classname)
121 	{ return m_widget && m_widget->inherits(classname); }
inherits(Type t)122     inline bool inherits(Type t)
123 	{ return inherits(s_types[t]); }
widget()124     inline QWidget* widget()
125 	{ return m_widget; }
operator ->()126     inline QWidget* operator ->()
127 	{ return m_widget; }
128     // Static cast methods
button()129     inline QPushButton* button()
130 	{ return static_cast<QPushButton*>(m_widget); }
check()131     inline QCheckBox* check()
132 	{ return static_cast<QCheckBox*>(m_widget); }
table()133     inline QTableWidget* table()
134 	{ return static_cast<QTableWidget*>(m_widget); }
list()135     inline QListWidget* list()
136 	{ return static_cast<QListWidget*>(m_widget); }
combo()137     inline QComboBox* combo()
138 	{ return static_cast<QComboBox*>(m_widget); }
tab()139     inline QTabWidget* tab()
140 	{ return static_cast<QTabWidget*>(m_widget); }
stackWidget()141     inline QStackedWidget* stackWidget()
142 	{ return static_cast<QStackedWidget*>(m_widget); }
textEdit()143     inline QTextEdit* textEdit()
144 	{ return static_cast<QTextEdit*>(m_widget); }
label()145     inline QLabel* label()
146 	{ return static_cast<QLabel*>(m_widget); }
lineEdit()147     inline QLineEdit* lineEdit()
148 	{ return static_cast<QLineEdit*>(m_widget); }
abstractButton()149     inline QAbstractButton* abstractButton()
150 	{ return static_cast<QAbstractButton*>(m_widget); }
slider()151     inline QSlider* slider()
152 	{ return static_cast<QSlider*>(m_widget); }
progressBar()153     inline QProgressBar* progressBar()
154 	{ return static_cast<QProgressBar*>(m_widget); }
spinBox()155     inline QSpinBox* spinBox()
156 	{ return static_cast<QSpinBox*>(m_widget); }
calendar()157     inline QCalendarWidget* calendar()
158 	{ return static_cast<QCalendarWidget*>(m_widget); }
splitter()159     inline QSplitter* splitter()
160 	{ return static_cast<QSplitter*>(m_widget); }
customTable()161     inline QtTable* customTable()
162 	{ return qobject_cast<QtTable*>(m_widget); }
customTree()163     inline QtTree* customTree()
164 	{ return qobject_cast<QtTree*>(m_widget); }
customWidget()165     inline QtCustomWidget* customWidget()
166 	{ return qobject_cast<QtCustomWidget*>(m_widget); }
customObject()167     inline QtCustomObject* customObject()
168 	{ return qobject_cast<QtCustomObject*>(m_object); }
uiWidget()169     inline UIWidget* uiWidget() {
170 	    switch (type()) {
171 		case CustomTable:
172 		    return static_cast<UIWidget*>(customTable());
173 		case CustomWidget:
174 		    return static_cast<UIWidget*>(customWidget());
175 		case CustomObject:
176 		    return static_cast<UIWidget*>(customObject());
177 		case CustomTree:
178 		    return static_cast<UIWidget*>(customTree());
179 	    }
180 	    return 0;
181 	}
182 
action()183     inline QAction* action()
184 	{ return m_action; }
185 
186     // Find a combo box item
findComboItem(const String & item)187     inline int findComboItem(const String& item) {
188 	    QComboBox* c = combo();
189 	    return c ? c->findText(QtClient::setUtf8(item)) : -1;
190 	}
191     // Add an item to a combo box
addComboItem(const String & item,bool atStart)192     inline bool addComboItem(const String& item, bool atStart) {
193 	    QComboBox* c = combo();
194 	    if (!c)
195 		return false;
196 	    QString it(QtClient::setUtf8(item));
197 	    if (atStart)
198 		c->insertItem(0,it);
199 	    else
200 		c->addItem(it);
201 	    return true;
202 	}
203     // Find a list box item
findListItem(const String & item)204     inline int findListItem(const String& item) {
205 	    QListWidget* l = list();
206 	    if (!l)
207 		return -1;
208 	    QString it(QtClient::setUtf8(item));
209 	    for (int i = l->count(); i >= 0 ; i--) {
210 		QListWidgetItem* tmp = l->item(i);
211 		if (tmp && it == tmp->text())
212 		    return i;
213 	    }
214 	    return -1;
215 	}
216     // Add an item to a list box
addListItem(const String & item,bool atStart)217     inline bool addListItem(const String& item, bool atStart) {
218 	    QListWidget* l = list();
219 	    if (!l)
220 		return false;
221 	    QString it(QtClient::setUtf8(item));
222 	    if (atStart)
223 		l->insertItem(0,it);
224 	    else
225 		l->addItem(it);
226 	    return true;
227 	}
228 
getType()229     int getType() {
230 	    if (m_widget) {
231 		String cls = m_widget->metaObject()->className();
232 		for (int i = 0; i < Unknown; i++)
233 		    if (s_types[i] == cls)
234 			return i;
235 		if (customTable())
236 		    return CustomTable;
237 		if (customWidget())
238 		    return CustomWidget;
239 		if (customTree())
240 		    return CustomTree;
241 		return Unknown;
242 	    }
243 	    if (m_action && m_action->inherits("QAction"))
244 		return Action;
245 	    if (customObject())
246 		return CustomObject;
247 	    return Missing;
248 	}
249     static String s_types[Unknown];
250 protected:
251     QWidget* m_widget;
252     QAction* m_action;
253     QObject* m_object;
254     int m_type;
255 private:
QtWidget()256     QtWidget() {}
257 };
258 
259 // Class used for temporary operations on QTableWidget objects
260 // NOTE: The methods of this class don't check the table pointer
261 class TableWidget : public GenObject
262 {
263 public:
264     TableWidget(QTableWidget* table, bool tmp = true);
265     TableWidget(QWidget* wid, const String& name, bool tmp = true);
266     TableWidget(QtWidget& table, bool tmp = true);
267     ~TableWidget();
table()268     inline QTableWidget* table()
269 	{ return m_table; }
valid()270     inline bool valid()
271 	{ return m_table != 0; }
customTable()272     inline QtTable* customTable()
273 	{ return (valid()) ? qobject_cast<QtTable*>(m_table) : 0; }
name()274     inline const String& name()
275 	{ return m_name; }
rowCount()276     inline int rowCount()
277 	{ return m_table->rowCount(); }
columnCount()278     inline int columnCount()
279 	{ return m_table->columnCount(); }
setHeaderText(int col,const char * text)280     inline void setHeaderText(int col, const char* text) {
281 	    if (col < columnCount())
282 		m_table->setHorizontalHeaderItem(col,
283 		    new QTableWidgetItem(QtClient::setUtf8(text)));
284 	}
getHeaderText(int col,String & dest,bool lower=true)285     inline bool getHeaderText(int col, String& dest, bool lower = true) {
286     	    QTableWidgetItem* item = m_table->horizontalHeaderItem(col);
287 	    if (item) {
288 		QtClient::getUtf8(dest,item->text());
289 		if (lower)
290 		    dest.toLower();
291 	    }
292 	    return item != 0;
293 	}
294     // Get the current selection's row
crtRow()295     inline int crtRow() {
296 	    QList<QTableWidgetItem*> items = m_table->selectedItems();
297 	    if (items.size())
298 		return items[0]->row();
299 	    return -1;
300 	}
repaint()301     inline void repaint()
302 	{ m_table->repaint(); }
addRow(int index)303     inline void addRow(int index)
304 	{ m_table->insertRow(index); }
delRow(int index)305     inline void delRow(int index) {
306 	    if (index >= 0)
307 		m_table->removeRow(index);
308 	}
addColumn(int index,int width=-1,const char * name=0)309     inline void addColumn(int index, int width = -1, const char* name = 0) {
310 	    m_table->insertColumn(index);
311 	    if (width >= 0)
312 		m_table->setColumnWidth(index,width);
313 	    setHeaderText(index,name);
314 	}
setImage(int row,int col,const String & image)315     inline void setImage(int row, int col, const String& image) {
316 	    QTableWidgetItem* item = m_table->item(row,col);
317 	    if (item)
318 		item->setIcon(QIcon(QtClient::setUtf8(image)));
319 	}
addCell(int row,int col,const String & value)320     inline void addCell(int row, int col, const String& value) {
321 	    QTableWidgetItem* item = new QTableWidgetItem(QtClient::setUtf8(value));
322 	    m_table->setItem(row,col,item);
323 	}
setCell(int row,int col,const String & value,bool addNew=true)324     inline void setCell(int row, int col, const String& value, bool addNew = true) {
325 	    QTableWidgetItem* item = m_table->item(row,col);
326 	    if (item)
327 		item->setText(QtClient::setUtf8(value));
328 	    else if (addNew)
329 		addCell(row,col,value);
330 	}
getCell(int row,int col,String & dest,bool lower=false)331     inline bool getCell(int row, int col, String& dest, bool lower = false) {
332     	    QTableWidgetItem* item = m_table->item(row,col);
333 	    if (item) {
334 		QtClient::getUtf8(dest,item->text());
335 		if (lower)
336 		    dest.toLower();
337 		return true;
338 	    }
339 	    return false;
340 	}
setID(int row,const String & value)341     inline void setID(int row, const String& value)
342 	{ setCell(row,0,value); }
343     // Add or set a row
344     void updateRow(const String& item, const NamedList* data, bool atStart);
345     // Update a row from a list of parameters
346     void updateRow(int row, const NamedList& data);
347     // Find a row by the first's column value. Return -1 if not found
348     int getRow(const String& item);
349     // Find a column by its label. Return -1 if not found
350     int getColumn(const String& name, bool caseInsentive = true);
351 protected:
352     void init(bool tmp);
353 private:
354     QTableWidget* m_table;               // The table
355     String m_name;                       // Table's name
356     int m_sortControl;                   // Flag used to set/reset sorting attribute of the table
357 };
358 
359 // Store an UI loaded from file to avoid loading it again
360 class UIBuffer : public String
361 {
362 public:
UIBuffer(const String & name,QByteArray * buf)363     inline UIBuffer(const String& name, QByteArray* buf)
364 	: String(name), m_buffer(buf)
365 	{ s_uiCache.append(this); }
buffer()366     inline QByteArray* buffer()
367 	{ return m_buffer; }
368     // Remove from list. Release memory
369     virtual void destruct();
370     // Return an already loaded UI. Load from file if not found.
371     // Add URLs paths when missing
372     static UIBuffer* build(const String& name);
373     // Find a buffer
374     static UIBuffer* find(const String& name);
375     // Buffer cache
376     static ObjList s_uiCache;
377 private:
378     QByteArray* m_buffer;                // The buffer
379 };
380 
381 }; // namespace TelEngine
382 
383 using namespace TelEngine;
384 
385 // Dynamic properies
386 static const String s_propsSave = "_yate_save_props"; // Save properties property name
387 static const String s_propColWidths = "_yate_col_widths"; // Column widths
388 static const String s_propSorting = "_yate_sorting";  // Table/List sorting
389 static const String s_propSizes = "_yate_sizes";      // Size int array
390 static const String s_propShowWndWhenActive = "_yate_showwnd_onactive"; // Show another window when a window become active
391 static String s_propHHeader = "dynamicHHeader";       // Tables: show/hide the horizontal header
392 static String s_propAction = "dynamicAction";         // Prefix for properties that would trigger some action
393 static String s_propWindowFlags = "_yate_windowflags"; // Window flags
394 static const String s_propContextMenu = "_yate_context_menu"; // Context menu name
395 static String s_propHideInactive = "dynamicHideOnInactive"; // Hide inactive window
396 static const String s_yatePropPrefix = "_yate_";      // Yate dynamic properties prefix
397 static NamedList s_qtStyles("");                      // Qt styles classname -> internal name
398 //
399 static Qt4ClientFactory s_qt4Factory;
400 static Configuration s_cfg;
401 static Configuration s_save;
402 ObjList UIBuffer::s_uiCache;
403 
404 // Values used to configure window title bar and border
405 static TokenDict s_windowFlags[] = {
406     // Window type
407     {"popup",              Qt::Popup},
408     {"tool",               Qt::Tool},
409     {"subwindow",          Qt::SubWindow},
410 #ifdef _WINDOWS
411     {"notificationtype",   Qt::Tool},
412 #else
413     {"notificationtype",   Qt::SubWindow},
414 #endif
415     // Window flags
416     {"title",              Qt::WindowTitleHint},
417     {"sysmenu",            Qt::WindowSystemMenuHint},
418     {"maximize",           Qt::WindowMaximizeButtonHint},
419     {"minimize",           Qt::WindowMinimizeButtonHint},
420     {"help",               Qt::WindowContextHelpButtonHint},
421     {"stayontop",          Qt::WindowStaysOnTopHint},
422     {"frameless",          Qt::FramelessWindowHint},
423 #if QT_VERSION >= 0x040500
424     {"close",              Qt::WindowCloseButtonHint},
425 #endif
426     {0,0}
427 };
428 
429 // Widget attribute names
430 static const TokenDict s_widgetAttributes[] = {
431     {"macshowfocusrect",   Qt::WA_MacShowFocusRect},
432     {0,0}
433 };
434 
435 String QtWidget::s_types[QtWidget::Unknown] = {
436     "QPushButton",
437     "QCheckBox",
438     "QTableWidget",
439     "QListWidget",
440     "QComboBox",
441     "QTabWidget",
442     "QStackedWidget",
443     "QTextEdit",
444     "QLabel",
445     "QLineEdit",
446     "QAbstractButton",
447     "QSlider",
448     "QProgressBar",
449     "QSpinBox",
450     "QCalendarWidget",
451     "QSplitter",
452     "QTextBrowser",
453 };
454 
455 // QVariant type translation dictionary
456 static const TokenDict s_qVarType[] = {
457     {"string",       QVariant::String},
458     {"bool",         QVariant::Bool},
459     {"int",          QVariant::Int},
460     {"uint",         QVariant::UInt},
461     {"stringlist",   QVariant::StringList},
462     {"icon",         QVariant::Icon},
463     {"pixmap",       QVariant::Pixmap},
464     {"double",       QVariant::Double},
465     {"keysequence",  QVariant::KeySequence},
466     {0,0}
467 };
468 
469 // Qt alignment flags translation
470 static const TokenDict s_qAlign[] = {
471     {"left",      Qt::AlignLeft},
472     {"right",     Qt::AlignRight},
473     {"hcenter",   Qt::AlignHCenter},
474     {"justify",   Qt::AlignJustify},
475     {"top",       Qt::AlignTop},
476     {"bottom",    Qt::AlignBottom},
477     {"vcenter",   Qt::AlignVCenter},
478     {"center",    Qt::AlignCenter},
479     {"absolute",  Qt::AlignAbsolute},
480     {0,0}
481 };
482 
483 // Qt alignment flags translation
484 static const TokenDict s_qEditTriggers[] = {
485     {"currentchanged", QAbstractItemView::CurrentChanged},
486     {"doubleclick",    QAbstractItemView::DoubleClicked},
487     {"selclick",       QAbstractItemView::SelectedClicked},
488     {"editkeypress",   QAbstractItemView::EditKeyPressed},
489     {"anykeypress",    QAbstractItemView::AnyKeyPressed},
490     {"all",            QAbstractItemView::AllEditTriggers},
491     {0,0}
492 };
493 
494 // QtClientSort name
495 static const TokenDict s_sorting[] = {
496     {"ascending",      QtClient::SortAsc},
497     {"descending",     QtClient::SortDesc},
498     {"none",           QtClient::SortNone},
499     {0,0}
500 };
501 
502 // Handler for QT library messages
qtMsgHandler(QtMsgType type,const char * text)503 static void qtMsgHandler(QtMsgType type, const char* text)
504 {
505     int dbg = DebugAll;
506     switch (type) {
507 	case QtDebugMsg:
508 	    dbg = DebugInfo;
509 	    break;
510 	case QtWarningMsg:
511 	    dbg = DebugWarn;
512 	    break;
513 	case QtCriticalMsg:
514 	    dbg = DebugCrit;
515 	    break;
516 	case QtFatalMsg:
517 	    dbg = DebugFail;
518 	    break;
519     }
520     Debug("QT",dbg,"%s",text);
521 }
522 
523 // Build a list of parameters from a string
524 // Return the number of parameters found
str2Params(NamedList & params,const String & buf,char sep='|')525 static unsigned int str2Params(NamedList& params, const String& buf, char sep = '|')
526 {
527     ObjList* list = 0;
528     // Check if we have another separator
529     if (buf.startsWith("separator=")) {
530 	sep = buf.at(10);
531 	list = buf.substr(11).split(sep,false);
532     }
533     else
534 	list = buf.split(sep,false);
535     unsigned int n = 0;
536     for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
537 	String* s = static_cast<String*>(o->get());
538 	int pos = s->find('=');
539 	if (pos < 1)
540 	    continue;
541 	params.addParam(s->substr(0,pos),s->substr(pos + 1));
542 	n++;
543     }
544     TelEngine::destruct(list);
545     return n;
546 }
547 
548 // Utility: fix QT path separator on Windows
549 // (display paths using only one separator to the user)
fixPathSep(QString str)550 static inline QString fixPathSep(QString str)
551 {
552 #ifdef _WINDOWS
553     QString tmp = str;
554     tmp.replace(QChar('/'),QtClient::setUtf8(Engine::pathSeparator()));
555     return tmp;
556 #else
557     return str;
558 #endif
559 }
560 
561 // Utility: find a stacked widget's page with the given name
findStackedWidget(QStackedWidget & w,const String & name)562 static int findStackedWidget(QStackedWidget& w, const String& name)
563 {
564     QString n(QtClient::setUtf8(name));
565     for (int i = 0; i < w.count(); i++) {
566 	QWidget* page = w.widget(i);
567 	if (page && n == page->objectName())
568 	    return i;
569     }
570     return -1;
571 }
572 
573 // Utility function used to get the name of a control
574 // The name of the control indicates actions, toggles ...
575 // The action name alias can contain parameters
translateName(QtWidget & w,String & name,NamedList ** params=0)576 static bool translateName(QtWidget& w, String& name, NamedList** params = 0)
577 {
578     if (w.invalid())
579 	return false;
580     if (w.type() != QtWidget::Action)
581 	QtClient::getIdentity(w.widget(),name);
582     else
583 	QtClient::getIdentity(w.action(),name);
584     if (!name)
585 	return true;
586     // Check params
587     int pos = name.find('|');
588     if (pos < 1)
589 	return true;
590     if (params) {
591 	*params = new NamedList("");
592 	if (!str2Params(**params,name.substr(pos + 1)))
593 	    TelEngine::destruct(*params);
594     }
595     name = name.substr(0,pos);
596     return true;
597 }
598 
599 // Utility: raise a select event if a list is empty
raiseSelectIfEmpty(int count,Window * wnd,const String & name)600 static inline void raiseSelectIfEmpty(int count, Window* wnd, const String& name)
601 {
602     if (!Client::exiting() && count <= 0 && Client::self())
603 	Client::self()->select(wnd,name,String::empty());
604 }
605 
606 // Add dynamic properties from a list of parameters
607 // Parameter format:
608 // property_name:property_type=property_value
addDynamicProps(QObject * obj,NamedList & props)609 static void addDynamicProps(QObject* obj, NamedList& props)
610 {
611     static String typeString = "string";
612     static String typeBool = "bool";
613     static String typeInt = "int";
614 
615     if (!obj)
616 	return;
617     unsigned int n = props.length();
618     for (unsigned int i = 0; i < n; i++) {
619 	NamedString* ns = props.getParam(i);
620 	if (!(ns && ns->name()))
621 	    continue;
622 	int pos = ns->name().find(':');
623 	if (pos < 1)
624 	    continue;
625 
626 	String prop = ns->name().substr(0,pos);
627 	String type = ns->name().substr(pos + 1);
628 	QVariant var;
629 	if (type == typeString)
630 	    var.setValue(QString(ns->c_str()));
631 	else if (type == typeBool)
632 	    var.setValue(ns->toBoolean());
633 	else if (type == typeInt)
634 	    var.setValue(ns->toInteger());
635 
636 	if (var.type() != QVariant::Invalid) {
637 	    obj->setProperty(prop,var);
638 	    DDebug(ClientDriver::self(),DebugAll,
639 		"Object '%s': added dynamic property %s='%s' type=%s",
640 		YQT_OBJECT_NAME(obj),prop.c_str(),ns->c_str(),var.typeName());
641 	}
642 	else
643 	    Debug(ClientDriver::self(),DebugStub,
644 		"Object '%s': dynamic property '%s' type '%s' is not supported",
645 		YQT_OBJECT_NAME(obj),prop.c_str(),type.c_str());
646     }
647 }
648 
649 // Find a QSystemTrayIcon child of an object
findSysTrayIcon(QObject * obj,const char * name)650 static inline QSystemTrayIcon* findSysTrayIcon(QObject* obj, const char* name)
651 {
652     return qFindChild<QSystemTrayIcon*>(obj,QtClient::setUtf8(name));
653 }
654 
655 // Utility used to create an object's property if not found
656 // Add it to a list of strings
657 // Return true if the list changed
createProperty(QObject * obj,const char * name,QVariant::Type t,QtWindow * wnd,QStringList * list)658 static bool createProperty(QObject* obj, const char* name, QVariant::Type t,
659     QtWindow* wnd, QStringList* list)
660 {
661     if (!obj || TelEngine::null(name))
662 	return false;
663     QVariant var = obj->property(name);
664     if (var.type() == QVariant::Invalid)
665 	obj->setProperty(name,QVariant(t));
666     else if (var.type() != t) {
667 	if (wnd)
668 	    Debug(QtDriver::self(),DebugNote,
669 		"Window(%s) child '%s' already has a %s property '%s' [%p]",
670 		wnd->toString().c_str(),YQT_OBJECT_NAME(obj),var.typeName(),name,wnd);
671 	return false;
672     }
673     if (!list)
674 	return false;
675     QString s = QtClient::setUtf8(name);
676     if (list->contains(s))
677 	return false;
678     *list << s;
679     return true;
680 }
681 
682 // Replace file path in URLs in a character array
addFilePathUrl(QByteArray & a,const String & file)683 static void addFilePathUrl(QByteArray& a, const String& file)
684 {
685     if (!file)
686 	return;
687     QString path = QDir::fromNativeSeparators(QtClient::setUtf8(file));
688     // Truncate after last path separator (lastIndexOf() returns -1 if not found)
689     path.truncate(path.lastIndexOf(QString("/")) + 1);
690     if (!path.size())
691 	return;
692     int start = 0;
693     int end = -1;
694     while ((start = a.indexOf("url(",end + 1)) > 0) {
695 	start += 4;
696 	end = a.indexOf(")",start);
697 	if (end <= start)
698 	    break;
699 	// Add
700 	int len = end - start;
701 	QByteArray tmp = a.mid(start,len);
702 	if (tmp.indexOf('/') != -1)
703 	    continue;
704 	tmp.insert(0,path);
705 	a.replace(start,len,tmp);
706     }
707 }
708 
709 // Read data from file and append it to a string buffer
710 // Optionally append suffix characters to file name
appendStyleSheet(QString & buf,const char * file,const char * suffix1=0,const char * suffix2=0)711 static bool appendStyleSheet(QString& buf, const char* file,
712     const char* suffix1 = 0, const char* suffix2 = 0)
713 {
714     if (TelEngine::null(file))
715 	return false;
716     String shf = file;
717     const char* oper = 0;
718     int pos = shf.rfind('/');
719     if (pos < 0)
720 	pos = shf.rfind('\\');
721     if (pos < 0)
722 	shf = Client::s_skinPath + shf;
723     int level = DebugNote;
724     if (!(TelEngine::null(suffix1) && TelEngine::null(suffix2))) {
725 	level = DebugAll;
726 	int dotPos = shf.rfind('.');
727 	if (dotPos > pos) {
728 	    String tmp = shf.substr(0,dotPos);
729 	    tmp.append(suffix1,"_");
730 	    tmp.append(suffix2,"_");
731 	    shf = tmp + shf.substr(dotPos);
732 	}
733     }
734     DDebug(ClientDriver::self(),DebugAll,"Loading stylesheet file '%s'",shf.c_str());
735     QFile f(QtClient::setUtf8(shf));
736     if (f.open(QIODevice::ReadOnly)) {
737 	QByteArray a = f.readAll();
738 	if (a.size()) {
739 	    addFilePathUrl(a,shf);
740 	    buf += QString::fromUtf8(a.constData());
741 	}
742 	else if (f.error() != QFile::NoError)
743 	    oper = "read";
744     }
745     else
746 	oper = "open";
747     if (!oper)
748 	return true;
749     Debug(ClientDriver::self(),level,"Failed to %s stylesheet file '%s': %d '%s'",
750 	oper,shf.c_str(),f.error(),f.errorString().toUtf8().constData());
751     return false;
752 }
753 
754 // Split an integer string list
755 // Result list length can be set by indicating a length
buildIntList(const String & buf,int len=0)756 static QList<int> buildIntList(const String& buf, int len = 0)
757 {
758     QList<int> ret;
759     ObjList* list = buf.split(',');
760     int pos = 0;
761     ObjList* o = list;
762     while (o || pos < len) {
763 	int val = 0;
764 	if (o) {
765 	    if (o->get())
766 		val = o->get()->toString().toInteger();
767 	    o = o->next();
768 	}
769 	ret.append(val);
770 	pos++;
771 	if (pos == len)
772 	    break;
773     }
774     TelEngine::destruct(list);
775     return ret;
776 }
777 
778 // Retrieve an object's property
779 // Check platform dependent value
getPropPlatform(QObject * obj,const String & name,String & val)780 static inline bool getPropPlatform(QObject* obj, const String& name, String& val)
781 {
782     if (!(obj && name))
783 	return false;
784     if (QtClient::getProperty(obj,name,val))
785 	return true;
786     return QtClient::getProperty(obj,name + "_os" + PLATFORM_LOWERCASE_NAME,val);
787 }
788 
789 
790 /**
791  * Qt4ClientFactory
792  */
Qt4ClientFactory(const char * name)793 Qt4ClientFactory::Qt4ClientFactory(const char* name)
794     : UIFactory(name)
795 {
796     m_types.append(new String("QSound"));
797 }
798 
799 // Build QSound
create(const String & type,const char * name,NamedList * params)800 void* Qt4ClientFactory::create(const String& type, const char* name, NamedList* params)
801 {
802     if (type == YSTRING("QSound"))
803 	return new QSound(QtClient::setUtf8(name));
804     return 0;
805 }
806 
807 
808 /**
809  * TableWidget
810  */
TableWidget(QTableWidget * table,bool tmp)811 TableWidget::TableWidget(QTableWidget* table, bool tmp)
812     : m_table(table), m_sortControl(-1)
813 {
814     if (!m_table)
815 	return;
816     init(tmp);
817 }
818 
TableWidget(QWidget * wid,const String & name,bool tmp)819 TableWidget::TableWidget(QWidget* wid, const String& name, bool tmp)
820     : m_table(0), m_sortControl(-1)
821 {
822     if (wid)
823 	m_table = qFindChild<QTableWidget*>(wid,QtClient::setUtf8(name));
824     if (!m_table)
825 	return;
826     init(tmp);
827 }
828 
TableWidget(QtWidget & table,bool tmp)829 TableWidget::TableWidget(QtWidget& table, bool tmp)
830     : m_table(static_cast<QTableWidget*>((QWidget*)table)), m_sortControl(-1)
831 {
832     if (m_table)
833 	init(tmp);
834 }
835 
~TableWidget()836 TableWidget::~TableWidget()
837 {
838     if (!m_table)
839 	return;
840     if (m_sortControl >= 0)
841 	m_table->setSortingEnabled((bool)m_sortControl);
842     m_table->repaint();
843 }
844 
845 // Add or set a row
updateRow(const String & item,const NamedList * data,bool atStart)846 void TableWidget::updateRow(const String& item, const NamedList* data, bool atStart)
847 {
848     int row = getRow(item);
849     // Add a new one ?
850     if (row < 0) {
851 	row = atStart ? 0 : rowCount();
852 	addRow(row);
853 	setID(row,item);
854     }
855     // Update
856     if (data)
857 	updateRow(row,*data);
858 }
859 
860 // Update a row from a list of parameters
updateRow(int row,const NamedList & data)861 void TableWidget::updateRow(int row, const NamedList& data)
862 {
863     int ncol = columnCount();
864     for (int i = 0; i < ncol; i++) {
865 	String header;
866 	if (!getHeaderText(i,header))
867 	    continue;
868 	NamedString* tmp = data.getParam(header);
869 	if (tmp)
870 	    setCell(row,i,*tmp);
871 	// Set image
872 	tmp = data.getParam(header + "_image");
873 	if (tmp)
874 	    setImage(row,i,*tmp);
875     }
876     // Init vertical header
877     String* rowText = data.getParam(YSTRING("row_text"));
878     String* rowImg = data.getParam(YSTRING("row_image"));
879     if (rowText || rowImg) {
880 	QTableWidgetItem* item = m_table->verticalHeaderItem(row);
881 	if (!item) {
882 	    item = new QTableWidgetItem;
883 	    m_table->setVerticalHeaderItem(row,item);
884 	}
885 	if (rowText)
886 	    item->setText(QtClient::setUtf8(*rowText));
887 	if (rowImg)
888 	    item->setIcon(QIcon(QtClient::setUtf8(*rowImg)));
889     }
890 }
891 
892 // Find a row by the first's column value. Return -1 if not found
getRow(const String & item)893 int TableWidget::getRow(const String& item)
894 {
895     int n = rowCount();
896     for (int i = 0; i < n; i++) {
897 	String val;
898 	if (getCell(i,0,val) && item == val)
899 	    return i;
900     }
901     return -1;
902 }
903 
904 // Find a column by its label. Return -1 if not found
getColumn(const String & name,bool caseInsensitive)905 int TableWidget::getColumn(const String& name, bool caseInsensitive)
906 {
907     int n = columnCount();
908     for (int i = 0; i < n; i++) {
909 	String val;
910 	if (!getHeaderText(i,val,false))
911 	    continue;
912 	if ((caseInsensitive && (name &= val)) || (!caseInsensitive && name == val))
913 	    return i;
914     }
915     return -1;
916 }
917 
init(bool tmp)918 void TableWidget::init(bool tmp)
919 {
920     QtClient::getUtf8(m_name,m_table->objectName());
921     if (tmp) {
922 	m_sortControl = m_table->isSortingEnabled() ? 1 : 0;
923 	if (m_sortControl)
924 	    m_table->setSortingEnabled(false);
925     }
926 }
927 
928 /**
929  * UIBuffer
930  */
931 // Remove from list. Release memory
destruct()932 void UIBuffer::destruct()
933 {
934     s_uiCache.remove(this,false);
935     if (m_buffer) {
936 	delete m_buffer;
937 	m_buffer = 0;
938     }
939     String::destruct();
940 }
941 
942 // Return an already loaded UI. Load from file if not found.
943 // Add URLs paths when missing
build(const String & name)944 UIBuffer* UIBuffer::build(const String& name)
945 {
946     // Check if already loaded from the same location
947     UIBuffer* buf = find(name);
948     if (buf)
949 	return buf;
950 
951     // Load
952     QFile file(QtClient::setUtf8(name));
953     file.open(QIODevice::ReadOnly);
954     QByteArray* qArray = new QByteArray;
955     *qArray = file.readAll();
956     file.close();
957     if (!qArray->size()) {
958 	delete qArray;
959 	return 0;
960     }
961     // Add URLs path when missing
962     addFilePathUrl(*qArray,name);
963     return new UIBuffer(name,qArray);
964 }
965 
966 // Find a buffer
find(const String & name)967 UIBuffer* UIBuffer::find(const String& name)
968 {
969     ObjList* o = s_uiCache.find(name);
970     return o ? static_cast<UIBuffer*>(o->get()) : 0;
971 }
972 
973 
974 /**
975  * QtWindow
976  */
QtWindow()977 QtWindow::QtWindow()
978     : m_x(0), m_y(0), m_width(0), m_height(0),
979     m_maximized(false), m_mainWindow(false), m_moving(0)
980 {
981 }
982 
QtWindow(const char * name,const char * description,const char * alias,QtWindow * parent)983 QtWindow::QtWindow(const char* name, const char* description, const char* alias, QtWindow* parent)
984     : QWidget(parent, Qt::Window),
985     Window(alias ? alias : name), m_description(description), m_oldId(name),
986     m_x(0), m_y(0), m_width(0), m_height(0),
987     m_maximized(false), m_mainWindow(false), m_moving(0)
988 {
989     setObjectName(QtClient::setUtf8(m_id));
990 }
991 
~QtWindow()992 QtWindow::~QtWindow()
993 {
994     // Update all hidden counter for tray icons owned by this window
995     QList<QSystemTrayIcon*> trayIcons = qFindChildren<QSystemTrayIcon*>(this);
996     if (trayIcons.size() > 0) {
997 	if (s_allHiddenQuit >= (unsigned int)trayIcons.size())
998 	    s_allHiddenQuit -= trayIcons.size();
999 	else {
1000 	    Debug(QtDriver::self(),DebugFail,
1001 		"QtWindow(%s) destroyed with all hidden counter %u greater then tray icons %d [%p]",
1002 		m_id.c_str(),s_allHiddenQuit,trayIcons.size(),this);
1003 	    s_allHiddenQuit = 0;
1004 	}
1005     }
1006 
1007     // Save settings
1008     if (m_saveOnClose) {
1009 	m_maximized = isMaximized();
1010 	s_save.setValue(m_id,"maximized",String::boolText(m_maximized));
1011 	// Don't save position if maximized: keep the old one
1012 	if (!m_maximized) {
1013 	    s_save.setValue(m_id,"x",m_x);
1014 	    s_save.setValue(m_id,"y",m_y);
1015 	    s_save.setValue(m_id,"width",m_width);
1016 	    s_save.setValue(m_id,"height",m_height);
1017 	}
1018 	s_save.setValue(m_id,"visible",m_visible);
1019 	// Set dynamic properties to be saved for native QT objects
1020 	QList<QTableWidget*> tables = qFindChildren<QTableWidget*>(this);
1021 	for (int i = 0; i < tables.size(); i++) {
1022 	    if (qobject_cast<QtTable*>(tables[i]))
1023 		continue;
1024 	    // Column widths
1025 	    unsigned int n = tables[i]->columnCount();
1026 	    String widths;
1027 	    for (unsigned int j = 0; j < n; j++)
1028 		widths.append(String(tables[i]->columnWidth(j)),",",true);
1029 	    tables[i]->setProperty(s_propColWidths,QVariant(QtClient::setUtf8(widths)));
1030 	    // Sorting
1031 	    String sorting;
1032 	    if (tables[i]->isSortingEnabled()) {
1033 		QHeaderView* h = tables[i]->horizontalHeader();
1034 		int col = h ? h->sortIndicatorSection() : -1;
1035 		if (col >= 0)
1036 		    sorting << col << "," <<
1037 			String::boolText(Qt::AscendingOrder == h->sortIndicatorOrder());
1038 	    }
1039 	    tables[i]->setProperty(s_propSorting,QVariant(QtClient::setUtf8(sorting)));
1040 	}
1041 	QList<QSplitter*> spl = qFindChildren<QSplitter*>(this);
1042 	for (int i = 0; i < spl.size(); i++) {
1043 	    String sizes;
1044 	    QtClient::intList2str(sizes,spl[i]->sizes());
1045 	    QtClient::setProperty(spl[i],s_propSizes,sizes);
1046 	}
1047 	// Save child objects properties
1048 	QList<QObject*> child = qFindChildren<QObject*>(this);
1049 	for (int i = 0; i < child.size(); i++) {
1050 	    NamedList props("");
1051 	    if (!QtClient::getProperty(child[i],s_propsSave,props))
1052 		continue;
1053 	    unsigned int n = props.length();
1054 	    for (unsigned int j = 0; j < n; j++) {
1055 		NamedString* ns = props.getParam(j);
1056 		if (ns && ns->name())
1057 		    QtClient::saveProperty(child[i],ns->name(),this);
1058 	    }
1059 	}
1060     }
1061 }
1062 
1063 // Set windows title
title(const String & text)1064 void QtWindow::title(const String& text)
1065 {
1066     XDebug(QtDriver::self(),DebugAll,"QtWindow::title(%s) [%p]",text.c_str(),this);
1067     Window::title(text);
1068     QWidget::setWindowTitle(QtClient::setUtf8(text));
1069 }
1070 
context(const String & text)1071 void QtWindow::context(const String& text)
1072 {
1073     XDebug(QtDriver::self(),DebugAll,"QtWindow::context(%s) [%p]",text.c_str(),this);
1074     m_context = text;
1075 }
1076 
setParams(const NamedList & params)1077 bool QtWindow::setParams(const NamedList& params)
1078 {
1079     XDebug(QtDriver::self(),DebugAll,"QtWindow::setParams() [%p]",this);
1080 
1081     setUpdatesEnabled(false);
1082     // Check for custom widget params
1083     if (params == YSTRING("customwidget")) {
1084 	// Each parameter is a list of parameters for a custom widget
1085 	// Parameter name is the widget's name
1086 	unsigned int n = params.length();
1087 	bool ok = true;
1088 	for (unsigned int i = 0; i < n; i++) {
1089 	    NamedString* ns = params.getParam(i);
1090 	    NamedList* nl = static_cast<NamedList*>(ns ? ns->getObject(YATOM("NamedList")) : 0);
1091 	    if (!(nl && ns->name()))
1092 		continue;
1093 	    // Find the widget and set its params
1094 	    QtWidget w(this,ns->name());
1095 	    if (w.type() == QtWidget::CustomTable)
1096 		ok = w.customTable()->setParams(*nl) && ok;
1097 	    else if (w.type() == QtWidget::CustomWidget)
1098 		ok = w.customWidget()->setParams(*nl) && ok;
1099 	    else if (w.type() == QtWidget::CustomObject)
1100 		ok = w.customObject()->setParams(*nl) && ok;
1101 	    else
1102 		ok = false;
1103 	}
1104 	setUpdatesEnabled(true);
1105 	return ok;
1106     }
1107     // Check for system tray icon params
1108     if (params == YSTRING("systemtrayicon")) {
1109 	// Each parameter is a list of parameters for a system tray icon
1110 	// Parameter name is the widget's name
1111 	// Parameter value indicates delete/create/set an existing one
1112 	unsigned int n = params.length();
1113 	bool ok = false;
1114 	for (unsigned int i = 0; i < n; i++) {
1115 	    NamedString* ns = params.getParam(i);
1116 	    if (!(ns && ns->name()))
1117 		continue;
1118 	    QSystemTrayIcon* trayIcon = findSysTrayIcon(this,ns->name());
1119 	    // Delete
1120 	    if (ns->null()) {
1121 		if (trayIcon) {
1122 		    // Reactivate program termination when the last window was hidden
1123 		    if (s_allHiddenQuit)
1124 			s_allHiddenQuit--;
1125 		    else
1126 			Debug(QtDriver::self(),DebugFail,
1127 			    "QtWindow(%s) all hidden counter is 0 while deleting '%s' tray icon [%p]",
1128 			    m_id.c_str(),YQT_OBJECT_NAME(trayIcon),this);
1129 		    QtClient::deleteLater(trayIcon);
1130 		}
1131 		continue;
1132 	    }
1133 	    NamedList* nl = YOBJECT(NamedList,ns);
1134 	    if (!nl)
1135 		continue;
1136 	    // Create a new one if needed
1137 	    if (!trayIcon) {
1138 		if (!ns->toBoolean())
1139 		    continue;
1140 		trayIcon = new QSystemTrayIcon(this);
1141 		trayIcon->setObjectName(QtClient::setUtf8(ns->name()));
1142 		QtClient::connectObjects(trayIcon,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
1143 		    this,SLOT(sysTrayIconAction(QSystemTrayIcon::ActivationReason)));
1144 		// Deactivate program termination when the last window was hidden
1145 		s_allHiddenQuit++;
1146 	    }
1147 	    ok = true;
1148 	    // Add dynamic properties
1149 	    // TODO: track the properties, clear the old ones if needed
1150 	    addDynamicProps(trayIcon,*nl);
1151 	    // Set icon and tooltip
1152 	    NamedString* tmp = nl->getParam(YSTRING("icon"));
1153 	    if (tmp && *tmp)
1154 		trayIcon->setIcon(QIcon(QtClient::setUtf8(*tmp)));
1155 	    tmp = nl->getParam(YSTRING("tooltip"));
1156 	    if (tmp && *tmp)
1157 		trayIcon->setToolTip(QtClient::setUtf8(*tmp));
1158 	    // Check context menu
1159 	    NamedString* menu = nl->getParam(YSTRING("menu"));
1160 	    if (menu) {
1161 		QMenu* oldMenu = trayIcon->contextMenu();
1162 		if (oldMenu)
1163 		    delete oldMenu;
1164 		NamedList* nlMenu = YOBJECT(NamedList,menu);
1165 		trayIcon->setContextMenu(nlMenu ? QtClient::buildMenu(*nlMenu,*menu,this,
1166 		    SLOT(action()),SLOT(toggled(bool)),this) : 0);
1167 	    }
1168 	    if (nl->getBoolValue(YSTRING("show"),true))
1169 		trayIcon->setVisible(true);
1170 	}
1171 	setUpdatesEnabled(true);
1172 	return ok;
1173     }
1174     // Parameters for the widget whose name is the list name
1175     if(params) {
1176 	QtWidget w(this, params);
1177 	// Handle UIWidget descendants
1178 	UIWidget* uiw = w.uiWidget();
1179 	if (uiw) {
1180 	    bool ok = uiw->setParams(params);
1181 	    setUpdatesEnabled(true);
1182 	    return ok;
1183 	}
1184 	if (w.type() == QtWidget::Calendar) {
1185 	    int year = params.getIntValue(YSTRING("year"));
1186 	    int month = params.getIntValue(YSTRING("month"));
1187 	    int day = params.getIntValue(YSTRING("day"));
1188 	    w.calendar()->setCurrentPage(year, month);
1189 	    w.calendar()->setSelectedDate(QDate(year, month, day));
1190 	    setUpdatesEnabled(true);
1191 	    return true;
1192 	}
1193     }
1194 
1195     // Window or other parameters
1196     if (params.getBoolValue(YSTRING("modal"))) {
1197 	if (parentWindow())
1198 	    setWindowModality(Qt::WindowModal);
1199 	else
1200 	    setWindowModality(Qt::ApplicationModal);
1201     }
1202     if (params.getBoolValue(YSTRING("minimized")))
1203 	QWidget::setWindowState(Qt::WindowMinimized);
1204     bool ok = Window::setParams(params);
1205     setUpdatesEnabled(true);
1206     return ok;
1207 }
1208 
setOver(const Window * parent)1209 void QtWindow::setOver(const Window* parent)
1210 {
1211     XDebug(QtDriver::self(),DebugAll,"QtWindow::setOver(%p) [%p]",parent,this);
1212     QWidget::raise();
1213 }
1214 
hasElement(const String & name)1215 bool QtWindow::hasElement(const String& name)
1216 {
1217     XDebug(QtDriver::self(),DebugAll,"QtWindow::hasElement(%s) [%p]",name.c_str(),this);
1218     QtWidget w(this,name);
1219     return w.valid();
1220 }
1221 
setActive(const String & name,bool active)1222 bool QtWindow::setActive(const String& name, bool active)
1223 {
1224     XDebug(QtDriver::self(),DebugAll,"QtWindow::setActive(%s,%s) [%p]",
1225 	name.c_str(),String::boolText(active),this);
1226     bool ok = (name == m_id);
1227     if (ok) {
1228 	if (QWidget::isMinimized())
1229 	    QWidget::showNormal();
1230 	QWidget::activateWindow();
1231 	QWidget::raise();
1232     }
1233     QtWidget w(this,name);
1234     if (w.invalid())
1235 	return ok;
1236     if (w.type() != QtWidget::Action)
1237 	w->setEnabled(active);
1238     else
1239 	w.action()->setEnabled(active);
1240     return true;
1241 }
1242 
setFocus(const String & name,bool select)1243 bool QtWindow::setFocus(const String& name, bool select)
1244 {
1245     XDebug(QtDriver::self(),DebugAll,"QtWindow::setFocus(%s,%s) [%p]",
1246 	name.c_str(),String::boolText(select),this);
1247     QtWidget w(this,name);
1248     if (w.invalid())
1249 	return false;
1250     w->setFocus();
1251     switch (w.type()) {
1252 	case  QtWidget::ComboBox:
1253 	    if (w.combo()->isEditable() && select)
1254 		w.combo()->lineEdit()->selectAll();
1255 	    break;
1256     }
1257     return true;
1258 }
1259 
setShow(const String & name,bool visible)1260 bool QtWindow::setShow(const String& name, bool visible)
1261 {
1262     XDebug(QtDriver::self(),DebugAll,"QtWindow::setShow(%s,%s) [%p]",
1263 	name.c_str(),String::boolText(visible),this);
1264     // Check system tray icons
1265     QSystemTrayIcon* trayIcon = findSysTrayIcon(this,name);
1266     if (trayIcon) {
1267 	trayIcon->setVisible(visible);
1268 	return true;
1269     }
1270     // Widgets
1271     QtWidget w(this,name);
1272     if (w.invalid())
1273 	return false;
1274     setUpdatesEnabled(false);
1275     if (w.type() != QtWidget::Action)
1276 	w->setVisible(visible);
1277     else
1278 	w.action()->setVisible(visible);
1279     setUpdatesEnabled(true);
1280     return true;
1281 }
1282 
setText(const String & name,const String & text,bool richText)1283 bool QtWindow::setText(const String& name, const String& text,
1284 	bool richText)
1285 {
1286     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setText(%s,%s) [%p]",
1287 	m_id.c_str(),name.c_str(),text.c_str(),this);
1288 
1289     QtWidget w(this,name);
1290     if (w.invalid())
1291 	return false;
1292     UIWidget* uiw = w.uiWidget();
1293     if (uiw)
1294 	return uiw->setText(text,richText);
1295     switch (w.type()) {
1296 	case QtWidget::CheckBox:
1297 	    w.check()->setText(QtClient::setUtf8(text));
1298 	    return true;
1299 	case QtWidget::LineEdit:
1300 	    w.lineEdit()->setText(QtClient::setUtf8(text));
1301 	    return true;
1302 	case QtWidget::TextBrowser:
1303 	case QtWidget::TextEdit:
1304 	    if (richText) {
1305 		w.textEdit()->clear();
1306 		w.textEdit()->insertHtml(QtClient::setUtf8(text));
1307 	    }
1308 	    else
1309 		w.textEdit()->setText(QtClient::setUtf8(text));
1310 	    {
1311 		QScrollBar* bar = w.textEdit()->verticalScrollBar();
1312 		if (bar)
1313 		    bar->setSliderPosition(bar->maximum());
1314 	    }
1315 	    return true;
1316 	case QtWidget::Label:
1317 	    w.label()->setText(QtClient::setUtf8(text));
1318 	    return true;
1319 	case QtWidget::ComboBox:
1320 	    if (w.combo()->lineEdit())
1321 		w.combo()->lineEdit()->setText(QtClient::setUtf8(text));
1322 	    else
1323 		setSelect(name,text);
1324 	    return true;
1325 	case QtWidget::Action:
1326 	    w.action()->setText(QtClient::setUtf8(text));
1327 	    return true;
1328 	case QtWidget::SpinBox:
1329 	    w.spinBox()->setValue(text.toInteger());
1330 	    return true;
1331     }
1332 
1333     // Handle some known base classes having a setText() method
1334     if (w.inherits(QtWidget::AbstractButton))
1335 	w.abstractButton()->setText(QtClient::setUtf8(text));
1336     else
1337 	return false;
1338     return true;
1339 }
1340 
setCheck(const String & name,bool checked)1341 bool QtWindow::setCheck(const String& name, bool checked)
1342 {
1343     XDebug(QtDriver::self(),DebugAll,"QtWindow::setCheck(%s,%s) [%p]",
1344 	name.c_str(),String::boolText(checked),this);
1345     QtWidget w(this,name);
1346     if (w.invalid())
1347 	return false;
1348     if (w.inherits(QtWidget::AbstractButton))
1349 	w.abstractButton()->setChecked(checked);
1350     else if (w.type() == QtWidget::Action)
1351 	w.action()->setChecked(checked);
1352     else
1353 	return false;
1354     return true;
1355 }
1356 
setSelect(const String & name,const String & item)1357 bool QtWindow::setSelect(const String& name, const String& item)
1358 {
1359     XDebug(QtDriver::self(),DebugAll,"QtWindow::setSelect(%s,%s) [%p]",
1360 	name.c_str(),item.c_str(),this);
1361 
1362     QtWidget w(this,name);
1363     if (w.invalid())
1364 	return false;
1365     UIWidget* uiw = w.uiWidget();
1366     if (uiw)
1367 	return uiw->setSelect(item);
1368 
1369     int d = 0;
1370     switch (w.type()) {
1371 	case QtWidget::Table:
1372 	    {
1373 		TableWidget t(w);
1374 		int row = t.getRow(item);
1375 		if (row < 0)
1376 		    return false;
1377 		t.table()->setCurrentCell(row,0);
1378 		return true;
1379 	    }
1380 	case QtWidget::ComboBox:
1381 	    if (item) {
1382 		d = w.findComboItem(item);
1383 		if (d < 0)
1384 		    return false;
1385 	        w.combo()->setCurrentIndex(d);
1386 	    }
1387 	    else if (w.combo()->lineEdit())
1388 		w.combo()->lineEdit()->setText("");
1389 	    else
1390 		return false;
1391 	    return true;
1392 	case QtWidget::ListBox:
1393 	    d = w.findListItem(item);
1394 	    if (d >= 0)
1395 		w.list()->setCurrentRow(d);
1396 	    return d >= 0;
1397 	case QtWidget::Slider:
1398 	    w.slider()->setValue(item.toInteger());
1399 	    return true;
1400 	case QtWidget::StackWidget:
1401 	    d = item.toInteger(-1);
1402 	    while (d < 0) {
1403 		d = findStackedWidget(*(w.stackWidget()),item);
1404 		if (d >= 0)
1405 		    break;
1406 		// Check for a default widget
1407 		String def = YQT_OBJECT_NAME(w.stackWidget());
1408 		def << "_default";
1409 		d = findStackedWidget(*(w.stackWidget()),def);
1410 		break;
1411 	    }
1412 	    if (d >= 0 && d < w.stackWidget()->count()) {
1413 		w.stackWidget()->setCurrentIndex(d);
1414 		return true;
1415 	    }
1416 	    return false;
1417 	case QtWidget::ProgressBar:
1418 	    d = item.toInteger();
1419 	    if (d >= w.progressBar()->minimum() && d <= w.progressBar()->maximum())
1420 		w.progressBar()->setValue(d);
1421 	    else if (d < w.progressBar()->minimum())
1422 		w.progressBar()->setValue(w.progressBar()->minimum());
1423 	    else
1424 		w.progressBar()->setValue(w.progressBar()->maximum());
1425 	    return true;
1426 	case QtWidget::Tab:
1427 	    d = w.tab()->count() - 1;
1428 	    for (QString tmp = QtClient::setUtf8(item); d >= 0; d--) {
1429 		QWidget* wid = w.tab()->widget(d);
1430 		if (wid && wid->objectName() == tmp)
1431 		    break;
1432 	    }
1433 	    if (d >= 0 && d < w.tab()->count()) {
1434 		w.tab()->setCurrentIndex(d);
1435 		return true;
1436 	    }
1437 	    return false;
1438 
1439     }
1440     return false;
1441 }
1442 
setUrgent(const String & name,bool urgent)1443 bool QtWindow::setUrgent(const String& name, bool urgent)
1444 {
1445     XDebug(QtDriver::self(),DebugAll,"QtWindow::setUrgent(%s,%s) [%p]",
1446 	name.c_str(),String::boolText(urgent),this);
1447 
1448     if (name == m_id) {
1449 	QApplication::alert(this,0);
1450 	return true;
1451     }
1452 
1453     QtWidget w(this,name);
1454     if (w.invalid())
1455 	return false;
1456     w->raise();
1457     return true;
1458 }
1459 
hasOption(const String & name,const String & item)1460 bool QtWindow::hasOption(const String& name, const String& item)
1461 {
1462     XDebug(QtDriver::self(),DebugAll,"QtWindow::hasOption(%s,%s) [%p]",
1463 	name.c_str(),item.c_str(),this);
1464     QtWidget w(this,name);
1465     if (w.invalid())
1466 	return false;
1467     switch (w.type()) {
1468 	case QtWidget::ComboBox:
1469 	    return -1 != w.findComboItem(item);
1470 	case QtWidget::Table:
1471 	    return getTableRow(name,item);
1472 	case QtWidget::ListBox:
1473 	    return -1 != w.findListItem(item);
1474     }
1475     return false;
1476 }
1477 
addOption(const String & name,const String & item,bool atStart,const String & text)1478 bool QtWindow::addOption(const String& name, const String& item, bool atStart,
1479 	const String& text)
1480 {
1481     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addOption(%s,%s,%s,%s) [%p]",
1482 	m_id.c_str(),name.c_str(),item.c_str(),
1483 	String::boolText(atStart),text.c_str(),this);
1484 
1485     QtWidget w(this,name);
1486     switch (w.type()) {
1487 	case QtWidget::ComboBox:
1488 	    w.addComboItem(item,atStart);
1489 	    if (atStart && w.combo()->lineEdit())
1490 		w.combo()->lineEdit()->setText(w.combo()->itemText(0));
1491 	    return true;
1492 	case QtWidget::Table:
1493 	    return addTableRow(name,item,0,atStart);
1494 	case QtWidget::ListBox:
1495 	    return w.addListItem(item,atStart);
1496     }
1497     return false;
1498 }
1499 
delOption(const String & name,const String & item)1500 bool QtWindow::delOption(const String& name, const String& item)
1501 {
1502     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) delOption(%s,%s) [%p]",
1503 	m_id.c_str(),name.c_str(),item.c_str(),this);
1504     return delTableRow(name,item);
1505 }
1506 
getOptions(const String & name,NamedList * items)1507 bool QtWindow::getOptions(const String& name, NamedList* items)
1508 {
1509     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getOptions(%s,%p) [%p]",
1510 	m_id.c_str(),name.c_str(),items,this);
1511 
1512     QtWidget w(this,name);
1513     if (w.invalid())
1514 	return false;
1515     if (!items)
1516 	return true;
1517     UIWidget* uiw = w.uiWidget();
1518     if (uiw)
1519 	return uiw->getOptions(*items);
1520 
1521     switch (w.type()) {
1522 	case QtWidget::ComboBox:
1523 	    for (int i = 0; i < w.combo()->count(); i++)
1524 		QtClient::getUtf8(*items,"",w.combo()->itemText(i),false);
1525 	    break;
1526 	case QtWidget::Table:
1527 	    {
1528 		TableWidget t(w.table(),false);
1529 		for (int i = 0; i < t.rowCount(); i++) {
1530 		    String item;
1531 		    if (t.getCell(i,0,item) && item)
1532 			items->addParam(item,"");
1533 		}
1534 	    }
1535 	    break;
1536 	case QtWidget::ListBox:
1537 	    for (int i = 0; i < w.list()->count(); i++) {
1538 		QListWidgetItem* tmp = w.list()->item(i);
1539 		if (tmp)
1540 		    QtClient::getUtf8(*items,"",tmp->text(),false);
1541 	    }
1542 	    break;
1543     }
1544     return true;
1545 }
1546 
1547 // Append or insert text lines to a widget
addLines(const String & name,const NamedList * lines,unsigned int max,bool atStart)1548 bool QtWindow::addLines(const String& name, const NamedList* lines, unsigned int max,
1549 	bool atStart)
1550 {
1551     DDebug(ClientDriver::self(),DebugAll,"QtWindow(%s) addLines('%s',%p,%u,%s) [%p]",
1552 	m_id.c_str(),name.c_str(),lines,max,String::boolText(atStart),this);
1553 
1554     QtWidget w(this,name);
1555     if (w.invalid())
1556 	return false;
1557     if (!lines)
1558 	return true;
1559     UIWidget* uiw = w.uiWidget();
1560     if (uiw)
1561 	return uiw->addLines(*lines,max,atStart);
1562     unsigned int count = lines->length();
1563     if (!count)
1564 	return true;
1565 
1566     switch (w.type()) {
1567 	case QtWidget::TextBrowser:
1568 	case QtWidget::TextEdit:
1569 	    // Limit the maximum number of paragraphs
1570 	    if (max) {
1571 		QTextDocument* doc = w.textEdit()->document();
1572 		if (!doc)
1573 		    return false;
1574 		doc->setMaximumBlockCount((int)max);
1575 	    }
1576 	    {
1577 		// FIXME: delete lines from begining if appending and the number
1578 		//  of lines exceeds the maximum allowed
1579 		QString s = w.textEdit()->toPlainText();
1580 		int pos = atStart ? 0 : s.length();
1581 		for (unsigned int i = 0; i < count; i++) {
1582 		    NamedString* ns = lines->getParam(i);
1583 		    if (!ns)
1584 			continue;
1585 		    if (ns->name().endsWith("\n"))
1586 			s.insert(pos,QtClient::setUtf8(ns->name()));
1587 		    else {
1588 			String tmp = ns->name() + "\n";
1589 			s.insert(pos,QtClient::setUtf8(tmp));
1590 			pos++;
1591 		    }
1592 		    pos += (int)ns->name().length();
1593 		}
1594 		w.textEdit()->setText(s);
1595 		// Scroll down if added at end
1596 		if (!atStart) {
1597 		    QScrollBar* bar = w.textEdit()->verticalScrollBar();
1598 		    if (bar)
1599 			bar->setSliderPosition(bar->maximum());
1600 		}
1601 	    }
1602 	    return true;
1603 	case QtWidget::Table:
1604 	    // TODO: implement
1605 	    break;
1606 	case QtWidget::ComboBox:
1607 	    if (atStart) {
1608 		for (; count; count--) {
1609 		    NamedString* ns = lines->getParam(count - 1);
1610 		    if (ns)
1611 			w.combo()->insertItem(0,QtClient::setUtf8(ns->name()));
1612 		}
1613 		if (w.combo()->lineEdit())
1614 		    w.combo()->lineEdit()->setText(w.combo()->itemText(0));
1615 	    }
1616 	    else {
1617 		for (unsigned int i = 0; i < count; i++) {
1618 		    NamedString* ns = lines->getParam(i);
1619 		    if (ns)
1620 			w.combo()->addItem(QtClient::setUtf8(ns->name()));
1621 		}
1622 	    }
1623 	    return true;
1624 	case QtWidget::ListBox:
1625 	    // TODO: implement
1626 	    break;
1627     }
1628     return false;
1629 }
1630 
addTableRow(const String & name,const String & item,const NamedList * data,bool atStart)1631 bool QtWindow::addTableRow(const String& name, const String& item,
1632 	const NamedList* data, bool atStart)
1633 {
1634     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addTableRow(%s,%s,%p,%s) [%p]",
1635 	m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this);
1636 
1637     QtWidget w(this,name);
1638     if (w.invalid())
1639 	return false;
1640     UIWidget* uiw = w.uiWidget();
1641     if (uiw)
1642 	return uiw->addTableRow(item,data,atStart);
1643     // Handle basic QTableWidget
1644     if (w.type() != QtWidget::Table)
1645 	return false;
1646     TableWidget tbl(w.table());
1647     int row = atStart ? 0 : tbl.rowCount();
1648     tbl.addRow(row);
1649     // Set item (the first column) and the rest of data
1650     tbl.setID(row,item);
1651     if (data)
1652 	tbl.updateRow(row,*data);
1653     return true;
1654 }
1655 
1656 // Insert or update multiple rows in a single operation
setMultipleRows(const String & name,const NamedList & data,const String & prefix)1657 bool QtWindow::setMultipleRows(const String& name, const NamedList& data, const String& prefix)
1658 {
1659     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setMultipleRows('%s',%p,'%s') [%p]",
1660 	m_id.c_str(),name.c_str(),&data,prefix.c_str(),this);
1661 
1662     QtWidget w(this,name);
1663     if (w.invalid())
1664 	return false;
1665     UIWidget* uiw = w.uiWidget();
1666     return uiw && uiw->setMultipleRows(data,prefix);
1667 }
1668 
1669 // Insert a row into a table owned by this window
insertTableRow(const String & name,const String & item,const String & before,const NamedList * data)1670 bool QtWindow::insertTableRow(const String& name, const String& item,
1671     const String& before, const NamedList* data)
1672 {
1673     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) insertTableRow(%s,%s,%s,%p) [%p]",
1674 	m_id.c_str(),name.c_str(),item.c_str(),before.c_str(),data,this);
1675 
1676     QtWidget w(this,name);
1677     if (w.invalid())
1678 	return false;
1679     UIWidget* uiw = w.uiWidget();
1680     if (uiw)
1681 	return uiw->insertTableRow(item,before,data);
1682     if (w.type() != QtWidget::Table)
1683 	return false;
1684     TableWidget tbl(w.table());
1685     int row = tbl.getRow(before);
1686     if (row == -1)
1687 	row = tbl.rowCount();
1688     tbl.addRow(row);
1689     // Set item (the first column) and the rest of data
1690     tbl.setID(row,item);
1691     if (data)
1692 	tbl.updateRow(row,*data);
1693     return true;
1694 }
1695 
delTableRow(const String & name,const String & item)1696 bool QtWindow::delTableRow(const String& name, const String& item)
1697 {
1698     XDebug(QtDriver::self(),DebugAll,"QtWindow::delTableRow(%s,%s) [%p]",
1699 	name.c_str(),item.c_str(),this);
1700     QtWidget w(this,name);
1701     if (w.invalid())
1702 	return false;
1703     int row = -1;
1704     int n = 0;
1705     switch (w.type()) {
1706 	case QtWidget::Table:
1707 	case QtWidget::CustomTable:
1708 	    {
1709 		TableWidget tbl(w.table());
1710 		QtTable* custom = tbl.customTable();
1711 		if (custom) {
1712 		    if (custom->delTableRow(item))
1713 			row = 0;
1714 		}
1715 		else {
1716 		    row = tbl.getRow(item);
1717 		    if (row >= 0)
1718 			tbl.delRow(row);
1719 		}
1720 		n = tbl.rowCount();
1721 	    }
1722 	    break;
1723 	case QtWidget::ComboBox:
1724 	    row = w.findComboItem(item);
1725 	    if (row >= 0) {
1726 		w.combo()->removeItem(row);
1727 		n = w.combo()->count();
1728 	    }
1729 	    break;
1730 	case QtWidget::ListBox:
1731 	    row = w.findListItem(item);
1732 	    if (row >= 0) {
1733 		QStringListModel* model = (QStringListModel*)w.list()->model();
1734 		if (!(model && model->removeRow(row)))
1735 		    row = -1;
1736 		n = w.list()->count();
1737 	    }
1738 	    break;
1739 	default:
1740 	    UIWidget* uiw = w.uiWidget();
1741 	    if (uiw && uiw->delTableRow(item)) {
1742 		row = 0;
1743 		// Don't notify empty: we don't know it
1744 		n = 1;
1745 	    }
1746     }
1747     if (row < 0)
1748 	return false;
1749     if (!n)
1750 	raiseSelectIfEmpty(0,this,name);
1751     return true;
1752 }
1753 
setTableRow(const String & name,const String & item,const NamedList * data)1754 bool QtWindow::setTableRow(const String& name, const String& item, const NamedList* data)
1755 {
1756     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setTableRow(%s,%s,%p) [%p]",
1757 	m_id.c_str(),name.c_str(),item.c_str(),data,this);
1758 
1759     QtWidget w(this,name);
1760     if (w.invalid())
1761 	return false;
1762     UIWidget* uiw = w.uiWidget();
1763     if (uiw)
1764 	return uiw->setTableRow(item,data);
1765     if (w.type() != QtWidget::Table)
1766 	return false;
1767     TableWidget tbl(w.table());
1768     int row = tbl.getRow(item);
1769     if (row < 0)
1770 	return false;
1771     if (data)
1772 	tbl.updateRow(row,*data);
1773     return true;
1774 }
1775 
getTableRow(const String & name,const String & item,NamedList * data)1776 bool QtWindow::getTableRow(const String& name, const String& item, NamedList* data)
1777 {
1778     XDebug(QtDriver::self(),DebugAll,"QtWindow::getTableRow(%s,%s,%p) [%p]",
1779 	name.c_str(),item.c_str(),data,this);
1780 
1781     QtWidget w(this,name);
1782     if (w.invalid())
1783 	return false;
1784     UIWidget* uiw = w.uiWidget();
1785     if (uiw)
1786 	return uiw->getTableRow(item,data);
1787     if (w.type() != QtWidget::Table)
1788 	return false;
1789     TableWidget tbl(w.table(),false);
1790     int row = tbl.getRow(item);
1791     if (row < 0)
1792 	return false;
1793     if (!data)
1794 	return true;
1795     int n = tbl.columnCount();
1796     for (int i = 0; i < n; i++) {
1797 	String name;
1798 	if (!tbl.getHeaderText(i,name))
1799 	    continue;
1800 	String value;
1801 	if (tbl.getCell(row,i,value))
1802 	    data->setParam(name,value);
1803     }
1804     return true;
1805 }
1806 
1807 // Set a table row or add a new one if not found
updateTableRow(const String & name,const String & item,const NamedList * data,bool atStart)1808 bool QtWindow::updateTableRow(const String& name, const String& item,
1809     const NamedList* data, bool atStart)
1810 {
1811     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRow('%s','%s',%p,%s) [%p]",
1812 	m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this);
1813     QtWidget w(this,name);
1814     if (w.invalid())
1815 	return false;
1816     switch (w.type()) {
1817 	case QtWidget::Table:
1818 	case QtWidget::CustomTable:
1819 	    {
1820 		TableWidget tbl(w.table());
1821 		QtTable* custom = tbl.customTable();
1822 		if (custom) {
1823 		    if (custom->getTableRow(item))
1824 			return custom->setTableRow(item,data);
1825 		    return custom->addTableRow(item,data,atStart);
1826 		}
1827 		tbl.updateRow(item,data,atStart);
1828 		return true;
1829 	    }
1830 	case QtWidget::CustomTree:
1831 	    {
1832 		QtTree* custom = w.customTree();
1833 		if (custom) {
1834 		    if (custom->getTableRow(item))
1835 			return custom->setTableRow(item,data);
1836 		    return custom->addTableRow(item,data,atStart);
1837 		}
1838 		return false;
1839 	    }
1840 	case QtWidget::ComboBox:
1841 	    return w.findComboItem(item) >= 0 || w.addComboItem(item,atStart);
1842 	case QtWidget::ListBox:
1843 	    return w.findListItem(item) >= 0 || w.addListItem(item,atStart);
1844     }
1845     return false;
1846 }
1847 
1848 // Add or set one or more table row(s). Screen update is locked while changing the table.
1849 // Each data list element is a NamedPointer carrying a NamedList with item parameters.
1850 // The name of an element is the item to update.
1851 // Element's value not empty: update the item
1852 // Else: delete it
updateTableRows(const String & name,const NamedList * data,bool atStart)1853 bool QtWindow::updateTableRows(const String& name, const NamedList* data, bool atStart)
1854 {
1855     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRows('%s',%p,%s) [%p]",
1856 	m_id.c_str(),name.c_str(),data,String::boolText(atStart),this);
1857 
1858     QtWidget w(this,name);
1859     if (w.invalid())
1860 	return false;
1861     UIWidget* uiw = w.uiWidget();
1862     if (uiw) {
1863 	bool ok = uiw->updateTableRows(data,atStart);
1864 	QtTable* ct = w.customTable();
1865 	if (ct)
1866 	    raiseSelectIfEmpty(ct->rowCount(),this,name);
1867 	return ok;
1868     }
1869     if (w.type() != QtWidget::Table)
1870 	return false;
1871     if (!data)
1872 	return true;
1873     TableWidget tbl(w.table());
1874     bool ok = true;
1875     tbl.table()->setUpdatesEnabled(false);
1876     ObjList add;
1877     unsigned int n = data->length();
1878     for (unsigned int i = 0; i < n; i++) {
1879 	if (Client::exiting())
1880 	    break;
1881 	// Get item and the list of parameters
1882 	NamedString* ns = data->getParam(i);
1883 	if (!ns)
1884 	    continue;
1885 	// Delete ?
1886 	if (ns->null()) {
1887 	    int row = tbl.getRow(ns->name());
1888 	    if (row >= 0)
1889 		tbl.delRow(row);
1890 	    else
1891 		ok = false;
1892 	    continue;
1893 	}
1894 	// Set existing row or postpone add
1895 	int row = tbl.getRow(ns->name());
1896 	if (row >= 0) {
1897 	    const NamedList* params = YOBJECT(NamedList,ns);
1898 	    if (params)
1899 		tbl.updateRow(row,*params);
1900 	}
1901 	else if (ns->toBoolean())
1902 	    add.append(ns)->setDelete(false);
1903 	else
1904 	    ok = false;
1905     }
1906     n = add.count();
1907     if (n) {
1908 	int row = tbl.rowCount();
1909 	if (row < 0)
1910 	    row = 0;
1911 	// Append if not requested to insert at start or table is empty
1912 	if (!(atStart && row))
1913 	    tbl.table()->setRowCount(row + n);
1914 	else {
1915 	    for (unsigned int i = 0; i < n; i++)
1916 		tbl.table()->insertRow(0);
1917 	}
1918 	for (ObjList* o = add.skipNull(); o; row++, o = o->skipNext()) {
1919 	    NamedString* ns = static_cast<NamedString*>(o->get());
1920 	    tbl.setID(row,ns->name());
1921 	    const NamedList* params = YOBJECT(NamedList,ns);
1922 	    if (params)
1923 		tbl.updateRow(row,*params);
1924 	}
1925     }
1926     tbl.table()->setUpdatesEnabled(true);
1927     raiseSelectIfEmpty(tbl.rowCount(),this,name);
1928     return ok;
1929 }
1930 
clearTable(const String & name)1931 bool QtWindow::clearTable(const String& name)
1932 {
1933     DDebug(QtDriver::self(),DebugAll,"QtWindow::clearTable(%s) [%p]",name.c_str(),this);
1934     QtWidget w(this,name);
1935     if (w.invalid())
1936 	return false;
1937     UIWidget* uiw = w.uiWidget();
1938     if (uiw)
1939 	return uiw->clearTable();
1940     bool ok = true;
1941     if (w.widget())
1942 	w->setUpdatesEnabled(false);
1943     switch (w.type()) {
1944 	case QtWidget::Table:
1945 	    w.table()->setRowCount(0);
1946 	    break;
1947 	case QtWidget::TextBrowser:
1948 	case QtWidget::TextEdit:
1949 	    w.textEdit()->clear();
1950 	    break;
1951 	case QtWidget::ListBox:
1952 	    w.list()->clear();
1953 	    break;
1954 	case QtWidget::ComboBox:
1955 	    w.combo()->clear();
1956 	    break;
1957 	default:
1958 	    ok = false;
1959     }
1960     if (w.widget())
1961 	w->setUpdatesEnabled(true);
1962     return ok;
1963 }
1964 
1965 // Show or hide control busy state
setBusy(const String & name,bool on)1966 bool QtWindow::setBusy(const String& name, bool on)
1967 {
1968     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setBusy(%s,%u) [%p]",
1969 	m_id.c_str(),name.c_str(),on,this);
1970     if (name == m_id)
1971 	return QtBusyWidget::showBusyChild(this,on);
1972     QtWidget w(this,name);
1973     if (w.invalid())
1974 	return false;
1975     UIWidget* uiw = w.uiWidget();
1976     if (uiw)
1977 	return uiw->setBusy(on);
1978     if (w.widget())
1979 	return QtBusyWidget::showBusyChild(w.widget(),on);
1980     return false;
1981 }
1982 
getText(const String & name,String & text,bool richText)1983 bool QtWindow::getText(const String& name, String& text, bool richText)
1984 {
1985     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getText(%s) [%p]",
1986 	m_id.c_str(),name.c_str(),this);
1987     QtWidget w(this,name);
1988     if (w.invalid())
1989 	return false;
1990     UIWidget* uiw = w.uiWidget();
1991     if (uiw)
1992 	return uiw->getText(text,richText);
1993     switch (w.type()) {
1994 	case QtWidget::ComboBox:
1995 	    QtClient::getUtf8(text,w.combo()->currentText());
1996 	    return true;
1997 	case QtWidget::LineEdit:
1998 	    QtClient::getUtf8(text,w.lineEdit()->text());
1999 	    return true;
2000 	case QtWidget::TextBrowser:
2001 	case QtWidget::TextEdit:
2002 	    if (!richText)
2003 		QtClient::getUtf8(text,w.textEdit()->toPlainText());
2004 	    else
2005 		QtClient::getUtf8(text,w.textEdit()->toHtml());
2006 	    return true;
2007 	case QtWidget::Label:
2008 	    QtClient::getUtf8(text,w.label()->text());
2009 	    return true;
2010 	case QtWidget::Action:
2011 	    QtClient::getUtf8(text,w.action()->text());
2012 	    return true;
2013 	case QtWidget::SpinBox:
2014 	    text = w.spinBox()->value();
2015 	    return true;
2016 	default:
2017 	    if (w.inherits(QtWidget::AbstractButton)) {
2018 		QtClient::getUtf8(text,w.abstractButton()->text());
2019 		return true;
2020 	    }
2021      }
2022      return false;
2023 }
2024 
getCheck(const String & name,bool & checked)2025 bool QtWindow::getCheck(const String& name, bool& checked)
2026 {
2027     DDebug(QtDriver::self(),DebugAll,"QtWindow::getCheck(%s) [%p]",name.c_str(),this);
2028 
2029     QtWidget w(this,name);
2030     if (w.invalid())
2031 	return false;
2032     if (w.inherits(QtWidget::AbstractButton))
2033 	checked = w.abstractButton()->isChecked();
2034     else if (w.type() == QtWidget::Action)
2035 	checked = w.action()->isChecked();
2036     else
2037 	return false;
2038     return true;
2039 }
2040 
getSelect(const String & name,String & item)2041 bool QtWindow::getSelect(const String& name, String& item)
2042 {
2043     XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%s) [%p]",name.c_str(),this);
2044     QtWidget w(this,name);
2045     if (w.invalid())
2046 	return false;
2047     UIWidget* uiw = w.uiWidget();
2048     if (uiw)
2049 	return uiw->getSelect(item);
2050     switch (w.type()) {
2051 	case QtWidget::ComboBox:
2052 	    if (w.combo()->lineEdit() && w.combo()->lineEdit()->selectedText().isEmpty())
2053 		return false;
2054 	    QtClient::getUtf8(item,w.combo()->currentText());
2055 	    return true;
2056 	case QtWidget::Table:
2057 	    {
2058 		TableWidget t(w);
2059 		int row = t.crtRow();
2060 		return row >= 0 ? t.getCell(row,0,item) : false;
2061 	    }
2062 	case QtWidget::ListBox:
2063 	    {
2064 		QListWidgetItem* crt = w.list()->currentItem();
2065 		if (!crt)
2066 		    return false;
2067 		QtClient::getUtf8(item,crt->text());
2068 	    }
2069 	    return true;
2070 	case QtWidget::Slider:
2071 	    item = w.slider()->value();
2072 	    return true;
2073 	case QtWidget::ProgressBar:
2074 	    item = w.progressBar()->value();
2075 	    return true;
2076 	case QtWidget::Tab:
2077 	    {
2078 		item = "";
2079 		QWidget* wid = w.tab()->currentWidget();
2080 		if (wid)
2081 		    QtClient::getUtf8(item,wid->objectName());
2082 	    }
2083 	    return true;
2084 	case QtWidget::StackWidget:
2085 	    {
2086 		item = "";
2087 		QWidget* wid = w.stackWidget()->currentWidget();
2088 		if (wid)
2089 		    QtClient::getUtf8(item,wid->objectName());
2090 	    }
2091 	    return true;
2092     }
2093     return false;
2094 }
2095 
2096 // Retrieve an element's multiple selection
getSelect(const String & name,NamedList & items)2097 bool QtWindow::getSelect(const String& name, NamedList& items)
2098 {
2099     XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%p) [%p]",&items,this);
2100     QtWidget w(this,name);
2101     if (w.invalid())
2102 	return false;
2103     UIWidget* uiw = w.uiWidget();
2104     if (uiw)
2105 	return uiw->getSelect(items);
2106     switch (w.type()) {
2107 	case QtWidget::ComboBox:
2108 	case QtWidget::Table:
2109 	case QtWidget::ListBox:
2110 	case QtWidget::Slider:
2111 	case QtWidget::ProgressBar:
2112 	case QtWidget::Tab:
2113 	case QtWidget::StackWidget:
2114 	    DDebug(QtDriver::self(),DebugStub,"QtWindow::getSelect(%p) not implemented for '%s; [%p]",
2115 		&items,w.widget()->metaObject()->className(),this);
2116     }
2117     return false;
2118 }
2119 
2120 // Build a menu from a list of parameters
buildMenu(const NamedList & params)2121 bool QtWindow::buildMenu(const NamedList& params)
2122 {
2123     QWidget* parent = this;
2124     // Retrieve the owner
2125     const String& owner = params[YSTRING("owner")];
2126     if (owner && owner != m_id) {
2127 	parent = qFindChild<QWidget*>(this,QtClient::setUtf8(owner));
2128 	if (!parent) {
2129 	    DDebug(QtDriver::self(),DebugNote,
2130 		"QtWindow(%s) buildMenu(%s) owner '%s' not found [%p]",
2131 		m_id.c_str(),params.c_str(),owner.c_str(),this);
2132 	    return false;
2133 	}
2134     }
2135     QWidget* target = parent;
2136     const String& t = params[YSTRING("target")];
2137     if (t) {
2138 	target = qFindChild<QWidget*>(this,QtClient::setUtf8(t));
2139 	if (!target) {
2140 	    DDebug(QtDriver::self(),DebugNote,
2141 		"QtWindow(%s) buildMenu(%s) target '%s' not found [%p]",
2142 		m_id.c_str(),params.c_str(),t.c_str(),this);
2143 	    return false;
2144 	}
2145     }
2146     // Remove existing menu
2147     removeMenu(params);
2148     QMenu* menu = QtClient::buildMenu(params,params.getValue(YSTRING("title"),params),this,
2149 	SLOT(action()),SLOT(toggled(bool)),parent);
2150     if (!menu) {
2151 	DDebug(QtDriver::self(),DebugNote,
2152 	    "QtWindow(%s) failed to build menu '%s' target='%s' [%p]",
2153 	    m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this);
2154 	return false;
2155     }
2156     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) built menu '%s' target='%s' [%p]",
2157 	m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this);
2158     QMenuBar* mbOwner = qobject_cast<QMenuBar*>(target);
2159     QMenu* mOwner = !mbOwner ? qobject_cast<QMenu*>(target) : 0;
2160     if (mbOwner || mOwner) {
2161 	QAction* before = 0;
2162 	const String& bef = params[YSTRING("before")];
2163 	// Retrieve the action to insert before
2164 	if (bef) {
2165 	    QString cmp = QtClient::setUtf8(bef);
2166 	    QList<QAction*> list = target->actions();
2167 	    for (int i = 0; !before && i < list.size(); i++) {
2168 		// Check action name or menu name if the action is associated with a menu
2169 		if (list[i]->objectName() == cmp)
2170 		    before = list[i];
2171 		else if (list[i]->menu() && list[i]->menu()->objectName() == cmp)
2172 		    before = list[i]->menu()->menuAction();
2173 		if (before && i && list[i - 1]->isSeparator() &&
2174 		    params.getBoolValue(YSTRING("before_separator"),true))
2175 		    before = list[i - 1];
2176 	    }
2177 	}
2178 	// Insert the menu
2179 	if (mbOwner)
2180 	    mbOwner->insertMenu(before,menu);
2181 	else
2182 	    mOwner->insertMenu(before,menu);
2183     }
2184     else {
2185 	QToolButton* tb = qobject_cast<QToolButton*>(target);
2186 	if (tb)
2187 	    tb->setMenu(menu);
2188 	else {
2189 	    QPushButton* pb = qobject_cast<QPushButton*>(target);
2190 	    if (pb)
2191 		pb->setMenu(menu);
2192 	    else if (!QtClient::setProperty(target,s_propContextMenu,params))
2193 		target->addAction(menu->menuAction());
2194 	}
2195     }
2196     return true;
2197 }
2198 
2199 // Remove a menu
removeMenu(const NamedList & params)2200 bool QtWindow::removeMenu(const NamedList& params)
2201 {
2202     QWidget* parent = this;
2203     // Retrieve the owner
2204     const String& owner = params[YSTRING("owner")];
2205     if (owner && owner != m_id) {
2206 	parent = qFindChild<QWidget*>(this,QtClient::setUtf8(owner));
2207 	if (!parent)
2208 	    return false;
2209     }
2210     QMenu* menu = qFindChild<QMenu*>(parent,QtClient::setUtf8(params));
2211     if (!menu)
2212 	return false;
2213     QtClient::deleteLater(menu);
2214     return true;
2215 }
2216 
2217 // Set an element's image
setImage(const String & name,const String & image,bool fit)2218 bool QtWindow::setImage(const String& name, const String& image, bool fit)
2219 {
2220     if (!name)
2221 	return false;
2222     if (name == m_id)
2223 	return QtClient::setImage(this,image);
2224     QObject* obj = qFindChild<QObject*>(this,QtClient::setUtf8(name));
2225     return obj && QtClient::setImage(obj,image,fit);
2226 }
2227 
2228 // Set a property for this window or for a widget owned by it
setProperty(const String & name,const String & item,const String & value)2229 bool QtWindow::setProperty(const String& name, const String& item, const String& value)
2230 {
2231     if (name == m_id)
2232 	return QtClient::setProperty(wndWidget(),item,value);
2233     QObject* obj = qFindChild<QObject*>(this,QtClient::setUtf8(name));
2234     return obj ? QtClient::setProperty(obj,item,value) : false;
2235 }
2236 
2237 // Get a property from this window or from a widget owned by it
getProperty(const String & name,const String & item,String & value)2238 bool QtWindow::getProperty(const String& name, const String& item, String& value)
2239 {
2240     if (name == m_id)
2241 	return QtClient::getProperty(wndWidget(),item,value);
2242     QObject* obj = qFindChild<QObject*>(this,QtClient::setUtf8(name));
2243     return obj ? QtClient::getProperty(obj,item,value) : false;
2244 }
2245 
moveEvent(QMoveEvent * event)2246 void QtWindow::moveEvent(QMoveEvent* event)
2247 {
2248     QWidget::moveEvent(event);
2249     // Don't update pos if not shown normal
2250     if (!isShownNormal())
2251 	return;
2252     m_x = pos().x();
2253     m_y = pos().y();
2254     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) moved x=%d y=%d [%p]",
2255 	m_id.c_str(),m_x,m_y,this);
2256 }
2257 
resizeEvent(QResizeEvent * event)2258 void QtWindow::resizeEvent(QResizeEvent* event)
2259 {
2260     QWidget::resizeEvent(event);
2261     // Don't update size if not shown normal
2262     if (!isShownNormal())
2263 	return;
2264     m_width = width();
2265     m_height = height();
2266     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) resized width=%d height=%d [%p]",
2267 	m_id.c_str(),m_width,m_height,this);
2268 }
2269 
event(QEvent * ev)2270 bool QtWindow::event(QEvent* ev)
2271 {
2272     static const String s_activeChg("window_active_changed");
2273     if (ev->type() == QEvent::WindowDeactivate) {
2274 	String hideProp;
2275 	QtClient::getProperty(wndWidget(),s_propHideInactive,hideProp);
2276 	if (hideProp && hideProp.toBoolean())
2277 	    setVisible(false);
2278 	m_active = false;
2279 	Client::self()->toggle(this,s_activeChg,false);
2280     }
2281     else if (ev->type() == QEvent::WindowActivate) {
2282 	m_active = true;
2283 	Client::self()->toggle(this,s_activeChg,true);
2284 	String wName;
2285 	if (getPropPlatform(wndWidget(),s_propShowWndWhenActive,wName) && wName)
2286 	    Client::setVisible(wName);
2287     }
2288     else if (ev->type() == QEvent::ApplicationDeactivate) {
2289 	if (m_active) {
2290 	    m_active = false;
2291 	    Client::self()->toggle(this,s_activeChg,true);
2292 	}
2293     }
2294     return QWidget::event(ev);
2295 }
2296 
closeEvent(QCloseEvent * event)2297 void QtWindow::closeEvent(QCloseEvent* event)
2298 {
2299     // NOTE: Don't access window's data after calling hide():
2300     //  some logics might destroy the window when hidden
2301 
2302     // Notify window closed
2303     String tmp;
2304     if (Client::self() &&
2305 	QtClient::getProperty(wndWidget(),"_yate_windowclosedaction",tmp))
2306 	Client::self()->action(this,tmp);
2307 
2308     // Hide the window when requested
2309     if (QtClient::getBoolProperty(wndWidget(),"_yate_hideonclose")) {
2310 	event->ignore();
2311 	hide();
2312 	return;
2313     }
2314 
2315     QWidget::closeEvent(event);
2316     if (m_mainWindow && Client::self()) {
2317 	Client::self()->quit();
2318 	return;
2319     }
2320     if (QtClient::getBoolProperty(wndWidget(),"_yate_destroyonclose")) {
2321 	XDebug(QtDriver::self(),DebugAll,
2322 	    "Window(%s) closeEvent() set delete later [%p]",m_id.c_str(),this);
2323 	QObject::deleteLater();
2324 	// Safe to call hide(): the window will be deleted when control returns
2325 	//  to the main loop
2326     }
2327     hide();
2328 }
2329 
changeEvent(QEvent * event)2330 void QtWindow::changeEvent(QEvent* event)
2331 {
2332     if (event->type() == QEvent::WindowStateChange)
2333 	m_maximized = isMaximized();
2334     QWidget::changeEvent(event);
2335 }
2336 
action()2337 void QtWindow::action()
2338 {
2339     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) action() sender=%s [%p]",
2340 	m_id.c_str(),YQT_OBJECT_NAME(sender()),this);
2341     if (!QtClient::self() || QtClient::changing())
2342 	return;
2343     String name;
2344     NamedList* params = 0;
2345     if (!QtClient::getBoolProperty(sender(),"_yate_translateidentity"))
2346 	QtClient::getIdentity(sender(),name);
2347     else {
2348 	QtWidget w(sender());
2349 	translateName(w,name,&params);
2350     }
2351     if (name)
2352 	QtClient::self()->action(this,name,params);
2353     TelEngine::destruct(params);
2354 }
2355 
2356 // Toggled actions
toggled(bool on)2357 void QtWindow::toggled(bool on)
2358 {
2359     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) toggled=%s sender=%s [%p]",
2360 	m_id.c_str(),String::boolText(on),YQT_OBJECT_NAME(sender()),this);
2361     QtClient::updateToggleImage(sender());
2362     if (!QtClient::self() || QtClient::changing())
2363 	return;
2364     QtWidget w(sender());
2365     String name;
2366     if (translateName(w,name))
2367 	QtClient::self()->toggle(this,name,on);
2368 }
2369 
2370 // System tray actions
sysTrayIconAction(QSystemTrayIcon::ActivationReason reason)2371 void QtWindow::sysTrayIconAction(QSystemTrayIcon::ActivationReason reason)
2372 {
2373     String action;
2374     switch (reason) {
2375 	case QSystemTrayIcon::Context:
2376 	    QtClient::getProperty(sender(),s_propAction + "Context",action);
2377 	    break;
2378 	case QSystemTrayIcon::DoubleClick:
2379 	    QtClient::getProperty(sender(),s_propAction + "DoubleClick",action);
2380 	    break;
2381 	case QSystemTrayIcon::Trigger:
2382 	    QtClient::getProperty(sender(),s_propAction + "Trigger",action);
2383 	    break;
2384 	case QSystemTrayIcon::MiddleClick:
2385 	    QtClient::getProperty(sender(),s_propAction + "MiddleClick",action);
2386 	    break;
2387 	default:
2388 	    return;
2389     }
2390     if (action)
2391 	Client::self()->action(this,action);
2392 }
2393 
2394 // Choose file window was accepted
chooseFileAccepted()2395 void QtWindow::chooseFileAccepted()
2396 {
2397     QFileDialog* dlg = qobject_cast<QFileDialog*>(sender());
2398     if (!dlg)
2399 	return;
2400     String action;
2401     QtClient::getUtf8(action,dlg->objectName());
2402     if (!action)
2403 	return;
2404     NamedList params("");
2405     QDir dir = dlg->directory();
2406     if (dir.absolutePath().length())
2407 	QtClient::getUtf8(params,"dir",fixPathSep(dir.absolutePath()));
2408     QStringList files = dlg->selectedFiles();
2409     for (int i = 0; i < files.size(); i++)
2410 	QtClient::getUtf8(params,"file",fixPathSep(files[i]));
2411     if (dlg->fileMode() != QFileDialog::DirectoryOnly &&
2412 	dlg->fileMode() != QFileDialog::Directory) {
2413 	QString filter = dlg->selectedFilter();
2414 	if (filter.length())
2415 	    QtClient::getUtf8(params,"filter",filter);
2416     }
2417     Client::self()->action(this,action,&params);
2418 }
2419 
2420 // Choose file window was cancelled
chooseFileRejected()2421 void QtWindow::chooseFileRejected()
2422 {
2423     QFileDialog* dlg = qobject_cast<QFileDialog*>(sender());
2424     if (!dlg)
2425 	return;
2426     String action;
2427     QtClient::getUtf8(action,dlg->objectName());
2428     if (!action)
2429 	return;
2430     Client::self()->action(this,action,0);
2431 }
2432 
openUrl(const QString & link)2433 void QtWindow::openUrl(const QString& link)
2434 {
2435     QDesktopServices::openUrl(QUrl(link));
2436 }
2437 
doubleClick()2438 void QtWindow::doubleClick()
2439 {
2440     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) doubleClick() sender=%s [%p]",
2441 	m_id.c_str(),YQT_OBJECT_NAME(sender()),this);
2442     if (QtClient::self() && sender())
2443 	Client::self()->action(this,YQT_OBJECT_NAME(sender()));
2444 }
2445 
2446 // A widget's selection changed
selectionChanged()2447 void QtWindow::selectionChanged()
2448 {
2449     XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) selectionChanged() sender=%s [%p]",
2450 	m_id.c_str(),YQT_OBJECT_NAME(sender()),this);
2451     if (!(QtClient::self() && sender()))
2452 	return;
2453     String name = YQT_OBJECT_NAME(sender());
2454     QtWidget w(sender());
2455     if (w.type() != QtWidget::Calendar) {
2456 	String item;
2457 	getSelect(name,item);
2458 	Client::self()->select(this,name,item);
2459     }
2460     else {
2461 	NamedList p("");
2462 	QDate d = w.calendar()->selectedDate();
2463 	p.addParam("year",String(d.year()));
2464 	p.addParam("month",String(d.month()));
2465 	p.addParam("day",String(d.day()));
2466 	Client::self()->action(this,name,&p);
2467     }
2468 }
2469 
2470 // Connect an object's text changed signal to window's slot
connectTextChanged(QObject * obj)2471 bool QtWindow::connectTextChanged(QObject* obj)
2472 {
2473     if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify")))
2474 	return false;
2475     QComboBox* combo = qobject_cast<QComboBox*>(obj);
2476     if (combo)
2477 	return QtClient::connectObjects(combo,SIGNAL(editTextChanged(const QString&)),
2478 	    this,SLOT(textChanged(const QString&)));
2479     QLineEdit* lineEdit = qobject_cast<QLineEdit*>(obj);
2480     if (lineEdit)
2481 	return QtClient::connectObjects(lineEdit,SIGNAL(textChanged(const QString&)),
2482 	    this,SLOT(textChanged(const QString&)));
2483     QTextEdit* textEdit = qobject_cast<QTextEdit*>(obj);
2484     if (textEdit)
2485 	return QtClient::connectObjects(textEdit,SIGNAL(textChanged()),
2486 	    this,SLOT(textChanged()));
2487     const QMetaObject* meta = obj->metaObject();
2488     Debug(DebugStub,"connectTextChanged() not implemented for class '%s'",
2489 	meta ? meta->className() :  "");
2490     return false;
2491 }
2492 
2493 // Notify text changed to the client
notifyTextChanged(QObject * obj,const QString & text)2494 void QtWindow::notifyTextChanged(QObject* obj, const QString& text)
2495 {
2496     if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify")))
2497 	return;
2498     // Detect QtUIWidget item. Get its container identity if found
2499     String item;
2500     QtUIWidget::getListItemProp(obj,item);
2501     QtUIWidget* uiw = item ? QtUIWidget::container(obj) : 0;
2502     String name;
2503     if (!uiw)
2504 	QtClient::getIdentity(obj,name);
2505     else
2506 	uiw->getIdentity(obj,name);
2507     if (!name)
2508 	return;
2509     NamedList p("");
2510     p.addParam("sender",name);
2511     if (text.size())
2512 	QtClient::getUtf8(p,"text",text);
2513     Client::self()->action(this,YSTRING("textchanged"),&p);
2514 }
2515 
2516 // Load a widget from file
loadUI(const char * fileName,QWidget * parent,const char * uiName,const char * path)2517 QWidget* QtWindow::loadUI(const char* fileName, QWidget* parent,
2518 	const char* uiName, const char* path)
2519 {
2520     if (Client::exiting())
2521 	return 0;
2522     if (!(fileName && *fileName && parent))
2523 	return 0;
2524 
2525     if (!(path && *path))
2526 	path = Client::s_skinPath.c_str();
2527     UIBuffer* buf = UIBuffer::build(fileName);
2528     const char* err = 0;
2529     if (buf && buf->buffer()) {
2530 	QBuffer b(buf->buffer());
2531 	QUiLoader loader;
2532         loader.setWorkingDirectory(QDir(QtClient::setUtf8(path)));
2533 	QWidget* w = loader.load(&b,parent);
2534 	if (w)
2535 	    return w;
2536 	err = "loader failed";
2537     }
2538     else
2539 	err = buf ? "file is empty" : "file not found";
2540     // Error
2541     TelEngine::destruct(buf);
2542     Debug(DebugWarn,"Failed to load widget '%s' file='%s' path='%s': %s",
2543         uiName,fileName,path,err);
2544     return 0;
2545 }
2546 
2547 // Clear the UI cache
clearUICache(const char * fileName)2548 void QtWindow::clearUICache(const char* fileName)
2549 {
2550     if (!fileName)
2551 	UIBuffer::s_uiCache.clear();
2552     else
2553 	TelEngine::destruct(UIBuffer::s_uiCache.find(fileName));
2554 }
2555 
2556 // Filter events
eventFilter(QObject * obj,QEvent * event)2557 bool QtWindow::eventFilter(QObject* obj, QEvent* event)
2558 {
2559     if (!obj)
2560 	return false;
2561     // Apply dynamic properties changes
2562     if (event->type() == QEvent::DynamicPropertyChange) {
2563 	String name = YQT_OBJECT_NAME(obj);
2564 	QDynamicPropertyChangeEvent* ev = static_cast<QDynamicPropertyChangeEvent*>(event);
2565 	String prop = ev->propertyName().constData();
2566 	// Handle only yate dynamic properties
2567 	if (!prop.startsWith(s_yatePropPrefix,false))
2568 	    return QWidget::eventFilter(obj,event);
2569 	XDebug(QtDriver::self(),DebugAll,"Window(%s) eventFilter(%s) prop=%s [%p]",
2570 	    m_id.c_str(),YQT_OBJECT_NAME(obj),prop.c_str(),this);
2571 	// Return false for now on: it's our property
2572 	QtWidget w(obj);
2573 	if (w.invalid())
2574 	    return false;
2575 	String value;
2576 	if (!QtClient::getProperty(obj,prop,value))
2577 	    return false;
2578 	bool ok = true;
2579 	bool handled = true;
2580 	if (prop == s_propColWidths) {
2581 	    if (w.type() == QtWidget::Table) {
2582 		QHeaderView* hdr = w.table()->horizontalHeader();
2583 		bool skipLast = hdr && hdr->stretchLastSection();
2584 		ObjList* list = value.split(',',false);
2585 		int col = 0;
2586 		for (ObjList* o = list->skipNull(); o; o = o->skipNext(), col++) {
2587 		    if (skipLast && col == w.table()->columnCount() - 1)
2588 			break;
2589 		    int width = (static_cast<String*>(o->get()))->toInteger(-1);
2590 		    if (width >= 0)
2591 			w.table()->setColumnWidth(col,width);
2592 		}
2593 		TelEngine::destruct(list);
2594 	    }
2595 	}
2596 	else if (prop == s_propSorting) {
2597 	    if (w.type() == QtWidget::Table) {
2598 		ObjList* list = value.split(',',false);
2599 		String* tmp = static_cast<String*>((*list)[0]);
2600 		int col = tmp ? tmp->toInteger(-1) : -1;
2601 		if (col >= 0) {
2602 		    tmp = static_cast<String*>((*list)[1]);
2603 		    bool asc = tmp ? tmp->toBoolean(true) : true;
2604 		    w.table()->sortItems(col,asc ? Qt::AscendingOrder : Qt::DescendingOrder);
2605 		}
2606 		TelEngine::destruct(list);
2607 	    }
2608 	}
2609 	else if (prop == s_propSizes) {
2610 	    if (w.type() == QtWidget::Splitter) {
2611 		QList<int> list = QtClient::str2IntList(value);
2612 		w.splitter()->setSizes(list);
2613 	    }
2614 	}
2615 	else if (prop == s_propWindowFlags) {
2616 	    QWidget* wid = (name == m_id || name == m_oldId) ? this : w.widget();
2617 	    QtClient::applyWindowFlags(wid,value);
2618 	}
2619 	else if (prop == s_propHHeader) {
2620 	    // Show/hide the horizontal header
2621 	    ok = ((w.type() == QtWidget::Table || w.type() == QtWidget::CustomTable) &&
2622 		value.isBoolean() && w.table()->horizontalHeader());
2623 	    if (ok)
2624 		w.table()->horizontalHeader()->setVisible(value.toBoolean());
2625 	}
2626 	else
2627 	    ok = handled = false;
2628 	if (ok)
2629 	    DDebug(ClientDriver::self(),DebugAll,
2630 		"Applied dynamic property %s='%s' for object='%s'",
2631 		prop.c_str(),value.c_str(),name.c_str());
2632 	else if (handled)
2633 	    Debug(ClientDriver::self(),DebugMild,
2634 		"Failed to apply dynamic property %s='%s' for object='%s'",
2635 		prop.c_str(),value.c_str(),name.c_str());
2636 	return false;
2637     }
2638     if (event->type() == QEvent::KeyPress) {
2639 	String action;
2640 	bool filter = false;
2641 	if (!QtClient::filterKeyEvent(obj,static_cast<QKeyEvent*>(event),
2642 	    action,filter,this))
2643 	    return QWidget::eventFilter(obj,event);
2644 	if (action && Client::self())
2645 	    Client::self()->action(this,action);
2646 	return filter;
2647     }
2648     if (event->type() == QEvent::ContextMenu) {
2649 	if (handleContextMenuEvent(static_cast<QContextMenuEvent*>(event),obj))
2650 	    return false;
2651     }
2652     if (event->type() == QEvent::Enter) {
2653 	QtClient::updateImageFromMouse(obj,true,true);
2654 	return QWidget::eventFilter(obj,event);
2655     }
2656     if (event->type() == QEvent::Leave) {
2657 	QtClient::updateImageFromMouse(obj,true,false);
2658 	return QWidget::eventFilter(obj,event);
2659     }
2660     if (event->type() == QEvent::MouseButtonPress) {
2661 	QtClient::updateImageFromMouse(obj,false,true);
2662 	return QWidget::eventFilter(obj,event);
2663     }
2664     if (event->type() == QEvent::MouseButtonRelease) {
2665 	QtClient::updateImageFromMouse(obj,false,false);
2666 	return QWidget::eventFilter(obj,event);
2667     }
2668     return QWidget::eventFilter(obj,event);
2669 }
2670 
2671 // Handle key pressed events
keyPressEvent(QKeyEvent * event)2672 void QtWindow::keyPressEvent(QKeyEvent* event)
2673 {
2674     if (!(Client::self() && event)) {
2675 	QWidget::keyPressEvent(event);
2676 	return;
2677     }
2678     QVariant var = this->property("_yate_keypress_redirect");
2679     QString child = var.toString();
2680     if (child.size() > 0 && QtClient::sendEvent(*event,this,child)) {
2681 	QWidget* wid = qFindChild<QWidget*>(this,child);
2682 	if (wid)
2683 	    wid->setFocus();
2684 	return;
2685     }
2686     if (event->key() == Qt::Key_Backspace)
2687 	Client::self()->backspace(m_id,this);
2688     QWidget::keyPressEvent(event);
2689 }
2690 
2691 // Show hide window. Notify the client
setVisible(bool visible)2692 void QtWindow::setVisible(bool visible)
2693 {
2694     // Override position for notification windows
2695     if (visible && isShownNormal() &&
2696 	QtClient::getBoolProperty(wndWidget(),"_yate_notificationwindow")) {
2697 	// Don't move
2698 	m_moving = -1;
2699 #ifndef Q_WS_MAC
2700 	// Detect unavailable screen space position and move the window in the apropriate position
2701 	// bottom/right/none: move it in the right/bottom corner.
2702 	// top: move it in the right/top corner.
2703 	// left: move it in the left/bottom corner.
2704 	int pos = QtClient::PosNone;
2705 	if (QtClient::getScreenUnavailPos(this,pos)) {
2706 	    if (0 != (pos & (QtClient::PosBottom | QtClient::PosRight)) || pos == QtClient::PosNone)
2707 		QtClient::moveWindow(this,QtClient::CornerBottomRight);
2708 	    else if (0 != (pos & QtClient::PosTop))
2709 		QtClient::moveWindow(this,QtClient::CornerTopRight);
2710 	    else
2711 		QtClient::moveWindow(this,QtClient::CornerBottomLeft);
2712 	}
2713 #else
2714 	QtClient::moveWindow(this,QtClient::CornerTopRight);
2715 #endif
2716     }
2717     if (visible && isMinimized())
2718 	showNormal();
2719     else
2720 	QWidget::setVisible(visible);
2721     // Notify the client on window visibility changes
2722     bool changed = (m_visible != visible);
2723     m_visible = visible;
2724     if (changed && Client::self()) {
2725 	QVariant var;
2726 	if (this)
2727 	    var = this->property("dynamicUiActionVisibleChanged");
2728 	if (!var.toBool())
2729 	    Client::self()->toggle(this,YSTRING("window_visible_changed"),m_visible);
2730 	else {
2731 	    Message* m = new Message("ui.action");
2732 	    m->addParam("action","window_visible_changed");
2733 	    m->addParam("visible",String::boolText(m_visible));
2734 	    m->addParam("window",m_id);
2735 	    Engine::enqueue(m);
2736 	}
2737     }
2738     if (!m_visible && QtClient::getBoolProperty(wndWidget(),"_yate_destroyonhide")) {
2739 	DDebug(QtDriver::self(),DebugAll,
2740 	    "Window(%s) setVisible(false) set delete later [%p]",m_id.c_str(),this);
2741 	QObject::deleteLater();
2742     }
2743     // Destroy owned dialogs
2744     if (!m_visible) {
2745 	QList<QDialog*> d = qFindChildren<QDialog*>(this);
2746 	for (int i = 0; i < d.size(); i++)
2747 	    d[i]->deleteLater();
2748     }
2749 }
2750 
2751 // Show the window
show()2752 void QtWindow::show()
2753 {
2754     setVisible(true);
2755     m_maximized = m_maximized || isMaximized();
2756     if (m_maximized)
2757 	setWindowState(Qt::WindowMaximized);
2758 }
2759 
2760 // Hide the window
hide()2761 void QtWindow::hide()
2762 {
2763     setVisible(false);
2764 }
2765 
size(int width,int height)2766 void QtWindow::size(int width, int height)
2767 {
2768     Debug(QtDriver::self(),DebugStub,"QtWindow(%s)::size(%d,%d) [%p]",m_id.c_str(),width,height,this);
2769 }
2770 
move(int x,int y)2771 void QtWindow::move(int x, int y)
2772 {
2773     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s)::move(%d,%d) [%p]",m_id.c_str(),x,y,this);
2774     QWidget::move(x,y);
2775 }
2776 
moveRel(int dx,int dy)2777 void QtWindow::moveRel(int dx, int dy)
2778 {
2779     DDebug(QtDriver::self(),DebugAll,"QtWindow::moveRel(%d,%d) [%p]",dx,dy,this);
2780 }
2781 
related(const Window * wnd) const2782 bool QtWindow::related(const Window* wnd) const
2783 {
2784     DDebug(QtDriver::self(),DebugAll,"QtWindow::related(%p) [%p]",wnd,this);
2785     return false;
2786 }
2787 
menu(int x,int y)2788 void QtWindow::menu(int x, int y)
2789 {
2790     DDebug(QtDriver::self(),DebugAll,"QtWindow::menu(%d,%d) [%p]",x,y,this);
2791 }
2792 
2793 // Create a modal dialog
createDialog(const String & name,const String & title,const String & alias,const NamedList * params)2794 bool QtWindow::createDialog(const String& name, const String& title, const String& alias,
2795     const NamedList* params)
2796 {
2797     QtDialog* d = new QtDialog(this);
2798     if (d->show(name,title,alias,params))
2799 	return true;
2800     d->deleteLater();
2801     return false;
2802 }
2803 
2804 // Destroy a modal dialog
closeDialog(const String & name)2805 bool QtWindow::closeDialog(const String& name)
2806 {
2807     QDialog* d = qFindChild<QDialog*>(this,QtClient::setUtf8(name));
2808     if (!d)
2809 	return false;
2810     d->deleteLater();
2811     return true;
2812 }
2813 
2814 // Load UI file and setup the window
doPopulate()2815 void QtWindow::doPopulate()
2816 {
2817     Debug(QtDriver::self(),DebugAll,"Populating window '%s' [%p]",m_id.c_str(),this);
2818     QWidget* formWidget = loadUI(m_description,this,m_id);
2819     if (!formWidget)
2820 	return;
2821     // Set window title decoration flags to avoid pos/size troubles with late decoration
2822     QVariant var = formWidget->property(s_propWindowFlags);
2823     if (var.type() == QVariant::Invalid) {
2824 	String flgs = "title,sysmenu,minimize,close";
2825 	// Add maximize only if allowed
2826 	if (formWidget->maximumWidth() == QWIDGETSIZE_MAX ||
2827 	    formWidget->maximumHeight() == QWIDGETSIZE_MAX)
2828 	    flgs.append("maximize",",");
2829 	formWidget->setProperty(s_propWindowFlags,QVariant(QtClient::setUtf8(flgs)));
2830     }
2831     setMinimumSize(formWidget->minimumSize().width(),formWidget->minimumSize().height());
2832     setMaximumSize(formWidget->maximumSize().width(),formWidget->maximumSize().height());
2833     m_x = formWidget->pos().x();
2834     m_y = formWidget->pos().y();
2835     m_width = formWidget->width();
2836     m_height = formWidget->height();
2837     move(m_x,m_y);
2838     QWidget::resize(m_width,m_height);
2839     QtClient::setWidget(this,formWidget);
2840     m_widget = YQT_OBJECT_NAME(formWidget);
2841     String wTitle;
2842     QtClient::getUtf8(wTitle,formWidget->windowTitle());
2843     title(wTitle);
2844     setWindowIcon(formWidget->windowIcon());
2845     setStyleSheet(formWidget->styleSheet());
2846 }
2847 
2848 // Initialize window
doInit()2849 void QtWindow::doInit()
2850 {
2851     DDebug(QtDriver::self(),DebugAll,"Initializing window '%s' [%p]",
2852 	m_id.c_str(),this);
2853 
2854     // Create window's dynamic properties from config
2855     Configuration cfg(Engine::configFile(m_oldId),false);
2856     NamedList* sectGeneral = cfg.getSection("general");
2857     if (sectGeneral)
2858 	addDynamicProps(this,*sectGeneral);
2859 
2860     // Load window data
2861     m_mainWindow = s_cfg.getBoolValue(m_oldId,"mainwindow");
2862     m_saveOnClose = s_cfg.getBoolValue(m_oldId,"save",true);
2863     if (m_id != m_oldId)
2864 	m_saveOnClose = s_cfg.getBoolValue(m_oldId,"savealias",m_saveOnClose);
2865     NamedList* sect = s_save.getSection(m_id);
2866     if (sect) {
2867 	m_maximized = sect->getBoolValue("maximized");
2868 	m_x = sect->getIntValue("x",m_x);
2869 	m_y = sect->getIntValue("y",m_y);
2870 	m_width = sect->getIntValue("width",m_width);
2871 	m_height = sect->getIntValue("height",m_height);
2872 	m_visible = sect->getBoolValue("visible");
2873     }
2874     else {
2875 	if (m_saveOnClose)
2876 	    Debug(QtDriver::self(),DebugNote,"Window(%s) not found in config [%p]",
2877 		m_id.c_str(),this);
2878 	m_visible = s_cfg.getBoolValue(m_oldId,"visible");
2879 	// Make sure the window is shown in the available geometry
2880 	QDesktopWidget* d = QApplication::desktop();
2881 	if (d) {
2882 	    QRect r = d->availableGeometry(this);
2883 	    m_x = r.x();
2884 	    m_y = r.y();
2885 	}
2886     }
2887     m_visible = m_mainWindow || m_visible;
2888     if (!m_width)
2889 	m_width = this->width();
2890     if (!m_height)
2891 	m_height = this->height();
2892     move(m_x,m_y);
2893     QWidget::resize(m_width,m_height);
2894 
2895     // Build custom UI widgets from frames owned by this widget
2896     QtClient::buildFrameUiWidgets(this);
2897 
2898     // Create custom widgets from
2899     // _yate_identity=customwidget|[separator=sep|] sep widgetclass sep widgetname [sep param=value]
2900     QList<QFrame*> frm = qFindChildren<QFrame*>(this);
2901     for (int i = 0; i < frm.size(); i++) {
2902 	String create;
2903 	QtClient::getProperty(frm[i],"_yate_identity",create);
2904 	if (!create.startSkip("customwidget|",false))
2905 	    continue;
2906 	char sep = '|';
2907 	// Check if we have another separator
2908 	if (create.startSkip("separator=",false)) {
2909 	    if (create.length() < 2)
2910 		continue;
2911 	    sep = create.at(0);
2912 	    create = create.substr(2);
2913 	}
2914 	ObjList* list = create.split(sep,false);
2915 	String type;
2916 	String name;
2917 	NamedList params("");
2918 	int what = 0;
2919 	for (ObjList* o = list->skipNull(); o; o = o->skipNext(), what++) {
2920 	    GenObject* p = o->get();
2921 	    if (what == 0)
2922 		type = p->toString();
2923 	    else if (what == 1)
2924 		name = p->toString();
2925 	    else {
2926 		// Decode param
2927 		int pos = p->toString().find('=');
2928 		if (pos != -1)
2929 		    params.addParam(p->toString().substr(0,pos),p->toString().substr(pos + 1));
2930 	    }
2931 	}
2932 	TelEngine::destruct(list);
2933 	params.addParam("parentwindow",m_id);
2934 	NamedString* pw = new NamedString("parentwidget");
2935 	QtClient::getUtf8(*pw,frm[i]->objectName());
2936 	params.addParam(pw);
2937 	QObject* obj = (QObject*)UIFactory::build(type,name,&params);
2938 	if (!obj)
2939 	    continue;
2940 	QWidget* wid = qobject_cast<QWidget*>(obj);
2941 	if (wid)
2942 	    QtClient::setWidget(frm[i],wid);
2943 	else {
2944 	    obj->setParent(frm[i]);
2945 	    QtCustomObject* customObj = qobject_cast<QtCustomObject*>(obj);
2946 	    if (customObj)
2947 		customObj->parentChanged();
2948 	}
2949     }
2950 
2951     // Add the first menubar to layout
2952     QList<QMenuBar*> menuBars = qFindChildren<QMenuBar*>(this);
2953     if (menuBars.size() && layout()) {
2954 	layout()->setMenuBar(menuBars[0]);
2955 	// Decrease minimum size policy to make sure the layout is made properly
2956 	if (wndWidget()) {
2957 	    int h = menuBars[0]->height();
2958 	    int min = wndWidget()->minimumHeight();
2959 	    if (min > h)
2960 		wndWidget()->setMinimumHeight(min - h);
2961 	    else
2962 		wndWidget()->setMinimumHeight(0);
2963 	}
2964 #ifdef Q_WS_MAC
2965 	if (m_mainWindow) {
2966 	    // Create a parentless menu bar to be set as the default application menu by copying it from the main window menu
2967 	    DDebug(QtDriver::self(),DebugAll,"Setting as default menu bar the menu bar of window '%s' [%p]",
2968 	    m_id.c_str(),this);
2969 	    QMenuBar* mainMenu = menuBars[0];
2970 	    QMenuBar* defaultMenu = new QMenuBar(0);
2971 	    QList<QAction*> topActions = mainMenu->actions();
2972 	    for (int i = 0; i < topActions.count(); i++) {
2973 		QMenu* menu = topActions[i]->menu();
2974 		if (menu) {
2975 		    QMenu* m = new QMenu(menu->title(),defaultMenu);
2976 		    String tmp;
2977 		    QtClient::getProperty(menu,YSTRING("_yate_menuNoCopy"),tmp);
2978 		    if (tmp.toBoolean())
2979 			continue;
2980 		    defaultMenu->addMenu(m);
2981 		    QList<QAction*> actions = menu->actions();
2982 		    for (int j = 0; j < actions.count(); j++) {
2983 			QAction* act = actions[j];
2984 			tmp.clear();
2985 			QtClient::getProperty(act,YSTRING("_yate_menuNoCopy"),tmp);
2986 			if (tmp.toBoolean())
2987 			    continue;
2988 			m->addAction(act);
2989 		    }
2990 		}
2991 	    }
2992 	}
2993 #endif
2994     }
2995 
2996     // Create window's children dynamic properties from config
2997     unsigned int n = cfg.sections();
2998     for (unsigned int i = 0; i < n; i++) {
2999 	NamedList* sect = cfg.getSection(i);
3000 	if (sect && *sect && *sect != "general")
3001 	    addDynamicProps(qFindChild<QObject*>(this,sect->c_str()),*sect);
3002     }
3003 
3004     // Process "_yate_setaction" property for our children
3005     QtClient::setAction(this);
3006 
3007     // Connect actions' signal
3008     QList<QAction*> actions = qFindChildren<QAction*>(this);
3009     for (int i = 0; i < actions.size(); i++) {
3010 	String addToWidget;
3011 	QtClient::getProperty(actions[i],"dynamicAddToParent",addToWidget);
3012 	if (addToWidget && addToWidget.toBoolean())
3013 	    QWidget::addAction(actions[i]);
3014 	if (actions[i]->isCheckable())
3015 	    QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool)));
3016 	else
3017 	    QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action()));
3018     }
3019 
3020     // Connect combo boxes signals
3021     QList<QComboBox*> combos = qFindChildren<QComboBox*>(this);
3022     for (int i = 0; i < combos.size(); i++) {
3023 	QtClient::connectObjects(combos[i],SIGNAL(activated(int)),this,SLOT(selectionChanged()));
3024     	connectTextChanged(combos[i]);
3025     }
3026 
3027     // Connect abstract buttons (check boxes and radio/push/tool buttons) signals
3028     QList<QAbstractButton*> buttons = qFindChildren<QAbstractButton*>(this);
3029     for(int i = 0; i < buttons.size(); i++)
3030 	if (QtClient::autoConnect(buttons[i]))
3031 	    connectButton(buttons[i]);
3032 
3033     // Connect group boxes signals
3034     QList<QGroupBox*> grp = qFindChildren<QGroupBox*>(this);
3035     for(int i = 0; i < grp.size(); i++)
3036 	if (grp[i]->isCheckable())
3037 	    QtClient::connectObjects(grp[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool)));
3038 
3039     // Connect sliders signals
3040     QList<QSlider*> sliders = qFindChildren<QSlider*>(this);
3041     for (int i = 0; i < sliders.size(); i++)
3042 	QtClient::connectObjects(sliders[i],SIGNAL(valueChanged(int)),this,SLOT(selectionChanged()));
3043 
3044     // Connect calendar widget signals
3045     QList<QCalendarWidget*> cals = qFindChildren<QCalendarWidget*>(this);
3046     for (int i = 0; i < cals.size(); i++)
3047 	QtClient::connectObjects(cals[i],SIGNAL(selectionChanged()),this,SLOT(selectionChanged()));
3048 
3049     // Connect list boxes signals
3050     QList<QListWidget*> lists = qFindChildren<QListWidget*>(this);
3051     for (int i = 0; i < lists.size(); i++) {
3052 	QtClient::connectObjects(lists[i],SIGNAL(itemDoubleClicked(QListWidgetItem*)),
3053 	    this,SLOT(doubleClick()));
3054 	QtClient::connectObjects(lists[i],SIGNAL(itemActivated(QListWidgetItem*)),
3055 	    this,SLOT(doubleClick()));
3056 	QtClient::connectObjects(lists[i],SIGNAL(currentRowChanged(int)),
3057 	    this,SLOT(selectionChanged()));
3058     }
3059 
3060     // Connect tab widget signals
3061     QList<QTabWidget*> tabs = qFindChildren<QTabWidget*>(this);
3062     for (int i = 0; i < tabs.size(); i++)
3063 	QtClient::connectObjects(tabs[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged()));
3064 
3065     // Connect stacked widget signals
3066     QList<QStackedWidget*> sw = qFindChildren<QStackedWidget*>(this);
3067     for (int i = 0; i < sw.size(); i++)
3068 	QtClient::connectObjects(sw[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged()));
3069 
3070     // Connect line edit signals
3071     QList<QLineEdit*> le = qFindChildren<QLineEdit*>(this);
3072     for (int i = 0; i < le.size(); i++)
3073 	connectTextChanged(le[i]);
3074 
3075     // Connect text edit signals
3076     QList<QTextEdit*> te = qFindChildren<QTextEdit*>(this);
3077     for (int i = 0; i < te.size(); i++)
3078 	connectTextChanged(te[i]);
3079 
3080     // Process tables:
3081     // Insert a column and connect signals
3082     // Hide columns starting with "hidden:"
3083     QList<QTableWidget*> tables = qFindChildren<QTableWidget*>(this);
3084     for (int i = 0; i < tables.size(); i++) {
3085 	bool nonCustom = (0 == qobject_cast<QtTable*>(tables[i]));
3086 	// Horizontal header
3087 	QHeaderView* hdr = tables[i]->horizontalHeader();
3088 	// Stretch last column
3089 	bool b = QtClient::getBoolProperty(tables[i],"_yate_horizontalstretch",true);
3090 	hdr->setStretchLastSection(b);
3091 	String tmp;
3092 	QtClient::getProperty(tables[i],"_yate_horizontalheader_align",tmp);
3093 	if (tmp) {
3094 	    int def = hdr->defaultAlignment();
3095 	    hdr->setDefaultAlignment((Qt::Alignment)QtClient::str2align(tmp,def));
3096 	}
3097 	if (!QtClient::getBoolProperty(tables[i],"_yate_horizontalheader",true))
3098 	    hdr->hide();
3099 	// Vertical header
3100 	hdr = tables[i]->verticalHeader();
3101 	int itemH = QtClient::getIntProperty(tables[i],"_yate_rowheight");
3102 	if (itemH > 0)
3103 	    hdr->setDefaultSectionSize(itemH);
3104 	if (!QtClient::getBoolProperty(tables[i],"_yate_verticalheader"))
3105 	    hdr->hide();
3106 	else {
3107 	    int width = QtClient::getIntProperty(tables[i],"_yate_verticalheaderwidth");
3108 	    if (width > 0)
3109 		hdr->setFixedWidth(width);
3110 	    if (!QtClient::getBoolProperty(tables[i],"_yate_allowvheaderresize"))
3111 		hdr->setResizeMode(QHeaderView::Fixed);
3112 	}
3113 	if (nonCustom) {
3114 	    // Set _yate_save_props
3115 	    QVariant var = tables[i]->property(s_propsSave);
3116 	    if (var.type() != QVariant::StringList) {
3117 		// Create the property if not found, ignore it if not a string list
3118 		if (var.type() == QVariant::Invalid)
3119 		    var = QVariant(QVariant::StringList);
3120 		else
3121 		    Debug(QtDriver::self(),DebugNote,
3122 			"Window(%s) table '%s' already has a non string list property %s [%p]",
3123 			m_id.c_str(),YQT_OBJECT_NAME(tables[i]),s_propsSave.c_str(),this);
3124 	    }
3125 	    if (var.type() == QVariant::StringList) {
3126 		// Make sure saved properties exists to allow them to be restored
3127 		QStringList sl = var.toStringList();
3128 		bool changed = createProperty(tables[i],s_propColWidths,QVariant::String,this,&sl);
3129 		changed = createProperty(tables[i],s_propSorting,QVariant::String,this,&sl) || changed;
3130 		if (changed)
3131 		    tables[i]->setProperty(s_propsSave,QVariant(sl));
3132 	    }
3133 	}
3134 	TableWidget t(tables[i]);
3135 	// Insert the column containing the ID
3136 	t.addColumn(0,0,"hidden:id");
3137 	// Hide columns
3138 	for (int i = 0; i < t.columnCount(); i++) {
3139 	    String name;
3140 	    t.getHeaderText(i,name,false);
3141 	    if (name.startsWith("hidden:"))
3142 		t.table()->setColumnHidden(i,true);
3143 	}
3144 	// Connect signals
3145 	QtClient::connectObjects(t.table(),SIGNAL(cellDoubleClicked(int,int)),
3146 	    this,SLOT(doubleClick()));
3147 #if 0
3148 	// This would generate action() twice since QT will signal both cell and
3149 	// table item double click
3150 	QtClient::connectObjects(t.table(),SIGNAL(itemDoubleClicked(QTableWidgetItem*)),
3151 	    this,SLOT(doubleClick()));
3152 #endif
3153 	String noSel;
3154 	getProperty(t.name(),"dynamicNoItemSelChanged",noSel);
3155 	if (!noSel.toBoolean())
3156 	    QtClient::connectObjects(t.table(),SIGNAL(itemSelectionChanged()),
3157 		this,SLOT(selectionChanged()));
3158 	// Optionally connect cell clicked
3159 	// This is done when we want to generate a select() or action() from cell clicked
3160 	String cellClicked;
3161 	getProperty(t.name(),"dynamicCellClicked",cellClicked);
3162 	if (cellClicked) {
3163 	    if (cellClicked == "selectionChanged")
3164 		QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)),
3165 		    this,SLOT(selectionChanged()));
3166 	    else if (cellClicked == "doubleClick")
3167 		QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)),
3168 		    this,SLOT(doubleClick()));
3169 	}
3170     }
3171 
3172     // Restore saved children properties
3173     if (sect) {
3174 	unsigned int n = sect->length();
3175 	for (unsigned int i = 0; i < n; i++) {
3176 	    NamedString* ns = sect->getParam(i);
3177 	    if (!ns)
3178 		continue;
3179 	    String prop(ns->name());
3180 	    if (!prop.startSkip("property:",false))
3181 		continue;
3182 	    int pos = prop.find(":");
3183 	    if (pos > 0) {
3184 		String wName = prop.substr(0,pos);
3185 		String pName = prop.substr(pos + 1);
3186 		DDebug(QtDriver::self(),DebugAll,
3187 		    "Window(%s) restoring property %s=%s for child '%s' [%p]",
3188 		    m_id.c_str(),pName.c_str(),ns->c_str(),wName.c_str(),this);
3189 		setProperty(wName,pName,*ns);
3190 	    }
3191 	}
3192     }
3193 
3194     // Install event filter and apply dynamic properties
3195     QList<QObject*> w = qFindChildren<QObject*>(this);
3196     w.append(this);
3197     for (int i = 0; i < w.size(); i++) {
3198 	QList<QByteArray> props = w[i]->dynamicPropertyNames();
3199 	// Check for our dynamic properties
3200 	int j = 0;
3201 	for (j = 0; j < props.size(); j++)
3202 	    if (props[j].startsWith(s_yatePropPrefix))
3203 		break;
3204 	if (j == props.size())
3205 	    continue;
3206 	// Add event hook to be used when a dynamic property changes
3207 	w[i]->installEventFilter(this);
3208 	// Fake dynamic property change to apply them
3209 	for (j = 0; j < props.size(); j++) {
3210 	    if (!props[j].startsWith(s_yatePropPrefix))
3211 		continue;
3212 	    QDynamicPropertyChangeEvent ev(props[j]);
3213 	    eventFilter(w[i],&ev);
3214 	}
3215     }
3216 
3217     qRegisterMetaType<QModelIndex>("QModelIndex");
3218     qRegisterMetaType<QTextCursor>("QTextCursor");
3219 
3220     // Force window visibility change notification by changing the visibility flag
3221     // Some controls might need to be updated
3222     m_visible = !m_visible;
3223     if (m_visible) {
3224 	// Disable _yate_destroyonhide property: avoid destroying the window now
3225 	String tmp;
3226 	getProperty(m_id,"_yate_destroyonhide",tmp);
3227 	if (tmp)
3228 	    setProperty(m_id,"_yate_destroyonhide",String::boolText(false));
3229 	hide();
3230 	if (tmp)
3231 	    setProperty(m_id,"_yate_destroyonhide",tmp);
3232     }
3233     else
3234 	show();
3235 }
3236 
3237 // Mouse button pressed notification
mousePressEvent(QMouseEvent * event)3238 void QtWindow::mousePressEvent(QMouseEvent* event)
3239 {
3240     if (m_moving >= 0 && Qt::LeftButton == event->button() && isShownNormal()) {
3241 	m_movePos = event->globalPos();
3242 	m_moving = 1;
3243     }
3244 }
3245 
3246 // Mouse button release notification
mouseReleaseEvent(QMouseEvent * event)3247 void QtWindow::mouseReleaseEvent(QMouseEvent* event)
3248 {
3249     if (m_moving >= 0 && Qt::LeftButton == event->button())
3250 	m_moving = 0;
3251 }
3252 
3253 // Move the window if the moving flag is set
mouseMoveEvent(QMouseEvent * event)3254 void QtWindow::mouseMoveEvent(QMouseEvent* event)
3255 {
3256     if (m_moving <= 0 || Qt::LeftButton != event->buttons() || !isShownNormal())
3257 	return;
3258     int cx = event->globalPos().x() - m_movePos.x();
3259     int cy = event->globalPos().y() - m_movePos.y();
3260     if (cx || cy) {
3261 	m_movePos = event->globalPos();
3262 	QWidget::move(x() + cx,y() + cy);
3263     }
3264 }
3265 
3266 // Handle context menu events. Return true if handled
handleContextMenuEvent(QContextMenuEvent * event,QObject * obj)3267 bool QtWindow::handleContextMenuEvent(QContextMenuEvent* event, QObject* obj)
3268 {
3269     if (!(event && obj))
3270 	return false;
3271     String mname;
3272     QtClient::getProperty(obj,s_propContextMenu,mname);
3273     XDebug(ClientDriver::self(),DebugAll,
3274 	"Window(%s) handleContextMenuEvent() obj=%s menu=%s [%p]",
3275 	m_id.c_str(),YQT_OBJECT_NAME(obj),mname.c_str(),this);
3276     QMenu* m = mname ? qFindChild<QMenu*>(this,QtClient::setUtf8(mname)) : 0;
3277     if (m)
3278 	m->exec(event->globalPos());
3279     return m != 0;
3280 }
3281 
3282 
3283 /*
3284  * QtDialog
3285  */
3286 // Destructor. Notify the client if not exiting
~QtDialog()3287 QtDialog::~QtDialog()
3288 {
3289     QtWindow* w = parentWindow();
3290     if (w && m_notifyOnClose && Client::valid())
3291 	QtClient::self()->action(w,buildActionName(m_notifyOnClose,m_notifyOnClose));
3292     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) QtDialog(%s) destroyed [%p]",
3293 	w ? w->id().c_str() : "",YQT_OBJECT_NAME(this),w);
3294 }
3295 
3296 // Initialize dialog. Load the widget.
3297 // Connect non checkable actions to own slot.
3298 // Connect checkable actions/buttons to parent window's slot
3299 // Display the dialog on success
show(const String & name,const String & title,const String & alias,const NamedList * params)3300 bool QtDialog::show(const String& name, const String& title, const String& alias,
3301     const NamedList* params)
3302 {
3303     QtWindow* w = parentWindow();
3304     if (!w)
3305 	return false;
3306     QWidget* widget = QtWindow::loadUI(Client::s_skinPath + s_cfg.getValue(name,"description"),this,name);
3307     if (!widget)
3308 	return false;
3309     QtClient::getProperty(widget,"_yate_notifyonclose",m_notifyOnClose);
3310     setObjectName(QtClient::setUtf8(alias ? alias : name));
3311     setMinimumSize(widget->minimumSize().width(),widget->minimumSize().height());
3312     setMaximumSize(widget->maximumSize().width(),widget->maximumSize().height());
3313     resize(widget->width(),widget->height());
3314     QtClient::setWidget(this,widget);
3315     if (title)
3316 	setWindowTitle(QtClient::setUtf8(title));
3317     else if (widget->windowTitle().length())
3318 	setWindowTitle(widget->windowTitle());
3319     else
3320 	setWindowTitle(w->windowTitle());
3321     // Connect abstract buttons (check boxes and radio/push/tool buttons) signals
3322     QList<QAbstractButton*> buttons = qFindChildren<QAbstractButton*>(widget);
3323     for(int i = 0; i < buttons.size(); i++) {
3324 	if (!QtClient::autoConnect(buttons[i]))
3325 	    continue;
3326 	if (!buttons[i]->isCheckable())
3327 	    QtClient::connectObjects(buttons[i],SIGNAL(clicked()),this,SLOT(action()));
3328 	else
3329 	    QtClient::connectObjects(buttons[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool)));
3330     }
3331     // Connect actions' signal
3332     QList<QAction*> actions = qFindChildren<QAction*>(widget);
3333     for (int i = 0; i < actions.size(); i++) {
3334 	if (!QtClient::autoConnect(actions[i]))
3335 	    continue;
3336 	if (!actions[i]->isCheckable())
3337 	    QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action()));
3338 	else
3339 	    QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool)));
3340     }
3341     String* flags = 0;
3342     String tmp;
3343     QtClient::getProperty(widget,s_propWindowFlags,tmp);
3344     if (tmp)
3345 	flags = &tmp;
3346     if (params) {
3347 	if (!flags)
3348 	    flags = params->getParam(s_propWindowFlags);
3349 	m_closable = params->getBoolValue(YSTRING("closable"),"true");
3350 	w->setParams(*params);
3351     }
3352     if (flags)
3353 	QtClient::applyWindowFlags(this,*flags);
3354     setWindowModality(Qt::WindowModal);
3355     QDialog::show();
3356     return true;
3357 }
3358 
3359 // Notify client
action()3360 void QtDialog::action()
3361 {
3362     QtWindow* w = parentWindow();
3363     if (!w)
3364 	return;
3365     DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) dialog action '%s' [%p]",
3366 	w->id().c_str(),YQT_OBJECT_NAME(sender()),w);
3367     if (!QtClient::self() || QtClient::changing())
3368 	return;
3369     String name;
3370     QtClient::getIdentity(sender(),name);
3371     if (name && QtClient::self()->action(w,buildActionName(name,name)))
3372 	deleteLater();
3373 }
3374 
3375 // Delete the dialog
closeEvent(QCloseEvent * event)3376 void QtDialog::closeEvent(QCloseEvent* event)
3377 {
3378     if (m_closable) {
3379 	QDialog::closeEvent(event);
3380 	deleteLater();
3381     }
3382     else
3383 	event->ignore();
3384 }
3385 
3386 // Destroy the dialog
reject()3387 void QtDialog::reject()
3388 {
3389     if (!m_closable)
3390 	return;
3391     QDialog::reject();
3392     deleteLater();
3393 }
3394 
3395 
3396 /**
3397  * QtClient
3398  */
QtClient()3399 QtClient::QtClient()
3400     : Client("Qt Client")
3401 {
3402     m_oneThread = Engine::config().getBoolValue("client","onethread",true);
3403 
3404     s_save = Engine::configFile("qt4client",true);
3405     s_save.load();
3406     // Fill QT styles
3407     s_qtStyles.addParam("IaOraKde","iaorakde");
3408     s_qtStyles.addParam("QWindowsStyle","windows");
3409     s_qtStyles.addParam("QMacStyle","mac");
3410     s_qtStyles.addParam("QMotifStyle","motif");
3411     s_qtStyles.addParam("QCDEStyle","cde");
3412     s_qtStyles.addParam("QWindowsXPStyle","windowsxp");
3413     s_qtStyles.addParam("QCleanlooksStyle","cleanlooks");
3414     s_qtStyles.addParam("QPlastiqueStyle","plastique");
3415     s_qtStyles.addParam("QGtkStyle","gtk");
3416     s_qtStyles.addParam("IaOraQt","iaoraqt");
3417     s_qtStyles.addParam("OxygenStyle","oxygen");
3418     s_qtStyles.addParam("PhaseStyle","phase");
3419 }
3420 
~QtClient()3421 QtClient::~QtClient()
3422 {
3423 }
3424 
cleanup()3425 void QtClient::cleanup()
3426 {
3427     Client::cleanup();
3428     m_events.clear();
3429     Client::save(s_save);
3430     QtWindow::clearUICache();
3431     m_app->quit();
3432     if (!m_app->startingUp())
3433 	delete m_app;
3434 }
3435 
run()3436 void QtClient::run()
3437 {
3438     const char* style = Engine::config().getValue("client","style");
3439     if (style && !QApplication::setStyle(QString::fromUtf8(style)))
3440 	Debug(ClientDriver::self(),DebugWarn,"Could not set Qt style '%s'",style);
3441     int argc = 0;
3442     char* argv =  0;
3443     m_app = new QApplication(argc,&argv);
3444     m_app->setQuitOnLastWindowClosed(false);
3445     updateAppStyleSheet();
3446     String imgRead;
3447     QList<QByteArray> imgs = QImageReader::supportedImageFormats();
3448     for (int i = 0; i < imgs.size(); i++)
3449 	imgRead.append(imgs[i].constData(),",");
3450     imgRead = "read image formats '" + imgRead + "'";
3451     Debug(ClientDriver::self(),DebugInfo,"QT client start running (version=%s) %s",
3452 	qVersion(),imgRead.c_str());
3453     if (!QSound::isAvailable())
3454 	Debug(ClientDriver::self(),DebugWarn,"QT sounds are not available");
3455     // Create events proxy
3456     m_events.append(new QtEventProxy(QtEventProxy::Timer));
3457     m_events.append(new QtEventProxy(QtEventProxy::AllHidden,m_app));
3458     if (Engine::exiting())
3459 	return;
3460     Client::run();
3461 }
3462 
main()3463 void QtClient::main()
3464 {
3465     if (!Engine::exiting())
3466 	m_app->exec();
3467 }
3468 
lock()3469 void QtClient::lock()
3470 {}
3471 
unlock()3472 void QtClient::unlock()
3473 {}
3474 
allHidden()3475 void QtClient::allHidden()
3476 {
3477     Debug(QtDriver::self(),DebugInfo,"QtClient::allHiden() counter=%d",s_allHiddenQuit);
3478     if (s_allHiddenQuit > 0)
3479 	return;
3480     quit();
3481 }
3482 
createWindow(const String & name,const String & alias)3483 bool QtClient::createWindow(const String& name, const String& alias)
3484 {
3485     String parent = s_cfg.getValue(name,"parent");
3486     QtWindow* parentWnd = 0;
3487     if (!TelEngine::null(parent)) {
3488 	ObjList* o = m_windows.find(parent);
3489 	if (o)
3490 	    parentWnd = YOBJECT(QtWindow,o->get());
3491     }
3492     QtWindow* w = new QtWindow(name,s_skinPath + s_cfg.getValue(name,"description"),alias,parentWnd);
3493     if (w) {
3494 	Debug(QtDriver::self(),DebugAll,"Created window name=%s alias=%s with parent=(%s [%p]) (%p)",
3495 	    name.c_str(),alias.c_str(),parent.c_str(),parentWnd,w);
3496 	// Remove the old window
3497 	ObjList* o = m_windows.find(w->id());
3498 	if (o)
3499 	    Client::self()->closeWindow(w->id(),false);
3500 	w->populate();
3501 	m_windows.append(w);
3502 	return true;
3503     }
3504     else
3505 	Debug(QtDriver::self(),DebugCrit,"Could not create window name=%s alias=%s",
3506 	    name.c_str(),alias.c_str());
3507     return false;
3508 }
3509 
loadWindows(const char * file)3510 void QtClient::loadWindows(const char* file)
3511 {
3512     if (!file)
3513 	s_cfg = s_skinPath + "qt4client.rc";
3514     else
3515 	s_cfg = String(file);
3516     s_cfg.load();
3517     Debug(QtDriver::self(),DebugInfo,"Loading Windows");
3518     unsigned int n = s_cfg.sections();
3519     for (unsigned int i = 0; i < n; i++) {
3520 	NamedList* l = s_cfg.getSection(i);
3521 	if (l && l->getBoolValue(YSTRING("enabled"),true))
3522 	    createWindow(*l);
3523     }
3524 }
3525 
isUIThread()3526 bool QtClient::isUIThread()
3527 {
3528     return (QApplication::instance() && QApplication::instance()->thread() == QThread::currentThread());
3529 }
3530 
3531 // Open a file open dialog window
3532 // Parameters that can be specified include 'caption',
3533 //  'dir', 'filter', 'selectedfilter', 'confirmoverwrite', 'choosedir'
chooseFile(Window * parent,NamedList & params)3534 bool QtClient::chooseFile(Window* parent, NamedList& params)
3535 {
3536     QtWindow* wnd = static_cast<QtWindow*>(parent);
3537     QFileDialog* dlg = new QFileDialog(wnd,setUtf8(params.getValue(YSTRING("caption"))),
3538 	setUtf8(params.getValue(YSTRING("dir"))));
3539 
3540     if (wnd)
3541 	dlg->setWindowIcon(wnd->windowIcon());
3542 
3543     // Connect signals
3544     String* action = params.getParam(YSTRING("action"));
3545     if (wnd && !null(action)) {
3546 	dlg->setObjectName(setUtf8(*action));
3547 	QtClient::connectObjects(dlg,SIGNAL(accepted()),wnd,SLOT(chooseFileAccepted()));
3548 	QtClient::connectObjects(dlg,SIGNAL(rejected()),wnd,SLOT(chooseFileRejected()));
3549     }
3550 
3551     // Destroy it when closed
3552     dlg->setAttribute(Qt::WA_DeleteOnClose);
3553     // This dialog should always stay on top
3554     dlg->setWindowFlags(dlg->windowFlags() | Qt::WindowStaysOnTopHint);
3555 
3556     if (params.getBoolValue(YSTRING("modal"),true))
3557 	dlg->setWindowModality(Qt::WindowModal);
3558 
3559     // Filters
3560     NamedString* f = params.getParam(YSTRING("filters"));
3561     if (f) {
3562 	QStringList filters;
3563 	ObjList* obj = f->split('|',false);
3564 	for (ObjList* o = obj->skipNull(); o; o = o->skipNext())
3565 	    filters.append(QtClient::setUtf8(o->get()->toString()));
3566 	TelEngine::destruct(obj);
3567 	dlg->setFilters(filters);
3568     }
3569     QString flt = QtClient::setUtf8(params.getValue(YSTRING("selectedfilter")));
3570     if (flt.length())
3571 	dlg->selectFilter(flt);
3572 
3573     if (params.getBoolValue(YSTRING("save")))
3574 	dlg->setAcceptMode(QFileDialog::AcceptSave);
3575     else
3576 	dlg->setAcceptMode(QFileDialog::AcceptOpen);
3577 
3578     // Choose options
3579     if (params.getBoolValue(YSTRING("choosefile"),true)) {
3580 	if (params.getBoolValue(YSTRING("chooseanyfile")))
3581 	    dlg->setFileMode(QFileDialog::AnyFile);
3582 	else if (params.getBoolValue(YSTRING("multiplefiles")))
3583 	    dlg->setFileMode(QFileDialog::ExistingFiles);
3584 	else
3585 	    dlg->setFileMode(QFileDialog::ExistingFile);
3586     }
3587     else
3588 	dlg->setFileMode(QFileDialog::DirectoryOnly);
3589 
3590     dlg->selectFile(QtClient::setUtf8(params.getValue(YSTRING("selectedfile"))));
3591 
3592     dlg->setVisible(true);
3593     return true;
3594 }
3595 
action(Window * wnd,const String & name,NamedList * params)3596 bool QtClient::action(Window* wnd, const String& name, NamedList* params)
3597 {
3598     String tmp = name;
3599     if (tmp.startSkip("openurl:",false))
3600 	return openUrl(tmp);
3601     return Client::action(wnd,name,params);
3602 }
3603 
3604 // Create a sound object. Append it to the global list
createSound(const char * name,const char * file,const char * device)3605 bool QtClient::createSound(const char* name, const char* file, const char* device)
3606 {
3607     if (!(QSound::isAvailable() && name && *name && file && *file))
3608 	return false;
3609     Lock lock(ClientSound::s_soundsMutex);
3610     if (ClientSound::s_sounds.find(name))
3611 	return false;
3612     ClientSound::s_sounds.append(new QtSound(name,file,device));
3613     DDebug(ClientDriver::self(),DebugAll,"Added sound=%s file=%s device=%s",
3614 	name,file,device);
3615     return true;
3616 }
3617 
3618 // Build a date/time string from UTC time
formatDateTime(String & dest,unsigned int secs,const char * format,bool utc)3619 bool QtClient::formatDateTime(String& dest, unsigned int secs,
3620     const char* format, bool utc)
3621 {
3622     if (!(format && *format))
3623 	return false;
3624     QtClient::getUtf8(dest,formatDateTime(secs,format,utc));
3625     return true;
3626 }
3627 
3628 // Build a date/time QT string from UTC time
formatDateTime(unsigned int secs,const char * format,bool utc)3629 QString QtClient::formatDateTime(unsigned int secs, const char* format, bool utc)
3630 {
3631     QDateTime time;
3632     if (utc)
3633 	time.setTimeSpec(Qt::UTC);
3634     time.setTime_t(secs);
3635     return time.toString(format);
3636 }
3637 
3638 // Retrieve an object's QtWindow parent
parentWindow(QObject * obj)3639 QtWindow* QtClient::parentWindow(QObject* obj)
3640 {
3641     for (; obj; obj = obj->parent()) {
3642 	QtWindow* w = qobject_cast<QtWindow*>(obj);
3643 	if (w)
3644 	    return w;
3645     }
3646     return 0;
3647 }
3648 
3649 // Save an object's property into parent window's section. Clear it on failure
saveProperty(QObject * obj,const String & prop,QtWindow * owner)3650 bool QtClient::saveProperty(QObject* obj, const String& prop, QtWindow* owner)
3651 {
3652     if (!obj)
3653 	return false;
3654     if (!owner)
3655 	owner = parentWindow(obj);
3656     if (!owner)
3657 	return false;
3658     String value;
3659     bool ok = getProperty(obj,prop,value);
3660     String pName;
3661     pName << "property:" << YQT_OBJECT_NAME(obj) << ":" << prop;
3662     if (ok)
3663 	s_save.setValue(owner->id(),pName,value);
3664     else
3665 	s_save.clearKey(owner->id(),pName);
3666     return ok;
3667 }
3668 
3669 // Set or an object's property
setProperty(QObject * obj,const char * name,const String & value)3670 bool QtClient::setProperty(QObject* obj, const char* name, const String& value)
3671 {
3672     if (!(obj && name && *name))
3673 	return false;
3674     QVariant var = obj->property(name);
3675     const char* err = 0;
3676     bool ok = false;
3677     switch (var.type()) {
3678 	case QVariant::String:
3679 	    ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value)));
3680 	    break;
3681 	case QVariant::Bool:
3682 	    ok = obj->setProperty(name,QVariant(value.toBoolean()));
3683 	    break;
3684 	case QVariant::Int:
3685 	    ok = obj->setProperty(name,QVariant(value.toInteger()));
3686 	    break;
3687 	case QVariant::UInt:
3688 	    ok = obj->setProperty(name,QVariant((unsigned int)value.toInteger()));
3689 	    break;
3690 	case QVariant::Icon:
3691 	    ok = obj->setProperty(name,QVariant(QIcon(QtClient::setUtf8(value))));
3692 	    break;
3693 	case QVariant::Pixmap:
3694 	    ok = obj->setProperty(name,QVariant(QPixmap(QtClient::setUtf8(value))));
3695 	    break;
3696 	case QVariant::Double:
3697 	    ok = obj->setProperty(name,QVariant(value.toDouble()));
3698 	    break;
3699 	case QVariant::KeySequence:
3700 	    ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value)));
3701 	    break;
3702 	case QVariant::StringList:
3703 	    {
3704 		QStringList qList;
3705 		if (value)
3706 		    qList.append(setUtf8(value));
3707 		ok = obj->setProperty(name,QVariant(qList));
3708 	    }
3709 	    break;
3710 	case QVariant::Invalid:
3711 	    err = "no such property";
3712 	    break;
3713 	default:
3714 	    err = "unsupported type";
3715     }
3716     YIGNORE(err);
3717     if (ok)
3718 	DDebug(ClientDriver::self(),DebugAll,"Set property %s=%s for object '%s'",
3719 	    name,value.c_str(),YQT_OBJECT_NAME(obj));
3720     else
3721 	DDebug(ClientDriver::self(),DebugNote,
3722 	    "Failed to set %s=%s (type=%s) for object '%s': %s",
3723 	    name,value.c_str(),var.typeName(),YQT_OBJECT_NAME(obj),err);
3724     return ok;
3725 }
3726 
3727 // Get an object's property
getProperty(QObject * obj,const char * name,String & value)3728 bool QtClient::getProperty(QObject* obj, const char* name, String& value)
3729 {
3730     if (!(obj && name && *name))
3731 	return false;
3732     QVariant var = obj->property(name);
3733     if (var.type() == QVariant::StringList) {
3734 	NamedList* l = static_cast<NamedList*>(value.getObject(YATOM("NamedList")));
3735 	if (l)
3736 	    copyParams(*l,var.toStringList());
3737 	else
3738 	    getUtf8(value,var.toStringList().join(","));
3739 	DDebug(ClientDriver::self(),DebugAll,"Got list property %s for object '%s'",
3740 	    name,YQT_OBJECT_NAME(obj));
3741 	return true;
3742     }
3743     if (var.canConvert(QVariant::String)) {
3744 	QtClient::getUtf8(value,var.toString());
3745 	DDebug(ClientDriver::self(),DebugAll,"Got property %s=%s for object '%s'",
3746 	    name,value.c_str(),YQT_OBJECT_NAME(obj));
3747 	return true;
3748     }
3749     DDebug(ClientDriver::self(),DebugNote,
3750 	"Failed to get property '%s' (type=%s) for object '%s': %s",
3751 	name,var.typeName(),YQT_OBJECT_NAME(obj),
3752 	((var.type() == QVariant::Invalid) ? "no such property" : "unsupported type"));
3753     return false;
3754 }
3755 
3756 // Copy a string list to a list of parameters
copyParams(NamedList & dest,const QStringList & src)3757 void QtClient::copyParams(NamedList& dest, const QStringList& src)
3758 {
3759     for (int i = 0; i < src.size(); i++) {
3760 	if (!src[i].length())
3761 	    continue;
3762 	int pos = src[i].indexOf('=');
3763 	String name;
3764 	if (pos >= 0) {
3765 	    getUtf8(name,src[i].left(pos));
3766 	    getUtf8(dest,name,src[i].right(src[i].length() - pos - 1));
3767 	}
3768 	else {
3769 	    getUtf8(name,src[i]);
3770 	    dest.addParam(name,"");
3771 	}
3772     }
3773 }
3774 
3775 // Copy a list of parameters to string list
copyParams(QStringList & dest,const NamedList & src)3776 void QtClient::copyParams(QStringList& dest, const NamedList& src)
3777 {
3778     unsigned int n = src.length();
3779     for (unsigned int i = 0; i < n; i++) {
3780 	NamedString* ns = src.getParam(i);
3781 	if (ns)
3782 	    dest.append(setUtf8(ns->name() + "=" + *ns));
3783     }
3784 }
3785 
3786 // Build QObject properties from list
buildProps(QObject * obj,const String & props)3787 void QtClient::buildProps(QObject* obj, const String& props)
3788 {
3789     if (!(obj && props))
3790 	return;
3791     ObjList* list = props.split(',',false);
3792     for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
3793 	String* s = static_cast<String*>(o->get());
3794 	int pos = s->find('=');
3795 	if (pos < 1)
3796 	    continue;
3797 	String ptype = s->substr(pos + 1);
3798 	QVariant::Type t = (QVariant::Type)lookup(ptype,s_qVarType,QVariant::Invalid);
3799 	if (t == QVariant::Invalid) {
3800 	    Debug(ClientDriver::self(),DebugStub,
3801 		"QtClient::buildProps() unhandled type '%s'",ptype.c_str());
3802 	    continue;
3803 	}
3804 	String pname = s->substr(0,pos);
3805 	QVariant existing = obj->property(pname);
3806 	if (existing.type() == QVariant::Invalid) {
3807 	    obj->setProperty(pname,QVariant(t));
3808 	    continue;
3809 	}
3810 	Debug(ClientDriver::self(),DebugNote,
3811 	    "Can't create property '%s' type=%s for object (%p,%s): already exists",
3812 	    pname.c_str(),ptype.c_str(),obj,YQT_OBJECT_NAME(obj));
3813     }
3814     TelEngine::destruct(list);
3815 }
3816 
3817 // Build custom UI widgets from frames owned by a widget
buildFrameUiWidgets(QWidget * parent)3818 void QtClient::buildFrameUiWidgets(QWidget* parent)
3819 {
3820     if (!parent)
3821 	return;
3822     QList<QFrame*> frm = qFindChildren<QFrame*>(parent);
3823     for (int i = 0; i < frm.size(); i++) {
3824 	if (!getBoolProperty(frm[i],"_yate_uiwidget"))
3825 	    continue;
3826 	String name;
3827 	String type;
3828 	getProperty(frm[i],"_yate_uiwidget_name",name);
3829 	getProperty(frm[i],"_yate_uiwidget_class",type);
3830 	if (!(name && type))
3831 	    continue;
3832 	NamedList params("");
3833 	getProperty(frm[i],"_yate_uiwidget_params",params);
3834 	QtWindow* w = static_cast<QtWindow*>(parent->window());
3835 	if (w)
3836 	    params.setParam("parentwindow",w->id());
3837 	getUtf8(params,"parentwidget",frm[i]->objectName(),true);
3838 	QObject* obj = (QObject*)UIFactory::build(type,name,&params);
3839 	if (!obj)
3840 	    continue;
3841 	QWidget* wid = qobject_cast<QWidget*>(obj);
3842 	if (wid)
3843 	    QtClient::setWidget(frm[i],wid);
3844 	else {
3845 	    obj->setParent(frm[i]);
3846 	    QtCustomObject* customObj = qobject_cast<QtCustomObject*>(obj);
3847 	    if (customObj)
3848 		customObj->parentChanged();
3849 	}
3850     }
3851 }
3852 
3853 // Associate actions to buttons with '_yate_setaction' property set
setAction(QWidget * parent)3854 void QtClient::setAction(QWidget* parent)
3855 {
3856     if (!parent)
3857 	return;
3858     QList<QToolButton*> tb = qFindChildren<QToolButton*>(parent);
3859     for (int i = 0; i < tb.size(); i++) {
3860 	QVariant var = tb[i]->property("_yate_setaction");
3861 	if (var.toString().isEmpty())
3862 	    continue;
3863 	QAction* a = qFindChild<QAction*>(parent,var.toString());
3864 	if (a)
3865 	    tb[i]->setDefaultAction(a);
3866     }
3867 }
3868 
3869 // Build a menu object from a list of parameters
buildMenu(const NamedList & params,const char * text,QObject * receiver,const char * triggerSlot,const char * toggleSlot,QWidget * parent,const char * aboutToShowSlot)3870 QMenu* QtClient::buildMenu(const NamedList& params, const char* text, QObject* receiver,
3871 	 const char* triggerSlot, const char* toggleSlot, QWidget* parent,
3872 	 const char* aboutToShowSlot)
3873 {
3874     QMenu* menu = 0;
3875     unsigned int n = params.length();
3876     for (unsigned int i = 0; i < n; i++) {
3877 	NamedString* param = params.getParam(i);
3878 	if (!(param && param->name().startsWith("item:")))
3879 	    continue;
3880 
3881 	if (!menu)
3882 	    menu = new QMenu(setUtf8(text),parent);
3883 
3884 	NamedList* p = YOBJECT(NamedList,param);
3885 	if (p)  {
3886 	    QMenu* subMenu = buildMenu(*p,*param ? param->c_str() : p->getValue(YSTRING("title"),*p),
3887 		receiver,triggerSlot,toggleSlot,menu);
3888 	    if (subMenu)
3889 		menu->addMenu(subMenu);
3890 	    continue;
3891 	}
3892 	String name = param->name().substr(5);
3893 	if (*param) {
3894 	    QAction* a = menu->addAction(QtClient::setUtf8(*param));
3895 	    a->setObjectName(QtClient::setUtf8(name));
3896 	    a->setParent(menu);
3897 	    setImage(a,params["image:" + name]);
3898 	}
3899 	else if (!name)
3900 	    menu->addSeparator()->setParent(menu);
3901 	else {
3902 	    // Check if the action is already there
3903 	    QAction* a = 0;
3904 	    if (parent && parent->window())
3905 		a = qFindChild<QAction*>(parent->window(),QtClient::setUtf8(name));
3906 	    if (a)
3907 		menu->addAction(a);
3908 	    else
3909 		Debug(ClientDriver::self(),DebugNote,
3910 		    "buildMenu(%s) action '%s' not found",params.c_str(),name.c_str());
3911 	}
3912     }
3913 
3914     if (!menu)
3915 	return 0;
3916 
3917     // Set name
3918     menu->setObjectName(setUtf8(params));
3919     setImage(menu,params["image:" + params]);
3920     // Apply properties
3921     // Format: property:object_name:property_name=value
3922     if (parent)
3923 	for (unsigned int i = 0; i < n; i++) {
3924 	    NamedString* param = params.getParam(i);
3925 	    if (!(param && param->name().startsWith("property:")))
3926 		continue;
3927 	    int pos = param->name().find(':',9);
3928 	    if (pos < 9)
3929 		continue;
3930 	    QObject* obj = qFindChild<QObject*>(parent,setUtf8(param->name().substr(9,pos - 9)));
3931 	    if (obj)
3932 		setProperty(obj,param->name().substr(pos + 1),*param);
3933 	}
3934     // Connect signals (direct children only: actions from sub-menus are already connected)
3935     QList<QAction*> list = qFindChildren<QAction*>(menu);
3936     for (int i = 0; i < list.size(); i++) {
3937 	if (list[i]->isSeparator() || list[i]->parent() != menu)
3938 	    continue;
3939 	if (list[i]->isCheckable())
3940 	    QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),receiver,toggleSlot);
3941 	else
3942 	    QtClient::connectObjects(list[i],SIGNAL(triggered()),receiver,triggerSlot);
3943     }
3944     if (!TelEngine::null(aboutToShowSlot))
3945 	QtClient::connectObjects(menu,SIGNAL(aboutToShow()),receiver,aboutToShowSlot);
3946 
3947     return menu;
3948 }
3949 
3950 // Wrapper for QObject::connect() used to put a debug mesage on failure
connectObjects(QObject * sender,const char * signal,QObject * receiver,const char * slot)3951 bool QtClient::connectObjects(QObject* sender, const char* signal,
3952     QObject* receiver, const char* slot)
3953 {
3954     if (!(sender && signal && *signal && receiver && slot && *slot))
3955 	return false;
3956     bool ok = QObject::connect(sender,signal,receiver,slot);
3957     if (ok)
3958 	DDebug(QtDriver::self(),DebugAll,
3959 	    "Connected sender=%s signal=%s to receiver=%s slot=%s",
3960 	    YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot);
3961     else
3962 	Debug(QtDriver::self(),DebugWarn,
3963 	    "Failed to connect sender=%s signal=%s to receiver=%s slot=%s",
3964 	    YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot);
3965     return ok;
3966 }
3967 
3968 // Insert a widget into another one replacing any existing children
setWidget(QWidget * parent,QWidget * child)3969 bool QtClient::setWidget(QWidget* parent, QWidget* child)
3970 {
3971     if (!(parent && child))
3972 	return false;
3973     QVBoxLayout* layout = new QVBoxLayout;
3974     layout->setSpacing(0);
3975     String margins;
3976     QtClient::getProperty(parent,"_yate_layout_margins",margins);
3977     if (!margins)
3978 	layout->setContentsMargins(0,0,0,0);
3979     else {
3980 	QList<int> m = buildIntList(margins,4);
3981 	layout->setContentsMargins(m[0],m[1],m[2],m[3]);
3982     }
3983     layout->addWidget(child);
3984     QLayout* l = parent->layout();
3985     if (l)
3986 	delete l;
3987     parent->setLayout(layout);
3988     return true;
3989 }
3990 
3991 // Set an object's image property from image file
setImage(QObject * obj,const String & img,bool fit)3992 bool QtClient::setImage(QObject* obj, const String& img, bool fit)
3993 {
3994     if (!obj)
3995 	return false;
3996     QPixmap pixmap(setUtf8(img));
3997     return setImage(obj,pixmap,fit);
3998 }
3999 
4000 // Set an object's image property from raw data.
setImage(QObject * obj,const DataBlock & data,const String & format,bool fit)4001 bool QtClient::setImage(QObject* obj, const DataBlock& data, const String& format, bool fit)
4002 {
4003     if (!obj)
4004 	return false;
4005     QPixmap pixmap;
4006     String f = format;
4007     f.startSkip("image/",false);
4008     if (!pixmap.loadFromData((const uchar*)data.data(),data.length(),f))
4009 	return false;
4010     return setImage(obj,pixmap,fit);
4011 }
4012 
4013 // Set an object's image property from QPixmap
setImage(QObject * obj,const QPixmap & img,bool fit)4014 bool QtClient::setImage(QObject* obj, const QPixmap& img, bool fit)
4015 {
4016     if (!obj)
4017 	return false;
4018     if (obj->isWidgetType()) {
4019 	QLabel* l = qobject_cast<QLabel*>(obj);
4020 	if (l) {
4021 	    if (fit && !l->hasScaledContents() &&
4022 		(img.width() > l->width() || img.height() > l->height())) {
4023 		QPixmap tmp;
4024 		if (l->width() <= l->height())
4025 		    tmp = img.scaledToWidth(l->width());
4026 		else
4027 		    tmp = img.scaledToHeight(l->height());
4028 		l->setPixmap(tmp);
4029 	    }
4030 	    else
4031 		l->setPixmap(img);
4032 	}
4033 	else {
4034 	    QAbstractButton* b = qobject_cast<QAbstractButton*>(obj);
4035 	    if (b)
4036 		b->setIcon(img);
4037 	    else {
4038 		QMenu* m = qobject_cast<QMenu*>(obj);
4039 		if (m)
4040 		    m->setIcon(img);
4041 		else
4042 		    return false;
4043 	    }
4044 	}
4045 	return true;
4046     }
4047     QAction* a = qobject_cast<QAction*>(obj);
4048     if (a) {
4049 	a->setIcon(img);
4050 	return true;
4051     }
4052     return false;
4053 }
4054 
4055 // Update a toggable object's image from properties
updateToggleImage(QObject * obj)4056 void QtClient::updateToggleImage(QObject* obj)
4057 {
4058     QtWidget w(obj);
4059     QAbstractButton* b = 0;
4060     if (w.inherits(QtWidget::AbstractButton))
4061 	b = w.abstractButton();
4062     if (!(b && b->isCheckable()))
4063 	return;
4064     String icon;
4065     bool set = false;
4066     if (b->isChecked())
4067 	set = QtClient::getProperty(w,"_yate_pressed_icon",icon);
4068     else
4069 	set = QtClient::getProperty(w,"_yate_normal_icon",icon);
4070     if (set)
4071         QtClient::setImage(obj,Client::s_skinPath + icon);
4072 }
4073 
4074 // Update an object's image from properties on mouse events
updateImageFromMouse(QObject * obj,bool inOut,bool on)4075 void QtClient::updateImageFromMouse(QObject* obj, bool inOut, bool on)
4076 {
4077     QtWidget w(obj);
4078     QAbstractButton* b = 0;
4079     if (w.inherits(QtWidget::AbstractButton))
4080 	b = w.abstractButton();
4081     if (!b)
4082 	return;
4083     if (!b->isEnabled())
4084 	return;
4085     String icon;
4086     bool set = false;
4087     if (inOut) {
4088 	if (on)
4089 	    set = QtClient::getProperty(obj,"_yate_hover_icon",icon);
4090 	else {
4091 	    if (b->isCheckable() && b->isChecked())
4092 		set = QtClient::getProperty(obj,"_yate_pressed_icon",icon);
4093 	    set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon);
4094 	}
4095     }
4096     else {
4097 	if (on) {
4098 	    if (!b->isCheckable())
4099 		set = QtClient::getProperty(obj,"_yate_pressed_icon",icon);
4100 	}
4101 	else {
4102 	    set = QtClient::getProperty(obj,"_yate_hover_icon",icon);
4103 	    if (!set && b->isCheckable() && b->isChecked())
4104 		set = QtClient::getProperty(obj,"_yate_pressed_icon",icon);
4105 	    set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon);
4106 	}
4107     }
4108     if (set)
4109 	QtClient::setImage(obj,Client::s_skinPath + icon);
4110 }
4111 
4112 // Process a key press event. Retrieve an action associated with the key
filterKeyEvent(QObject * obj,QKeyEvent * event,String & action,bool & filter,QObject * parent)4113 bool QtClient::filterKeyEvent(QObject* obj, QKeyEvent* event, String& action,
4114     bool& filter, QObject* parent)
4115 {
4116     static int mask = Qt::SHIFT | Qt::CTRL | Qt::ALT;
4117     if (!(obj && event))
4118 	return false;
4119     // Try to match key and modifiers
4120     QKeySequence ks(event->key());
4121     String prop;
4122     getUtf8(prop,ks.toString());
4123     prop = "dynamicAction" + prop;
4124     // Get modifiers from property and check them against event
4125     QVariant v = obj->property(prop + "Modifiers");
4126     int tmp = 0;
4127     if (v.type() == QVariant::String) {
4128 	QKeySequence ks(v.toString());
4129 	for (unsigned int i = 0; i < ks.count(); i++)
4130 	    tmp |= ks[i];
4131     }
4132     if (tmp != (mask & event->modifiers()))
4133 	return false;
4134     // We matched the key and modifiers
4135     // Set filter flag
4136     filter = getBoolProperty(obj,prop + "Filter");
4137     // Retrieve the action
4138     getProperty(obj,prop,action);
4139     if (!action)
4140 	return true;
4141     if (!parent)
4142 	return true;
4143     parent = qFindChild<QObject*>(parent,setUtf8(action));
4144     if (!parent)
4145 	return true;
4146     // Avoid notifying a disabled action
4147     bool ok = true;
4148     if (parent->isWidgetType())
4149 	ok = (qobject_cast<QWidget*>(parent))->isEnabled();
4150     else {
4151 	QAction* a = qobject_cast<QAction*>(parent);
4152 	ok = !a || a->isEnabled();
4153     }
4154     if (!ok)
4155 	action.clear();
4156     return true;
4157 }
4158 
4159 // Safely delete a QObject (reset its parent, calls it's deleteLater() method)
deleteLater(QObject * obj)4160 void QtClient::deleteLater(QObject* obj)
4161 {
4162     if (!obj)
4163 	return;
4164     obj->disconnect();
4165     if (obj->isWidgetType())
4166 	(static_cast<QWidget*>(obj))->setParent(0);
4167     else
4168 	obj->setParent(0);
4169     obj->deleteLater();
4170 }
4171 
4172 // Retrieve unavailable space position (if any) in the screen containing a given widget.
getScreenUnavailPos(QWidget * w,int & pos)4173 QDesktopWidget* QtClient::getScreenUnavailPos(QWidget* w, int& pos)
4174 {
4175     if (!w)
4176 	return 0;
4177     QDesktopWidget* d = QApplication::desktop();
4178     if (!d)
4179 	return 0;
4180     pos = PosNone;
4181     QRect rScreen = d->screenGeometry(w);
4182     QRect rClient = d->availableGeometry(w);
4183     int dx = rClient.x() - rScreen.x();
4184     if (dx > 0)
4185 	pos |= PosLeft;
4186     int dy = rClient.y() - rScreen.y();
4187     if (dy > 0)
4188 	pos |= PosTop;
4189     int dw = rScreen.width() - rClient.width();
4190     if (dw > 0 && (!dx || (dx > 0 && dw > dx)))
4191 	pos |= PosRight;
4192     int dh = rScreen.height() - rClient.height();
4193     if (dh > 0 && (!dy || (dy > 0 && dh > dy)))
4194 	pos |= PosBottom;
4195     return d;
4196 }
4197 
4198 // Move a window to a specified position
moveWindow(QtWindow * w,int pos)4199 void QtClient::moveWindow(QtWindow* w, int pos)
4200 {
4201     if (!w)
4202 	return;
4203     QDesktopWidget* d = QApplication::desktop();
4204     if (!d)
4205 	return;
4206     QRect r = d->availableGeometry(w);
4207     int x = r.x();
4208     int y = r.y();
4209     QSize sz = w->frameSize();
4210     if (pos == CornerBottomRight) {
4211         if (r.width() > sz.width())
4212 	    x += r.width() - sz.width();
4213 	if (r.height() > sz.height())
4214 	    y += r.height() - sz.height();
4215     }
4216     else if (pos == CornerTopRight) {
4217         if (r.width() > sz.width())
4218 	    x += r.width() - sz.width();
4219     }
4220     else if (pos == CornerBottomLeft) {
4221 	if (r.height() > sz.height())
4222 	    y += r.height() - sz.height();
4223     }
4224     else if (pos != CornerTopLeft)
4225 	return;
4226     w->move(x,y);
4227 }
4228 
4229 // Build a QStringList from a list of strings
str2list(const String & str,char sep,bool emptyOk)4230 QStringList QtClient::str2list(const String& str, char sep, bool emptyOk)
4231 {
4232     QStringList l;
4233     if (!str)
4234 	return l;
4235     ObjList* list = str.split(sep,emptyOk);
4236     for (ObjList* o = list->skipNull(); o; o = o->skipNext())
4237 	l.append(setUtf8(static_cast<String*>(o->get())->c_str()));
4238     TelEngine::destruct(list);
4239     return l;
4240 }
4241 
4242 // Split a string. Returns a list of int values
str2IntList(const String & str,int defVal,bool emptyOk)4243 QList<int> QtClient::str2IntList(const String& str, int defVal, bool emptyOk)
4244 {
4245     QList<int> list;
4246     ObjList* l = str.split(',',emptyOk);
4247     for (ObjList* o = l->skipNull(); o; o = o->skipNext())
4248 	list.append(o->get()->toString().toInteger(defVal));
4249     TelEngine::destruct(l);
4250     return list;
4251 }
4252 
4253 // Build a comma separated list of integers
intList2str(String & str,QList<int> list)4254 void QtClient::intList2str(String& str, QList<int> list)
4255 {
4256     for (int i = 0; i < list.size(); i++)
4257 	str.append(String(list[i]),",");
4258 }
4259 
4260 // Get sorting from string
str2sort(const String & str,int defVal)4261 int QtClient::str2sort(const String& str, int defVal)
4262 {
4263     return lookup(str,s_sorting,defVal);
4264 }
4265 
4266 // Apply a comma separated list of window flags to a widget
applyWindowFlags(QWidget * w,const String & value)4267 void QtClient::applyWindowFlags(QWidget* w, const String& value)
4268 {
4269     if (!w)
4270 	return;
4271     // Set window flags from enclosed widget:
4272     //  custom window title/border/sysmenu config
4273     ObjList* f = value.split(',',false);
4274     int flags = Qt::CustomizeWindowHint | w->windowFlags();
4275     // Clear settable flags
4276     TokenDict* dict = s_windowFlags;
4277     for (int i = 0; dict[i].token; i++)
4278 	flags &= ~dict[i].value;
4279     // Set flags
4280     for (ObjList* o = f->skipNull(); o; o = o->skipNext())
4281 	flags |= lookup(o->get()->toString(),s_windowFlags,0);
4282     TelEngine::destruct(f);
4283     w->setWindowFlags((Qt::WindowFlags)flags);
4284 }
4285 
4286 // Build a QT Alignment mask from a comma separated list of flags
str2align(const String & flags,int initVal)4287 int QtClient::str2align(const String& flags, int initVal)
4288 {
4289     ObjList* list = flags.split(',',false);
4290     for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
4291 	int val = ::lookup((static_cast<String*>(o->get()))->c_str(),s_qAlign);
4292 	if (0 != (val & Qt::AlignHorizontal_Mask))
4293 	    initVal &= ~Qt::AlignHorizontal_Mask;
4294 	if (0 != (val & Qt::AlignVertical_Mask))
4295 	    initVal &= ~Qt::AlignVertical_Mask;
4296 	initVal |= val;
4297     }
4298     TelEngine::destruct(list);
4299     return initVal;
4300 }
4301 
4302 // Retrieve QT selection mode from a string value
str2selmode(const String & value,QAbstractItemView::SelectionMode defVal)4303 QAbstractItemView::SelectionMode QtClient::str2selmode(const String& value,
4304     QAbstractItemView::SelectionMode defVal)
4305 {
4306     if (!value)
4307 	return defVal;
4308     if (value == YSTRING("none"))
4309 	return QAbstractItemView::NoSelection;
4310     if (value == YSTRING("single"))
4311 	return QAbstractItemView::SingleSelection;
4312     if (value == YSTRING("multi"))
4313 	return QAbstractItemView::MultiSelection;
4314     if (value == YSTRING("extended"))
4315 	return QAbstractItemView::ExtendedSelection;
4316     if (value == YSTRING("contiguous"))
4317 	return QAbstractItemView::ContiguousSelection;
4318     return defVal;
4319 }
4320 
4321 // Retrieve QT edit triggers from a string value
str2editTriggers(const String & value,QAbstractItemView::EditTrigger defVal)4322 QAbstractItemView::EditTriggers QtClient::str2editTriggers(const String& value,
4323     QAbstractItemView::EditTrigger defVal)
4324 {
4325     return (QAbstractItemView::EditTriggers)Client::decodeFlags(s_qEditTriggers,value,defVal);
4326 }
4327 
4328 // Send an event to an object's child
sendEvent(QEvent & e,QObject * parent,const QString & name)4329 bool QtClient::sendEvent(QEvent& e, QObject* parent, const QString& name)
4330 {
4331     if (!(parent && e.isAccepted()))
4332 	return false;
4333     QObject* child = qFindChild<QObject*>(parent,name);
4334     if (!child)
4335 	return false;
4336     e.setAccepted(false);
4337     bool ok = QCoreApplication::sendEvent(child,&e);
4338     if (!ok)
4339 	e.setAccepted(true);
4340     return ok;
4341 }
4342 
4343 // Retrieve a pixmap from global application cache.
4344 // Load and add it to the cache if not found
getPixmapFromCache(QPixmap & pixmap,const QString & file)4345 bool QtClient::getPixmapFromCache(QPixmap& pixmap, const QString& file)
4346 {
4347     if (file.isEmpty())
4348 	return false;
4349     QPixmap* cached = QPixmapCache::find(file);
4350     if (cached) {
4351 	pixmap = *cached;
4352 	return true;
4353     }
4354     if (!pixmap.load(file))
4355 	return false;
4356 #ifdef XDEBUG
4357     String f;
4358     getUtf8(f,file);
4359     Debug(ClientDriver::self(),DebugAll,"Loaded '%s' in pixmap cache",f.c_str());
4360 #endif
4361     QPixmapCache::insert(file,pixmap);
4362     return true;
4363 }
4364 
4365 // Update application style sheet from config
4366 // Build style sheet from files:
4367 // stylesheet.css
4368 // stylesheet_stylename.css
4369 // stylesheet_osname.css
4370 // stylesheet_osname_stylename.css
updateAppStyleSheet()4371 void QtClient::updateAppStyleSheet()
4372 {
4373     if (!qApp) {
4374 	Debug(ClientDriver::self(),DebugWarn,"Update app stylesheet called without app");
4375 	return;
4376     }
4377     String shf = Engine::config().getValue("client","stylesheet_file","stylesheet.css");
4378     if (!shf)
4379 	return;
4380     QString sh;
4381     if (!appendStyleSheet(sh,shf))
4382 	return;
4383     String styleName;
4384     QStyle* style = qApp->style();
4385     const QMetaObject* meta = style ? style->metaObject() : 0;
4386     if (meta) {
4387 	styleName = s_qtStyles.getValue(meta->className());
4388 	if (!styleName)
4389 	    styleName = meta->className();
4390     }
4391     if (styleName)
4392 	appendStyleSheet(sh,shf,styleName);
4393     String osname;
4394     osname << "os" << PLATFORM_LOWERCASE_NAME;
4395     appendStyleSheet(sh,shf,osname);
4396     if (styleName)
4397 	appendStyleSheet(sh,shf,osname,styleName);
4398     qApp->setStyleSheet(sh);
4399 }
4400 
4401 // Set widget attributes from list
setWidgetAttributes(QWidget * w,const String & attrs)4402 void QtClient::setWidgetAttributes(QWidget* w, const String& attrs)
4403 {
4404     if (!(w && attrs))
4405 	return;
4406     ObjList* list = attrs.split(',',false);
4407     for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
4408 	const String& attr = *static_cast<String*>(o->get());
4409 	bool on = (attr[0] != '!');
4410 	const char* name = attr.c_str();
4411 	int val = lookup(on ? name : name + 1,s_widgetAttributes);
4412 	if (val)
4413 	    w->setAttribute((Qt::WidgetAttribute)val,on);
4414     }
4415     TelEngine::destruct(list);
4416 }
4417 
4418 // Adjust widget height
setWidgetHeight(QWidget * w,const String & height)4419 void QtClient::setWidgetHeight(QWidget* w, const String& height)
4420 {
4421     if (!w)
4422 	return;
4423     int h = 0;
4424     if (height.isBoolean()) {
4425 	h = QtClient::getIntProperty(w,"_yate_height_delta",-1);
4426 	if (h > 0) {
4427 	    if (height.toBoolean())
4428 		h += w->height();
4429 	    else if (h < w->height())
4430 		h = w->height() - h;
4431 	    else
4432 		h = 0;
4433 	}
4434     }
4435     else
4436 	h = height.toInteger();
4437     if (h < 0)
4438 	return;
4439     QSizePolicy sp = w->sizePolicy();
4440     sp.setVerticalPolicy(QSizePolicy::Fixed);
4441     w->setSizePolicy(sp);
4442     w->setMinimumHeight(h);
4443     w->setMaximumHeight(h);
4444 }
4445 
4446 // Build a busy widget child for a given widget
buildBusy(QWidget * parent,QWidget * target,const String & ui,const NamedList & params)4447 QWidget* QtClient::buildBusy(QWidget* parent, QWidget* target, const String& ui,
4448     const NamedList& params)
4449 {
4450     QtBusyWidget* w = new QtBusyWidget(parent);
4451     w->init(ui,params,target);
4452     return w;
4453 }
4454 
4455 // Load a movie
loadMovie(const char * file,QObject * parent,const char * path)4456 QMovie* QtClient::loadMovie(const char* file, QObject* parent, const char* path)
4457 {
4458     static NamedList s_failed("");
4459 
4460     if (TelEngine::null(file))
4461 	return 0;
4462     String tmp = path;
4463     if (!path)
4464 	tmp = Client::s_skinPath;
4465     else if (tmp && !tmp.endsWith(Engine::pathSeparator()))
4466 	tmp << Engine::pathSeparator();
4467     tmp << file;
4468     QMovie* m = new QMovie(setUtf8(tmp),QByteArray(),parent);
4469     NamedString* ns = s_failed.getParam(tmp);
4470     if (m->isValid()) {
4471 	if (ns)
4472 	    s_failed.clearParam(ns);
4473 	return m;
4474     }
4475     if (!ns) {
4476 	s_failed.addParam(tmp,"");
4477 	String error;
4478 	error << "Failed to load movie '" << tmp << "'";
4479 	Debug(QtDriver::self(),DebugNote,"%s",error.c_str());
4480 	if (self())
4481 	    self()->addToLog(error);
4482     }
4483     delete m;
4484     return 0;
4485 }
4486 
4487 // Fill a list from URL parameters
fillUrlParams(const QUrl & url,NamedList & list,QString * path,bool pathToList)4488 void QtClient::fillUrlParams(const QUrl& url, NamedList& list, QString* path,
4489     bool pathToList)
4490 {
4491     safeGetUtf8(list,"protocol",url.scheme());
4492     safeGetUtf8(list,"host",url.host());
4493     if (url.port() >= 0)
4494 	list.addParam("port",String(url.port()));
4495     safeGetUtf8(list,"username",url.userName());
4496     safeGetUtf8(list,"password",url.password());
4497     QString tmp;
4498     if (!path) {
4499 	tmp = url.path();
4500 	path = &tmp;
4501     }
4502     if (pathToList)
4503 	list.assign(path->toUtf8().constData());
4504     else
4505 	safeGetUtf8(list,"path",*path);
4506     QList<QPair<QString, QString> > items = url.queryItems();
4507     for (int i = 0; i < items.size(); i++)
4508 	list.addParam(items[i].first.toUtf8().constData(),items[i].second.toUtf8().constData());
4509 }
4510 
4511 // Dump MIME data for debug purposes
dumpMime(String & buf,const QMimeData * m)4512 void QtClient::dumpMime(String& buf, const QMimeData* m)
4513 {
4514     static const char* indent = "\r\n    ";
4515     if (!m)
4516 	return;
4517     QStringList fmts = m->formats();
4518     if (fmts.size() > 0) {
4519 	buf.append("FORMATS:","\r\n") << indent;
4520 	QString s = fmts.join(indent);
4521 	buf << s.toUtf8().constData();
4522     }
4523     if (m->html().length() > 0)
4524 	buf.append("HTML: ","\r\n") << m->html().toUtf8().constData();
4525     if (m->text().length() > 0)
4526 	buf.append("TEXT: ","\r\n") << m->text().toUtf8().constData();
4527     QList<QUrl> urls = m->urls();
4528     if (urls.size() > 0) {
4529 	buf.append("URLS:","\r\n");
4530 	for (int i = 0; i < urls.size(); i++)
4531 	    buf << indent << urls[i].toString().toUtf8().constData();
4532     }
4533 }
4534 
4535 
4536 /**
4537  * QtDriver
4538  */
QtDriver(bool buildClientThread)4539 QtDriver::QtDriver(bool buildClientThread)
4540     : m_init(false), m_clientThread(buildClientThread)
4541 {
4542     qInstallMsgHandler(qtMsgHandler);
4543 }
4544 
~QtDriver()4545 QtDriver::~QtDriver()
4546 {
4547     qInstallMsgHandler(0);
4548 }
4549 
initialize()4550 void QtDriver::initialize()
4551 {
4552     Output("Initializing module Qt4 client");
4553     s_device = Engine::config().getValue("client","device",DEFAULT_DEVICE);
4554     if (!QtClient::self()) {
4555 	debugCopy();
4556 	QtClient::setSelf(new QtClient);
4557 	if (m_clientThread)
4558 	    QtClient::self()->startup();
4559     }
4560     if (!m_init) {
4561 	m_init = true;
4562 	setup();
4563     }
4564 }
4565 
4566 /**
4567  * QtEventProxy
4568  */
QtEventProxy(Type type,QApplication * app)4569 QtEventProxy::QtEventProxy(Type type, QApplication* app)
4570 {
4571 #define SET_NAME(n) { m_name = n; setObjectName(QtClient::setUtf8(m_name)); }
4572     switch (type) {
4573 	case Timer:
4574 	    SET_NAME("qtClientTimerProxy");
4575 	    {
4576 		QTimer* timer = new QTimer(this);
4577 		timer->setObjectName("qtClientIdleTimer");
4578 		QtClient::connectObjects(timer,SIGNAL(timeout()),this,SLOT(timerTick()));
4579 		timer->start(0);
4580 	    }
4581 	    break;
4582 	case AllHidden:
4583 	    SET_NAME("qtClientAllHidden");
4584 	    if (app)
4585 		QtClient::connectObjects(app,SIGNAL(lastWindowClosed()),this,SLOT(allHidden()));
4586 	    break;
4587 	default:
4588 	    return;
4589     }
4590 #undef SET_NAME
4591 }
4592 
timerTick()4593 void QtEventProxy::timerTick()
4594 {
4595     if (Client::self())
4596 	Client::self()->idleActions();
4597     Thread::idle();
4598 }
4599 
allHidden()4600 void QtEventProxy::allHidden()
4601 {
4602     if (Client::self())
4603 	Client::self()->allHidden();
4604 }
4605 
4606 
4607 //
4608 // QtUrlBuilder
4609 //
QtUrlBuilder(QObject * parent,const String & format,const String & queryParams)4610 QtUrlBuilder::QtUrlBuilder(QObject* parent, const String& format,
4611     const String& queryParams)
4612     : QObject(parent),
4613     m_format(format),
4614     m_queryParams(0)
4615 {
4616     if (queryParams) {
4617 	m_queryParams = queryParams.split(',',false);
4618 	if (!m_queryParams->skipNull())
4619 	    TelEngine::destruct(m_queryParams);
4620     }
4621 }
4622 
~QtUrlBuilder()4623 QtUrlBuilder::~QtUrlBuilder()
4624 {
4625     TelEngine::destruct(m_queryParams);
4626 }
4627 
4628 // Build URL
build(const NamedList & params) const4629 QUrl QtUrlBuilder::build(const NamedList& params) const
4630 {
4631     String tmp;
4632     if (m_format) {
4633 	tmp = m_format;
4634 	params.replaceParams(tmp);
4635     }
4636     QUrl url(QtClient::setUtf8(tmp));
4637     if (m_queryParams) {
4638 	NamedIterator iter(params);
4639 	for (const NamedString* ns = 0; 0 != (ns = iter.get());)
4640 	    if (m_queryParams->find(ns->name()))
4641 		url.addQueryItem(QtClient::setUtf8(ns->name()),QtClient::setUtf8(*ns));
4642     }
4643     return url;
4644 }
4645 
4646 
4647 /*
4648  * QtUIWidget
4649  */
4650 // Retrieve item type definition from [type:]value. Create it if not found
getItemProps(QString & in,String & value)4651 QtUIWidgetItemProps* QtUIWidget::getItemProps(QString& in, String& value)
4652 {
4653     String type;
4654     int pos = in.indexOf(':');
4655     if (pos >= 0) {
4656 	QtClient::getUtf8(type,in.left(pos));
4657 	QtClient::getUtf8(value,in.right(in.length() - pos - 1));
4658     }
4659     else
4660 	QtClient::getUtf8(value,in);
4661     QtUIWidgetItemProps* p = QtUIWidget::getItemProps(type);
4662     if (!p) {
4663 	p = new QtUIWidgetItemProps(type);
4664 	m_itemProps.append(p);
4665     }
4666     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) getItemProps(%s,%s) got (%p) ui=%s [%p]",
4667 	name().c_str(),in.toUtf8().constData(),value.c_str(),p,p->m_ui.c_str(),this);
4668     return p;
4669 }
4670 
4671 // Set widget's parameters.
4672 // Handle an 'applyall' parameter carrying a NamedList to apply to all items
setParams(const NamedList & params)4673 bool QtUIWidget::setParams(const NamedList& params)
4674 {
4675     bool ok = false;
4676     NamedIterator iter(params);
4677     for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
4678 	if (ns->name() == YSTRING("applyall")) {
4679 	    const NamedList* list = YOBJECT(NamedList,ns);
4680 	    if (list) {
4681 		ok = true;
4682 		applyAllParams(*list);
4683 	    }
4684 	}
4685 	else if (ns->name().startsWith("beginedit:"))
4686 	    beginEdit(ns->name().substr(10),ns);
4687     }
4688     return ok;
4689 }
4690 
4691 // Apply a list of parameters to all container items
applyAllParams(const NamedList & params)4692 void QtUIWidget::applyAllParams(const NamedList& params)
4693 {
4694     QList<QObject*> list = getContainerItems();
4695     for (int i = 0; i < list.size(); i++)
4696 	setParams(list[i],params);
4697 }
4698 
4699 // Find an item widget by id
findItem(const String & id)4700 QWidget* QtUIWidget::findItem(const String& id)
4701 {
4702     QString item = QtClient::setUtf8(id);
4703     QList<QObject*> list = getContainerItems();
4704     for (int i = 0; i < list.size(); i++) {
4705 	if (!list[i]->isWidgetType())
4706 	    continue;
4707 	String item;
4708 	getListItemIdProp(list[i],item);
4709 	if (id == item)
4710 	    return static_cast<QWidget*>(list[i]);
4711     }
4712     return 0;
4713 }
4714 
4715 // Retrieve the object identity from '_yate_identity' property or name
4716 // Retrieve the object item from '_yate_widgetlistitem' property.
4717 // Set 'identity' to object_identity[:item_name]
getIdentity(QObject * obj,String & identity)4718 void QtUIWidget::getIdentity(QObject* obj, String& identity)
4719 {
4720     if (!obj)
4721 	return;
4722     String ident;
4723     QtClient::getIdentity(obj,ident);
4724     if (!ident)
4725 	return;
4726     String item;
4727     getListItemProp(obj,item);
4728     identity.append(ident,":");
4729     identity.append(item,":");
4730 }
4731 
4732 // Update a widget and children from a list a parameters
setParams(QObject * parent,const NamedList & params)4733 bool QtUIWidget::setParams(QObject* parent, const NamedList& params)
4734 {
4735     static const String s_property = "property";
4736     static const String s_active = "active";
4737     static const String s_image = "image";
4738     static const String s_show = "show";
4739     static const String s_display = "display";
4740     static const String s_check = "check";
4741     static const String s_select = "select";
4742     static const String s_addlines = "addlines";
4743     static const String s_setrichtext = "setrichtext";
4744     static const String s_updatetablerows = "updatetablerows";
4745     static const String s_cleartable = "cleartable";
4746     static const String s_rawimage = "rawimage";
4747     static const String s_setparams = "setparams";
4748     static const String s_setmenu = "setmenu";
4749     static const String s_height = "height";
4750 
4751     if (!parent)
4752 	return false;
4753     QtWindow* wnd = QtClient::parentWindow(parent);
4754     if (!wnd)
4755 	return false;
4756 #ifdef DEBUG
4757     String tmp;
4758     params.dump(tmp," ");
4759     Debug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::setParams(%p,%s) %s",
4760 	name().c_str(),parent,YQT_OBJECT_NAME(parent),tmp.c_str());
4761 #endif
4762     String pName(YQT_OBJECT_NAME(parent));
4763     bool ok = true;
4764     unsigned int n = params.length();
4765     for (unsigned int i = 0; i < n; i++) {
4766 	NamedString* ns = params.getParam(i);
4767 	if (!ns)
4768 	    continue;
4769 	XDebug(ClientDriver::self(),DebugInfo,"QtUIWidget(%s)::setParams() %s=%s",
4770 	    name().c_str(),ns->name().c_str(),ns->c_str());
4771 	String buf;
4772 	int pos = ns->name().find(':');
4773 	if (pos < 0) {
4774 	    if (ns->name() != s_setmenu)
4775 		ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok;
4776 	    else
4777 		buildWidgetItemMenu(qobject_cast<QWidget*>(parent),YOBJECT(NamedList,ns));
4778 	    continue;
4779 	}
4780 	String n(ns->name().substr(0,pos));
4781 	String cName = ns->name().substr(pos + 1);
4782 	if (n == s_property) {
4783 	    // Handle property[:child]:property_name
4784 	    int pos = cName.find(':');
4785 	    if (pos >= 0) {
4786 		QString tmp = buildQChildName(pName,cName.substr(0,pos));
4787 		QObject* c = qFindChild<QObject*>(parent,tmp);
4788 		ok = c && QtClient::setProperty(c,cName.substr(pos + 1),*ns) && ok;
4789 	    }
4790 	    else
4791 		ok = QtClient::setProperty(parent,cName,*ns) && ok;
4792 	}
4793 	else if (n == s_active)
4794 	    ok = wnd->setActive(buildChildName(buf,pName,cName),ns->toBoolean()) && ok;
4795 	else if (n == s_image)
4796 	    ok = wnd->setImage(buildChildName(buf,pName,cName),*ns) && ok;
4797 	else if (n == s_show || n == s_display)
4798 	    ok = wnd->setShow(buildChildName(buf,pName,cName),ns->toBoolean()) && ok;
4799 	else if (n == s_check)
4800 	    ok = wnd->setCheck(buildChildName(buf,pName,cName),ns->toBoolean()) && ok;
4801 	else if (n == s_select)
4802 	    ok = wnd->setSelect(buildChildName(buf,pName,cName),*ns) && ok;
4803 	if (n == s_setparams) {
4804 	    NamedList* p = YOBJECT(NamedList,ns);
4805 	    if (!p)
4806 		continue;
4807 	    QtWidget w(parent,buildChildName(buf,pName,cName));
4808 	    UIWidget* uiw = w.uiWidget();
4809 	    ok = uiw && uiw->setParams(*p) && ok;
4810 	}
4811 	else if (n == s_addlines) {
4812 	    NamedList* p = YOBJECT(NamedList,ns);
4813 	    if (p)
4814 		ok = wnd->addLines(buildChildName(buf,pName,cName),p,0,ns->toBoolean()) && ok;
4815 	}
4816 	else if (n == s_setrichtext)
4817 	    ok = wnd->setText(buildChildName(buf,pName,cName),*ns,true) && ok;
4818 	else if (n == s_updatetablerows) {
4819 	    NamedList* p = YOBJECT(NamedList,ns);
4820 	    if (p)
4821 		ok = wnd->updateTableRows(buildChildName(buf,pName,cName),p,ns->toBoolean()) && ok;
4822 	}
4823 	else if (n == s_cleartable)
4824 	    ok = wnd->clearTable(buildChildName(buf,pName,cName)) && ok;
4825 	else if (n == s_rawimage) {
4826 	    DataBlock* data = YOBJECT(DataBlock,ns);
4827 	    if (data) {
4828 		QString tmp = buildQChildName(pName,cName.substr(0,pos));
4829 		QObject* c = qFindChild<QObject*>(parent,tmp);
4830 		ok = c && QtClient::setImage(c,*data,*ns) && ok;
4831 	    }
4832 	}
4833 	else if (n == s_setmenu)
4834 	    buildWidgetItemMenu(qobject_cast<QWidget*>(parent),YOBJECT(NamedList,ns),cName);
4835 	else if (n == s_height) {
4836 	    QString tmp = buildQChildName(pName,cName);
4837 	    QWidget* w = qFindChild<QWidget*>(qobject_cast<QWidget*>(parent),tmp);
4838 	    QtClient::setWidgetHeight(w,*ns);
4839 	}
4840 	else
4841 	    ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok;
4842     }
4843     // Set item parameters
4844     NamedString* yparams = params.getParam(YSTRING("_yate_itemparams"));
4845     if (!TelEngine::null(yparams)) {
4846 	QVariant var = parent->property(yparams->name().c_str());
4847 	if (var.type() == QVariant::Invalid || var.type() == QVariant::StringList) {
4848 	    QStringList list;
4849 	    if (var.type() == QVariant::StringList)
4850 		list = var.toStringList();
4851 	    NamedList tmp("");
4852 	    tmp.copyParams(params,*yparams);
4853 	    QtClient::copyParams(list,tmp);
4854 	    parent->setProperty(yparams->name().c_str(),QVariant(list));
4855 	}
4856 	else
4857 	    ok = false;
4858     }
4859     return ok;
4860 }
4861 
4862 // Get an item object's parameters
getParams(QObject * parent,NamedList & params)4863 bool QtUIWidget::getParams(QObject* parent, NamedList& params)
4864 {
4865     static const String s_property = "property";
4866     static const String s_getcheck = "getcheck";
4867     static const String s_getselect = "getselect";
4868     static const String s_getrichtext = "getrichtext";
4869 
4870     if (!parent)
4871 	return false;
4872     QtWindow* wnd = QtClient::parentWindow(parent);
4873     if (!wnd)
4874 	return false;
4875     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::getParams(%p,%s)",
4876 	name().c_str(),parent,YQT_OBJECT_NAME(parent));
4877     String pName;
4878     QtClient::getUtf8(pName,parent->objectName());
4879     bool ok = true;
4880     unsigned int n = params.length();
4881     for (unsigned int i = 0; i < n; i++) {
4882 	NamedString* ns = params.getParam(i);
4883 	if (!ns)
4884 	    continue;
4885 	String buf;
4886 	int pos = ns->name().find(':');
4887 	if (pos < 0) {
4888 	    ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok;
4889 	    continue;
4890 	}
4891 	String n(ns->name().substr(0,pos));
4892 	String cName = ns->name().substr(pos + 1);
4893 	if (n == s_property) {
4894 	    // Handle property[:child]:property_name
4895 	    int pos = cName.find(':');
4896 	    if (pos >= 0) {
4897 		QString tmp = buildQChildName(pName,cName.substr(0,pos));
4898 		QObject* c = qFindChild<QObject*>(parent,tmp);
4899 		ok = c && QtClient::getProperty(c,cName.substr(pos + 1),*ns) && ok;
4900 	    }
4901 	    else
4902 		ok = QtClient::getProperty(parent,cName,*ns) && ok;
4903 	}
4904 	else if (n == s_getselect)
4905 	    ok = wnd->getSelect(buildChildName(buf,pName,cName),*ns) && ok;
4906 	else if (n == s_getcheck) {
4907 	    bool on = false;
4908 	    ok = wnd->getCheck(buildChildName(buf,pName,cName),on) && ok;
4909 	    *ns = String::boolText(on);
4910 	}
4911 	else if (n == s_getrichtext)
4912 	    ok = wnd->getText(buildChildName(buf,pName,cName),*ns,true) && ok;
4913 	else
4914 	    ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok;
4915     }
4916     // Get item parameters
4917     QtClient::getProperty(parent,"_yate_itemparams",params);
4918     return ok;
4919 }
4920 
4921 // Show or hide control busy state
setBusy(bool on)4922 bool QtUIWidget::setBusy(bool on)
4923 {
4924     QObject* o = getQObject();
4925     QWidget* w = (o && o->isWidgetType()) ? static_cast<QWidget*>(o) : 0;
4926     return w && QtBusyWidget::showBusyChild(w,on);
4927 }
4928 
4929 // Apply properties for QAbstractItemView descendents
applyItemViewProps(const NamedList & params)4930 void QtUIWidget::applyItemViewProps(const NamedList& params)
4931 {
4932     static const String s_selMode = "_yate_selection_mode";
4933     static const String s_editTriggers = "_yate_edit_triggers";
4934 
4935     QObject* obj = getQObject();
4936     QAbstractItemView* av = qobject_cast<QAbstractItemView*>(obj);
4937     if (!av)
4938 	return;
4939     NamedIterator iter(params);
4940     for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
4941 	if (ns->name() == s_selMode)
4942 	    av->setSelectionMode(QtClient::str2selmode(*ns));
4943 	else if (ns->name() == s_editTriggers)
4944 	    av->setEditTriggers(QtClient::str2editTriggers(*ns));
4945     }
4946 }
4947 
4948 // Begin item edit. The default behaviour start edit for QAbstractItemView descendants
beginEdit(const String & item,const String * what)4949 bool QtUIWidget::beginEdit(const String& item, const String* what)
4950 {
4951     QObject* obj = getQObject();
4952     QAbstractItemView* av = qobject_cast<QAbstractItemView*>(obj);
4953     if (!av)
4954 	return false;
4955     QModelIndex idx = modelIndex(item,what);
4956     if (!idx.isValid())
4957 	return false;
4958     av->setCurrentIndex(idx);
4959     av->edit(idx);
4960     return true;
4961 }
4962 
4963 // Build item widget menu
buildWidgetItemMenu(QWidget * w,const NamedList * params,const String & child,bool set)4964 QMenu* QtUIWidget::buildWidgetItemMenu(QWidget* w, const NamedList* params,
4965     const String& child, bool set)
4966 {
4967     if (!(w && params))
4968 	return 0;
4969     QWidget* parent = w;
4970     // Retrieve the item owner
4971     QWidget* pItem = 0;
4972     String item;
4973     getListItemIdProp(w,item);
4974     if (item)
4975 	pItem = findItem(item);
4976     else {
4977 	getListItemProp(w,item);
4978 	pItem = item ? findItem(item) : 0;
4979     }
4980     XDebug(ClientDriver::self(),DebugAll,
4981 	"QtUIWidget(%s)::buildMenu() widget=%s item=%s [%p]",
4982 	this->name().c_str(),YQT_OBJECT_NAME(w),item.c_str(),this);
4983     String pName(YQT_OBJECT_NAME(w));
4984     const String& owner = (*params)[YSTRING("owner")];
4985     if (owner && owner != item) {
4986 	QString tmp = buildQChildName(pName,owner);
4987 	parent = qFindChild<QWidget*>(w,tmp);
4988 	if (!parent) {
4989 	    Debug(QtDriver::self(),DebugNote,
4990 		"QtUIWidget(%s) buildMenu() owner '%s' not found [%p]",
4991 		name().c_str(),owner.c_str(),this);
4992 	    return 0;
4993 	}
4994     }
4995     QWidget* target = parent;
4996     String t = child ? child : (*params)[YSTRING("target")];
4997     if (t) {
4998 	QString tmp = buildQChildName(pName,t);
4999 	target = qFindChild<QWidget*>(w,tmp);
5000 	if (!target) {
5001 	    Debug(QtDriver::self(),DebugNote,
5002 		"QtUIWidget(%s) buildMenu() target '%s' not found [%p]",
5003 		name().c_str(),t.c_str(),this);
5004 	    return 0;
5005 	}
5006     }
5007     QString menuName = buildQChildName(pName,t + "_menu");
5008     // Remove existing menu
5009     QMenu* menu = qFindChild<QMenu*>(parent,menuName);
5010     if (menu) {
5011 	delete menu;
5012 	menu = 0;
5013     }
5014     // Build the menu
5015     QObject* thisObj = getQObject();
5016     if (!thisObj)
5017 	return 0;
5018     String actionSlot;
5019     String toggleSlot;
5020     String selectSlot;
5021     getSlots(actionSlot,toggleSlot,selectSlot);
5022     if (!(actionSlot || toggleSlot))
5023 	return 0;
5024     bool addActions = set && target->contextMenuPolicy() == Qt::ActionsContextMenu;
5025     unsigned int n = params->length();
5026     for (unsigned int i = 0; i < n; i++) {
5027 	NamedString* param = params->getParam(i);
5028 	if (!(param && param->name().startsWith("item:")))
5029 	    continue;
5030 	if (!menu)
5031 	    menu = new QMenu(QtClient::setUtf8(params->getValue(YSTRING("title"))),parent);
5032 	NamedList* p = YOBJECT(NamedList,param);
5033 	if (p)  {
5034 	    QMenu* subMenu = QtClient::buildMenu(*p,
5035 		*param ? param->c_str() : p->getValue(YSTRING("title"),*p),
5036 		thisObj,actionSlot,toggleSlot,menu);
5037 	    if (subMenu) {
5038 		menu->addMenu(subMenu);
5039 		if (addActions)
5040 		    target->addAction(subMenu->menuAction());
5041 	    }
5042 	    continue;
5043 	}
5044 	QAction* a = 0;
5045 	String name = param->name().substr(5);
5046 	if (*param) {
5047 	    a = menu->addAction(QtClient::setUtf8(*param));
5048 	    a->setObjectName(buildQChildName(pName,name));
5049 	    a->setParent(menu);
5050 	    QtClient::setImage(a,(*params)["image:" + name]);
5051 	}
5052 	else if (!name) {
5053 	    a = menu->addSeparator();
5054 	    a->setParent(menu);
5055 	}
5056 	else if (pItem) {
5057 	    // Check if the action is already there
5058 	    QString aName = buildQChildName(pItem->objectName(),QtClient::setUtf8(name));
5059 	    a = qFindChild<QAction*>(pItem,aName);
5060 	    if (a)
5061 		menu->addAction(a);
5062 	}
5063 	if (a) {
5064 	    if (addActions)
5065 		target->addAction(a);
5066 	}
5067 	else
5068 	    Debug(ClientDriver::self(),DebugNote,
5069 		"QtUIWidget(%s)::buildMenu() action '%s' not found for item=%s [%p]",
5070 		this->name().c_str(),name.c_str(),item.c_str(),this);
5071     }
5072     if (!menu)
5073 	return 0;
5074     // Set name
5075     menu->setObjectName(menuName);
5076     // Apply properties
5077     // Format: property:object_name:property_name=value
5078     if (parent)
5079 	for (unsigned int i = 0; i < n; i++) {
5080 	    NamedString* param = params->getParam(i);
5081 	    if (!(param && param->name().startsWith("property:")))
5082 		continue;
5083 	    int pos = param->name().find(':',9);
5084 	    if (pos < 9)
5085 		continue;
5086 	    QString n = buildQChildName(pName,param->name().substr(9,pos - 9));
5087 	    QObject* obj = qFindChild<QObject*>(parent,n);
5088 	    if (obj)
5089 		QtClient::setProperty(obj,param->name().substr(pos + 1),*param);
5090 	}
5091     // Connect signals (direct children only: actions from sub-menus are already connected)
5092     QList<QAction*> list = qFindChildren<QAction*>(menu);
5093     for (int i = 0; i < list.size(); i++) {
5094 	if (list[i]->isSeparator() || list[i]->parent() != menu)
5095 	    continue;
5096 	if (list[i]->isCheckable())
5097 	    QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),thisObj,toggleSlot);
5098 	else
5099 	    QtClient::connectObjects(list[i],SIGNAL(triggered()),thisObj,actionSlot);
5100     }
5101     if (addActions)
5102 	return menu;
5103     QMenu* mOwner = qobject_cast<QMenu*>(target);
5104     if (mOwner)
5105 	mOwner->insertMenu(0,menu);
5106     else {
5107 	QToolButton* tb = qobject_cast<QToolButton*>(target);
5108 	if (tb)
5109 	    tb->setMenu(menu);
5110 	else {
5111 	    QPushButton* pb = qobject_cast<QPushButton*>(target);
5112 	    if (pb)
5113 		pb->setMenu(menu);
5114 	    else if (!QtClient::setProperty(target,s_propContextMenu,params))
5115 		target->addAction(menu->menuAction());
5116 	}
5117     }
5118     return menu;
5119 }
5120 
5121 // Build a container child name from parent property
buildQChildNameProp(QString & dest,QObject * parent,const char * prop)5122 bool QtUIWidget::buildQChildNameProp(QString& dest, QObject* parent, const char* prop)
5123 {
5124     if (!(parent && prop))
5125 	return false;
5126     QVariant var = parent->property(prop);
5127     if (!var.isValid() || var.toString().size() <= 0)
5128 	return false;
5129     dest = buildQChildName(parent->objectName(),var.toString());
5130     return true;
5131 }
5132 
5133 // Retrieve the top level QtUIWidget container parent of an object
container(QObject * obj)5134 QtUIWidget* QtUIWidget::container(QObject* obj)
5135 {
5136     if (!obj)
5137 	return 0;
5138     QtUIWidget* uiw = 0;
5139     while (0 != (obj = obj->parent())) {
5140 	QtWidget w(obj);
5141 	UIWidget* u = w.uiWidget();
5142 	if (u)
5143 	    uiw = static_cast<QtUIWidget*>(u);
5144     }
5145     return uiw;
5146 }
5147 
5148 // Utility used in QtUIWidget::initNavigation
initNavAction(QObject * obj,const String & name,const String & actionSlot)5149 static bool initNavAction(QObject* obj, const String& name, const String& actionSlot)
5150 {
5151     if (!(obj && name))
5152 	return false;
5153     QtWindow* wnd = QtClient::parentWindow(obj);
5154     QObject* child = qFindChild<QObject*>(wnd,QtClient::setUtf8(name));
5155     if (!child)
5156 	return false;
5157     QAbstractButton* b = 0;
5158     QAction* a = 0;
5159     if (child->isWidgetType())
5160 	b = qobject_cast<QAbstractButton*>(child);
5161     else
5162 	a = qobject_cast<QAction*>(child);
5163     if (b || a) {
5164 	if (b)
5165 	    QtClient::connectObjects(b,SIGNAL(clicked()),obj,actionSlot);
5166 	else
5167 	    QtClient::connectObjects(a,SIGNAL(triggered()),obj,actionSlot);
5168     }
5169     return b || a;
5170 }
5171 
5172 // Initialize navigation controls
initNavigation(const NamedList & params)5173 void QtUIWidget::initNavigation(const NamedList& params)
5174 {
5175     String actionSlot;
5176     String toggleSlot;
5177     String selectSlot;
5178     getSlots(actionSlot,toggleSlot,selectSlot);
5179     QObject* qObj = getQObject();
5180     if (qObj && actionSlot) {
5181 	m_prev = params.getValue(YSTRING("navigate_prev"));
5182 	if (!initNavAction(qObj,m_prev,actionSlot))
5183 	    m_prev = "";
5184 	m_next = params.getValue(YSTRING("navigate_next"));
5185 	if (!initNavAction(qObj,m_next,actionSlot))
5186 	    m_next = "";
5187     }
5188     m_info = params.getValue(YSTRING("navigate_info"));
5189     m_infoFormat = params.getValue(YSTRING("navigate_info_format"));
5190     m_title = params.getValue(YSTRING("navigate_title"));
5191     updateNavigation();
5192 }
5193 
5194 // Update navigation controls
updateNavigation()5195 void QtUIWidget::updateNavigation()
5196 {
5197     if (!(m_prev || m_next || m_info || m_title))
5198 	return;
5199     QtWindow* wnd = QtClient::parentWindow(getQObject());
5200     if (!wnd)
5201 	return;
5202     NamedList p("");
5203     int crt = currentItemIndex();
5204     if (crt < 0)
5205 	crt = 0;
5206     else
5207 	crt++;
5208     int n = itemCount();
5209     if (n < crt)
5210 	n = crt;
5211     if (m_prev || m_next) {
5212 	if (m_prev)
5213 	    p.addParam("active:" + m_prev,String::boolText(crt > 1));
5214 	if (m_next)
5215 	    p.addParam("active:" + m_next,String::boolText(crt < n));
5216     }
5217     if (m_info) {
5218 	String tmp = m_infoFormat;
5219 	NamedList pp("");
5220 	pp.addParam("index",String(crt));
5221 	pp.addParam("count",String(n));
5222 	pp.replaceParams(tmp);
5223 	p.addParam(m_info,tmp);
5224     }
5225     if (m_title) {
5226 	String crt;
5227 	getSelect(crt);
5228 	NamedList pp("");
5229 	if (crt)
5230 	    getTableRow(crt,&pp);
5231 	p.addParam(m_title,pp[YSTRING("title")]);
5232     }
5233     wnd->setParams(p);
5234 }
5235 
5236 // Trigger a custom action from an item
triggerAction(const String & item,const String & action,QObject * sender,NamedList * params)5237 bool QtUIWidget::triggerAction(const String& item, const String& action, QObject* sender,
5238     NamedList* params)
5239 {
5240     if (!(Client::self() && action))
5241 	return false;
5242     if (!sender)
5243 	sender = getQObject();
5244     String s;
5245     getIdentity(sender,s);
5246     if (!s)
5247 	return false;
5248     NamedList p("");
5249     if (!params)
5250 	params = &p;
5251     params->addParam("item",item,false);
5252     params->addParam("widget",s);
5253     return QtClient::self()->action(QtClient::parentWindow(sender),action,params);
5254 }
5255 
5256 // Trigger a custom action from already built list params
triggerAction(const String & action,NamedList & params,QObject * sender)5257 bool QtUIWidget::triggerAction(const String& action, NamedList& params, QObject* sender)
5258 {
5259     if (!(Client::self() && action))
5260 	return false;
5261     if (!sender)
5262 	sender = getQObject();
5263     String s;
5264     getIdentity(sender,s);
5265     if (!s)
5266 	return false;
5267     params.setParam("widget",s);
5268     return QtClient::self()->action(QtClient::parentWindow(sender),action,&params);
5269 }
5270 
5271 // Handle a child's action
onAction(QObject * sender)5272 void QtUIWidget::onAction(QObject* sender)
5273 {
5274     if (!Client::self())
5275 	return;
5276     String s;
5277     getIdentity(sender,s);
5278     if (!s)
5279 	return;
5280     int dir = 0;
5281     if (s == m_next)
5282 	dir = 1;
5283     else if (s == m_prev)
5284 	dir = -1;
5285     if (dir) {
5286 	int crt = currentItemIndex();
5287 	if (crt >= 0)
5288 	    setSelectIndex(crt + dir);
5289 	return;
5290     }
5291     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising action %s",
5292 	name().c_str(),s.c_str());
5293     Client::self()->action(QtClient::parentWindow(sender),s);
5294 }
5295 
5296 // Handle a child's toggle notification
onToggle(QObject * sender,bool on)5297 void QtUIWidget::onToggle(QObject* sender, bool on)
5298 {
5299     if (!Client::self())
5300 	return;
5301     QtClient::updateToggleImage(sender);
5302     String s;
5303     getIdentity(sender,s);
5304     if (!s)
5305 	return;
5306     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising toggle %s",
5307 	name().c_str(),s.c_str());
5308     Client::self()->toggle(QtClient::parentWindow(sender),s,on);
5309 }
5310 
5311 // Handle a child's selection change
onSelect(QObject * sender,const String * item)5312 void QtUIWidget::onSelect(QObject* sender, const String* item)
5313 {
5314     if (!Client::self())
5315 	return;
5316     String s;
5317     getIdentity(sender,s);
5318     if (!s)
5319 	return;
5320     QtWindow* wnd = QtClient::parentWindow(sender);
5321     String tmp;
5322     if (!item) {
5323 	item = &tmp;
5324 	if (wnd)
5325 	    wnd->getSelect(YQT_OBJECT_NAME(sender),tmp);
5326     }
5327     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select %s",
5328 	name().c_str(),s.c_str());
5329     Client::self()->select(wnd,s,*item);
5330 }
5331 
5332 // Handle a child's multiple selection change
onSelectMultiple(QObject * sender,const NamedList * items)5333 void QtUIWidget::onSelectMultiple(QObject* sender, const NamedList* items)
5334 {
5335     if (!Client::self())
5336 	return;
5337     String s;
5338     getIdentity(sender,s);
5339     if (!s)
5340 	return;
5341     QtWindow* wnd = QtClient::parentWindow(sender);
5342     DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select multiple",
5343 	name().c_str());
5344     if (items) {
5345 	Client::self()->select(wnd,s,*items);
5346 	return;
5347     }
5348     NamedList tmp("");
5349     if (wnd)
5350 	wnd->getSelect(YQT_OBJECT_NAME(sender),tmp);
5351     Client::self()->select(wnd,s,tmp);
5352 }
5353 
5354 // Filter wathed events for children.
5355 // Handle child image changing on mouse events
onChildEvent(QObject * watched,QEvent * event)5356 bool QtUIWidget::onChildEvent(QObject* watched, QEvent* event)
5357 {
5358     if (event->type() == QEvent::Enter)
5359 	QtClient::updateImageFromMouse(watched,true,true);
5360     else if (event->type() == QEvent::Leave)
5361 	QtClient::updateImageFromMouse(watched,true,false);
5362     else if (event->type() == QEvent::MouseButtonPress)
5363 	QtClient::updateImageFromMouse(watched,false,true);
5364     else if (event->type() == QEvent::MouseButtonRelease)
5365 	QtClient::updateImageFromMouse(watched,false,false);
5366     return false;
5367 }
5368 
5369 // Load an item's widget. Rename children. Connect actions
loadWidget(QWidget * parent,const String & name,const String & ui)5370 QWidget* QtUIWidget::loadWidget(QWidget* parent, const String& name, const String& ui)
5371 {
5372     // Build a new widget name to make sure there are no duplicates:
5373     //   Some containers (like QTreeWidget) calls deleteLater() for widget's
5374     //   set to items which might lead to wrong widget update
5375     // Make sure the widget name contains only 'standard' characters
5376     //   to avoid errors when replaced in style sheets
5377     MD5 md5(name);
5378     String wName;
5379     buildChildName(wName,md5.hexDigest());
5380     wName << "_" << (unsigned int)Time::now();
5381     QWidget* w = QtWindow::loadUI(Client::s_skinPath + ui,parent,ui);
5382     DDebug(ClientDriver::self(),w ? DebugAll : DebugNote,
5383 	"QtUIWidget(%s)::loadWidget(%p,%s,%s) widget=%p",
5384 	this->name().c_str(),parent,wName.c_str(),ui.c_str(),w);
5385     if (!w)
5386 	return 0;
5387     QObject* qObj = getQObject();
5388     QtWindow* wnd = getWindow();
5389     // Install event filter in parent window
5390     if (!m_wndEvHooked && wnd && qObj) {
5391 	QVariant var = w->property("_yate_keypress_redirect");
5392 	if (var.isValid()) {
5393 	    m_wndEvHooked = true;
5394 	    wnd->installEventFilter(qObj);
5395 	}
5396     }
5397     String actionSlot;
5398     String toggleSlot;
5399     String selectSlot;
5400     getSlots(actionSlot,toggleSlot,selectSlot);
5401     QString wListItem = QtClient::setUtf8(name);
5402     w->setObjectName(QtClient::setUtf8(wName));
5403     setListItemIdProp(w,wListItem);
5404     // Build custom UI widgets
5405     QtClient::buildFrameUiWidgets(w);
5406     // Process "_yate_setaction" property before changing names
5407     QtClient::setAction(w);
5408     // Process children
5409     QList<QObject*> c = qFindChildren<QObject*>(w);
5410     for (int i = 0; i < c.size(); i++) {
5411 	// Set object item owner name
5412 	setListItemProp(c[i],wListItem);
5413 	// Rename child
5414 	String n;
5415 	QtClient::getUtf8(n,c[i]->objectName());
5416 	c[i]->setObjectName(buildQChildName(wName,n));
5417 	// Install event filters
5418 	if (qObj && QtClient::getBoolProperty(c[i],"_yate_filterevents"))
5419 	    c[i]->installEventFilter(qObj);
5420 	// Connect text changed to window's slot
5421 	bool connect = QtClient::autoConnect(c[i]);
5422 	if (wnd && connect)
5423 	    wnd->connectTextChanged(c[i]);
5424 	// Connect signals
5425 	if (!(qObj && connect && (actionSlot || toggleSlot || selectSlot)))
5426 	    continue;
5427 	// Use isWidgetType() (faster then qobject_cast)
5428 	if (c[i]->isWidgetType()) {
5429 	    // Connect abstract buttons (check boxes and radio/push/tool buttons) signals
5430 	    QAbstractButton* b = qobject_cast<QAbstractButton*>(c[i]);
5431 	    if (b) {
5432 		if (!b->isCheckable())
5433 		    QtClient::connectObjects(b,SIGNAL(clicked()),qObj,actionSlot);
5434 		else
5435 		    QtClient::connectObjects(b,SIGNAL(toggled(bool)),qObj,toggleSlot);
5436 		continue;
5437 	    }
5438 	    // Connect group boxes
5439 	    QGroupBox* gb = qobject_cast<QGroupBox*>(c[i]);
5440 	    if (gb) {
5441 		if (gb->isCheckable())
5442 		    QtClient::connectObjects(gb,SIGNAL(toggled(bool)),qObj,toggleSlot);
5443 		continue;
5444 	    }
5445 	    // Connect combo boxes
5446 	    QComboBox* combo = qobject_cast<QComboBox*>(c[i]);
5447 	    if (combo) {
5448 		QtClient::connectObjects(combo,SIGNAL(activated(int)),qObj,selectSlot);
5449 		continue;
5450 	    }
5451 	    // Connect list boxes
5452 	    QListWidget* lst = qobject_cast<QListWidget*>(c[i]);
5453 	    if (lst) {
5454 		QtClient::connectObjects(lst,SIGNAL(currentRowChanged(int)),qObj,selectSlot);
5455 		continue;
5456 	    }
5457 	    // Connect sliders
5458 	    QSlider* sld = qobject_cast<QSlider*>(c[i]);
5459 	    if (sld) {
5460 		QtClient::connectObjects(sld,SIGNAL(valueChanged(int)),qObj,selectSlot);
5461 		continue;
5462 	    }
5463 	    continue;
5464 	}
5465 	// Connect actions signals
5466 	QAction* a = qobject_cast<QAction*>(c[i]);
5467 	if (a) {
5468 	    if (!a->isCheckable())
5469 		QtClient::connectObjects(a,SIGNAL(triggered()),qObj,actionSlot);
5470 	    else
5471 		QtClient::connectObjects(a,SIGNAL(toggled(bool)),qObj,toggleSlot);
5472 	    continue;
5473 	}
5474     }
5475     return w;
5476 }
5477 
5478 // Apply a QWidget style sheet. Replace ${name} with widget name in style
applyWidgetStyle(QWidget * w,const String & style)5479 void QtUIWidget::applyWidgetStyle(QWidget* w, const String& style)
5480 {
5481     if (!(w && style))
5482 	return;
5483     QString s = QtClient::setUtf8(style);
5484     s.replace("${name}",w->objectName());
5485     w->setStyleSheet(s);
5486 }
5487 
5488 // Filter key press events. Retrieve an action associated with the key.
5489 // Check if the object is allowed to process the key.
5490 // Raise the action
filterKeyEvent(QObject * watched,QKeyEvent * event,bool & filter)5491 bool QtUIWidget::filterKeyEvent(QObject* watched, QKeyEvent* event, bool& filter)
5492 {
5493     String action;
5494     if (!QtClient::filterKeyEvent(watched,event,action,filter))
5495 	return false;
5496     if (!action)
5497 	return true;
5498     String item;
5499     getListItemProp(watched,item);
5500     // Avoid raising a disabled actions
5501     if (item) {
5502 	bool ok = true;
5503 	QWidget* w = findItem(item);
5504 	if (w) {
5505 	    QString n = buildQChildName(w->objectName(),QtClient::setUtf8(action));
5506 	    QObject* act = qFindChild<QObject*>(w,n);
5507 	    if (act) {
5508 		if (act->isWidgetType())
5509 		    ok = (qobject_cast<QWidget*>(act))->isEnabled();
5510 		else {
5511 		    QAction* a = qobject_cast<QAction*>(act);
5512 		    ok = !a || a->isEnabled();
5513 		}
5514 	    }
5515 	}
5516 	if (!ok)
5517 	    return true;
5518 	// Append container item to action
5519 	action.append(item,":");
5520     }
5521     Client::self()->action(QtClient::parentWindow(getQObject()),action);
5522     return true;
5523 }
5524 
5525 
5526 /**
5527  * QtSound
5528  */
doStart()5529 bool QtSound::doStart()
5530 {
5531     doStop();
5532     if (Client::self())
5533 	Client::self()->createObject((void**)&m_sound,"QSound",m_file);
5534     if (m_sound)
5535 	DDebug(ClientDriver::self(),DebugAll,"Sound(%s) started file=%s",
5536 	    c_str(),m_file.c_str());
5537     else {
5538 	Debug(ClientDriver::self(),DebugNote,"Sound(%s) failed to start file=%s",
5539 	    c_str(),m_file.c_str());
5540 	return false;
5541     }
5542     m_sound->setLoops(m_repeat ? m_repeat : -1);
5543     m_sound->play();
5544     return true;
5545 }
5546 
doStop()5547 void QtSound::doStop()
5548 {
5549     if (!m_sound)
5550 	return;
5551     m_sound->stop();
5552     delete m_sound;
5553     DDebug(ClientDriver::self(),DebugAll,"Sound(%s) stopped",c_str());
5554     m_sound = 0;
5555 }
5556 
5557 
5558 //
5559 // QtDragAndDrop
5560 //
5561 // Reset data
reset()5562 void QtDragAndDrop::reset()
5563 {
5564     m_started = false;
5565 }
5566 
5567 // Check a string value for 'drag', 'drop', 'both'
checkEnable(const String & s,bool & drag,bool & drop)5568 void QtDragAndDrop::checkEnable(const String& s, bool& drag, bool& drop)
5569 {
5570     drag = (s == YSTRING("drag"));
5571     drop = !drag && (s == YSTRING("drop"));
5572     if (!(drag || drop))
5573 	drag = drop = (s == YSTRING("both"));
5574 }
5575 
5576 //
5577 // QtDrop
5578 //
5579 const String QtDrop::s_askClientAcceptDrop = "_yate_event_drop_accept";
5580 const String QtDrop::s_notifyClientDrop = "_yate_event_drop";
5581 const QString QtDrop::s_fileScheme = "file";
5582 
5583 const TokenDict QtDrop::s_acceptDropName[] = {
5584     {"always", Always},
5585     {"ask", Ask},
5586     {"none", 0},
5587     {0,0}
5588 };
5589 
QtDrop(QObject * parent,const NamedList * params)5590 QtDrop::QtDrop(QObject* parent, const NamedList* params)
5591     : QtDragAndDrop(parent),
5592     m_dropParams(""),
5593     m_acceptFiles(false),
5594     m_acceptDirs(false)
5595 {
5596     if (!params)
5597 	return;
5598     NamedIterator iter(*params);
5599     for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
5600 	if (ns->name() == YSTRING("_yate_accept_drop_schemes"))
5601 	    QtClient::addStrListUnique(m_schemes,QtClient::str2list(*ns));
5602 	else if (ns->name() == YSTRING("_yate_accept_drop_file"))
5603 	    m_acceptFiles = ns->toBoolean();
5604 	else if (ns->name() == YSTRING("_yate_accept_drop_dir"))
5605 	    m_acceptDirs = ns->toBoolean();
5606     }
5607 }
5608 
5609 // Update parameters from drag enter event
start(QDragEnterEvent & e)5610 bool QtDrop::start(QDragEnterEvent& e)
5611 {
5612     static const String s_prefix = "drop:";
5613 
5614     reset();
5615     const QMimeData* m = e.mimeData();
5616     if (!(m && m->hasUrls()))
5617 	return false;
5618     int nUrls = m->urls().size();
5619     unsigned int nItems = 0;
5620     for (int i = 0; i < nUrls; i++) {
5621 	QString scheme = m->urls()[i].scheme();
5622 	if (m_schemes.size() > 0 && !m_schemes.contains(scheme)) {
5623 	    reset();
5624 	    return false;
5625 	}
5626 	QString path = m->urls()[i].path();
5627 	String what = scheme.toUtf8().constData();
5628 	if (scheme == s_fileScheme) {
5629 #ifdef _WINDOWS
5630 	    path = path.mid(1);
5631 #endif
5632 	    path = QDir::toNativeSeparators(path);
5633 	    QFileInfo fi(path);
5634 	    if (fi.isDir()) {
5635 		if (!m_acceptDirs) {
5636 		    reset();
5637 		    return false;
5638 		}
5639 		what = "directory";
5640 	    }
5641 	    else if (fi.isFile() && !m_acceptFiles) {
5642 		reset();
5643 		return false;
5644 	    }
5645 	}
5646 	nItems++;
5647 	NamedList* nl = new NamedList("");
5648 	QtClient::fillUrlParams(m->urls()[i],*nl,&path);
5649 	m_dropParams.addParam(new NamedPointer(s_prefix + what,nl,*nl));
5650     }
5651     if (!nItems) {
5652 	reset();
5653 	return false;
5654     }
5655     if (e.source()) {
5656 	QtWindow* wnd = QtClient::parentWindow(e.source());
5657 	if (wnd) {
5658 	    m_dropParams.addParam("source_window",wnd->toString());
5659 	    QtClient::getUtf8(m_dropParams,"source",e.source()->objectName());
5660 	}
5661     }
5662     m_started = true;
5663     return true;
5664 }
5665 
5666 // Reset data
reset()5667 void QtDrop::reset()
5668 {
5669     m_dropParams.clearParams();
5670     QtDragAndDrop::reset();
5671 }
5672 
5673 
5674 //
5675 // QtListDrop
5676 //
QtListDrop(QObject * parent,const NamedList * params)5677 QtListDrop::QtListDrop(QObject* parent, const NamedList* params)
5678     : QtDrop(parent,params),
5679     m_acceptOnEmpty(None)
5680 {
5681 }
5682 
5683 // Update accept
updateAcceptType(const String list,int type)5684 void QtListDrop::updateAcceptType(const String list, int type)
5685 {
5686     if (!list)
5687 	return;
5688     ObjList* l = list.split(',',false);
5689     for (ObjList* o = l->skipNull(); o; o = o->skipNext()) {
5690 	NamedInt* ni = new NamedInt(*static_cast<String*>(o->get()),type);
5691 	NamedInt::addToListUniqueName(m_acceptItemTypes,ni);
5692     }
5693     TelEngine::destruct(l);
5694 }
5695 
5696 // Update accept from parameters list
updateAccept(const NamedList & params)5697 void QtListDrop::updateAccept(const NamedList& params)
5698 {
5699     NamedIterator iter(params);
5700     for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
5701 	if (ns->name() == YSTRING("_yate_accept_drop_onempty"))
5702 	    m_acceptOnEmpty = this->acceptDropType(*ns,None);
5703 	else if (ns->name() == YSTRING("_yate_accept_drop_item_type_always"))
5704 	    updateAcceptType(*ns,Always);
5705 	else if (ns->name() == YSTRING("_yate_accept_drop_item_type_none"))
5706 	    updateAcceptType(*ns,None);
5707 	else if (ns->name() == YSTRING("_yate_accept_drop_item_type_ask"))
5708 	    updateAcceptType(*ns,Ask);
5709     }
5710 }
5711 
5712 // Reset data
reset()5713 void QtListDrop::reset()
5714 {
5715     m_acceptItemTypes.clear();
5716     QtDrop::reset();
5717 }
5718 
5719 
5720 //
5721 // QtBusyWidget
5722 //
5723 const QString QtBusyWidget::s_busySuffix("_yate_busy_widget_generated");
5724 
5725 // Constructor
QtBusyWidget(QWidget * parent)5726 QtBusyWidget::QtBusyWidget(QWidget* parent)
5727     : QtCustomWidget(0,parent),
5728     m_target(0), m_shown(false), m_delayMs(0), m_delayTimer(0),
5729     m_movieLabel(0)
5730 {
5731     if (parent)
5732 	setObjectName(parent->objectName() + s_busySuffix);
5733     QWidget::hide();
5734 }
5735 
5736 // Initialize
init(const String & ui,const NamedList & params,QWidget * target)5737 void QtBusyWidget::init(const String& ui, const NamedList& params, QWidget* target)
5738 {
5739     hideBusy();
5740     m_target = target;
5741     m_movieLabel = 0;
5742     unsigned int delay = 0;
5743     QWidget* w = ui ? loadWidget(this,"",ui) : 0;
5744     if (w) {
5745 	QtClient::setWidget(this,w);
5746 	int tmp = QtClient::getIntProperty(w,"_yate_busywidget_delay");
5747 	if (tmp > 0)
5748 	    delay = tmp;
5749 	QList<QWidget*> c = qFindChildren<QWidget*>(w);
5750 	for (int i = 0; i < c.size(); i++) {
5751 	    QLabel* l = qobject_cast<QLabel*>(c[i]);
5752 	    if (l) {
5753 		if (!m_movieLabel) {
5754 		    String file;
5755 		    QtClient::getProperty(l,"_yate_movie_file",file);
5756 		    if (file) {
5757 			l->setMovie(QtClient::loadMovie(file,l));
5758 			if (l->movie())
5759 			    m_movieLabel = l;
5760 		    }
5761 		}
5762 	    }
5763 	}
5764     }
5765     m_delayMs = params.getIntValue(YSTRING("_yate_busywidget_delay"),delay,0);
5766 }
5767 
5768 // Show the widget
showBusy()5769 void QtBusyWidget::showBusy()
5770 {
5771     if (m_shown)
5772 	return;
5773     m_shown = true;
5774     if (m_delayMs)
5775 	m_delayTimer = startTimer(m_delayMs);
5776     if (!m_delayTimer)
5777 	internalShow();
5778 }
5779 
5780 // Hide the widget
hideBusy()5781 void QtBusyWidget::hideBusy()
5782 {
5783     if (!m_shown)
5784 	return;
5785     m_shown = false;
5786     stopDelayTimer();
5787     if (m_target)
5788 	m_target->removeEventFilter(this);
5789     setContent(false);
5790     lower();
5791     hide();
5792 }
5793 
5794 // Filter wathed events
onChildEvent(QObject * watched,QEvent * event)5795 bool QtBusyWidget::onChildEvent(QObject* watched, QEvent* event)
5796 {
5797     if (m_target && m_target == watched) {
5798 	if (event->type() == QEvent::Resize)
5799 	    resize(m_target->size());
5800     }
5801     return false;
5802 }
5803 
timerEvent(QTimerEvent * ev)5804 void QtBusyWidget::timerEvent(QTimerEvent* ev)
5805 {
5806     if (m_delayTimer && ev->timerId() == m_delayTimer) {
5807 	stopDelayTimer();
5808 	internalShow();
5809 	return;
5810     }
5811     QtCustomWidget::timerEvent(ev);
5812 }
5813 
5814 // Show/hide busy content
setContent(bool on)5815 void QtBusyWidget::setContent(bool on)
5816 {
5817     QMovie* movie = m_movieLabel ? m_movieLabel->movie() : 0;
5818     if (!movie)
5819 	return;
5820     if (on)
5821 	movie->start();
5822     else
5823 	movie->stop();
5824 }
5825 
internalShow()5826 void QtBusyWidget::internalShow()
5827 {
5828     if (m_target) {
5829 	resize(m_target->size());
5830 	m_target->installEventFilter(this);
5831     }
5832     setContent(true);
5833     raise();
5834     show();
5835 }
5836 
5837 /* vi: set ts=8 sw=4 sts=4 noet: */
5838