/** * qt4client.cpp * This file is part of the YATE Project http://YATE.null.ro * * A Qt-4 based universal telephony client * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include "qt4client.h" #include #ifdef _WINDOWS #define DEFAULT_DEVICE "dsound/*" #define PLATFORM_LOWERCASE_NAME "windows" #elif defined(__APPLE__) #define DEFAULT_DEVICE "coreaudio/*" #define PLATFORM_LOWERCASE_NAME "apple" #elif defined(__linux__) #define DEFAULT_DEVICE "alsa/default" #define PLATFORM_LOWERCASE_NAME "linux" #else #define DEFAULT_DEVICE "oss//dev/dsp" #define PLATFORM_LOWERCASE_NAME "unknown" #endif namespace TelEngine { static unsigned int s_allHiddenQuit = 0; // Quit on all hidden notification if this counter is 0 // Factory used to create objects in client's thread class Qt4ClientFactory : public UIFactory { public: Qt4ClientFactory(const char* name = "Qt4ClientFactory"); virtual void* create(const String& type, const char* name, NamedList* params = 0); }; // Class used for temporary operations on QT widgets // Keeps a pointer to a widget and its type // NOTE: The methods of this class don't check the widget pointer class QtWidget { public: enum Type { PushButton = 0, CheckBox = 1, Table = 2, ListBox = 3, ComboBox = 4, Tab = 5, StackWidget = 6, TextEdit = 7, Label = 8, LineEdit = 9, AbstractButton = 10, Slider = 11, ProgressBar = 12, SpinBox = 13, Calendar = 14, Splitter = 15, TextBrowser = 16, Unknown, // Unknown type Action, // QAction descendant CustomTable, // QtTable descendant CustomTree, // QtTree descendant CustomWidget, // QtCustomWidget descendant CustomObject, // QtCustomObject descendant Missing // Invalid pointer }; // Set widget from object inline QtWidget(QObject* w) : m_widget(0), m_action(0), m_object(0), m_type(Missing) { if (!w) return; if (w->inherits("QWidget")) m_widget = static_cast(w); else if (w->inherits("QAction")) m_action = static_cast(w); m_type = getType(); } // Set widget from object and type inline QtWidget(QWidget* w, int t) : m_widget(w), m_action(0), m_object(0), m_type(t) { if (!m_widget) m_type = Missing; } // Set widget/action from object and name inline QtWidget(QObject* parent, const String& name) : m_widget(0), m_action(0), m_object(0), m_type(Missing) { QString what = QtClient::setUtf8(name); m_widget = qFindChild(parent,what); if (!m_widget) { m_action = qFindChild(parent,what); if (!m_action) m_object = qFindChild(parent,what); } m_type = getType(); } inline bool valid() const { return type() != Missing; } inline bool invalid() const { return type() == Missing; } inline int type() const { return m_type; } inline operator QWidget*() { return m_widget; } inline bool inherits(const char* classname) { return m_widget && m_widget->inherits(classname); } inline bool inherits(Type t) { return inherits(s_types[t]); } inline QWidget* widget() { return m_widget; } inline QWidget* operator ->() { return m_widget; } // Static cast methods inline QPushButton* button() { return static_cast(m_widget); } inline QCheckBox* check() { return static_cast(m_widget); } inline QTableWidget* table() { return static_cast(m_widget); } inline QListWidget* list() { return static_cast(m_widget); } inline QComboBox* combo() { return static_cast(m_widget); } inline QTabWidget* tab() { return static_cast(m_widget); } inline QStackedWidget* stackWidget() { return static_cast(m_widget); } inline QTextEdit* textEdit() { return static_cast(m_widget); } inline QLabel* label() { return static_cast(m_widget); } inline QLineEdit* lineEdit() { return static_cast(m_widget); } inline QAbstractButton* abstractButton() { return static_cast(m_widget); } inline QSlider* slider() { return static_cast(m_widget); } inline QProgressBar* progressBar() { return static_cast(m_widget); } inline QSpinBox* spinBox() { return static_cast(m_widget); } inline QCalendarWidget* calendar() { return static_cast(m_widget); } inline QSplitter* splitter() { return static_cast(m_widget); } inline QtTable* customTable() { return qobject_cast(m_widget); } inline QtTree* customTree() { return qobject_cast(m_widget); } inline QtCustomWidget* customWidget() { return qobject_cast(m_widget); } inline QtCustomObject* customObject() { return qobject_cast(m_object); } inline UIWidget* uiWidget() { switch (type()) { case CustomTable: return static_cast(customTable()); case CustomWidget: return static_cast(customWidget()); case CustomObject: return static_cast(customObject()); case CustomTree: return static_cast(customTree()); } return 0; } inline QAction* action() { return m_action; } // Find a combo box item inline int findComboItem(const String& item) { QComboBox* c = combo(); return c ? c->findText(QtClient::setUtf8(item)) : -1; } // Add an item to a combo box inline bool addComboItem(const String& item, bool atStart) { QComboBox* c = combo(); if (!c) return false; QString it(QtClient::setUtf8(item)); if (atStart) c->insertItem(0,it); else c->addItem(it); return true; } // Find a list box item inline int findListItem(const String& item) { QListWidget* l = list(); if (!l) return -1; QString it(QtClient::setUtf8(item)); for (int i = l->count(); i >= 0 ; i--) { QListWidgetItem* tmp = l->item(i); if (tmp && it == tmp->text()) return i; } return -1; } // Add an item to a list box inline bool addListItem(const String& item, bool atStart) { QListWidget* l = list(); if (!l) return false; QString it(QtClient::setUtf8(item)); if (atStart) l->insertItem(0,it); else l->addItem(it); return true; } int getType() { if (m_widget) { String cls = m_widget->metaObject()->className(); for (int i = 0; i < Unknown; i++) if (s_types[i] == cls) return i; if (customTable()) return CustomTable; if (customWidget()) return CustomWidget; if (customTree()) return CustomTree; return Unknown; } if (m_action && m_action->inherits("QAction")) return Action; if (customObject()) return CustomObject; return Missing; } static String s_types[Unknown]; protected: QWidget* m_widget; QAction* m_action; QObject* m_object; int m_type; private: QtWidget() {} }; // Class used for temporary operations on QTableWidget objects // NOTE: The methods of this class don't check the table pointer class TableWidget : public GenObject { public: TableWidget(QTableWidget* table, bool tmp = true); TableWidget(QWidget* wid, const String& name, bool tmp = true); TableWidget(QtWidget& table, bool tmp = true); ~TableWidget(); inline QTableWidget* table() { return m_table; } inline bool valid() { return m_table != 0; } inline QtTable* customTable() { return (valid()) ? qobject_cast(m_table) : 0; } inline const String& name() { return m_name; } inline int rowCount() { return m_table->rowCount(); } inline int columnCount() { return m_table->columnCount(); } inline void setHeaderText(int col, const char* text) { if (col < columnCount()) m_table->setHorizontalHeaderItem(col, new QTableWidgetItem(QtClient::setUtf8(text))); } inline bool getHeaderText(int col, String& dest, bool lower = true) { QTableWidgetItem* item = m_table->horizontalHeaderItem(col); if (item) { QtClient::getUtf8(dest,item->text()); if (lower) dest.toLower(); } return item != 0; } // Get the current selection's row inline int crtRow() { QList items = m_table->selectedItems(); if (items.size()) return items[0]->row(); return -1; } inline void repaint() { m_table->repaint(); } inline void addRow(int index) { m_table->insertRow(index); } inline void delRow(int index) { if (index >= 0) m_table->removeRow(index); } inline void addColumn(int index, int width = -1, const char* name = 0) { m_table->insertColumn(index); if (width >= 0) m_table->setColumnWidth(index,width); setHeaderText(index,name); } inline void setImage(int row, int col, const String& image) { QTableWidgetItem* item = m_table->item(row,col); if (item) item->setIcon(QIcon(QtClient::setUtf8(image))); } inline void addCell(int row, int col, const String& value) { QTableWidgetItem* item = new QTableWidgetItem(QtClient::setUtf8(value)); m_table->setItem(row,col,item); } inline void setCell(int row, int col, const String& value, bool addNew = true) { QTableWidgetItem* item = m_table->item(row,col); if (item) item->setText(QtClient::setUtf8(value)); else if (addNew) addCell(row,col,value); } inline bool getCell(int row, int col, String& dest, bool lower = false) { QTableWidgetItem* item = m_table->item(row,col); if (item) { QtClient::getUtf8(dest,item->text()); if (lower) dest.toLower(); return true; } return false; } inline void setID(int row, const String& value) { setCell(row,0,value); } // Add or set a row void updateRow(const String& item, const NamedList* data, bool atStart); // Update a row from a list of parameters void updateRow(int row, const NamedList& data); // Find a row by the first's column value. Return -1 if not found int getRow(const String& item); // Find a column by its label. Return -1 if not found int getColumn(const String& name, bool caseInsentive = true); protected: void init(bool tmp); private: QTableWidget* m_table; // The table String m_name; // Table's name int m_sortControl; // Flag used to set/reset sorting attribute of the table }; // Store an UI loaded from file to avoid loading it again class UIBuffer : public String { public: inline UIBuffer(const String& name, QByteArray* buf) : String(name), m_buffer(buf) { s_uiCache.append(this); } inline QByteArray* buffer() { return m_buffer; } // Remove from list. Release memory virtual void destruct(); // Return an already loaded UI. Load from file if not found. // Add URLs paths when missing static UIBuffer* build(const String& name); // Find a buffer static UIBuffer* find(const String& name); // Buffer cache static ObjList s_uiCache; private: QByteArray* m_buffer; // The buffer }; }; // namespace TelEngine using namespace TelEngine; // Dynamic properies static const String s_propsSave = "_yate_save_props"; // Save properties property name static const String s_propColWidths = "_yate_col_widths"; // Column widths static const String s_propSorting = "_yate_sorting"; // Table/List sorting static const String s_propSizes = "_yate_sizes"; // Size int array static const String s_propShowWndWhenActive = "_yate_showwnd_onactive"; // Show another window when a window become active static String s_propHHeader = "dynamicHHeader"; // Tables: show/hide the horizontal header static String s_propAction = "dynamicAction"; // Prefix for properties that would trigger some action static String s_propWindowFlags = "_yate_windowflags"; // Window flags static const String s_propContextMenu = "_yate_context_menu"; // Context menu name static String s_propHideInactive = "dynamicHideOnInactive"; // Hide inactive window static const String s_yatePropPrefix = "_yate_"; // Yate dynamic properties prefix static NamedList s_qtStyles(""); // Qt styles classname -> internal name // static Qt4ClientFactory s_qt4Factory; static Configuration s_cfg; static Configuration s_save; ObjList UIBuffer::s_uiCache; // Values used to configure window title bar and border static TokenDict s_windowFlags[] = { // Window type {"popup", Qt::Popup}, {"tool", Qt::Tool}, {"subwindow", Qt::SubWindow}, #ifdef _WINDOWS {"notificationtype", Qt::Tool}, #else {"notificationtype", Qt::SubWindow}, #endif // Window flags {"title", Qt::WindowTitleHint}, {"sysmenu", Qt::WindowSystemMenuHint}, {"maximize", Qt::WindowMaximizeButtonHint}, {"minimize", Qt::WindowMinimizeButtonHint}, {"help", Qt::WindowContextHelpButtonHint}, {"stayontop", Qt::WindowStaysOnTopHint}, {"frameless", Qt::FramelessWindowHint}, #if QT_VERSION >= 0x040500 {"close", Qt::WindowCloseButtonHint}, #endif {0,0} }; // Widget attribute names static const TokenDict s_widgetAttributes[] = { {"macshowfocusrect", Qt::WA_MacShowFocusRect}, {0,0} }; String QtWidget::s_types[QtWidget::Unknown] = { "QPushButton", "QCheckBox", "QTableWidget", "QListWidget", "QComboBox", "QTabWidget", "QStackedWidget", "QTextEdit", "QLabel", "QLineEdit", "QAbstractButton", "QSlider", "QProgressBar", "QSpinBox", "QCalendarWidget", "QSplitter", "QTextBrowser", }; // QVariant type translation dictionary static const TokenDict s_qVarType[] = { {"string", QVariant::String}, {"bool", QVariant::Bool}, {"int", QVariant::Int}, {"uint", QVariant::UInt}, {"stringlist", QVariant::StringList}, {"icon", QVariant::Icon}, {"pixmap", QVariant::Pixmap}, {"double", QVariant::Double}, {"keysequence", QVariant::KeySequence}, {0,0} }; // Qt alignment flags translation static const TokenDict s_qAlign[] = { {"left", Qt::AlignLeft}, {"right", Qt::AlignRight}, {"hcenter", Qt::AlignHCenter}, {"justify", Qt::AlignJustify}, {"top", Qt::AlignTop}, {"bottom", Qt::AlignBottom}, {"vcenter", Qt::AlignVCenter}, {"center", Qt::AlignCenter}, {"absolute", Qt::AlignAbsolute}, {0,0} }; // Qt alignment flags translation static const TokenDict s_qEditTriggers[] = { {"currentchanged", QAbstractItemView::CurrentChanged}, {"doubleclick", QAbstractItemView::DoubleClicked}, {"selclick", QAbstractItemView::SelectedClicked}, {"editkeypress", QAbstractItemView::EditKeyPressed}, {"anykeypress", QAbstractItemView::AnyKeyPressed}, {"all", QAbstractItemView::AllEditTriggers}, {0,0} }; // QtClientSort name static const TokenDict s_sorting[] = { {"ascending", QtClient::SortAsc}, {"descending", QtClient::SortDesc}, {"none", QtClient::SortNone}, {0,0} }; // Handler for QT library messages static void qtMsgHandler(QtMsgType type, const char* text) { int dbg = DebugAll; switch (type) { case QtDebugMsg: dbg = DebugInfo; break; case QtWarningMsg: dbg = DebugWarn; break; case QtCriticalMsg: dbg = DebugGoOn; break; case QtFatalMsg: dbg = DebugFail; break; } Debug("QT",dbg,"%s",text); } // Build a list of parameters from a string // Return the number of parameters found static unsigned int str2Params(NamedList& params, const String& buf, char sep = '|') { ObjList* list = 0; // Check if we have another separator if (buf.startsWith("separator=")) { sep = buf.at(10); list = buf.substr(11).split(sep,false); } else list = buf.split(sep,false); unsigned int n = 0; for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { String* s = static_cast(o->get()); int pos = s->find('='); if (pos < 1) continue; params.addParam(s->substr(0,pos),s->substr(pos + 1)); n++; } TelEngine::destruct(list); return n; } // Utility: fix QT path separator on Windows // (display paths using only one separator to the user) static inline QString fixPathSep(QString str) { #ifdef _WINDOWS QString tmp = str; tmp.replace(QChar('/'),QtClient::setUtf8(Engine::pathSeparator())); return tmp; #else return str; #endif } // Utility: find a stacked widget's page with the given name static int findStackedWidget(QStackedWidget& w, const String& name) { QString n(QtClient::setUtf8(name)); for (int i = 0; i < w.count(); i++) { QWidget* page = w.widget(i); if (page && n == page->objectName()) return i; } return -1; } // Utility function used to get the name of a control // The name of the control indicates actions, toggles ... // The action name alias can contain parameters static bool translateName(QtWidget& w, String& name, NamedList** params = 0) { if (w.invalid()) return false; if (w.type() != QtWidget::Action) QtClient::getIdentity(w.widget(),name); else QtClient::getIdentity(w.action(),name); if (!name) return true; // Check params int pos = name.find('|'); if (pos < 1) return true; if (params) { *params = new NamedList(""); if (!str2Params(**params,name.substr(pos + 1))) TelEngine::destruct(*params); } name = name.substr(0,pos); return true; } // Utility: raise a select event if a list is empty static inline void raiseSelectIfEmpty(int count, Window* wnd, const String& name) { if (!Client::exiting() && count <= 0 && Client::self()) Client::self()->select(wnd,name,String::empty()); } // Add dynamic properties from a list of parameters // Parameter format: // property_name:property_type=property_value static void addDynamicProps(QObject* obj, NamedList& props) { static String typeString = "string"; static String typeBool = "bool"; static String typeInt = "int"; if (!obj) return; unsigned int n = props.length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = props.getParam(i); if (!(ns && ns->name())) continue; int pos = ns->name().find(':'); if (pos < 1) continue; String prop = ns->name().substr(0,pos); String type = ns->name().substr(pos + 1); QVariant var; if (type == typeString) var.setValue(QString(ns->c_str())); else if (type == typeBool) var.setValue(ns->toBoolean()); else if (type == typeInt) var.setValue(ns->toInteger()); if (var.type() != QVariant::Invalid) { obj->setProperty(prop,var); DDebug(ClientDriver::self(),DebugAll, "Object '%s': added dynamic property %s='%s' type=%s", YQT_OBJECT_NAME(obj),prop.c_str(),ns->c_str(),var.typeName()); } else Debug(ClientDriver::self(),DebugStub, "Object '%s': dynamic property '%s' type '%s' is not supported", YQT_OBJECT_NAME(obj),prop.c_str(),type.c_str()); } } // Find a QSystemTrayIcon child of an object static inline QSystemTrayIcon* findSysTrayIcon(QObject* obj, const char* name) { return qFindChild(obj,QtClient::setUtf8(name)); } // Utility used to create an object's property if not found // Add it to a list of strings // Return true if the list changed static bool createProperty(QObject* obj, const char* name, QVariant::Type t, QtWindow* wnd, QStringList* list) { if (!obj || TelEngine::null(name)) return false; QVariant var = obj->property(name); if (var.type() == QVariant::Invalid) obj->setProperty(name,QVariant(t)); else if (var.type() != t) { if (wnd) Debug(QtDriver::self(),DebugNote, "Window(%s) child '%s' already has a %s property '%s' [%p]", wnd->toString().c_str(),YQT_OBJECT_NAME(obj),var.typeName(),name,wnd); return false; } if (!list) return false; QString s = QtClient::setUtf8(name); if (list->contains(s)) return false; *list << s; return true; } // Replace file path in URLs in a character array static void addFilePathUrl(QByteArray& a, const String& file) { if (!file) return; QString path = QDir::fromNativeSeparators(QtClient::setUtf8(file)); // Truncate after last path separator (lastIndexOf() returns -1 if not found) path.truncate(path.lastIndexOf(QString("/")) + 1); if (!path.size()) return; int start = 0; int end = -1; while ((start = a.indexOf("url(",end + 1)) > 0) { start += 4; end = a.indexOf(")",start); if (end <= start) break; // Add int len = end - start; QByteArray tmp = a.mid(start,len); if (tmp.indexOf('/') != -1) continue; tmp.insert(0,path); a.replace(start,len,tmp); } } // Read data from file and append it to a string buffer // Optionally append suffix characters to file name static bool appendStyleSheet(QString& buf, const char* file, const char* suffix1 = 0, const char* suffix2 = 0) { if (TelEngine::null(file)) return false; String shf = file; const char* oper = 0; int pos = shf.rfind('/'); if (pos < 0) pos = shf.rfind('\\'); if (pos < 0) shf = Client::s_skinPath + shf; int level = DebugNote; if (!(TelEngine::null(suffix1) && TelEngine::null(suffix2))) { level = DebugAll; int dotPos = shf.rfind('.'); if (dotPos > pos) { String tmp = shf.substr(0,dotPos); tmp.append(suffix1,"_"); tmp.append(suffix2,"_"); shf = tmp + shf.substr(dotPos); } } DDebug(ClientDriver::self(),DebugAll,"Loading stylesheet file '%s'",shf.c_str()); QFile f(QtClient::setUtf8(shf)); if (f.open(QIODevice::ReadOnly)) { QByteArray a = f.readAll(); if (a.size()) { addFilePathUrl(a,shf); buf += QString::fromUtf8(a.constData()); } else if (f.error() != QFile::NoError) oper = "read"; } else oper = "open"; if (!oper) return true; Debug(ClientDriver::self(),level,"Failed to %s stylesheet file '%s': %d '%s'", oper,shf.c_str(),f.error(),f.errorString().toUtf8().constData()); return false; } // Split an integer string list // Result list length can be set by indicating a length static QList buildIntList(const String& buf, int len = 0) { QList ret; ObjList* list = buf.split(','); int pos = 0; ObjList* o = list; while (o || pos < len) { int val = 0; if (o) { if (o->get()) val = o->get()->toString().toInteger(); o = o->next(); } ret.append(val); pos++; if (pos == len) break; } TelEngine::destruct(list); return ret; } // Retrieve an object's property // Check platform dependent value static inline bool getPropPlatform(QObject* obj, const String& name, String& val) { if (!(obj && name)) return false; if (QtClient::getProperty(obj,name,val)) return true; return QtClient::getProperty(obj,name + "_os" + PLATFORM_LOWERCASE_NAME,val); } /** * Qt4ClientFactory */ Qt4ClientFactory::Qt4ClientFactory(const char* name) : UIFactory(name) { m_types.append(new String("QSound")); } // Build QSound void* Qt4ClientFactory::create(const String& type, const char* name, NamedList* params) { if (type == YSTRING("QSound")) return new QSound(QtClient::setUtf8(name)); return 0; } /** * TableWidget */ TableWidget::TableWidget(QTableWidget* table, bool tmp) : m_table(table), m_sortControl(-1) { if (!m_table) return; init(tmp); } TableWidget::TableWidget(QWidget* wid, const String& name, bool tmp) : m_table(0), m_sortControl(-1) { if (wid) m_table = qFindChild(wid,QtClient::setUtf8(name)); if (!m_table) return; init(tmp); } TableWidget::TableWidget(QtWidget& table, bool tmp) : m_table(static_cast((QWidget*)table)), m_sortControl(-1) { if (m_table) init(tmp); } TableWidget::~TableWidget() { if (!m_table) return; if (m_sortControl >= 0) m_table->setSortingEnabled((bool)m_sortControl); m_table->repaint(); } // Add or set a row void TableWidget::updateRow(const String& item, const NamedList* data, bool atStart) { int row = getRow(item); // Add a new one ? if (row < 0) { row = atStart ? 0 : rowCount(); addRow(row); setID(row,item); } // Update if (data) updateRow(row,*data); } // Update a row from a list of parameters void TableWidget::updateRow(int row, const NamedList& data) { int ncol = columnCount(); for (int i = 0; i < ncol; i++) { String header; if (!getHeaderText(i,header)) continue; NamedString* tmp = data.getParam(header); if (tmp) setCell(row,i,*tmp); // Set image tmp = data.getParam(header + "_image"); if (tmp) setImage(row,i,*tmp); } // Init vertical header String* rowText = data.getParam(YSTRING("row_text")); String* rowImg = data.getParam(YSTRING("row_image")); if (rowText || rowImg) { QTableWidgetItem* item = m_table->verticalHeaderItem(row); if (!item) { item = new QTableWidgetItem; m_table->setVerticalHeaderItem(row,item); } if (rowText) item->setText(QtClient::setUtf8(*rowText)); if (rowImg) item->setIcon(QIcon(QtClient::setUtf8(*rowImg))); } } // Find a row by the first's column value. Return -1 if not found int TableWidget::getRow(const String& item) { int n = rowCount(); for (int i = 0; i < n; i++) { String val; if (getCell(i,0,val) && item == val) return i; } return -1; } // Find a column by its label. Return -1 if not found int TableWidget::getColumn(const String& name, bool caseInsensitive) { int n = columnCount(); for (int i = 0; i < n; i++) { String val; if (!getHeaderText(i,val,false)) continue; if ((caseInsensitive && (name &= val)) || (!caseInsensitive && name == val)) return i; } return -1; } void TableWidget::init(bool tmp) { QtClient::getUtf8(m_name,m_table->objectName()); if (tmp) { m_sortControl = m_table->isSortingEnabled() ? 1 : 0; if (m_sortControl) m_table->setSortingEnabled(false); } } /** * UIBuffer */ // Remove from list. Release memory void UIBuffer::destruct() { s_uiCache.remove(this,false); if (m_buffer) { delete m_buffer; m_buffer = 0; } String::destruct(); } // Return an already loaded UI. Load from file if not found. // Add URLs paths when missing UIBuffer* UIBuffer::build(const String& name) { // Check if already loaded from the same location UIBuffer* buf = find(name); if (buf) return buf; // Load QFile file(QtClient::setUtf8(name)); file.open(QIODevice::ReadOnly); QByteArray* qArray = new QByteArray; *qArray = file.readAll(); file.close(); if (!qArray->size()) { delete qArray; return 0; } // Add URLs path when missing addFilePathUrl(*qArray,name); return new UIBuffer(name,qArray); } // Find a buffer UIBuffer* UIBuffer::find(const String& name) { ObjList* o = s_uiCache.find(name); return o ? static_cast(o->get()) : 0; } /** * QtWindow */ QtWindow::QtWindow() : m_x(0), m_y(0), m_width(0), m_height(0), m_maximized(false), m_mainWindow(false), m_moving(0) { } QtWindow::QtWindow(const char* name, const char* description, const char* alias, QtWindow* parent) : QWidget(parent, Qt::Window), Window(alias ? alias : name), m_description(description), m_oldId(name), m_x(0), m_y(0), m_width(0), m_height(0), m_maximized(false), m_mainWindow(false), m_moving(0) { setObjectName(QtClient::setUtf8(m_id)); } QtWindow::~QtWindow() { // Update all hidden counter for tray icons owned by this window QList trayIcons = qFindChildren(this); if (trayIcons.size() > 0) { if (s_allHiddenQuit >= (unsigned int)trayIcons.size()) s_allHiddenQuit -= trayIcons.size(); else { Debug(QtDriver::self(),DebugFail, "QtWindow(%s) destroyed with all hidden counter %u greater then tray icons %d [%p]", m_id.c_str(),s_allHiddenQuit,trayIcons.size(),this); s_allHiddenQuit = 0; } } // Save settings if (m_saveOnClose) { m_maximized = isMaximized(); s_save.setValue(m_id,"maximized",String::boolText(m_maximized)); // Don't save position if maximized: keep the old one if (!m_maximized) { s_save.setValue(m_id,"x",m_x); s_save.setValue(m_id,"y",m_y); s_save.setValue(m_id,"width",m_width); s_save.setValue(m_id,"height",m_height); } s_save.setValue(m_id,"visible",m_visible); // Set dynamic properties to be saved for native QT objects QList tables = qFindChildren(this); for (int i = 0; i < tables.size(); i++) { if (qobject_cast(tables[i])) continue; // Column widths unsigned int n = tables[i]->columnCount(); String widths; for (unsigned int j = 0; j < n; j++) widths.append(String(tables[i]->columnWidth(j)),",",true); tables[i]->setProperty(s_propColWidths,QVariant(QtClient::setUtf8(widths))); // Sorting String sorting; if (tables[i]->isSortingEnabled()) { QHeaderView* h = tables[i]->horizontalHeader(); int col = h ? h->sortIndicatorSection() : -1; if (col >= 0) sorting << col << "," << String::boolText(Qt::AscendingOrder == h->sortIndicatorOrder()); } tables[i]->setProperty(s_propSorting,QVariant(QtClient::setUtf8(sorting))); } QList spl = qFindChildren(this); for (int i = 0; i < spl.size(); i++) { String sizes; QtClient::intList2str(sizes,spl[i]->sizes()); QtClient::setProperty(spl[i],s_propSizes,sizes); } // Save child objects properties QList child = qFindChildren(this); for (int i = 0; i < child.size(); i++) { NamedList props(""); if (!QtClient::getProperty(child[i],s_propsSave,props)) continue; unsigned int n = props.length(); for (unsigned int j = 0; j < n; j++) { NamedString* ns = props.getParam(j); if (ns && ns->name()) QtClient::saveProperty(child[i],ns->name(),this); } } } } // Set windows title void QtWindow::title(const String& text) { XDebug(QtDriver::self(),DebugAll,"QtWindow::title(%s) [%p]",text.c_str(),this); Window::title(text); QWidget::setWindowTitle(QtClient::setUtf8(text)); } void QtWindow::context(const String& text) { XDebug(QtDriver::self(),DebugAll,"QtWindow::context(%s) [%p]",text.c_str(),this); m_context = text; } bool QtWindow::setParams(const NamedList& params) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setParams() [%p]",this); setUpdatesEnabled(false); // Check for custom widget params if (params == YSTRING("customwidget")) { // Each parameter is a list of parameters for a custom widget // Parameter name is the widget's name unsigned int n = params.length(); bool ok = true; for (unsigned int i = 0; i < n; i++) { NamedString* ns = params.getParam(i); NamedList* nl = static_cast(ns ? ns->getObject(YATOM("NamedList")) : 0); if (!(nl && ns->name())) continue; // Find the widget and set its params QtWidget w(this,ns->name()); if (w.type() == QtWidget::CustomTable) ok = w.customTable()->setParams(*nl) && ok; else if (w.type() == QtWidget::CustomWidget) ok = w.customWidget()->setParams(*nl) && ok; else if (w.type() == QtWidget::CustomObject) ok = w.customObject()->setParams(*nl) && ok; else ok = false; } setUpdatesEnabled(true); return ok; } // Check for system tray icon params if (params == YSTRING("systemtrayicon")) { // Each parameter is a list of parameters for a system tray icon // Parameter name is the widget's name // Parameter value indicates delete/create/set an existing one unsigned int n = params.length(); bool ok = false; for (unsigned int i = 0; i < n; i++) { NamedString* ns = params.getParam(i); if (!(ns && ns->name())) continue; QSystemTrayIcon* trayIcon = findSysTrayIcon(this,ns->name()); // Delete if (ns->null()) { if (trayIcon) { // Reactivate program termination when the last window was hidden if (s_allHiddenQuit) s_allHiddenQuit--; else Debug(QtDriver::self(),DebugFail, "QtWindow(%s) all hidden counter is 0 while deleting '%s' tray icon [%p]", m_id.c_str(),YQT_OBJECT_NAME(trayIcon),this); QtClient::deleteLater(trayIcon); } continue; } NamedList* nl = YOBJECT(NamedList,ns); if (!nl) continue; // Create a new one if needed if (!trayIcon) { if (!ns->toBoolean()) continue; trayIcon = new QSystemTrayIcon(this); trayIcon->setObjectName(QtClient::setUtf8(ns->name())); QtClient::connectObjects(trayIcon,SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this,SLOT(sysTrayIconAction(QSystemTrayIcon::ActivationReason))); // Deactivate program termination when the last window was hidden s_allHiddenQuit++; } ok = true; // Add dynamic properties // TODO: track the properties, clear the old ones if needed addDynamicProps(trayIcon,*nl); // Set icon and tooltip NamedString* tmp = nl->getParam(YSTRING("icon")); if (tmp && *tmp) trayIcon->setIcon(QIcon(QtClient::setUtf8(*tmp))); tmp = nl->getParam(YSTRING("tooltip")); if (tmp && *tmp) trayIcon->setToolTip(QtClient::setUtf8(*tmp)); // Check context menu NamedString* menu = nl->getParam(YSTRING("menu")); if (menu) { QMenu* oldMenu = trayIcon->contextMenu(); if (oldMenu) delete oldMenu; NamedList* nlMenu = YOBJECT(NamedList,menu); trayIcon->setContextMenu(nlMenu ? QtClient::buildMenu(*nlMenu,*menu,this, SLOT(action()),SLOT(toggled(bool)),this) : 0); } if (nl->getBoolValue(YSTRING("show"),true)) trayIcon->setVisible(true); } setUpdatesEnabled(true); return ok; } // Parameters for the widget whose name is the list name if(params) { QtWidget w(this, params); // Handle UIWidget descendants UIWidget* uiw = w.uiWidget(); if (uiw) { bool ok = uiw->setParams(params); setUpdatesEnabled(true); return ok; } if (w.type() == QtWidget::Calendar) { int year = params.getIntValue(YSTRING("year")); int month = params.getIntValue(YSTRING("month")); int day = params.getIntValue(YSTRING("day")); w.calendar()->setCurrentPage(year, month); w.calendar()->setSelectedDate(QDate(year, month, day)); setUpdatesEnabled(true); return true; } } // Window or other parameters if (params.getBoolValue(YSTRING("modal"))) { if (parentWindow()) setWindowModality(Qt::WindowModal); else setWindowModality(Qt::ApplicationModal); } if (params.getBoolValue(YSTRING("minimized"))) QWidget::setWindowState(Qt::WindowMinimized); bool ok = Window::setParams(params); setUpdatesEnabled(true); return ok; } void QtWindow::setOver(const Window* parent) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setOver(%p) [%p]",parent,this); QWidget::raise(); } bool QtWindow::hasElement(const String& name) { XDebug(QtDriver::self(),DebugAll,"QtWindow::hasElement(%s) [%p]",name.c_str(),this); QtWidget w(this,name); return w.valid(); } bool QtWindow::setActive(const String& name, bool active) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setActive(%s,%s) [%p]", name.c_str(),String::boolText(active),this); bool ok = (name == m_id); if (ok) { if (QWidget::isMinimized()) QWidget::showNormal(); QWidget::activateWindow(); QWidget::raise(); } QtWidget w(this,name); if (w.invalid()) return ok; if (w.type() != QtWidget::Action) w->setEnabled(active); else w.action()->setEnabled(active); return true; } bool QtWindow::setFocus(const String& name, bool select) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setFocus(%s,%s) [%p]", name.c_str(),String::boolText(select),this); QtWidget w(this,name); if (w.invalid()) return false; w->setFocus(); switch (w.type()) { case QtWidget::ComboBox: if (w.combo()->isEditable() && select) w.combo()->lineEdit()->selectAll(); break; } return true; } bool QtWindow::setShow(const String& name, bool visible) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setShow(%s,%s) [%p]", name.c_str(),String::boolText(visible),this); // Check system tray icons QSystemTrayIcon* trayIcon = findSysTrayIcon(this,name); if (trayIcon) { trayIcon->setVisible(visible); return true; } // Widgets QtWidget w(this,name); if (w.invalid()) return false; setUpdatesEnabled(false); if (w.type() != QtWidget::Action) w->setVisible(visible); else w.action()->setVisible(visible); setUpdatesEnabled(true); return true; } bool QtWindow::setText(const String& name, const String& text, bool richText) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setText(%s,%s) [%p]", m_id.c_str(),name.c_str(),text.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->setText(text,richText); switch (w.type()) { case QtWidget::CheckBox: w.check()->setText(QtClient::setUtf8(text)); return true; case QtWidget::LineEdit: w.lineEdit()->setText(QtClient::setUtf8(text)); return true; case QtWidget::TextBrowser: case QtWidget::TextEdit: if (richText) { w.textEdit()->clear(); w.textEdit()->insertHtml(QtClient::setUtf8(text)); } else w.textEdit()->setText(QtClient::setUtf8(text)); { QScrollBar* bar = w.textEdit()->verticalScrollBar(); if (bar) bar->setSliderPosition(bar->maximum()); } return true; case QtWidget::Label: w.label()->setText(QtClient::setUtf8(text)); return true; case QtWidget::ComboBox: if (w.combo()->lineEdit()) w.combo()->lineEdit()->setText(QtClient::setUtf8(text)); else setSelect(name,text); return true; case QtWidget::Action: w.action()->setText(QtClient::setUtf8(text)); return true; case QtWidget::SpinBox: w.spinBox()->setValue(text.toInteger()); return true; } // Handle some known base classes having a setText() method if (w.inherits(QtWidget::AbstractButton)) w.abstractButton()->setText(QtClient::setUtf8(text)); else return false; return true; } bool QtWindow::setCheck(const String& name, bool checked) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setCheck(%s,%s) [%p]", name.c_str(),String::boolText(checked),this); QtWidget w(this,name); if (w.invalid()) return false; if (w.inherits(QtWidget::AbstractButton)) w.abstractButton()->setChecked(checked); else if (w.type() == QtWidget::Action) w.action()->setChecked(checked); else return false; return true; } bool QtWindow::setSelect(const String& name, const String& item) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setSelect(%s,%s) [%p]", name.c_str(),item.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->setSelect(item); int d = 0; switch (w.type()) { case QtWidget::Table: { TableWidget t(w); int row = t.getRow(item); if (row < 0) return false; t.table()->setCurrentCell(row,0); return true; } case QtWidget::ComboBox: if (item) { d = w.findComboItem(item); if (d < 0) return false; w.combo()->setCurrentIndex(d); } else if (w.combo()->lineEdit()) w.combo()->lineEdit()->setText(""); else return false; return true; case QtWidget::ListBox: d = w.findListItem(item); if (d >= 0) w.list()->setCurrentRow(d); return d >= 0; case QtWidget::Slider: w.slider()->setValue(item.toInteger()); return true; case QtWidget::StackWidget: d = item.toInteger(-1); while (d < 0) { d = findStackedWidget(*(w.stackWidget()),item); if (d >= 0) break; // Check for a default widget String def = YQT_OBJECT_NAME(w.stackWidget()); def << "_default"; d = findStackedWidget(*(w.stackWidget()),def); break; } if (d >= 0 && d < w.stackWidget()->count()) { w.stackWidget()->setCurrentIndex(d); return true; } return false; case QtWidget::ProgressBar: d = item.toInteger(); if (d >= w.progressBar()->minimum() && d <= w.progressBar()->maximum()) w.progressBar()->setValue(d); else if (d < w.progressBar()->minimum()) w.progressBar()->setValue(w.progressBar()->minimum()); else w.progressBar()->setValue(w.progressBar()->maximum()); return true; case QtWidget::Tab: d = w.tab()->count() - 1; for (QString tmp = QtClient::setUtf8(item); d >= 0; d--) { QWidget* wid = w.tab()->widget(d); if (wid && wid->objectName() == tmp) break; } if (d >= 0 && d < w.tab()->count()) { w.tab()->setCurrentIndex(d); return true; } return false; } return false; } bool QtWindow::setUrgent(const String& name, bool urgent) { XDebug(QtDriver::self(),DebugAll,"QtWindow::setUrgent(%s,%s) [%p]", name.c_str(),String::boolText(urgent),this); if (name == m_id) { QApplication::alert(this,0); return true; } QtWidget w(this,name); if (w.invalid()) return false; w->raise(); return true; } bool QtWindow::hasOption(const String& name, const String& item) { XDebug(QtDriver::self(),DebugAll,"QtWindow::hasOption(%s,%s) [%p]", name.c_str(),item.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; switch (w.type()) { case QtWidget::ComboBox: return -1 != w.findComboItem(item); case QtWidget::Table: return getTableRow(name,item); case QtWidget::ListBox: return -1 != w.findListItem(item); } return false; } bool QtWindow::addOption(const String& name, const String& item, bool atStart, const String& text) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addOption(%s,%s,%s,%s) [%p]", m_id.c_str(),name.c_str(),item.c_str(), String::boolText(atStart),text.c_str(),this); QtWidget w(this,name); switch (w.type()) { case QtWidget::ComboBox: w.addComboItem(item,atStart); if (atStart && w.combo()->lineEdit()) w.combo()->lineEdit()->setText(w.combo()->itemText(0)); return true; case QtWidget::Table: return addTableRow(name,item,0,atStart); case QtWidget::ListBox: return w.addListItem(item,atStart); } return false; } bool QtWindow::delOption(const String& name, const String& item) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) delOption(%s,%s) [%p]", m_id.c_str(),name.c_str(),item.c_str(),this); return delTableRow(name,item); } bool QtWindow::getOptions(const String& name, NamedList* items) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getOptions(%s,%p) [%p]", m_id.c_str(),name.c_str(),items,this); QtWidget w(this,name); if (w.invalid()) return false; if (!items) return true; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->getOptions(*items); switch (w.type()) { case QtWidget::ComboBox: for (int i = 0; i < w.combo()->count(); i++) QtClient::getUtf8(*items,"",w.combo()->itemText(i),false); break; case QtWidget::Table: { TableWidget t(w.table(),false); for (int i = 0; i < t.rowCount(); i++) { String item; if (t.getCell(i,0,item) && item) items->addParam(item,""); } } break; case QtWidget::ListBox: for (int i = 0; i < w.list()->count(); i++) { QListWidgetItem* tmp = w.list()->item(i); if (tmp) QtClient::getUtf8(*items,"",tmp->text(),false); } break; } return true; } // Append or insert text lines to a widget bool QtWindow::addLines(const String& name, const NamedList* lines, unsigned int max, bool atStart) { DDebug(ClientDriver::self(),DebugAll,"QtWindow(%s) addLines('%s',%p,%u,%s) [%p]", m_id.c_str(),name.c_str(),lines,max,String::boolText(atStart),this); QtWidget w(this,name); if (w.invalid()) return false; if (!lines) return true; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->addLines(*lines,max,atStart); unsigned int count = lines->length(); if (!count) return true; switch (w.type()) { case QtWidget::TextBrowser: case QtWidget::TextEdit: // Limit the maximum number of paragraphs if (max) { QTextDocument* doc = w.textEdit()->document(); if (!doc) return false; doc->setMaximumBlockCount((int)max); } { // FIXME: delete lines from begining if appending and the number // of lines exceeds the maximum allowed QString s = w.textEdit()->toPlainText(); int pos = atStart ? 0 : s.length(); for (unsigned int i = 0; i < count; i++) { NamedString* ns = lines->getParam(i); if (!ns) continue; if (ns->name().endsWith("\n")) s.insert(pos,QtClient::setUtf8(ns->name())); else { String tmp = ns->name() + "\n"; s.insert(pos,QtClient::setUtf8(tmp)); pos++; } pos += (int)ns->name().length(); } w.textEdit()->setText(s); // Scroll down if added at end if (!atStart) { QScrollBar* bar = w.textEdit()->verticalScrollBar(); if (bar) bar->setSliderPosition(bar->maximum()); } } return true; case QtWidget::Table: // TODO: implement break; case QtWidget::ComboBox: if (atStart) { for (unsigned int i = count; i >= 0; i--) { NamedString* ns = lines->getParam(i); if (ns) w.combo()->insertItem(0,QtClient::setUtf8(ns->name())); } if (w.combo()->lineEdit()) w.combo()->lineEdit()->setText(w.combo()->itemText(0)); } else { for (unsigned int i = 0; i < count; i++) { NamedString* ns = lines->getParam(i); if (ns) w.combo()->addItem(QtClient::setUtf8(ns->name())); } } return true; case QtWidget::ListBox: // TODO: implement break; } return false; } bool QtWindow::addTableRow(const String& name, const String& item, const NamedList* data, bool atStart) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) addTableRow(%s,%s,%p,%s) [%p]", m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->addTableRow(item,data,atStart); // Handle basic QTableWidget if (w.type() != QtWidget::Table) return false; TableWidget tbl(w.table()); int row = atStart ? 0 : tbl.rowCount(); tbl.addRow(row); // Set item (the first column) and the rest of data tbl.setID(row,item); if (data) tbl.updateRow(row,*data); return true; } // Insert or update multiple rows in a single operation bool QtWindow::setMultipleRows(const String& name, const NamedList& data, const String& prefix) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setMultipleRows('%s',%p,'%s') [%p]", m_id.c_str(),name.c_str(),&data,prefix.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); return uiw && uiw->setMultipleRows(data,prefix); } // Insert a row into a table owned by this window bool QtWindow::insertTableRow(const String& name, const String& item, const String& before, const NamedList* data) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) insertTableRow(%s,%s,%s,%p) [%p]", m_id.c_str(),name.c_str(),item.c_str(),before.c_str(),data,this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->insertTableRow(item,before,data); if (w.type() != QtWidget::Table) return false; TableWidget tbl(w.table()); int row = tbl.getRow(before); if (row == -1) row = tbl.rowCount(); tbl.addRow(row); // Set item (the first column) and the rest of data tbl.setID(row,item); if (data) tbl.updateRow(row,*data); return true; } bool QtWindow::delTableRow(const String& name, const String& item) { XDebug(QtDriver::self(),DebugAll,"QtWindow::delTableRow(%s,%s) [%p]", name.c_str(),item.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; int row = -1; int n = 0; switch (w.type()) { case QtWidget::Table: case QtWidget::CustomTable: { TableWidget tbl(w.table()); QtTable* custom = tbl.customTable(); if (custom) { if (custom->delTableRow(item)) row = 0; } else { row = tbl.getRow(item); if (row >= 0) tbl.delRow(row); } n = tbl.rowCount(); } break; case QtWidget::ComboBox: row = w.findComboItem(item); if (row >= 0) { w.combo()->removeItem(row); n = w.combo()->count(); } break; case QtWidget::ListBox: row = w.findListItem(item); if (row >= 0) { QStringListModel* model = (QStringListModel*)w.list()->model(); if (!(model && model->removeRow(row))) row = -1; n = w.list()->count(); } break; default: UIWidget* uiw = w.uiWidget(); if (uiw && uiw->delTableRow(item)) { row = 0; // Don't notify empty: we don't know it n = 1; } } if (row < 0) return false; if (!n) raiseSelectIfEmpty(0,this,name); return true; } bool QtWindow::setTableRow(const String& name, const String& item, const NamedList* data) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setTableRow(%s,%s,%p) [%p]", m_id.c_str(),name.c_str(),item.c_str(),data,this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->setTableRow(item,data); if (w.type() != QtWidget::Table) return false; TableWidget tbl(w.table()); int row = tbl.getRow(item); if (row < 0) return false; if (data) tbl.updateRow(row,*data); return true; } bool QtWindow::getTableRow(const String& name, const String& item, NamedList* data) { XDebug(QtDriver::self(),DebugAll,"QtWindow::getTableRow(%s,%s,%p) [%p]", name.c_str(),item.c_str(),data,this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->getTableRow(item,data); if (w.type() != QtWidget::Table) return false; TableWidget tbl(w.table(),false); int row = tbl.getRow(item); if (row < 0) return false; if (!data) return true; int n = tbl.columnCount(); for (int i = 0; i < n; i++) { String name; if (!tbl.getHeaderText(i,name)) continue; String value; if (tbl.getCell(row,i,value)) data->setParam(name,value); } return true; } // Set a table row or add a new one if not found bool QtWindow::updateTableRow(const String& name, const String& item, const NamedList* data, bool atStart) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRow('%s','%s',%p,%s) [%p]", m_id.c_str(),name.c_str(),item.c_str(),data,String::boolText(atStart),this); QtWidget w(this,name); if (w.invalid()) return false; switch (w.type()) { case QtWidget::Table: case QtWidget::CustomTable: { TableWidget tbl(w.table()); QtTable* custom = tbl.customTable(); if (custom) { if (custom->getTableRow(item)) return custom->setTableRow(item,data); return custom->addTableRow(item,data,atStart); } tbl.updateRow(item,data,atStart); return true; } case QtWidget::CustomTree: { QtTree* custom = w.customTree(); if (custom) { if (custom->getTableRow(item)) return custom->setTableRow(item,data); return custom->addTableRow(item,data,atStart); } return false; } case QtWidget::ComboBox: return w.findComboItem(item) >= 0 || w.addComboItem(item,atStart); case QtWidget::ListBox: return w.findListItem(item) >= 0 || w.addListItem(item,atStart); } return false; } // Add or set one or more table row(s). Screen update is locked while changing the table. // Each data list element is a NamedPointer carrying a NamedList with item parameters. // The name of an element is the item to update. // Element's value not empty: update the item // Else: delete it bool QtWindow::updateTableRows(const String& name, const NamedList* data, bool atStart) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) updateTableRows('%s',%p,%s) [%p]", m_id.c_str(),name.c_str(),data,String::boolText(atStart),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) { bool ok = uiw->updateTableRows(data,atStart); QtTable* ct = w.customTable(); if (ct) raiseSelectIfEmpty(ct->rowCount(),this,name); return ok; } if (w.type() != QtWidget::Table) return false; if (!data) return true; TableWidget tbl(w.table()); bool ok = true; tbl.table()->setUpdatesEnabled(false); ObjList add; unsigned int n = data->length(); for (unsigned int i = 0; i < n; i++) { if (Client::exiting()) break; // Get item and the list of parameters NamedString* ns = data->getParam(i); if (!ns) continue; // Delete ? if (ns->null()) { int row = tbl.getRow(ns->name()); if (row >= 0) tbl.delRow(row); else ok = false; continue; } // Set existing row or postpone add int row = tbl.getRow(ns->name()); if (row >= 0) { const NamedList* params = YOBJECT(NamedList,ns); if (params) tbl.updateRow(row,*params); } else if (ns->toBoolean()) add.append(ns)->setDelete(false); else ok = false; } n = add.count(); if (n) { int row = tbl.rowCount(); if (row < 0) row = 0; // Append if not requested to insert at start or table is empty if (!(atStart && row)) tbl.table()->setRowCount(row + n); else { for (unsigned int i = 0; i < n; i++) tbl.table()->insertRow(0); } for (ObjList* o = add.skipNull(); o; row++, o = o->skipNext()) { NamedString* ns = static_cast(o->get()); tbl.setID(row,ns->name()); const NamedList* params = YOBJECT(NamedList,ns); if (params) tbl.updateRow(row,*params); } } tbl.table()->setUpdatesEnabled(true); raiseSelectIfEmpty(tbl.rowCount(),this,name); return ok; } bool QtWindow::clearTable(const String& name) { DDebug(QtDriver::self(),DebugAll,"QtWindow::clearTable(%s) [%p]",name.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->clearTable(); bool ok = true; if (w.widget()) w->setUpdatesEnabled(false); switch (w.type()) { case QtWidget::Table: w.table()->setRowCount(0); break; case QtWidget::TextBrowser: case QtWidget::TextEdit: w.textEdit()->clear(); break; case QtWidget::ListBox: w.list()->clear(); break; case QtWidget::ComboBox: w.combo()->clear(); break; default: ok = false; } if (w.widget()) w->setUpdatesEnabled(true); return ok; } // Show or hide control busy state bool QtWindow::setBusy(const String& name, bool on) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) setBusy(%s,%u) [%p]", m_id.c_str(),name.c_str(),on,this); if (name == m_id) return QtBusyWidget::showBusyChild(this,on); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->setBusy(on); if (w.widget()) return QtBusyWidget::showBusyChild(w.widget(),on); return false; } bool QtWindow::getText(const String& name, String& text, bool richText) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) getText(%s) [%p]", m_id.c_str(),name.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->getText(text,richText); switch (w.type()) { case QtWidget::ComboBox: QtClient::getUtf8(text,w.combo()->currentText()); return true; case QtWidget::LineEdit: QtClient::getUtf8(text,w.lineEdit()->text()); return true; case QtWidget::TextBrowser: case QtWidget::TextEdit: if (!richText) QtClient::getUtf8(text,w.textEdit()->toPlainText()); else QtClient::getUtf8(text,w.textEdit()->toHtml()); return true; case QtWidget::Label: QtClient::getUtf8(text,w.label()->text()); return true; case QtWidget::Action: QtClient::getUtf8(text,w.action()->text()); return true; case QtWidget::SpinBox: text = w.spinBox()->value(); return true; default: if (w.inherits(QtWidget::AbstractButton)) { QtClient::getUtf8(text,w.abstractButton()->text()); return true; } } return false; } bool QtWindow::getCheck(const String& name, bool& checked) { DDebug(QtDriver::self(),DebugAll,"QtWindow::getCheck(%s) [%p]",name.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; if (w.inherits(QtWidget::AbstractButton)) checked = w.abstractButton()->isChecked(); else if (w.type() == QtWidget::Action) checked = w.action()->isChecked(); else return false; return true; } bool QtWindow::getSelect(const String& name, String& item) { XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%s) [%p]",name.c_str(),this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->getSelect(item); switch (w.type()) { case QtWidget::ComboBox: if (w.combo()->lineEdit() && w.combo()->lineEdit()->selectedText().isEmpty()) return false; QtClient::getUtf8(item,w.combo()->currentText()); return true; case QtWidget::Table: { TableWidget t(w); int row = t.crtRow(); return row >= 0 ? t.getCell(row,0,item) : false; } case QtWidget::ListBox: { QListWidgetItem* crt = w.list()->currentItem(); if (!crt) return false; QtClient::getUtf8(item,crt->text()); } return true; case QtWidget::Slider: item = w.slider()->value(); return true; case QtWidget::ProgressBar: item = w.progressBar()->value(); return true; case QtWidget::Tab: { item = ""; QWidget* wid = w.tab()->currentWidget(); if (wid) QtClient::getUtf8(item,wid->objectName()); } return true; case QtWidget::StackWidget: { item = ""; QWidget* wid = w.stackWidget()->currentWidget(); if (wid) QtClient::getUtf8(item,wid->objectName()); } return true; } return false; } // Retrieve an element's multiple selection bool QtWindow::getSelect(const String& name, NamedList& items) { XDebug(QtDriver::self(),DebugAll,"QtWindow::getSelect(%p) [%p]",&items,this); QtWidget w(this,name); if (w.invalid()) return false; UIWidget* uiw = w.uiWidget(); if (uiw) return uiw->getSelect(items); switch (w.type()) { case QtWidget::ComboBox: case QtWidget::Table: case QtWidget::ListBox: case QtWidget::Slider: case QtWidget::ProgressBar: case QtWidget::Tab: case QtWidget::StackWidget: DDebug(QtDriver::self(),DebugStub,"QtWindow::getSelect(%p) not implemented for '%s; [%p]", &items,w.widget()->metaObject()->className(),this); } return false; } // Build a menu from a list of parameters bool QtWindow::buildMenu(const NamedList& params) { QWidget* parent = this; // Retrieve the owner const String& owner = params[YSTRING("owner")]; if (owner && owner != m_id) { parent = qFindChild(this,QtClient::setUtf8(owner)); if (!parent) { DDebug(QtDriver::self(),DebugNote, "QtWindow(%s) buildMenu(%s) owner '%s' not found [%p]", m_id.c_str(),params.c_str(),owner.c_str(),this); return false; } } QWidget* target = parent; const String& t = params[YSTRING("target")]; if (t) { target = qFindChild(this,QtClient::setUtf8(t)); if (!target) { DDebug(QtDriver::self(),DebugNote, "QtWindow(%s) buildMenu(%s) target '%s' not found [%p]", m_id.c_str(),params.c_str(),t.c_str(),this); return false; } } // Remove existing menu removeMenu(params); QMenu* menu = QtClient::buildMenu(params,params.getValue(YSTRING("title"),params),this, SLOT(action()),SLOT(toggled(bool)),parent); if (!menu) { DDebug(QtDriver::self(),DebugNote, "QtWindow(%s) failed to build menu '%s' target='%s' [%p]", m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this); return false; } DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) built menu '%s' target='%s' [%p]", m_id.c_str(),params.c_str(),YQT_OBJECT_NAME(target),this); QMenuBar* mbOwner = qobject_cast(target); QMenu* mOwner = !mbOwner ? qobject_cast(target) : 0; if (mbOwner || mOwner) { QAction* before = 0; const String& bef = params[YSTRING("before")]; // Retrieve the action to insert before if (bef) { QString cmp = QtClient::setUtf8(bef); QList list = target->actions(); for (int i = 0; !before && i < list.size(); i++) { // Check action name or menu name if the action is associated with a menu if (list[i]->objectName() == cmp) before = list[i]; else if (list[i]->menu() && list[i]->menu()->objectName() == cmp) before = list[i]->menu()->menuAction(); if (before && i && list[i - 1]->isSeparator() && params.getBoolValue(YSTRING("before_separator"),true)) before = list[i - 1]; } } // Insert the menu if (mbOwner) mbOwner->insertMenu(before,menu); else mOwner->insertMenu(before,menu); } else { QToolButton* tb = qobject_cast(target); if (tb) tb->setMenu(menu); else { QPushButton* pb = qobject_cast(target); if (pb) pb->setMenu(menu); else if (!QtClient::setProperty(target,s_propContextMenu,params)) target->addAction(menu->menuAction()); } } return true; } // Remove a menu bool QtWindow::removeMenu(const NamedList& params) { QWidget* parent = this; // Retrieve the owner const String& owner = params[YSTRING("owner")]; if (owner && owner != m_id) { parent = qFindChild(this,QtClient::setUtf8(owner)); if (!parent) return false; } QMenu* menu = qFindChild(parent,QtClient::setUtf8(params)); if (!menu) return false; QtClient::deleteLater(menu); return true; } // Set an element's image bool QtWindow::setImage(const String& name, const String& image, bool fit) { if (!name) return false; if (name == m_id) return QtClient::setImage(this,image); QObject* obj = qFindChild(this,QtClient::setUtf8(name)); return obj && QtClient::setImage(obj,image,fit); } // Set a property for this window or for a widget owned by it bool QtWindow::setProperty(const String& name, const String& item, const String& value) { if (name == m_id) return QtClient::setProperty(wndWidget(),item,value); QObject* obj = qFindChild(this,QtClient::setUtf8(name)); return obj ? QtClient::setProperty(obj,item,value) : false; } // Get a property from this window or from a widget owned by it bool QtWindow::getProperty(const String& name, const String& item, String& value) { if (name == m_id) return QtClient::getProperty(wndWidget(),item,value); QObject* obj = qFindChild(this,QtClient::setUtf8(name)); return obj ? QtClient::getProperty(obj,item,value) : false; } void QtWindow::moveEvent(QMoveEvent* event) { QWidget::moveEvent(event); // Don't update pos if not shown normal if (!isShownNormal()) return; m_x = pos().x(); m_y = pos().y(); DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) moved x=%d y=%d [%p]", m_id.c_str(),m_x,m_y,this); } void QtWindow::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); // Don't update size if not shown normal if (!isShownNormal()) return; m_width = width(); m_height = height(); DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) resized width=%d height=%d [%p]", m_id.c_str(),m_width,m_height,this); } bool QtWindow::event(QEvent* ev) { static const String s_activeChg("window_active_changed"); if (ev->type() == QEvent::WindowDeactivate) { String hideProp; QtClient::getProperty(wndWidget(),s_propHideInactive,hideProp); if (hideProp && hideProp.toBoolean()) setVisible(false); m_active = false; Client::self()->toggle(this,s_activeChg,false); } else if (ev->type() == QEvent::WindowActivate) { m_active = true; Client::self()->toggle(this,s_activeChg,true); String wName; if (getPropPlatform(wndWidget(),s_propShowWndWhenActive,wName) && wName) Client::setVisible(wName); } else if (ev->type() == QEvent::ApplicationDeactivate) { if (m_active) { m_active = false; Client::self()->toggle(this,s_activeChg,true); } } return QWidget::event(ev); } void QtWindow::closeEvent(QCloseEvent* event) { // NOTE: Don't access window's data after calling hide(): // some logics might destroy the window when hidden // Notify window closed String tmp; if (Client::self() && QtClient::getProperty(wndWidget(),"_yate_windowclosedaction",tmp)) Client::self()->action(this,tmp); // Hide the window when requested if (QtClient::getBoolProperty(wndWidget(),"_yate_hideonclose")) { event->ignore(); hide(); return; } QWidget::closeEvent(event); if (m_mainWindow && Client::self()) { Client::self()->quit(); return; } if (QtClient::getBoolProperty(wndWidget(),"_yate_destroyonclose")) { XDebug(QtDriver::self(),DebugAll, "Window(%s) closeEvent() set delete later [%p]",m_id.c_str(),this); QObject::deleteLater(); // Safe to call hide(): the window will be deleted when control returns // to the main loop } hide(); } void QtWindow::changeEvent(QEvent* event) { if (event->type() == QEvent::WindowStateChange) m_maximized = isMaximized(); QWidget::changeEvent(event); } void QtWindow::action() { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) action() sender=%s [%p]", m_id.c_str(),YQT_OBJECT_NAME(sender()),this); if (!QtClient::self() || QtClient::changing()) return; String name; NamedList* params = 0; if (!QtClient::getBoolProperty(sender(),"_yate_translateidentity")) QtClient::getIdentity(sender(),name); else { QtWidget w(sender()); translateName(w,name,¶ms); } if (name) QtClient::self()->action(this,name,params); TelEngine::destruct(params); } // Toggled actions void QtWindow::toggled(bool on) { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) toggled=%s sender=%s [%p]", m_id.c_str(),String::boolText(on),YQT_OBJECT_NAME(sender()),this); QtClient::updateToggleImage(sender()); if (!QtClient::self() || QtClient::changing()) return; QtWidget w(sender()); String name; if (translateName(w,name)) QtClient::self()->toggle(this,name,on); } // System tray actions void QtWindow::sysTrayIconAction(QSystemTrayIcon::ActivationReason reason) { String action; switch (reason) { case QSystemTrayIcon::Context: QtClient::getProperty(sender(),s_propAction + "Context",action); break; case QSystemTrayIcon::DoubleClick: QtClient::getProperty(sender(),s_propAction + "DoubleClick",action); break; case QSystemTrayIcon::Trigger: QtClient::getProperty(sender(),s_propAction + "Trigger",action); break; case QSystemTrayIcon::MiddleClick: QtClient::getProperty(sender(),s_propAction + "MiddleClick",action); break; default: return; } if (action) Client::self()->action(this,action); } // Choose file window was accepted void QtWindow::chooseFileAccepted() { QFileDialog* dlg = qobject_cast(sender()); if (!dlg) return; String action; QtClient::getUtf8(action,dlg->objectName()); if (!action) return; NamedList params(""); QDir dir = dlg->directory(); if (dir.absolutePath().length()) QtClient::getUtf8(params,"dir",fixPathSep(dir.absolutePath())); QStringList files = dlg->selectedFiles(); for (int i = 0; i < files.size(); i++) QtClient::getUtf8(params,"file",fixPathSep(files[i])); if (dlg->fileMode() != QFileDialog::DirectoryOnly && dlg->fileMode() != QFileDialog::Directory) { QString filter = dlg->selectedFilter(); if (filter.length()) QtClient::getUtf8(params,"filter",filter); } Client::self()->action(this,action,¶ms); } // Choose file window was cancelled void QtWindow::chooseFileRejected() { QFileDialog* dlg = qobject_cast(sender()); if (!dlg) return; String action; QtClient::getUtf8(action,dlg->objectName()); if (!action) return; Client::self()->action(this,action,0); } void QtWindow::openUrl(const QString& link) { QDesktopServices::openUrl(QUrl(link)); } void QtWindow::doubleClick() { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) doubleClick() sender=%s [%p]", m_id.c_str(),YQT_OBJECT_NAME(sender()),this); if (QtClient::self() && sender()) Client::self()->action(this,YQT_OBJECT_NAME(sender())); } // A widget's selection changed void QtWindow::selectionChanged() { XDebug(QtDriver::self(),DebugAll,"QtWindow(%s) selectionChanged() sender=%s [%p]", m_id.c_str(),YQT_OBJECT_NAME(sender()),this); if (!(QtClient::self() && sender())) return; String name = YQT_OBJECT_NAME(sender()); QtWidget w(sender()); if (w.type() != QtWidget::Calendar) { String item; getSelect(name,item); Client::self()->select(this,name,item); } else { NamedList p(""); QDate d = w.calendar()->selectedDate(); p.addParam("year",String(d.year())); p.addParam("month",String(d.month())); p.addParam("day",String(d.day())); Client::self()->action(this,name,&p); } } // Connect an object's text changed signal to window's slot bool QtWindow::connectTextChanged(QObject* obj) { if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify"))) return false; QComboBox* combo = qobject_cast(obj); if (combo) return QtClient::connectObjects(combo,SIGNAL(editTextChanged(const QString&)), this,SLOT(textChanged(const QString&))); QLineEdit* lineEdit = qobject_cast(obj); if (lineEdit) return QtClient::connectObjects(lineEdit,SIGNAL(textChanged(const QString&)), this,SLOT(textChanged(const QString&))); QTextEdit* textEdit = qobject_cast(obj); if (textEdit) return QtClient::connectObjects(textEdit,SIGNAL(textChanged()), this,SLOT(textChanged())); const QMetaObject* meta = obj->metaObject(); Debug(DebugStub,"connectTextChanged() not implemented for class '%s'", meta ? meta->className() : ""); return false; } // Notify text changed to the client void QtWindow::notifyTextChanged(QObject* obj, const QString& text) { if (!(obj && QtClient::getBoolProperty(obj,"_yate_textchangednotify"))) return; // Detect QtUIWidget item. Get its container identity if found String item; QtUIWidget::getListItemProp(obj,item); QtUIWidget* uiw = item ? QtUIWidget::container(obj) : 0; String name; if (!uiw) QtClient::getIdentity(obj,name); else uiw->getIdentity(obj,name); if (!name) return; NamedList p(""); p.addParam("sender",name); if (text.size()) QtClient::getUtf8(p,"text",text); Client::self()->action(this,YSTRING("textchanged"),&p); } // Load a widget from file QWidget* QtWindow::loadUI(const char* fileName, QWidget* parent, const char* uiName, const char* path) { if (Client::exiting()) return 0; if (!(fileName && *fileName && parent)) return 0; if (!(path && *path)) path = Client::s_skinPath.c_str(); UIBuffer* buf = UIBuffer::build(fileName); const char* err = 0; if (buf && buf->buffer()) { QBuffer b(buf->buffer()); QUiLoader loader; loader.setWorkingDirectory(QDir(QtClient::setUtf8(path))); QWidget* w = loader.load(&b,parent); if (w) return w; err = "loader failed"; } else err = buf ? "file is empty" : "file not found"; // Error TelEngine::destruct(buf); Debug(DebugWarn,"Failed to load widget '%s' file='%s' path='%s': %s", uiName,fileName,path,err); return 0; } // Clear the UI cache void QtWindow::clearUICache(const char* fileName) { if (!fileName) UIBuffer::s_uiCache.clear(); else TelEngine::destruct(UIBuffer::s_uiCache.find(fileName)); } // Filter events bool QtWindow::eventFilter(QObject* obj, QEvent* event) { if (!obj) return false; // Apply dynamic properties changes if (event->type() == QEvent::DynamicPropertyChange) { String name = YQT_OBJECT_NAME(obj); QDynamicPropertyChangeEvent* ev = static_cast(event); String prop = ev->propertyName().constData(); // Handle only yate dynamic properties if (!prop.startsWith(s_yatePropPrefix,false)) return QWidget::eventFilter(obj,event); XDebug(QtDriver::self(),DebugAll,"Window(%s) eventFilter(%s) prop=%s [%p]", m_id.c_str(),YQT_OBJECT_NAME(obj),prop.c_str(),this); // Return false for now on: it's our property QtWidget w(obj); if (w.invalid()) return false; String value; if (!QtClient::getProperty(obj,prop,value)) return false; bool ok = true; bool handled = true; if (prop == s_propColWidths) { if (w.type() == QtWidget::Table) { QHeaderView* hdr = w.table()->horizontalHeader(); bool skipLast = hdr && hdr->stretchLastSection(); ObjList* list = value.split(',',false); int col = 0; for (ObjList* o = list->skipNull(); o; o = o->skipNext(), col++) { if (skipLast && col == w.table()->columnCount() - 1) break; int width = (static_cast(o->get()))->toInteger(-1); if (width >= 0) w.table()->setColumnWidth(col,width); } TelEngine::destruct(list); } } else if (prop == s_propSorting) { if (w.type() == QtWidget::Table) { ObjList* list = value.split(',',false); String* tmp = static_cast((*list)[0]); int col = tmp ? tmp->toInteger(-1) : -1; if (col >= 0) { tmp = static_cast((*list)[1]); bool asc = tmp ? tmp->toBoolean(true) : true; w.table()->sortItems(col,asc ? Qt::AscendingOrder : Qt::DescendingOrder); } TelEngine::destruct(list); } } else if (prop == s_propSizes) { if (w.type() == QtWidget::Splitter) { QList list = QtClient::str2IntList(value); w.splitter()->setSizes(list); } } else if (prop == s_propWindowFlags) { QWidget* wid = (name == m_id || name == m_oldId) ? this : w.widget(); QtClient::applyWindowFlags(wid,value); } else if (prop == s_propHHeader) { // Show/hide the horizontal header ok = ((w.type() == QtWidget::Table || w.type() == QtWidget::CustomTable) && value.isBoolean() && w.table()->horizontalHeader()); if (ok) w.table()->horizontalHeader()->setVisible(value.toBoolean()); } else ok = handled = false; if (ok) DDebug(ClientDriver::self(),DebugAll, "Applied dynamic property %s='%s' for object='%s'", prop.c_str(),value.c_str(),name.c_str()); else if (handled) Debug(ClientDriver::self(),DebugMild, "Failed to apply dynamic property %s='%s' for object='%s'", prop.c_str(),value.c_str(),name.c_str()); return false; } if (event->type() == QEvent::KeyPress) { String action; bool filter = false; if (!QtClient::filterKeyEvent(obj,static_cast(event), action,filter,this)) return QWidget::eventFilter(obj,event); if (action && Client::self()) Client::self()->action(this,action); return filter; } if (event->type() == QEvent::ContextMenu) { if (handleContextMenuEvent(static_cast(event),obj)) return false; } if (event->type() == QEvent::Enter) { QtClient::updateImageFromMouse(obj,true,true); return QWidget::eventFilter(obj,event); } if (event->type() == QEvent::Leave) { QtClient::updateImageFromMouse(obj,true,false); return QWidget::eventFilter(obj,event); } if (event->type() == QEvent::MouseButtonPress) { QtClient::updateImageFromMouse(obj,false,true); return QWidget::eventFilter(obj,event); } if (event->type() == QEvent::MouseButtonRelease) { QtClient::updateImageFromMouse(obj,false,false); return QWidget::eventFilter(obj,event); } return QWidget::eventFilter(obj,event); } // Handle key pressed events void QtWindow::keyPressEvent(QKeyEvent* event) { if (!(Client::self() && event)) { QWidget::keyPressEvent(event); return; } QVariant var = this->property("_yate_keypress_redirect"); QString child = var.toString(); if (child.size() > 0 && QtClient::sendEvent(*event,this,child)) { QWidget* wid = qFindChild(this,child); if (wid) wid->setFocus(); return; } if (event->key() == Qt::Key_Backspace) Client::self()->backspace(m_id,this); QWidget::keyPressEvent(event); } // Show hide window. Notify the client void QtWindow::setVisible(bool visible) { // Override position for notification windows if (visible && isShownNormal() && QtClient::getBoolProperty(wndWidget(),"_yate_notificationwindow")) { // Don't move m_moving = -1; #ifndef Q_WS_MAC // Detect unavailable screen space position and move the window in the apropriate position // bottom/right/none: move it in the right/bottom corner. // top: move it in the right/top corner. // left: move it in the left/bottom corner. int pos = QtClient::PosNone; if (QtClient::getScreenUnavailPos(this,pos)) { if (0 != (pos & (QtClient::PosBottom | QtClient::PosRight)) || pos == QtClient::PosNone) QtClient::moveWindow(this,QtClient::CornerBottomRight); else if (0 != (pos & QtClient::PosTop)) QtClient::moveWindow(this,QtClient::CornerTopRight); else QtClient::moveWindow(this,QtClient::CornerBottomLeft); } #else QtClient::moveWindow(this,QtClient::CornerTopRight); #endif } if (visible && isMinimized()) showNormal(); else QWidget::setVisible(visible); // Notify the client on window visibility changes bool changed = (m_visible != visible); m_visible = visible; if (changed && Client::self()) { QVariant var; if (this) var = this->property("dynamicUiActionVisibleChanged"); if (!var.toBool()) Client::self()->toggle(this,YSTRING("window_visible_changed"),m_visible); else { Message* m = new Message("ui.action"); m->addParam("action","window_visible_changed"); m->addParam("visible",String::boolText(m_visible)); m->addParam("window",m_id); Engine::enqueue(m); } } if (!m_visible && QtClient::getBoolProperty(wndWidget(),"_yate_destroyonhide")) { DDebug(QtDriver::self(),DebugAll, "Window(%s) setVisible(false) set delete later [%p]",m_id.c_str(),this); QObject::deleteLater(); } // Destroy owned dialogs if (!m_visible) { QList d = qFindChildren(this); for (int i = 0; i < d.size(); i++) d[i]->deleteLater(); } } // Show the window void QtWindow::show() { setVisible(true); m_maximized = m_maximized || isMaximized(); if (m_maximized) setWindowState(Qt::WindowMaximized); } // Hide the window void QtWindow::hide() { setVisible(false); } void QtWindow::size(int width, int height) { Debug(QtDriver::self(),DebugStub,"QtWindow(%s)::size(%d,%d) [%p]",m_id.c_str(),width,height,this); } void QtWindow::move(int x, int y) { DDebug(QtDriver::self(),DebugAll,"QtWindow(%s)::move(%d,%d) [%p]",m_id.c_str(),x,y,this); QWidget::move(x,y); } void QtWindow::moveRel(int dx, int dy) { DDebug(QtDriver::self(),DebugAll,"QtWindow::moveRel(%d,%d) [%p]",dx,dy,this); } bool QtWindow::related(const Window* wnd) const { DDebug(QtDriver::self(),DebugAll,"QtWindow::related(%p) [%p]",wnd,this); return false; } void QtWindow::menu(int x, int y) { DDebug(QtDriver::self(),DebugAll,"QtWindow::menu(%d,%d) [%p]",x,y,this); } // Create a modal dialog bool QtWindow::createDialog(const String& name, const String& title, const String& alias, const NamedList* params) { QtDialog* d = new QtDialog(this); if (d->show(name,title,alias,params)) return true; d->deleteLater(); return false; } // Destroy a modal dialog bool QtWindow::closeDialog(const String& name) { QDialog* d = qFindChild(this,QtClient::setUtf8(name)); if (!d) return false; d->deleteLater(); return true; } // Load UI file and setup the window void QtWindow::doPopulate() { Debug(QtDriver::self(),DebugAll,"Populating window '%s' [%p]",m_id.c_str(),this); QWidget* formWidget = loadUI(m_description,this,m_id); if (!formWidget) return; // Set window title decoration flags to avoid pos/size troubles with late decoration QVariant var = formWidget->property(s_propWindowFlags); if (var.type() == QVariant::Invalid) { String flgs = "title,sysmenu,minimize,close"; // Add maximize only if allowed if (formWidget->maximumWidth() == QWIDGETSIZE_MAX || formWidget->maximumHeight() == QWIDGETSIZE_MAX) flgs.append("maximize",","); formWidget->setProperty(s_propWindowFlags,QVariant(QtClient::setUtf8(flgs))); } setMinimumSize(formWidget->minimumSize().width(),formWidget->minimumSize().height()); setMaximumSize(formWidget->maximumSize().width(),formWidget->maximumSize().height()); m_x = formWidget->pos().x(); m_y = formWidget->pos().y(); m_width = formWidget->width(); m_height = formWidget->height(); move(m_x,m_y); QWidget::resize(m_width,m_height); QtClient::setWidget(this,formWidget); m_widget = YQT_OBJECT_NAME(formWidget); String wTitle; QtClient::getUtf8(wTitle,formWidget->windowTitle()); title(wTitle); setWindowIcon(formWidget->windowIcon()); setStyleSheet(formWidget->styleSheet()); } // Initialize window void QtWindow::doInit() { DDebug(QtDriver::self(),DebugAll,"Initializing window '%s' [%p]", m_id.c_str(),this); // Create window's dynamic properties from config Configuration cfg(Engine::configFile(m_oldId),false); NamedList* sectGeneral = cfg.getSection("general"); if (sectGeneral) addDynamicProps(this,*sectGeneral); // Load window data m_mainWindow = s_cfg.getBoolValue(m_oldId,"mainwindow"); m_saveOnClose = s_cfg.getBoolValue(m_oldId,"save",true); if (m_id != m_oldId) m_saveOnClose = s_cfg.getBoolValue(m_oldId,"savealias",m_saveOnClose); NamedList* sect = s_save.getSection(m_id); if (sect) { m_maximized = sect->getBoolValue("maximized"); m_x = sect->getIntValue("x",m_x); m_y = sect->getIntValue("y",m_y); m_width = sect->getIntValue("width",m_width); m_height = sect->getIntValue("height",m_height); m_visible = sect->getBoolValue("visible"); } else { if (m_saveOnClose) Debug(QtDriver::self(),DebugNote,"Window(%s) not found in config [%p]", m_id.c_str(),this); m_visible = s_cfg.getBoolValue(m_oldId,"visible"); // Make sure the window is shown in the available geometry QDesktopWidget* d = QApplication::desktop(); if (d) { QRect r = d->availableGeometry(this); m_x = r.x(); m_y = r.y(); } } m_visible = m_mainWindow || m_visible; if (!m_width) m_width = this->width(); if (!m_height) m_height = this->height(); move(m_x,m_y); QWidget::resize(m_width,m_height); // Build custom UI widgets from frames owned by this widget QtClient::buildFrameUiWidgets(this); // Create custom widgets from // _yate_identity=customwidget|[separator=sep|] sep widgetclass sep widgetname [sep param=value] QList frm = qFindChildren(this); for (int i = 0; i < frm.size(); i++) { String create; QtClient::getProperty(frm[i],"_yate_identity",create); if (!create.startSkip("customwidget|",false)) continue; char sep = '|'; // Check if we have another separator if (create.startSkip("separator=",false)) { if (create.length() < 2) continue; sep = create.at(0); create = create.substr(2); } ObjList* list = create.split(sep,false); String type; String name; NamedList params(""); int what = 0; for (ObjList* o = list->skipNull(); o; o = o->skipNext(), what++) { GenObject* p = o->get(); if (what == 0) type = p->toString(); else if (what == 1) name = p->toString(); else { // Decode param int pos = p->toString().find('='); if (pos != -1) params.addParam(p->toString().substr(0,pos),p->toString().substr(pos + 1)); } } TelEngine::destruct(list); params.addParam("parentwindow",m_id); NamedString* pw = new NamedString("parentwidget"); QtClient::getUtf8(*pw,frm[i]->objectName()); params.addParam(pw); QObject* obj = (QObject*)UIFactory::build(type,name,¶ms); if (!obj) continue; QWidget* wid = qobject_cast(obj); if (wid) QtClient::setWidget(frm[i],wid); else { obj->setParent(frm[i]); QtCustomObject* customObj = qobject_cast(obj); if (customObj) customObj->parentChanged(); } } // Add the first menubar to layout QList menuBars = qFindChildren(this); if (menuBars.size() && layout()) { layout()->setMenuBar(menuBars[0]); // Decrease minimum size policy to make sure the layout is made properly if (wndWidget()) { int h = menuBars[0]->height(); int min = wndWidget()->minimumHeight(); if (min > h) wndWidget()->setMinimumHeight(min - h); else wndWidget()->setMinimumHeight(0); } #ifdef Q_WS_MAC if (m_mainWindow) { // Create a parentless menu bar to be set as the default application menu by copying it from the main window menu DDebug(QtDriver::self(),DebugAll,"Setting as default menu bar the menu bar of window '%s' [%p]", m_id.c_str(),this); QMenuBar* mainMenu = menuBars[0]; QMenuBar* defaultMenu = new QMenuBar(0); QList topActions = mainMenu->actions(); for (int i = 0; i < topActions.count(); i++) { QMenu* menu = topActions[i]->menu(); if (menu) { QMenu* m = new QMenu(menu->title(),defaultMenu); String tmp; QtClient::getProperty(menu,YSTRING("_yate_menuNoCopy"),tmp); if (tmp.toBoolean()) continue; defaultMenu->addMenu(m); QList actions = menu->actions(); for (int j = 0; j < actions.count(); j++) { QAction* act = actions[j]; tmp.clear(); QtClient::getProperty(act,YSTRING("_yate_menuNoCopy"),tmp); if (tmp.toBoolean()) continue; m->addAction(act); } } } } #endif } // Create window's children dynamic properties from config unsigned int n = cfg.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* sect = cfg.getSection(i); if (sect && *sect && *sect != "general") addDynamicProps(qFindChild(this,sect->c_str()),*sect); } // Process "_yate_setaction" property for our children QtClient::setAction(this); // Connect actions' signal QList actions = qFindChildren(this); for (int i = 0; i < actions.size(); i++) { String addToWidget; QtClient::getProperty(actions[i],"dynamicAddToParent",addToWidget); if (addToWidget && addToWidget.toBoolean()) QWidget::addAction(actions[i]); if (actions[i]->isCheckable()) QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool))); else QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action())); } // Connect combo boxes signals QList combos = qFindChildren(this); for (int i = 0; i < combos.size(); i++) { QtClient::connectObjects(combos[i],SIGNAL(activated(int)),this,SLOT(selectionChanged())); connectTextChanged(combos[i]); } // Connect abstract buttons (check boxes and radio/push/tool buttons) signals QList buttons = qFindChildren(this); for(int i = 0; i < buttons.size(); i++) if (QtClient::autoConnect(buttons[i])) connectButton(buttons[i]); // Connect group boxes signals QList grp = qFindChildren(this); for(int i = 0; i < grp.size(); i++) if (grp[i]->isCheckable()) QtClient::connectObjects(grp[i],SIGNAL(toggled(bool)),this,SLOT(toggled(bool))); // Connect sliders signals QList sliders = qFindChildren(this); for (int i = 0; i < sliders.size(); i++) QtClient::connectObjects(sliders[i],SIGNAL(valueChanged(int)),this,SLOT(selectionChanged())); // Connect calendar widget signals QList cals = qFindChildren(this); for (int i = 0; i < cals.size(); i++) QtClient::connectObjects(cals[i],SIGNAL(selectionChanged()),this,SLOT(selectionChanged())); // Connect list boxes signals QList lists = qFindChildren(this); for (int i = 0; i < lists.size(); i++) { QtClient::connectObjects(lists[i],SIGNAL(itemDoubleClicked(QListWidgetItem*)), this,SLOT(doubleClick())); QtClient::connectObjects(lists[i],SIGNAL(itemActivated(QListWidgetItem*)), this,SLOT(doubleClick())); QtClient::connectObjects(lists[i],SIGNAL(currentRowChanged(int)), this,SLOT(selectionChanged())); } // Connect tab widget signals QList tabs = qFindChildren(this); for (int i = 0; i < tabs.size(); i++) QtClient::connectObjects(tabs[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged())); // Connect stacked widget signals QList sw = qFindChildren(this); for (int i = 0; i < sw.size(); i++) QtClient::connectObjects(sw[i],SIGNAL(currentChanged(int)),this,SLOT(selectionChanged())); // Connect line edit signals QList le = qFindChildren(this); for (int i = 0; i < le.size(); i++) connectTextChanged(le[i]); // Connect text edit signals QList te = qFindChildren(this); for (int i = 0; i < te.size(); i++) connectTextChanged(te[i]); // Process tables: // Insert a column and connect signals // Hide columns starting with "hidden:" QList tables = qFindChildren(this); for (int i = 0; i < tables.size(); i++) { bool nonCustom = (0 == qobject_cast(tables[i])); // Horizontal header QHeaderView* hdr = tables[i]->horizontalHeader(); // Stretch last column bool b = QtClient::getBoolProperty(tables[i],"_yate_horizontalstretch",true); hdr->setStretchLastSection(b); String tmp; QtClient::getProperty(tables[i],"_yate_horizontalheader_align",tmp); if (tmp) { int def = hdr->defaultAlignment(); hdr->setDefaultAlignment((Qt::Alignment)QtClient::str2align(tmp,def)); } if (!QtClient::getBoolProperty(tables[i],"_yate_horizontalheader",true)) hdr->hide(); // Vertical header hdr = tables[i]->verticalHeader(); int itemH = QtClient::getIntProperty(tables[i],"_yate_rowheight"); if (itemH > 0) hdr->setDefaultSectionSize(itemH); if (!QtClient::getBoolProperty(tables[i],"_yate_verticalheader")) hdr->hide(); else { int width = QtClient::getIntProperty(tables[i],"_yate_verticalheaderwidth"); if (width > 0) hdr->setFixedWidth(width); if (!QtClient::getBoolProperty(tables[i],"_yate_allowvheaderresize")) hdr->setResizeMode(QHeaderView::Fixed); } if (nonCustom) { // Set _yate_save_props QVariant var = tables[i]->property(s_propsSave); if (var.type() != QVariant::StringList) { // Create the property if not found, ignore it if not a string list if (var.type() == QVariant::Invalid) var = QVariant(QVariant::StringList); else Debug(QtDriver::self(),DebugNote, "Window(%s) table '%s' already has a non string list property %s [%p]", m_id.c_str(),YQT_OBJECT_NAME(tables[i]),s_propsSave.c_str(),this); } if (var.type() == QVariant::StringList) { // Make sure saved properties exists to allow them to be restored QStringList sl = var.toStringList(); bool changed = createProperty(tables[i],s_propColWidths,QVariant::String,this,&sl); changed = createProperty(tables[i],s_propSorting,QVariant::String,this,&sl) || changed; if (changed) tables[i]->setProperty(s_propsSave,QVariant(sl)); } } TableWidget t(tables[i]); // Insert the column containing the ID t.addColumn(0,0,"hidden:id"); // Hide columns for (int i = 0; i < t.columnCount(); i++) { String name; t.getHeaderText(i,name,false); if (name.startsWith("hidden:")) t.table()->setColumnHidden(i,true); } // Connect signals QtClient::connectObjects(t.table(),SIGNAL(cellDoubleClicked(int,int)), this,SLOT(doubleClick())); #if 0 // This would generate action() twice since QT will signal both cell and // table item double click QtClient::connectObjects(t.table(),SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this,SLOT(doubleClick())); #endif String noSel; getProperty(t.name(),"dynamicNoItemSelChanged",noSel); if (!noSel.toBoolean()) QtClient::connectObjects(t.table(),SIGNAL(itemSelectionChanged()), this,SLOT(selectionChanged())); // Optionally connect cell clicked // This is done when we want to generate a select() or action() from cell clicked String cellClicked; getProperty(t.name(),"dynamicCellClicked",cellClicked); if (cellClicked) { if (cellClicked == "selectionChanged") QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)), this,SLOT(selectionChanged())); else if (cellClicked == "doubleClick") QtClient::connectObjects(t.table(),SIGNAL(cellClicked(int,int)), this,SLOT(doubleClick())); } } // Restore saved children properties if (sect) { unsigned int n = sect->length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = sect->getParam(i); if (!ns) continue; String prop(ns->name()); if (!prop.startSkip("property:",false)) continue; int pos = prop.find(":"); if (pos > 0) { String wName = prop.substr(0,pos); String pName = prop.substr(pos + 1); DDebug(QtDriver::self(),DebugAll, "Window(%s) restoring property %s=%s for child '%s' [%p]", m_id.c_str(),pName.c_str(),ns->c_str(),wName.c_str(),this); setProperty(wName,pName,*ns); } } } // Install event filter and apply dynamic properties QList w = qFindChildren(this); w.append(this); for (int i = 0; i < w.size(); i++) { QList props = w[i]->dynamicPropertyNames(); // Check for our dynamic properties int j = 0; for (j = 0; j < props.size(); j++) if (props[j].startsWith(s_yatePropPrefix)) break; if (j == props.size()) continue; // Add event hook to be used when a dynamic property changes w[i]->installEventFilter(this); // Fake dynamic property change to apply them for (j = 0; j < props.size(); j++) { if (!props[j].startsWith(s_yatePropPrefix)) continue; QDynamicPropertyChangeEvent ev(props[j]); eventFilter(w[i],&ev); } } qRegisterMetaType("QModelIndex"); qRegisterMetaType("QTextCursor"); // Force window visibility change notification by changing the visibility flag // Some controls might need to be updated m_visible = !m_visible; if (m_visible) { // Disable _yate_destroyonhide property: avoid destroying the window now String tmp; getProperty(m_id,"_yate_destroyonhide",tmp); if (tmp) setProperty(m_id,"_yate_destroyonhide",String::boolText(false)); hide(); if (tmp) setProperty(m_id,"_yate_destroyonhide",tmp); } else show(); } // Mouse button pressed notification void QtWindow::mousePressEvent(QMouseEvent* event) { if (m_moving >= 0 && Qt::LeftButton == event->button() && isShownNormal()) { m_movePos = event->globalPos(); m_moving = 1; } } // Mouse button release notification void QtWindow::mouseReleaseEvent(QMouseEvent* event) { if (m_moving >= 0 && Qt::LeftButton == event->button()) m_moving = 0; } // Move the window if the moving flag is set void QtWindow::mouseMoveEvent(QMouseEvent* event) { if (m_moving <= 0 || Qt::LeftButton != event->buttons() || !isShownNormal()) return; int cx = event->globalPos().x() - m_movePos.x(); int cy = event->globalPos().y() - m_movePos.y(); if (cx || cy) { m_movePos = event->globalPos(); QWidget::move(x() + cx,y() + cy); } } // Handle context menu events. Return true if handled bool QtWindow::handleContextMenuEvent(QContextMenuEvent* event, QObject* obj) { if (!(event && obj)) return false; String mname; QtClient::getProperty(obj,s_propContextMenu,mname); XDebug(ClientDriver::self(),DebugAll, "Window(%s) handleContextMenuEvent() obj=%s menu=%s [%p]", m_id.c_str(),YQT_OBJECT_NAME(obj),mname.c_str(),this); QMenu* m = mname ? qFindChild(this,QtClient::setUtf8(mname)) : 0; if (m) m->exec(event->globalPos()); return m != 0; } /* * QtDialog */ // Destructor. Notify the client if not exiting QtDialog::~QtDialog() { QtWindow* w = parentWindow(); if (w && m_notifyOnClose && Client::valid()) QtClient::self()->action(w,buildActionName(m_notifyOnClose,m_notifyOnClose)); DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) QtDialog(%s) destroyed [%p]", w ? w->id().c_str() : "",YQT_OBJECT_NAME(this),w); } // Initialize dialog. Load the widget. // Connect non checkable actions to own slot. // Connect checkable actions/buttons to parent window's slot // Display the dialog on success bool QtDialog::show(const String& name, const String& title, const String& alias, const NamedList* params) { QtWindow* w = parentWindow(); if (!w) return false; QWidget* widget = QtWindow::loadUI(Client::s_skinPath + s_cfg.getValue(name,"description"),this,name); if (!widget) return false; QtClient::getProperty(widget,"_yate_notifyonclose",m_notifyOnClose); setObjectName(QtClient::setUtf8(alias ? alias : name)); setMinimumSize(widget->minimumSize().width(),widget->minimumSize().height()); setMaximumSize(widget->maximumSize().width(),widget->maximumSize().height()); resize(widget->width(),widget->height()); QtClient::setWidget(this,widget); if (title) setWindowTitle(QtClient::setUtf8(title)); else if (widget->windowTitle().length()) setWindowTitle(widget->windowTitle()); else setWindowTitle(w->windowTitle()); // Connect abstract buttons (check boxes and radio/push/tool buttons) signals QList buttons = qFindChildren(widget); for(int i = 0; i < buttons.size(); i++) { if (!QtClient::autoConnect(buttons[i])) continue; if (!buttons[i]->isCheckable()) QtClient::connectObjects(buttons[i],SIGNAL(clicked()),this,SLOT(action())); else QtClient::connectObjects(buttons[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool))); } // Connect actions' signal QList actions = qFindChildren(widget); for (int i = 0; i < actions.size(); i++) { if (!QtClient::autoConnect(actions[i])) continue; if (!actions[i]->isCheckable()) QtClient::connectObjects(actions[i],SIGNAL(triggered()),this,SLOT(action())); else QtClient::connectObjects(actions[i],SIGNAL(toggled(bool)),w,SLOT(toggled(bool))); } String* flags = 0; String tmp; QtClient::getProperty(widget,s_propWindowFlags,tmp); if (tmp) flags = &tmp; if (params) { if (!flags) flags = params->getParam(s_propWindowFlags); m_closable = params->getBoolValue(YSTRING("closable"),"true"); w->setParams(*params); } if (flags) QtClient::applyWindowFlags(this,*flags); setWindowModality(Qt::WindowModal); QDialog::show(); return true; } // Notify client void QtDialog::action() { QtWindow* w = parentWindow(); if (!w) return; DDebug(QtDriver::self(),DebugAll,"QtWindow(%s) dialog action '%s' [%p]", w->id().c_str(),YQT_OBJECT_NAME(sender()),w); if (!QtClient::self() || QtClient::changing()) return; String name; QtClient::getIdentity(sender(),name); if (name && QtClient::self()->action(w,buildActionName(name,name))) deleteLater(); } // Delete the dialog void QtDialog::closeEvent(QCloseEvent* event) { if (m_closable) { QDialog::closeEvent(event); deleteLater(); } else event->ignore(); } // Destroy the dialog void QtDialog::reject() { if (!m_closable) return; QDialog::reject(); deleteLater(); } /** * QtClient */ QtClient::QtClient() : Client("Qt Client") { m_oneThread = Engine::config().getBoolValue("client","onethread",true); s_save = Engine::configFile("qt4client",true); s_save.load(); // Fill QT styles s_qtStyles.addParam("IaOraKde","iaorakde"); s_qtStyles.addParam("QWindowsStyle","windows"); s_qtStyles.addParam("QMacStyle","mac"); s_qtStyles.addParam("QMotifStyle","motif"); s_qtStyles.addParam("QCDEStyle","cde"); s_qtStyles.addParam("QWindowsXPStyle","windowsxp"); s_qtStyles.addParam("QCleanlooksStyle","cleanlooks"); s_qtStyles.addParam("QPlastiqueStyle","plastique"); s_qtStyles.addParam("QGtkStyle","gtk"); s_qtStyles.addParam("IaOraQt","iaoraqt"); s_qtStyles.addParam("OxygenStyle","oxygen"); s_qtStyles.addParam("PhaseStyle","phase"); } QtClient::~QtClient() { } void QtClient::cleanup() { Client::cleanup(); m_events.clear(); Client::save(s_save); QtWindow::clearUICache(); m_app->quit(); if (!m_app->startingUp()) delete m_app; } void QtClient::run() { const char* style = Engine::config().getValue("client","style"); if (style && !QApplication::setStyle(QString::fromUtf8(style))) Debug(ClientDriver::self(),DebugWarn,"Could not set Qt style '%s'",style); int argc = 0; char* argv = 0; m_app = new QApplication(argc,&argv); m_app->setQuitOnLastWindowClosed(false); updateAppStyleSheet(); String imgRead; QList imgs = QImageReader::supportedImageFormats(); for (int i = 0; i < imgs.size(); i++) imgRead.append(imgs[i].constData(),","); imgRead = "read image formats '" + imgRead + "'"; Debug(ClientDriver::self(),DebugInfo,"QT client start running (version=%s) %s", qVersion(),imgRead.c_str()); if (!QSound::isAvailable()) Debug(ClientDriver::self(),DebugWarn,"QT sounds are not available"); // Create events proxy m_events.append(new QtEventProxy(QtEventProxy::Timer)); m_events.append(new QtEventProxy(QtEventProxy::AllHidden,m_app)); if (Engine::exiting()) return; Client::run(); } void QtClient::main() { if (!Engine::exiting()) m_app->exec(); } void QtClient::lock() {} void QtClient::unlock() {} void QtClient::allHidden() { Debug(QtDriver::self(),DebugInfo,"QtClient::allHiden() counter=%d",s_allHiddenQuit); if (s_allHiddenQuit > 0) return; quit(); } bool QtClient::createWindow(const String& name, const String& alias) { String parent = s_cfg.getValue(name,"parent"); QtWindow* parentWnd = 0; if (!TelEngine::null(parent)) { ObjList* o = m_windows.find(parent); if (o) parentWnd = YOBJECT(QtWindow,o->get()); } QtWindow* w = new QtWindow(name,s_skinPath + s_cfg.getValue(name,"description"),alias,parentWnd); if (w) { Debug(QtDriver::self(),DebugAll,"Created window name=%s alias=%s with parent=(%s [%p]) (%p)", name.c_str(),alias.c_str(),parent.c_str(),parentWnd,w); // Remove the old window ObjList* o = m_windows.find(w->id()); if (o) Client::self()->closeWindow(w->id(),false); w->populate(); m_windows.append(w); return true; } else Debug(QtDriver::self(),DebugGoOn,"Could not create window name=%s alias=%s", name.c_str(),alias.c_str()); return false; } void QtClient::loadWindows(const char* file) { if (!file) s_cfg = s_skinPath + "qt4client.rc"; else s_cfg = String(file); s_cfg.load(); Debug(QtDriver::self(),DebugInfo,"Loading Windows"); unsigned int n = s_cfg.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* l = s_cfg.getSection(i); if (l && l->getBoolValue(YSTRING("enabled"),true)) createWindow(*l); } } bool QtClient::isUIThread() { return (QApplication::instance() && QApplication::instance()->thread() == QThread::currentThread()); } // Open a file open dialog window // Parameters that can be specified include 'caption', // 'dir', 'filter', 'selectedfilter', 'confirmoverwrite', 'choosedir' bool QtClient::chooseFile(Window* parent, NamedList& params) { QtWindow* wnd = static_cast(parent); QFileDialog* dlg = new QFileDialog(wnd,setUtf8(params.getValue(YSTRING("caption"))), setUtf8(params.getValue(YSTRING("dir")))); if (wnd) dlg->setWindowIcon(wnd->windowIcon()); // Connect signals String* action = params.getParam(YSTRING("action")); if (wnd && !null(action)) { dlg->setObjectName(setUtf8(*action)); QtClient::connectObjects(dlg,SIGNAL(accepted()),wnd,SLOT(chooseFileAccepted())); QtClient::connectObjects(dlg,SIGNAL(rejected()),wnd,SLOT(chooseFileRejected())); } // Destroy it when closed dlg->setAttribute(Qt::WA_DeleteOnClose); // This dialog should always stay on top dlg->setWindowFlags(dlg->windowFlags() | Qt::WindowStaysOnTopHint); if (params.getBoolValue(YSTRING("modal"),true)) dlg->setWindowModality(Qt::WindowModal); // Filters NamedString* f = params.getParam(YSTRING("filters")); if (f) { QStringList filters; ObjList* obj = f->split('|',false); for (ObjList* o = obj->skipNull(); o; o = o->skipNext()) filters.append(QtClient::setUtf8(o->get()->toString())); TelEngine::destruct(obj); dlg->setFilters(filters); } QString flt = QtClient::setUtf8(params.getValue(YSTRING("selectedfilter"))); if (flt.length()) dlg->selectFilter(flt); if (params.getBoolValue(YSTRING("save"))) dlg->setAcceptMode(QFileDialog::AcceptSave); else dlg->setAcceptMode(QFileDialog::AcceptOpen); // Choose options if (params.getBoolValue(YSTRING("choosefile"),true)) { if (params.getBoolValue(YSTRING("chooseanyfile"))) dlg->setFileMode(QFileDialog::AnyFile); else if (params.getBoolValue(YSTRING("multiplefiles"))) dlg->setFileMode(QFileDialog::ExistingFiles); else dlg->setFileMode(QFileDialog::ExistingFile); } else dlg->setFileMode(QFileDialog::DirectoryOnly); dlg->selectFile(QtClient::setUtf8(params.getValue(YSTRING("selectedfile")))); dlg->setVisible(true); return true; } bool QtClient::action(Window* wnd, const String& name, NamedList* params) { String tmp = name; if (tmp.startSkip("openurl:",false)) return openUrl(tmp); return Client::action(wnd,name,params); } // Create a sound object. Append it to the global list bool QtClient::createSound(const char* name, const char* file, const char* device) { if (!(QSound::isAvailable() && name && *name && file && *file)) return false; Lock lock(ClientSound::s_soundsMutex); if (ClientSound::s_sounds.find(name)) return false; ClientSound::s_sounds.append(new QtSound(name,file,device)); DDebug(ClientDriver::self(),DebugAll,"Added sound=%s file=%s device=%s", name,file,device); return true; } // Build a date/time string from UTC time bool QtClient::formatDateTime(String& dest, unsigned int secs, const char* format, bool utc) { if (!(format && *format)) return false; QtClient::getUtf8(dest,formatDateTime(secs,format,utc)); return true; } // Build a date/time QT string from UTC time QString QtClient::formatDateTime(unsigned int secs, const char* format, bool utc) { QDateTime time; if (utc) time.setTimeSpec(Qt::UTC); time.setTime_t(secs); return time.toString(format); } // Retrieve an object's QtWindow parent QtWindow* QtClient::parentWindow(QObject* obj) { for (; obj; obj = obj->parent()) { QtWindow* w = qobject_cast(obj); if (w) return w; } return 0; } // Save an object's property into parent window's section. Clear it on failure bool QtClient::saveProperty(QObject* obj, const String& prop, QtWindow* owner) { if (!obj) return false; if (!owner) owner = parentWindow(obj); if (!owner) return false; String value; bool ok = getProperty(obj,prop,value); String pName; pName << "property:" << YQT_OBJECT_NAME(obj) << ":" << prop; if (ok) s_save.setValue(owner->id(),pName,value); else s_save.clearKey(owner->id(),pName); return ok; } // Set or an object's property bool QtClient::setProperty(QObject* obj, const char* name, const String& value) { if (!(obj && name && *name)) return false; QVariant var = obj->property(name); const char* err = 0; bool ok = false; switch (var.type()) { case QVariant::String: ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value))); break; case QVariant::Bool: ok = obj->setProperty(name,QVariant(value.toBoolean())); break; case QVariant::Int: ok = obj->setProperty(name,QVariant(value.toInteger())); break; case QVariant::UInt: ok = obj->setProperty(name,QVariant((unsigned int)value.toInteger())); break; case QVariant::Icon: ok = obj->setProperty(name,QVariant(QIcon(QtClient::setUtf8(value)))); break; case QVariant::Pixmap: ok = obj->setProperty(name,QVariant(QPixmap(QtClient::setUtf8(value)))); break; case QVariant::Double: ok = obj->setProperty(name,QVariant(value.toDouble())); break; case QVariant::KeySequence: ok = obj->setProperty(name,QVariant(QtClient::setUtf8(value))); break; case QVariant::StringList: { QStringList qList; if (value) qList.append(setUtf8(value)); ok = obj->setProperty(name,QVariant(qList)); } break; case QVariant::Invalid: err = "no such property"; break; default: err = "unsupported type"; } if (ok) DDebug(ClientDriver::self(),DebugAll,"Set property %s=%s for object '%s'", name,value.c_str(),YQT_OBJECT_NAME(obj)); else DDebug(ClientDriver::self(),DebugNote, "Failed to set %s=%s (type=%s) for object '%s': %s", name,value.c_str(),var.typeName(),YQT_OBJECT_NAME(obj),err); return ok; } // Get an object's property bool QtClient::getProperty(QObject* obj, const char* name, String& value) { if (!(obj && name && *name)) return false; QVariant var = obj->property(name); if (var.type() == QVariant::StringList) { NamedList* l = static_cast(value.getObject(YATOM("NamedList"))); if (l) copyParams(*l,var.toStringList()); else getUtf8(value,var.toStringList().join(",")); DDebug(ClientDriver::self(),DebugAll,"Got list property %s for object '%s'", name,YQT_OBJECT_NAME(obj)); return true; } if (var.canConvert(QVariant::String)) { QtClient::getUtf8(value,var.toString()); DDebug(ClientDriver::self(),DebugAll,"Got property %s=%s for object '%s'", name,value.c_str(),YQT_OBJECT_NAME(obj)); return true; } DDebug(ClientDriver::self(),DebugNote, "Failed to get property '%s' (type=%s) for object '%s': %s", name,var.typeName(),YQT_OBJECT_NAME(obj), ((var.type() == QVariant::Invalid) ? "no such property" : "unsupported type")); return false; } // Copy a string list to a list of parameters void QtClient::copyParams(NamedList& dest, const QStringList& src) { for (int i = 0; i < src.size(); i++) { if (!src[i].length()) continue; int pos = src[i].indexOf('='); String name; if (pos >= 0) { getUtf8(name,src[i].left(pos)); getUtf8(dest,name,src[i].right(src[i].length() - pos - 1)); } else { getUtf8(name,src[i]); dest.addParam(name,""); } } } // Copy a list of parameters to string list void QtClient::copyParams(QStringList& dest, const NamedList& src) { unsigned int n = src.length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = src.getParam(i); if (ns) dest.append(setUtf8(ns->name() + "=" + *ns)); } } // Build QObject properties from list void QtClient::buildProps(QObject* obj, const String& props) { if (!(obj && props)) return; ObjList* list = props.split(',',false); for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { String* s = static_cast(o->get()); int pos = s->find('='); if (pos < 1) continue; String ptype = s->substr(pos + 1); QVariant::Type t = (QVariant::Type)lookup(ptype,s_qVarType,QVariant::Invalid); if (t == QVariant::Invalid) { Debug(ClientDriver::self(),DebugStub, "QtClient::buildProps() unhandled type '%s'",ptype.c_str()); continue; } String pname = s->substr(0,pos); QVariant existing = obj->property(pname); if (existing.type() == QVariant::Invalid) { obj->setProperty(pname,QVariant(t)); continue; } Debug(ClientDriver::self(),DebugNote, "Can't create property '%s' type=%s for object (%p,%s): already exists", pname.c_str(),ptype.c_str(),obj,YQT_OBJECT_NAME(obj)); } TelEngine::destruct(list); } // Build custom UI widgets from frames owned by a widget void QtClient::buildFrameUiWidgets(QWidget* parent) { if (!parent) return; QList frm = qFindChildren(parent); for (int i = 0; i < frm.size(); i++) { if (!getBoolProperty(frm[i],"_yate_uiwidget")) continue; String name; String type; getProperty(frm[i],"_yate_uiwidget_name",name); getProperty(frm[i],"_yate_uiwidget_class",type); if (!(name && type)) continue; NamedList params(""); getProperty(frm[i],"_yate_uiwidget_params",params); QtWindow* w = static_cast(parent->window()); if (w) params.setParam("parentwindow",w->id()); getUtf8(params,"parentwidget",frm[i]->objectName(),true); QObject* obj = (QObject*)UIFactory::build(type,name,¶ms); if (!obj) continue; QWidget* wid = qobject_cast(obj); if (wid) QtClient::setWidget(frm[i],wid); else { obj->setParent(frm[i]); QtCustomObject* customObj = qobject_cast(obj); if (customObj) customObj->parentChanged(); } } } // Associate actions to buttons with '_yate_setaction' property set void QtClient::setAction(QWidget* parent) { if (!parent) return; QList tb = qFindChildren(parent); for (int i = 0; i < tb.size(); i++) { QVariant var = tb[i]->property("_yate_setaction"); if (var.toString().isEmpty()) continue; QAction* a = qFindChild(parent,var.toString()); if (a) tb[i]->setDefaultAction(a); } } // Build a menu object from a list of parameters QMenu* QtClient::buildMenu(const NamedList& params, const char* text, QObject* receiver, const char* triggerSlot, const char* toggleSlot, QWidget* parent, const char* aboutToShowSlot) { QMenu* menu = 0; unsigned int n = params.length(); for (unsigned int i = 0; i < n; i++) { NamedString* param = params.getParam(i); if (!(param && param->name().startsWith("item:"))) continue; if (!menu) menu = new QMenu(setUtf8(text),parent); NamedList* p = YOBJECT(NamedList,param); if (p) { QMenu* subMenu = buildMenu(*p,*param ? param->c_str() : p->getValue(YSTRING("title"),*p), receiver,triggerSlot,toggleSlot,menu); if (subMenu) menu->addMenu(subMenu); continue; } String name = param->name().substr(5); if (*param) { QAction* a = menu->addAction(QtClient::setUtf8(*param)); a->setObjectName(QtClient::setUtf8(name)); a->setParent(menu); setImage(a,params["image:" + name]); } else if (!name) menu->addSeparator()->setParent(menu); else { // Check if the action is already there QAction* a = 0; if (parent && parent->window()) a = qFindChild(parent->window(),QtClient::setUtf8(name)); if (a) menu->addAction(a); else Debug(ClientDriver::self(),DebugNote, "buildMenu(%s) action '%s' not found",params.c_str(),name.c_str()); } } if (!menu) return 0; // Set name menu->setObjectName(setUtf8(params)); setImage(menu,params["image:" + params]); // Apply properties // Format: property:object_name:property_name=value if (parent) for (unsigned int i = 0; i < n; i++) { NamedString* param = params.getParam(i); if (!(param && param->name().startsWith("property:"))) continue; int pos = param->name().find(':',9); if (pos < 9) continue; QObject* obj = qFindChild(parent,setUtf8(param->name().substr(9,pos - 9))); if (obj) setProperty(obj,param->name().substr(pos + 1),*param); } // Connect signals (direct children only: actions from sub-menus are already connected) QList list = qFindChildren(menu); for (int i = 0; i < list.size(); i++) { if (list[i]->isSeparator() || list[i]->parent() != menu) continue; if (list[i]->isCheckable()) QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),receiver,toggleSlot); else QtClient::connectObjects(list[i],SIGNAL(triggered()),receiver,triggerSlot); } if (!TelEngine::null(aboutToShowSlot)) QtClient::connectObjects(menu,SIGNAL(aboutToShow()),receiver,aboutToShowSlot); return menu; } // Wrapper for QObject::connect() used to put a debug mesage on failure bool QtClient::connectObjects(QObject* sender, const char* signal, QObject* receiver, const char* slot) { if (!(sender && signal && *signal && receiver && slot && *slot)) return false; bool ok = QObject::connect(sender,signal,receiver,slot); if (ok) DDebug(QtDriver::self(),DebugAll, "Connected sender=%s signal=%s to receiver=%s slot=%s", YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot); else Debug(QtDriver::self(),DebugWarn, "Failed to connect sender=%s signal=%s to receiver=%s slot=%s", YQT_OBJECT_NAME(sender),signal,YQT_OBJECT_NAME(receiver),slot); return ok; } // Insert a widget into another one replacing any existing children bool QtClient::setWidget(QWidget* parent, QWidget* child) { if (!(parent && child)) return false; QVBoxLayout* layout = new QVBoxLayout; layout->setSpacing(0); String margins; QtClient::getProperty(parent,"_yate_layout_margins",margins); if (!margins) layout->setContentsMargins(0,0,0,0); else { QList m = buildIntList(margins,4); layout->setContentsMargins(m[0],m[1],m[2],m[3]); } layout->addWidget(child); QLayout* l = parent->layout(); if (l) delete l; parent->setLayout(layout); return true; } // Set an object's image property from image file bool QtClient::setImage(QObject* obj, const String& img, bool fit) { if (!obj) return false; QPixmap pixmap(setUtf8(img)); return setImage(obj,pixmap,fit); } // Set an object's image property from raw data. bool QtClient::setImage(QObject* obj, const DataBlock& data, const String& format, bool fit) { if (!obj) return false; QPixmap pixmap; String f = format; f.startSkip("image/",false); if (!pixmap.loadFromData((const uchar*)data.data(),data.length(),f)) return false; return setImage(obj,pixmap,fit); } // Set an object's image property from QPixmap bool QtClient::setImage(QObject* obj, const QPixmap& img, bool fit) { if (!obj) return false; if (obj->isWidgetType()) { QLabel* l = qobject_cast(obj); if (l) { if (fit && !l->hasScaledContents() && (img.width() > l->width() || img.height() > l->height())) { QPixmap tmp; if (l->width() <= l->height()) tmp = img.scaledToWidth(l->width()); else tmp = img.scaledToHeight(l->height()); l->setPixmap(tmp); } else l->setPixmap(img); } else { QAbstractButton* b = qobject_cast(obj); if (b) b->setIcon(img); else { QMenu* m = qobject_cast(obj); if (m) m->setIcon(img); else return false; } } return true; } QAction* a = qobject_cast(obj); if (a) { a->setIcon(img); return true; } return false; } // Update a toggable object's image from properties void QtClient::updateToggleImage(QObject* obj) { QtWidget w(obj); QAbstractButton* b = 0; if (w.inherits(QtWidget::AbstractButton)) b = w.abstractButton(); if (!(b && b->isCheckable())) return; String icon; bool set = false; if (b->isChecked()) set = QtClient::getProperty(w,"_yate_pressed_icon",icon); else set = QtClient::getProperty(w,"_yate_normal_icon",icon); if (set) QtClient::setImage(obj,Client::s_skinPath + icon); } // Update an object's image from properties on mouse events void QtClient::updateImageFromMouse(QObject* obj, bool inOut, bool on) { QtWidget w(obj); QAbstractButton* b = 0; if (w.inherits(QtWidget::AbstractButton)) b = w.abstractButton(); if (!b) return; if (!b->isEnabled()) return; String icon; bool set = false; if (inOut) { if (on) set = QtClient::getProperty(obj,"_yate_hover_icon",icon); else { if (b->isCheckable() && b->isChecked()) set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon); } } else { if (on) { if (!b->isCheckable()) set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); } else { set = QtClient::getProperty(obj,"_yate_hover_icon",icon); if (!set && b->isCheckable() && b->isChecked()) set = QtClient::getProperty(obj,"_yate_pressed_icon",icon); set = set || QtClient::getProperty(obj,"_yate_normal_icon",icon); } } if (set) QtClient::setImage(obj,Client::s_skinPath + icon); } // Process a key press event. Retrieve an action associated with the key bool QtClient::filterKeyEvent(QObject* obj, QKeyEvent* event, String& action, bool& filter, QObject* parent) { static int mask = Qt::SHIFT | Qt::CTRL | Qt::ALT; if (!(obj && event)) return false; // Try to match key and modifiers QKeySequence ks(event->key()); String prop; getUtf8(prop,ks.toString()); prop = "dynamicAction" + prop; // Get modifiers from property and check them against event QVariant v = obj->property(prop + "Modifiers"); int tmp = 0; if (v.type() == QVariant::String) { QKeySequence ks(v.toString()); for (unsigned int i = 0; i < ks.count(); i++) tmp |= ks[i]; } if (tmp != (mask & event->modifiers())) return false; // We matched the key and modifiers // Set filter flag filter = getBoolProperty(obj,prop + "Filter"); // Retrieve the action getProperty(obj,prop,action); if (!action) return true; if (!parent) return true; parent = qFindChild(parent,setUtf8(action)); if (!parent) return true; // Avoid notifying a disabled action bool ok = true; if (parent->isWidgetType()) ok = (qobject_cast(parent))->isEnabled(); else { QAction* a = qobject_cast(parent); ok = !a || a->isEnabled(); } if (!ok) action.clear(); return true; } // Safely delete a QObject (reset its parent, calls it's deleteLater() method) void QtClient::deleteLater(QObject* obj) { if (!obj) return; obj->disconnect(); if (obj->isWidgetType()) (static_cast(obj))->setParent(0); else obj->setParent(0); obj->deleteLater(); } // Retrieve unavailable space position (if any) in the screen containing a given widget. QDesktopWidget* QtClient::getScreenUnavailPos(QWidget* w, int& pos) { if (!w) return 0; QDesktopWidget* d = QApplication::desktop(); if (!d) return 0; pos = PosNone; QRect rScreen = d->screenGeometry(w); QRect rClient = d->availableGeometry(w); int dx = rClient.x() - rScreen.x(); if (dx > 0) pos |= PosLeft; int dy = rClient.y() - rScreen.y(); if (dy > 0) pos |= PosTop; int dw = rScreen.width() - rClient.width(); if (dw > 0 && (!dx || (dx > 0 && dw > dx))) pos |= PosRight; int dh = rScreen.height() - rClient.height(); if (dh > 0 && (!dy || (dy > 0 && dh > dy))) pos |= PosBottom; return d; } // Move a window to a specified position void QtClient::moveWindow(QtWindow* w, int pos) { if (!w) return; QDesktopWidget* d = QApplication::desktop(); if (!d) return; QRect r = d->availableGeometry(w); int x = r.x(); int y = r.y(); QSize sz = w->frameSize(); if (pos == CornerBottomRight) { if (r.width() > sz.width()) x += r.width() - sz.width(); if (r.height() > sz.height()) y += r.height() - sz.height(); } else if (pos == CornerTopRight) { if (r.width() > sz.width()) x += r.width() - sz.width(); } else if (pos == CornerBottomLeft) { if (r.height() > sz.height()) y += r.height() - sz.height(); } else if (pos != CornerTopLeft) return; w->move(x,y); } // Build a QStringList from a list of strings QStringList QtClient::str2list(const String& str, char sep, bool emptyOk) { QStringList l; if (!str) return l; ObjList* list = str.split(sep,emptyOk); for (ObjList* o = list->skipNull(); o; o = o->skipNext()) l.append(setUtf8(static_cast(o->get())->c_str())); TelEngine::destruct(list); return l; } // Split a string. Returns a list of int values QList QtClient::str2IntList(const String& str, int defVal, bool emptyOk) { QList list; ObjList* l = str.split(',',emptyOk); for (ObjList* o = l->skipNull(); o; o = o->skipNext()) list.append(o->get()->toString().toInteger(defVal)); TelEngine::destruct(l); return list; } // Build a comma separated list of integers void QtClient::intList2str(String& str, QList list) { for (int i = 0; i < list.size(); i++) str.append(String(list[i]),","); } // Get sorting from string int QtClient::str2sort(const String& str, int defVal) { return lookup(str,s_sorting,defVal); } // Apply a comma separated list of window flags to a widget void QtClient::applyWindowFlags(QWidget* w, const String& value) { if (!w) return; // Set window flags from enclosed widget: // custom window title/border/sysmenu config ObjList* f = value.split(',',false); int flags = Qt::CustomizeWindowHint | w->windowFlags(); // Clear settable flags TokenDict* dict = s_windowFlags; for (int i = 0; dict[i].token; i++) flags &= ~dict[i].value; // Set flags for (ObjList* o = f->skipNull(); o; o = o->skipNext()) flags |= lookup(o->get()->toString(),s_windowFlags,0); TelEngine::destruct(f); w->setWindowFlags((Qt::WindowFlags)flags); } // Build a QT Alignment mask from a comma separated list of flags int QtClient::str2align(const String& flags, int initVal) { ObjList* list = flags.split(',',false); for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { int val = ::lookup((static_cast(o->get()))->c_str(),s_qAlign); if (0 != (val & Qt::AlignHorizontal_Mask)) initVal &= ~Qt::AlignHorizontal_Mask; if (0 != (val & Qt::AlignVertical_Mask)) initVal &= ~Qt::AlignVertical_Mask; initVal |= val; } TelEngine::destruct(list); return initVal; } // Retrieve QT selection mode from a string value QAbstractItemView::SelectionMode QtClient::str2selmode(const String& value, QAbstractItemView::SelectionMode defVal) { if (!value) return defVal; if (value == YSTRING("none")) return QAbstractItemView::NoSelection; if (value == YSTRING("single")) return QAbstractItemView::SingleSelection; if (value == YSTRING("multi")) return QAbstractItemView::MultiSelection; if (value == YSTRING("extended")) return QAbstractItemView::ExtendedSelection; if (value == YSTRING("contiguous")) return QAbstractItemView::ContiguousSelection; return defVal; } // Retrieve QT edit triggers from a string value QAbstractItemView::EditTriggers QtClient::str2editTriggers(const String& value, QAbstractItemView::EditTrigger defVal) { return (QAbstractItemView::EditTriggers)Client::decodeFlags(s_qEditTriggers,value,defVal); } // Send an event to an object's child bool QtClient::sendEvent(QEvent& e, QObject* parent, const QString& name) { if (!(parent && e.isAccepted())) return false; QObject* child = qFindChild(parent,name); if (!child) return false; e.setAccepted(false); bool ok = QCoreApplication::sendEvent(child,&e); if (!ok) e.setAccepted(true); return ok; } // Retrieve a pixmap from global application cache. // Load and add it to the cache if not found bool QtClient::getPixmapFromCache(QPixmap& pixmap, const QString& file) { if (file.isEmpty()) return false; QPixmap* cached = QPixmapCache::find(file); if (cached) { pixmap = *cached; return true; } if (!pixmap.load(file)) return false; #ifdef XDEBUG String f; getUtf8(f,file); Debug(ClientDriver::self(),DebugAll,"Loaded '%s' in pixmap cache",f.c_str()); #endif QPixmapCache::insert(file,pixmap); return true; } // Update application style sheet from config // Build style sheet from files: // stylesheet.css // stylesheet_stylename.css // stylesheet_osname.css // stylesheet_osname_stylename.css void QtClient::updateAppStyleSheet() { if (!qApp) { Debug(ClientDriver::self(),DebugWarn,"Update app stylesheet called without app"); return; } String shf = Engine::config().getValue("client","stylesheet_file","stylesheet.css"); if (!shf) return; QString sh; if (!appendStyleSheet(sh,shf)) return; String styleName; QStyle* style = qApp->style(); const QMetaObject* meta = style ? style->metaObject() : 0; if (meta) { styleName = s_qtStyles.getValue(meta->className()); if (!styleName) styleName = meta->className(); } if (styleName) appendStyleSheet(sh,shf,styleName); String osname; osname << "os" << PLATFORM_LOWERCASE_NAME; appendStyleSheet(sh,shf,osname); if (styleName) appendStyleSheet(sh,shf,osname,styleName); qApp->setStyleSheet(sh); } // Set widget attributes from list void QtClient::setWidgetAttributes(QWidget* w, const String& attrs) { if (!(w && attrs)) return; ObjList* list = attrs.split(',',false); for (ObjList* o = list->skipNull(); o; o = o->skipNext()) { const String& attr = *static_cast(o->get()); bool on = (attr[0] != '!'); const char* name = attr.c_str(); int val = lookup(on ? name : name + 1,s_widgetAttributes); if (val) w->setAttribute((Qt::WidgetAttribute)val,on); } TelEngine::destruct(list); } // Adjust widget height void QtClient::setWidgetHeight(QWidget* w, const String& height) { if (!w) return; int h = 0; if (height.isBoolean()) { h = QtClient::getIntProperty(w,"_yate_height_delta",-1); if (h > 0) { if (height.toBoolean()) h += w->height(); else if (h < w->height()) h = w->height() - h; else h = 0; } } else h = height.toInteger(); if (h < 0) return; QSizePolicy sp = w->sizePolicy(); sp.setVerticalPolicy(QSizePolicy::Fixed); w->setSizePolicy(sp); w->setMinimumHeight(h); w->setMaximumHeight(h); } // Build a busy widget child for a given widget QWidget* QtClient::buildBusy(QWidget* parent, QWidget* target, const String& ui, const NamedList& params) { QtBusyWidget* w = new QtBusyWidget(parent); w->init(ui,params,target); return w; } // Load a movie QMovie* QtClient::loadMovie(const char* file, QObject* parent, const char* path) { static NamedList s_failed(""); if (TelEngine::null(file)) return 0; String tmp = path; if (!path) tmp = Client::s_skinPath; else if (tmp && !tmp.endsWith(Engine::pathSeparator())) tmp << Engine::pathSeparator(); tmp << file; QMovie* m = new QMovie(setUtf8(tmp),QByteArray(),parent); NamedString* ns = s_failed.getParam(tmp); if (m->isValid()) { if (ns) s_failed.clearParam(ns); return m; } if (!ns) { s_failed.addParam(tmp,""); String error; error << "Failed to load movie '" << tmp << "'"; Debug(QtDriver::self(),DebugNote,"%s",error.c_str()); if (self()) self()->addToLog(error); } delete m; return 0; } // Fill a list from URL parameters void QtClient::fillUrlParams(const QUrl& url, NamedList& list, QString* path, bool pathToList) { safeGetUtf8(list,"protocol",url.scheme()); safeGetUtf8(list,"host",url.host()); if (url.port() >= 0) list.addParam("port",String(url.port())); safeGetUtf8(list,"username",url.userName()); safeGetUtf8(list,"password",url.password()); QString tmp; if (!path) { tmp = url.path(); path = &tmp; } if (pathToList) list.assign(path->toUtf8().constData()); else safeGetUtf8(list,"path",*path); QList > items = url.queryItems(); for (int i = 0; i < items.size(); i++) list.addParam(items[i].first.toUtf8().constData(),items[i].second.toUtf8().constData()); } // Dump MIME data for debug purposes void QtClient::dumpMime(String& buf, const QMimeData* m) { static const char* indent = "\r\n "; if (!m) return; QStringList fmts = m->formats(); if (fmts.size() > 0) { buf.append("FORMATS:","\r\n") << indent; QString s = fmts.join(indent); buf << s.toUtf8().constData(); } if (m->html().length() > 0) buf.append("HTML: ","\r\n") << m->html().toUtf8().constData(); if (m->text().length() > 0) buf.append("TEXT: ","\r\n") << m->text().toUtf8().constData(); QList urls = m->urls(); if (urls.size() > 0) { buf.append("URLS:","\r\n"); for (int i = 0; i < urls.size(); i++) buf << indent << urls[i].toString().toUtf8().constData(); } } /** * QtDriver */ QtDriver::QtDriver(bool buildClientThread) : m_init(false), m_clientThread(buildClientThread) { qInstallMsgHandler(qtMsgHandler); } QtDriver::~QtDriver() { qInstallMsgHandler(0); } void QtDriver::initialize() { Output("Initializing module Qt4 client"); s_device = Engine::config().getValue("client","device",DEFAULT_DEVICE); if (!QtClient::self()) { debugCopy(); QtClient::setSelf(new QtClient); if (m_clientThread) QtClient::self()->startup(); } if (!m_init) { m_init = true; setup(); } } /** * QtEventProxy */ QtEventProxy::QtEventProxy(Type type, QApplication* app) { #define SET_NAME(n) { m_name = n; setObjectName(QtClient::setUtf8(m_name)); } switch (type) { case Timer: SET_NAME("qtClientTimerProxy"); { QTimer* timer = new QTimer(this); timer->setObjectName("qtClientIdleTimer"); QtClient::connectObjects(timer,SIGNAL(timeout()),this,SLOT(timerTick())); timer->start(0); } break; case AllHidden: SET_NAME("qtClientAllHidden"); if (app) QtClient::connectObjects(app,SIGNAL(lastWindowClosed()),this,SLOT(allHidden())); break; default: return; } #undef SET_NAME } void QtEventProxy::timerTick() { if (Client::self()) Client::self()->idleActions(); Thread::idle(); } void QtEventProxy::allHidden() { if (Client::self()) Client::self()->allHidden(); } // // QtUrlBuilder // QtUrlBuilder::QtUrlBuilder(QObject* parent, const String& format, const String& queryParams) : QObject(parent), m_format(format), m_queryParams(0) { if (queryParams) { m_queryParams = queryParams.split(',',false); if (!m_queryParams->skipNull()) TelEngine::destruct(m_queryParams); } } QtUrlBuilder::~QtUrlBuilder() { TelEngine::destruct(m_queryParams); } // Build URL QUrl QtUrlBuilder::build(const NamedList& params) const { String tmp; if (m_format) { tmp = m_format; params.replaceParams(tmp); } QUrl url(QtClient::setUtf8(tmp)); if (m_queryParams) { NamedIterator iter(params); for (const NamedString* ns = 0; 0 != (ns = iter.get());) if (m_queryParams->find(ns->name())) url.addQueryItem(QtClient::setUtf8(ns->name()),QtClient::setUtf8(*ns)); } return url; } /* * QtUIWidget */ // Retrieve item type definition from [type:]value. Create it if not found QtUIWidgetItemProps* QtUIWidget::getItemProps(QString& in, String& value) { String type; int pos = in.indexOf(':'); if (pos >= 0) { QtClient::getUtf8(type,in.left(pos)); QtClient::getUtf8(value,in.right(in.length() - pos - 1)); } else QtClient::getUtf8(value,in); QtUIWidgetItemProps* p = QtUIWidget::getItemProps(type); if (!p) { p = new QtUIWidgetItemProps(type); m_itemProps.append(p); } DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) getItemProps(%s,%s) got (%p) ui=%s [%p]", name().c_str(),in.toUtf8().constData(),value.c_str(),p,p->m_ui.c_str(),this); return p; } // Set widget's parameters. // Handle an 'applyall' parameter carrying a NamedList to apply to all items bool QtUIWidget::setParams(const NamedList& params) { bool ok = false; NamedIterator iter(params); for (const NamedString* ns = 0; 0 != (ns = iter.get());) { if (ns->name() == YSTRING("applyall")) { const NamedList* list = YOBJECT(NamedList,ns); if (list) { ok = true; applyAllParams(*list); } } else if (ns->name().startsWith("beginedit:")) beginEdit(ns->name().substr(10),ns); } return ok; } // Apply a list of parameters to all container items void QtUIWidget::applyAllParams(const NamedList& params) { QList list = getContainerItems(); for (int i = 0; i < list.size(); i++) setParams(list[i],params); } // Find an item widget by id QWidget* QtUIWidget::findItem(const String& id) { QString item = QtClient::setUtf8(id); QList list = getContainerItems(); for (int i = 0; i < list.size(); i++) { if (!list[i]->isWidgetType()) continue; String item; getListItemIdProp(list[i],item); if (id == item) return static_cast(list[i]); } return 0; } // Retrieve the object identity from '_yate_identity' property or name // Retrieve the object item from '_yate_widgetlistitem' property. // Set 'identity' to object_identity[:item_name] void QtUIWidget::getIdentity(QObject* obj, String& identity) { if (!obj) return; String ident; QtClient::getIdentity(obj,ident); if (!ident) return; String item; getListItemProp(obj,item); identity.append(ident,":"); identity.append(item,":"); } // Update a widget and children from a list a parameters bool QtUIWidget::setParams(QObject* parent, const NamedList& params) { static const String s_property = "property"; static const String s_active = "active"; static const String s_image = "image"; static const String s_show = "show"; static const String s_display = "display"; static const String s_check = "check"; static const String s_select = "select"; static const String s_addlines = "addlines"; static const String s_setrichtext = "setrichtext"; static const String s_updatetablerows = "updatetablerows"; static const String s_cleartable = "cleartable"; static const String s_rawimage = "rawimage"; static const String s_setparams = "setparams"; static const String s_setmenu = "setmenu"; static const String s_height = "height"; if (!parent) return false; QtWindow* wnd = QtClient::parentWindow(parent); if (!wnd) return false; #ifdef DEBUG String tmp; params.dump(tmp," "); Debug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::setParams(%p,%s) %s", name().c_str(),parent,YQT_OBJECT_NAME(parent),tmp.c_str()); #endif String pName(YQT_OBJECT_NAME(parent)); bool ok = true; unsigned int n = params.length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = params.getParam(i); if (!ns) continue; XDebug(ClientDriver::self(),DebugInfo,"QtUIWidget(%s)::setParams() %s=%s", name().c_str(),ns->name().c_str(),ns->c_str()); String buf; int pos = ns->name().find(':'); if (pos < 0) { if (ns->name() != s_setmenu) ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; else buildWidgetItemMenu(qobject_cast(parent),YOBJECT(NamedList,ns)); continue; } String n(ns->name().substr(0,pos)); String cName = ns->name().substr(pos + 1); if (n == s_property) { // Handle property[:child]:property_name int pos = cName.find(':'); if (pos >= 0) { QString tmp = buildQChildName(pName,cName.substr(0,pos)); QObject* c = qFindChild(parent,tmp); ok = c && QtClient::setProperty(c,cName.substr(pos + 1),*ns) && ok; } else ok = QtClient::setProperty(parent,cName,*ns) && ok; } else if (n == s_active) ok = wnd->setActive(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; else if (n == s_image) ok = wnd->setImage(buildChildName(buf,pName,cName),*ns) && ok; else if (n == s_show || n == s_display) ok = wnd->setShow(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; else if (n == s_check) ok = wnd->setCheck(buildChildName(buf,pName,cName),ns->toBoolean()) && ok; else if (n == s_select) ok = wnd->setSelect(buildChildName(buf,pName,cName),*ns) && ok; if (n == s_setparams) { NamedList* p = YOBJECT(NamedList,ns); if (!p) continue; QtWidget w(parent,buildChildName(buf,pName,cName)); UIWidget* uiw = w.uiWidget(); ok = uiw && uiw->setParams(*p) && ok; } else if (n == s_addlines) { NamedList* p = YOBJECT(NamedList,ns); if (p) ok = wnd->addLines(buildChildName(buf,pName,cName),p,0,ns->toBoolean()) && ok; } else if (n == s_setrichtext) ok = wnd->setText(buildChildName(buf,pName,cName),*ns,true) && ok; else if (n == s_updatetablerows) { NamedList* p = YOBJECT(NamedList,ns); if (p) ok = wnd->updateTableRows(buildChildName(buf,pName,cName),p,ns->toBoolean()) && ok; } else if (n == s_cleartable) ok = wnd->clearTable(buildChildName(buf,pName,cName)) && ok; else if (n == s_rawimage) { DataBlock* data = YOBJECT(DataBlock,ns); if (data) { QString tmp = buildQChildName(pName,cName.substr(0,pos)); QObject* c = qFindChild(parent,tmp); ok = c && QtClient::setImage(c,*data,*ns) && ok; } } else if (n == s_setmenu) buildWidgetItemMenu(qobject_cast(parent),YOBJECT(NamedList,ns),cName); else if (n == s_height) { QString tmp = buildQChildName(pName,cName); QWidget* w = qFindChild(qobject_cast(parent),tmp); QtClient::setWidgetHeight(w,*ns); } else ok = wnd->setText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; } // Set item parameters NamedString* yparams = params.getParam(YSTRING("_yate_itemparams")); if (!TelEngine::null(yparams)) { QVariant var = parent->property(yparams->name().c_str()); if (var.type() == QVariant::Invalid || var.type() == QVariant::StringList) { QStringList list; if (var.type() == QVariant::StringList) list = var.toStringList(); NamedList tmp(""); tmp.copyParams(params,*yparams); QtClient::copyParams(list,tmp); parent->setProperty(yparams->name().c_str(),QVariant(list)); } else ok = false; } return ok; } // Get an item object's parameters bool QtUIWidget::getParams(QObject* parent, NamedList& params) { static const String s_property = "property"; static const String s_getcheck = "getcheck"; static const String s_getselect = "getselect"; static const String s_getrichtext = "getrichtext"; if (!parent) return false; QtWindow* wnd = QtClient::parentWindow(parent); if (!wnd) return false; DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s)::getParams(%p,%s)", name().c_str(),parent,YQT_OBJECT_NAME(parent)); String pName; QtClient::getUtf8(pName,parent->objectName()); bool ok = true; unsigned int n = params.length(); for (unsigned int i = 0; i < n; i++) { NamedString* ns = params.getParam(i); if (!ns) continue; String buf; int pos = ns->name().find(':'); if (pos < 0) { ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; continue; } String n(ns->name().substr(0,pos)); String cName = ns->name().substr(pos + 1); if (n == s_property) { // Handle property[:child]:property_name int pos = cName.find(':'); if (pos >= 0) { QString tmp = buildQChildName(pName,cName.substr(0,pos)); QObject* c = qFindChild(parent,tmp); ok = c && QtClient::getProperty(c,cName.substr(pos + 1),*ns) && ok; } else ok = QtClient::getProperty(parent,cName,*ns) && ok; } else if (n == s_getselect) ok = wnd->getSelect(buildChildName(buf,pName,cName),*ns) && ok; else if (n == s_getcheck) { bool on = false; ok = wnd->getCheck(buildChildName(buf,pName,cName),on) && ok; *ns = String::boolText(on); } else if (n == s_getrichtext) ok = wnd->getText(buildChildName(buf,pName,cName),*ns,true) && ok; else ok = wnd->getText(buildChildName(buf,pName,ns->name()),*ns,false) && ok; } // Get item parameters QtClient::getProperty(parent,"_yate_itemparams",params); return ok; } // Show or hide control busy state bool QtUIWidget::setBusy(bool on) { QObject* o = getQObject(); QWidget* w = (o && o->isWidgetType()) ? static_cast(o) : 0; return w && QtBusyWidget::showBusyChild(w,on); } // Apply properties for QAbstractItemView descendents void QtUIWidget::applyItemViewProps(const NamedList& params) { static const String s_selMode = "_yate_selection_mode"; static const String s_editTriggers = "_yate_edit_triggers"; QObject* obj = getQObject(); QAbstractItemView* av = qobject_cast(obj); if (!av) return; NamedIterator iter(params); for (const NamedString* ns = 0; 0 != (ns = iter.get());) { if (ns->name() == s_selMode) av->setSelectionMode(QtClient::str2selmode(*ns)); else if (ns->name() == s_editTriggers) av->setEditTriggers(QtClient::str2editTriggers(*ns)); } } // Begin item edit. The default behaviour start edit for QAbstractItemView descendants bool QtUIWidget::beginEdit(const String& item, const String* what) { QObject* obj = getQObject(); QAbstractItemView* av = qobject_cast(obj); if (!av) return false; QModelIndex idx = modelIndex(item,what); if (!idx.isValid()) return false; av->setCurrentIndex(idx); av->edit(idx); return true; } // Build item widget menu QMenu* QtUIWidget::buildWidgetItemMenu(QWidget* w, const NamedList* params, const String& child, bool set) { if (!(w && params)) return 0; QWidget* parent = w; // Retrieve the item owner QWidget* pItem = 0; String item; getListItemIdProp(w,item); if (item) pItem = findItem(item); else { getListItemProp(w,item); pItem = item ? findItem(item) : 0; } XDebug(ClientDriver::self(),DebugAll, "QtUIWidget(%s)::buildMenu() widget=%s item=%s [%p]", this->name().c_str(),YQT_OBJECT_NAME(w),item.c_str(),this); String pName(YQT_OBJECT_NAME(w)); const String& owner = (*params)[YSTRING("owner")]; if (owner && owner != item) { QString tmp = buildQChildName(pName,owner); parent = qFindChild(w,tmp); if (!parent) { Debug(QtDriver::self(),DebugNote, "QtUIWidget(%s) buildMenu() owner '%s' not found [%p]", name().c_str(),owner.c_str(),this); return 0; } } QWidget* target = parent; String t = child ? child : (*params)[YSTRING("target")]; if (t) { QString tmp = buildQChildName(pName,t); target = qFindChild(w,tmp); if (!target) { Debug(QtDriver::self(),DebugNote, "QtUIWidget(%s) buildMenu() target '%s' not found [%p]", name().c_str(),t.c_str(),this); return 0; } } QString menuName = buildQChildName(pName,t + "_menu"); // Remove existing menu QMenu* menu = qFindChild(parent,menuName); if (menu) { delete menu; menu = 0; } // Build the menu QObject* thisObj = getQObject(); if (!thisObj) return 0; String actionSlot; String toggleSlot; String selectSlot; getSlots(actionSlot,toggleSlot,selectSlot); if (!(actionSlot || toggleSlot)) return 0; bool addActions = set && target->contextMenuPolicy() == Qt::ActionsContextMenu; unsigned int n = params->length(); for (unsigned int i = 0; i < n; i++) { NamedString* param = params->getParam(i); if (!(param && param->name().startsWith("item:"))) continue; if (!menu) menu = new QMenu(QtClient::setUtf8(params->getValue(YSTRING("title"))),parent); NamedList* p = YOBJECT(NamedList,param); if (p) { QMenu* subMenu = QtClient::buildMenu(*p, *param ? param->c_str() : p->getValue(YSTRING("title"),*p), thisObj,actionSlot,toggleSlot,menu); if (subMenu) { menu->addMenu(subMenu); if (addActions) target->addAction(subMenu->menuAction()); } continue; } QAction* a = 0; String name = param->name().substr(5); if (*param) { a = menu->addAction(QtClient::setUtf8(*param)); a->setObjectName(buildQChildName(pName,name)); a->setParent(menu); QtClient::setImage(a,(*params)["image:" + name]); } else if (!name) { a = menu->addSeparator(); a->setParent(menu); } else if (pItem) { // Check if the action is already there QString aName = buildQChildName(pItem->objectName(),QtClient::setUtf8(name)); a = qFindChild(pItem,aName); if (a) menu->addAction(a); } if (a) { if (addActions) target->addAction(a); } else Debug(ClientDriver::self(),DebugNote, "QtUIWidget(%s)::buildMenu() action '%s' not found for item=%s [%p]", this->name().c_str(),name.c_str(),item.c_str(),this); } if (!menu) return 0; // Set name menu->setObjectName(menuName); // Apply properties // Format: property:object_name:property_name=value if (parent) for (unsigned int i = 0; i < n; i++) { NamedString* param = params->getParam(i); if (!(param && param->name().startsWith("property:"))) continue; int pos = param->name().find(':',9); if (pos < 9) continue; QString n = buildQChildName(pName,param->name().substr(9,pos - 9)); QObject* obj = qFindChild(parent,n); if (obj) QtClient::setProperty(obj,param->name().substr(pos + 1),*param); } // Connect signals (direct children only: actions from sub-menus are already connected) QList list = qFindChildren(menu); for (int i = 0; i < list.size(); i++) { if (list[i]->isSeparator() || list[i]->parent() != menu) continue; if (list[i]->isCheckable()) QtClient::connectObjects(list[i],SIGNAL(toggled(bool)),thisObj,toggleSlot); else QtClient::connectObjects(list[i],SIGNAL(triggered()),thisObj,actionSlot); } if (addActions) return menu; QMenu* mOwner = qobject_cast(target); if (mOwner) mOwner->insertMenu(0,menu); else { QToolButton* tb = qobject_cast(target); if (tb) tb->setMenu(menu); else { QPushButton* pb = qobject_cast(target); if (pb) pb->setMenu(menu); else if (!QtClient::setProperty(target,s_propContextMenu,params)) target->addAction(menu->menuAction()); } } return menu; } // Build a container child name from parent property bool QtUIWidget::buildQChildNameProp(QString& dest, QObject* parent, const char* prop) { if (!(parent && prop)) return false; QVariant var = parent->property(prop); if (!var.isValid() || var.toString().size() <= 0) return false; dest = buildQChildName(parent->objectName(),var.toString()); return true; } // Retrieve the top level QtUIWidget container parent of an object QtUIWidget* QtUIWidget::container(QObject* obj) { if (!obj) return 0; QtUIWidget* uiw = 0; while (0 != (obj = obj->parent())) { QtWidget w(obj); UIWidget* u = w.uiWidget(); if (u) uiw = static_cast(u); } return uiw; } // Utility used in QtUIWidget::initNavigation static bool initNavAction(QObject* obj, const String& name, const String& actionSlot) { if (!(obj && name)) return false; QtWindow* wnd = QtClient::parentWindow(obj); QObject* child = qFindChild(wnd,QtClient::setUtf8(name)); if (!child) return false; QAbstractButton* b = 0; QAction* a = 0; if (child->isWidgetType()) b = qobject_cast(child); else a = qobject_cast(child); if (b || a) { if (b) QtClient::connectObjects(b,SIGNAL(clicked()),obj,actionSlot); else QtClient::connectObjects(a,SIGNAL(triggered()),obj,actionSlot); } return b || a; } // Initialize navigation controls void QtUIWidget::initNavigation(const NamedList& params) { String actionSlot; String toggleSlot; String selectSlot; getSlots(actionSlot,toggleSlot,selectSlot); QObject* qObj = getQObject(); if (qObj && actionSlot) { m_prev = params.getValue(YSTRING("navigate_prev")); if (!initNavAction(qObj,m_prev,actionSlot)) m_prev = ""; m_next = params.getValue(YSTRING("navigate_next")); if (!initNavAction(qObj,m_next,actionSlot)) m_next = ""; } m_info = params.getValue(YSTRING("navigate_info")); m_infoFormat = params.getValue(YSTRING("navigate_info_format")); m_title = params.getValue(YSTRING("navigate_title")); updateNavigation(); } // Update navigation controls void QtUIWidget::updateNavigation() { if (!(m_prev || m_next || m_info || m_title)) return; QtWindow* wnd = QtClient::parentWindow(getQObject()); if (!wnd) return; NamedList p(""); int crt = currentItemIndex(); if (crt < 0) crt = 0; else crt++; int n = itemCount(); if (n < crt) n = crt; if (m_prev || m_next) { if (m_prev) p.addParam("active:" + m_prev,String::boolText(crt > 1)); if (m_next) p.addParam("active:" + m_next,String::boolText(crt < n)); } if (m_info) { String tmp = m_infoFormat; NamedList pp(""); pp.addParam("index",String(crt)); pp.addParam("count",String(n)); pp.replaceParams(tmp); p.addParam(m_info,tmp); } if (m_title) { String crt; getSelect(crt); NamedList pp(""); if (crt) getTableRow(crt,&pp); p.addParam(m_title,pp[YSTRING("title")]); } wnd->setParams(p); } // Trigger a custom action from an item bool QtUIWidget::triggerAction(const String& item, const String& action, QObject* sender, NamedList* params) { if (!(Client::self() && action)) return false; if (!sender) sender = getQObject(); String s; getIdentity(sender,s); if (!s) return false; NamedList p(""); if (!params) params = &p; params->addParam("item",item,false); params->addParam("widget",s); return QtClient::self()->action(QtClient::parentWindow(sender),action,params); } // Trigger a custom action from already built list params bool QtUIWidget::triggerAction(const String& action, NamedList& params, QObject* sender) { if (!(Client::self() && action)) return false; if (!sender) sender = getQObject(); String s; getIdentity(sender,s); if (!s) return false; params.setParam("widget",s); return QtClient::self()->action(QtClient::parentWindow(sender),action,¶ms); } // Handle a child's action void QtUIWidget::onAction(QObject* sender) { if (!Client::self()) return; String s; getIdentity(sender,s); if (!s) return; int dir = 0; if (s == m_next) dir = 1; else if (s == m_prev) dir = -1; if (dir) { int crt = currentItemIndex(); if (crt >= 0) setSelectIndex(crt + dir); return; } DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising action %s", name().c_str(),s.c_str()); Client::self()->action(QtClient::parentWindow(sender),s); } // Handle a child's toggle notification void QtUIWidget::onToggle(QObject* sender, bool on) { if (!Client::self()) return; QtClient::updateToggleImage(sender); String s; getIdentity(sender,s); if (!s) return; DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising toggle %s", name().c_str(),s.c_str()); Client::self()->toggle(QtClient::parentWindow(sender),s,on); } // Handle a child's selection change void QtUIWidget::onSelect(QObject* sender, const String* item) { if (!Client::self()) return; String s; getIdentity(sender,s); if (!s) return; QtWindow* wnd = QtClient::parentWindow(sender); String tmp; if (!item) { item = &tmp; if (wnd) wnd->getSelect(YQT_OBJECT_NAME(sender),tmp); } DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select %s", name().c_str(),s.c_str()); Client::self()->select(wnd,s,*item); } // Handle a child's multiple selection change void QtUIWidget::onSelectMultiple(QObject* sender, const NamedList* items) { if (!Client::self()) return; String s; getIdentity(sender,s); if (!s) return; QtWindow* wnd = QtClient::parentWindow(sender); DDebug(ClientDriver::self(),DebugAll,"QtUIWidget(%s) raising select multiple", name().c_str()); if (items) { Client::self()->select(wnd,s,*items); return; } NamedList tmp(""); if (wnd) wnd->getSelect(YQT_OBJECT_NAME(sender),tmp); Client::self()->select(wnd,s,tmp); } // Filter wathed events for children. // Handle child image changing on mouse events bool QtUIWidget::onChildEvent(QObject* watched, QEvent* event) { if (event->type() == QEvent::Enter) QtClient::updateImageFromMouse(watched,true,true); else if (event->type() == QEvent::Leave) QtClient::updateImageFromMouse(watched,true,false); else if (event->type() == QEvent::MouseButtonPress) QtClient::updateImageFromMouse(watched,false,true); else if (event->type() == QEvent::MouseButtonRelease) QtClient::updateImageFromMouse(watched,false,false); return false; } // Load an item's widget. Rename children. Connect actions QWidget* QtUIWidget::loadWidget(QWidget* parent, const String& name, const String& ui) { // Build a new widget name to make sure there are no duplicates: // Some containers (like QTreeWidget) calls deleteLater() for widget's // set to items which might lead to wrong widget update // Make sure the widget name contains only 'standard' characters // to avoid errors when replaced in style sheets MD5 md5(name); String wName; buildChildName(wName,md5.hexDigest()); wName << "_" << (unsigned int)Time::now(); QWidget* w = QtWindow::loadUI(Client::s_skinPath + ui,parent,ui); DDebug(ClientDriver::self(),w ? DebugAll : DebugNote, "QtUIWidget(%s)::loadWidget(%p,%s,%s) widget=%p", this->name().c_str(),parent,wName.c_str(),ui.c_str(),w); if (!w) return 0; QObject* qObj = getQObject(); QtWindow* wnd = getWindow(); // Install event filter in parent window if (!m_wndEvHooked && wnd && qObj) { QVariant var = w->property("_yate_keypress_redirect"); if (var.isValid()) { m_wndEvHooked = true; wnd->installEventFilter(qObj); } } String actionSlot; String toggleSlot; String selectSlot; getSlots(actionSlot,toggleSlot,selectSlot); QString wListItem = QtClient::setUtf8(name); w->setObjectName(QtClient::setUtf8(wName)); setListItemIdProp(w,wListItem); // Build custom UI widgets QtClient::buildFrameUiWidgets(w); // Process "_yate_setaction" property before changing names QtClient::setAction(w); // Process children QList c = qFindChildren(w); for (int i = 0; i < c.size(); i++) { // Set object item owner name setListItemProp(c[i],wListItem); // Rename child String n; QtClient::getUtf8(n,c[i]->objectName()); c[i]->setObjectName(buildQChildName(wName,n)); // Install event filters if (qObj && QtClient::getBoolProperty(c[i],"_yate_filterevents")) c[i]->installEventFilter(qObj); // Connect text changed to window's slot bool connect = QtClient::autoConnect(c[i]); if (wnd && connect) wnd->connectTextChanged(c[i]); // Connect signals if (!(qObj && connect && (actionSlot || toggleSlot || selectSlot))) continue; // Use isWidgetType() (faster then qobject_cast) if (c[i]->isWidgetType()) { // Connect abstract buttons (check boxes and radio/push/tool buttons) signals QAbstractButton* b = qobject_cast(c[i]); if (b) { if (!b->isCheckable()) QtClient::connectObjects(b,SIGNAL(clicked()),qObj,actionSlot); else QtClient::connectObjects(b,SIGNAL(toggled(bool)),qObj,toggleSlot); continue; } // Connect group boxes QGroupBox* gb = qobject_cast(c[i]); if (gb) { if (gb->isCheckable()) QtClient::connectObjects(gb,SIGNAL(toggled(bool)),qObj,toggleSlot); continue; } // Connect combo boxes QComboBox* combo = qobject_cast(c[i]); if (combo) { QtClient::connectObjects(combo,SIGNAL(activated(int)),qObj,selectSlot); continue; } // Connect list boxes QListWidget* lst = qobject_cast(c[i]); if (lst) { QtClient::connectObjects(lst,SIGNAL(currentRowChanged(int)),qObj,selectSlot); continue; } // Connect sliders QSlider* sld = qobject_cast(c[i]); if (sld) { QtClient::connectObjects(sld,SIGNAL(valueChanged(int)),qObj,selectSlot); continue; } continue; } // Connect actions signals QAction* a = qobject_cast(c[i]); if (a) { if (!a->isCheckable()) QtClient::connectObjects(a,SIGNAL(triggered()),qObj,actionSlot); else QtClient::connectObjects(a,SIGNAL(toggled(bool)),qObj,toggleSlot); continue; } } return w; } // Apply a QWidget style sheet. Replace ${name} with widget name in style void QtUIWidget::applyWidgetStyle(QWidget* w, const String& style) { if (!(w && style)) return; QString s = QtClient::setUtf8(style); s.replace("${name}",w->objectName()); w->setStyleSheet(s); } // Filter key press events. Retrieve an action associated with the key. // Check if the object is allowed to process the key. // Raise the action bool QtUIWidget::filterKeyEvent(QObject* watched, QKeyEvent* event, bool& filter) { String action; if (!QtClient::filterKeyEvent(watched,event,action,filter)) return false; if (!action) return true; String item; getListItemProp(watched,item); // Avoid raising a disabled actions if (item) { bool ok = true; QWidget* w = findItem(item); if (w) { QString n = buildQChildName(w->objectName(),QtClient::setUtf8(action)); QObject* act = qFindChild(w,n); if (act) { if (act->isWidgetType()) ok = (qobject_cast(act))->isEnabled(); else { QAction* a = qobject_cast(act); ok = !a || a->isEnabled(); } } } if (!ok) return true; // Append container item to action action.append(item,":"); } Client::self()->action(QtClient::parentWindow(getQObject()),action); return true; } /** * QtSound */ bool QtSound::doStart() { doStop(); if (Client::self()) Client::self()->createObject((void**)&m_sound,"QSound",m_file); if (m_sound) DDebug(ClientDriver::self(),DebugAll,"Sound(%s) started file=%s", c_str(),m_file.c_str()); else { Debug(ClientDriver::self(),DebugNote,"Sound(%s) failed to start file=%s", c_str(),m_file.c_str()); return false; } m_sound->setLoops(m_repeat ? m_repeat : -1); m_sound->play(); return true; } void QtSound::doStop() { if (!m_sound) return; m_sound->stop(); delete m_sound; DDebug(ClientDriver::self(),DebugAll,"Sound(%s) stopped",c_str()); m_sound = 0; } // // QtDragAndDrop // // Reset data void QtDragAndDrop::reset() { m_started = false; } // Check a string value for 'drag', 'drop', 'both' void QtDragAndDrop::checkEnable(const String& s, bool& drag, bool& drop) { drag = (s == YSTRING("drag")); drop = !drag && (s == YSTRING("drop")); if (!(drag || drop)) drag = drop = (s == YSTRING("both")); } // // QtDrop // const String QtDrop::s_askClientAcceptDrop = "_yate_event_drop_accept"; const String QtDrop::s_notifyClientDrop = "_yate_event_drop"; const QString QtDrop::s_fileScheme = "file"; const TokenDict QtDrop::s_acceptDropName[] = { {"always", Always}, {"ask", Ask}, {"none", 0}, {0,0} }; QtDrop::QtDrop(QObject* parent, const NamedList* params) : QtDragAndDrop(parent), m_dropParams(""), m_acceptFiles(false), m_acceptDirs(false) { if (!params) return; NamedIterator iter(*params); for (const NamedString* ns = 0; 0 != (ns = iter.get());) { if (ns->name() == YSTRING("_yate_accept_drop_schemes")) QtClient::addStrListUnique(m_schemes,QtClient::str2list(*ns)); else if (ns->name() == YSTRING("_yate_accept_drop_file")) m_acceptFiles = ns->toBoolean(); else if (ns->name() == YSTRING("_yate_accept_drop_dir")) m_acceptDirs = ns->toBoolean(); } } // Update parameters from drag enter event bool QtDrop::start(QDragEnterEvent& e) { static const String s_prefix = "drop:"; reset(); const QMimeData* m = e.mimeData(); if (!(m && m->hasUrls())) return false; int nUrls = m->urls().size(); unsigned int nItems = 0; for (int i = 0; i < nUrls; i++) { QString scheme = m->urls()[i].scheme(); if (m_schemes.size() > 0 && !m_schemes.contains(scheme)) { reset(); return false; } QString path = m->urls()[i].path(); String what = scheme.toUtf8().constData(); if (scheme == s_fileScheme) { #ifdef _WINDOWS path = path.mid(1); #endif path = QDir::toNativeSeparators(path); QFileInfo fi(path); if (fi.isDir()) { if (!m_acceptDirs) { reset(); return false; } what = "directory"; } else if (fi.isFile() && !m_acceptFiles) { reset(); return false; } } nItems++; NamedList* nl = new NamedList(""); QtClient::fillUrlParams(m->urls()[i],*nl,&path); m_dropParams.addParam(new NamedPointer(s_prefix + what,nl,*nl)); } if (!nItems) { reset(); return false; } if (e.source()) { QtWindow* wnd = QtClient::parentWindow(e.source()); if (wnd) { m_dropParams.addParam("source_window",wnd->toString()); QtClient::getUtf8(m_dropParams,"source",e.source()->objectName()); } } m_started = true; return true; } // Reset data void QtDrop::reset() { m_dropParams.clearParams(); QtDragAndDrop::reset(); } // // QtListDrop // QtListDrop::QtListDrop(QObject* parent, const NamedList* params) : QtDrop(parent,params), m_acceptOnEmpty(None) { } // Update accept void QtListDrop::updateAcceptType(const String list, int type) { if (!list) return; ObjList* l = list.split(',',false); for (ObjList* o = l->skipNull(); o; o = o->skipNext()) { NamedInt* ni = new NamedInt(*static_cast(o->get()),type); NamedInt::addToListUniqueName(m_acceptItemTypes,ni); } TelEngine::destruct(l); } // Update accept from parameters list void QtListDrop::updateAccept(const NamedList& params) { NamedIterator iter(params); for (const NamedString* ns = 0; 0 != (ns = iter.get());) { if (ns->name() == YSTRING("_yate_accept_drop_onempty")) m_acceptOnEmpty = this->acceptDropType(*ns,None); else if (ns->name() == YSTRING("_yate_accept_drop_item_type_always")) updateAcceptType(*ns,Always); else if (ns->name() == YSTRING("_yate_accept_drop_item_type_none")) updateAcceptType(*ns,None); else if (ns->name() == YSTRING("_yate_accept_drop_item_type_ask")) updateAcceptType(*ns,Ask); } } // Reset data void QtListDrop::reset() { m_acceptItemTypes.clear(); QtDrop::reset(); } // // QtBusyWidget // const QString QtBusyWidget::s_busySuffix("_yate_busy_widget_generated"); // Constructor QtBusyWidget::QtBusyWidget(QWidget* parent) : QtCustomWidget(0,parent), m_target(0), m_shown(false), m_delayMs(0), m_delayTimer(0), m_movieLabel(0) { if (parent) setObjectName(parent->objectName() + s_busySuffix); QWidget::hide(); } // Initialize void QtBusyWidget::init(const String& ui, const NamedList& params, QWidget* target) { hideBusy(); m_target = target; m_movieLabel = 0; unsigned int delay = 0; QWidget* w = ui ? loadWidget(this,"",ui) : 0; if (w) { QtClient::setWidget(this,w); int tmp = QtClient::getIntProperty(w,"_yate_busywidget_delay"); if (tmp > 0) delay = tmp; QList c = qFindChildren(w); for (int i = 0; i < c.size(); i++) { QLabel* l = qobject_cast(c[i]); if (l) { if (!m_movieLabel) { String file; QtClient::getProperty(l,"_yate_movie_file",file); if (file) { l->setMovie(QtClient::loadMovie(file,l)); if (l->movie()) m_movieLabel = l; } } } } } m_delayMs = params.getIntValue(YSTRING("_yate_busywidget_delay"),delay,0); } // Show the widget void QtBusyWidget::showBusy() { if (m_shown) return; m_shown = true; if (m_delayMs) m_delayTimer = startTimer(m_delayMs); if (!m_delayTimer) internalShow(); } // Hide the widget void QtBusyWidget::hideBusy() { if (!m_shown) return; m_shown = false; stopDelayTimer(); if (m_target) m_target->removeEventFilter(this); setContent(false); lower(); hide(); } // Filter wathed events bool QtBusyWidget::onChildEvent(QObject* watched, QEvent* event) { if (m_target && m_target == watched) { if (event->type() == QEvent::Resize) resize(m_target->size()); } return false; } void QtBusyWidget::timerEvent(QTimerEvent* ev) { if (m_delayTimer && ev->timerId() == m_delayTimer) { stopDelayTimer(); internalShow(); return; } QtCustomWidget::timerEvent(ev); } // Show/hide busy content void QtBusyWidget::setContent(bool on) { QMovie* movie = m_movieLabel ? m_movieLabel->movie() : 0; if (!movie) return; if (on) movie->start(); else movie->stop(); } void QtBusyWidget::internalShow() { if (m_target) { resize(m_target->size()); m_target->installEventFilter(this); } setContent(true); raise(); show(); } /* vi: set ts=8 sw=4 sts=4 noet: */