yate/modules/qt4/customtable.cpp

714 lines
20 KiB
C++

/**
* customtable.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Custom table implementation
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2010 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "customtable.h"
using namespace TelEngine;
namespace { // anonymous
// The factory
class CustomTableFactory : public UIFactory
{
public:
inline CustomTableFactory(const char* name = "CustomTableFactory")
: UIFactory(name)
{ m_types.append(new String("CustomTable")); }
virtual void* create(const String& type, const char* name, NamedList* params = 0);
};
// Utility class used to disable/enable a table sorting and widget update flag
class SafeWidget
{
public:
SafeWidget(QTableWidget* table)
: m_widget(table), m_table(0) {
if (m_widget)
m_widget->setUpdatesEnabled(false);
if (table && table->isSortingEnabled()) {
m_table = table;
m_table->setSortingEnabled(false);
}
}
~SafeWidget()
{ drop(); }
inline void drop() {
if (m_table)
m_table->setSortingEnabled(true);
if (m_widget)
m_widget->setUpdatesEnabled(true);
m_widget = 0;
m_table = 0;
}
private:
QWidget* m_widget;
QTableWidget* m_table;
};
static CustomTableFactory s_factory;
static inline const String& objListItem(ObjList* list, int index)
{
GenObject* gen = list ? (*list)[index] : 0;
return gen ? gen->toString() : String::empty();
}
/*
* CustomTable
*/
// Constructor for a custom table
CustomTable::CustomTable(const char *name, const NamedList& params, QWidget* parent)
: QtTable(name,parent),
m_rowHeight(0), m_horzHeader(true),
m_notifyItemChanged(false), m_notifySelChgOnRClick(true),
m_contextMenu(0), m_changing(false)
{
// Build properties
QtClient::buildProps(this,params["buildprops"]);
// Set horizontal header
QHeaderView* h = horizontalHeader();
if (h)
h->setHighlightSections(false);
ObjList* cols = params["hheader_columns"].split(',',false);
ObjList* title = params["hheader_columns_title"].split(',',true);
ObjList* check = params["hheader_columns_check"].split(',',false);
ObjList* size = params["hheader_columns_size"].split(',',true);
ObjList* resize = params["hheader_columns_resize"].split(',',true);
ObjList* emptyTitle = params["hheader_columns_allowemptytitle"].split(',',true);
int n = cols->count();
setColumnCount(n);
for (int i = 0; i < n; i++) {
String id = objListItem(cols,i);
String text = objListItem(title,i);
if (!text) {
String tmp = id;
if (!emptyTitle->find(tmp.toLower()))
text = id;
}
QTableWidgetItem* it = new QTableWidgetItem(QtClient::setUtf8(text));
id.toLower();
it->setData(ColumnId,QVariant(QtClient::setUtf8(id)));
if (check->find(id))
it->setData(ColumnItemCheckable,QVariant(true));
setHorizontalHeaderItem(i,it);
if (!h)
continue;
// Set column width
int w = objListItem(size,i).toInteger();
if (w > 0)
h->resizeSection(i,w);
// Set column resize mode
const String& resizeMode = objListItem(resize,i);
if (resizeMode == "fixed")
h->setResizeMode(i,QHeaderView::Fixed);
else if (resizeMode == "stretch")
h->setResizeMode(i,QHeaderView::Stretch);
else if (resizeMode == "contents")
h->setResizeMode(i,QHeaderView::ResizeToContents);
else
h->setResizeMode(i,QHeaderView::Interactive);
}
TelEngine::destruct(cols);
TelEngine::destruct(title);
TelEngine::destruct(check);
TelEngine::destruct(size);
TelEngine::destruct(resize);
TelEngine::destruct(emptyTitle);
// Init properties
m_saveProps << "_yate_col_widths";
m_saveProps << "_yate_sorting";
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
// Connect signals
QtClient::connectObjects(this,SIGNAL(cellChanged(int,int)),this,SLOT(itemCellChanged(int,int)));
// Apply parameters
setParams(params);
}
CustomTable::~CustomTable()
{
}
bool CustomTable::setParams(const NamedList& params)
{
SafeWidget tbl(this);
QtUIWidget::setParams(params);
unsigned int n = params.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* param = params.getParam(i);
if (!param)
continue;
if (param->name() == "filtervalue")
setFilter(*param);
else if (param->name() == "dynamiccellclicked")
setProperty("dynamicCellClicked",QVariant(QString(*param)));
else if (param->name() == "dynamicnoitemselchanged")
setProperty("dynamicNoItemSelChanged",QVariant(QString(*param)));
else if (param->name().startsWith("property:")) {
String prop = param->name().substr(9);
QWidget* target = this;
if (prop.startSkip("hheader:",false))
target = horizontalHeader();
if (target)
QtClient::setProperty(target,prop,*param);
}
else if (param->name() == "menu") {
// Re-build the context menu
if (m_contextMenu) {
QtClient::deleteLater(m_contextMenu);
m_contextMenu = 0;
}
NamedList* menu = static_cast<NamedList*>(param->getObject(YATOM("NamedList")));
if (menu) {
// Get parent window receiving menu events
QtWindow* wnd = static_cast<QtWindow*>(window());
if (wnd)
m_contextMenu = QtClient::buildMenu(*menu,*menu,wnd,SLOT(action()),
SLOT(toggled(bool)),this);
}
}
else if (param->name() == "notifyselchgonrightclick")
m_notifySelChgOnRClick = param->toBoolean(m_notifySelChgOnRClick);
else if (param->name() == "filterby") {
setFilter();
m_filterBy.clear();
ObjList* list = param->split(',',false);
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
m_filterBy.append(QtClient::setUtf8(s->toLower()));
}
TelEngine::destruct(list);
}
}
tbl.drop();
return true;
}
bool CustomTable::getOptions(NamedList& items)
{
int n = rowCount();
for (int i = 0; i < n; i++) {
String id;
if (getId(id,i) && id)
items.addParam(id,"");
}
return true;
}
bool CustomTable::addTableRow(const String& item, const NamedList* data, bool atStart)
{
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::addTableRow(%s,%p,%u)",
name().c_str(),item.c_str(),data,atStart);
SafeWidget tbl(this);
int row = atStart ? 0 : rowCount();
insertRow(row);
if (setRow(row,data,item))
return true;
removeRow(row);
return false;
}
// Add or set one or more table row(s). Screen update is locked while changing the table.
// Each data list element is a NamedPointer carrying a NamedList with item parameters.
// The name of an element is the item to update.
// Set element's value to boolean value 'true' to add a new item if not found
// or 'false' to set an existing one. Set it to empty string to delete the item
bool CustomTable::updateTableRows(const NamedList* data, bool atStart)
{
if (!data)
return true;
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::updateTableRows(%p,%u)",
name().c_str(),data,atStart);
// Remember selected item
bool ok = true;
SafeWidget tbl(this);
unsigned int n = data->length();
ObjList add;
// Delete and update rows
for (unsigned int i = 0; i < n; i++) {
if (Client::exiting())
break;
// Get item and the list of parameters
NamedString* ns = data->getParam(i);
if (!ns)
continue;
// Delete ?
if (ns->null()) {
int row = getRow(ns->name());
if (row >= 0)
removeRow(row);
else
ok = false;
continue;
}
// Set item or postpone add
int row = getRow(ns->name());
if (row >= 0)
setRow(row,YOBJECT(NamedList,ns));
else if (ns->toBoolean())
add.append(ns)->setDelete(false);
else
ok = false;
}
n = add.count();
if (n) {
int row = rowCount();
if (row < 0)
row = 0;
// Append if not requested to insert at start or table is empty
if (!(atStart && row))
setRowCount(row + n);
else {
for (unsigned int i = 0; i < n; i++)
insertRow(0);
}
for (ObjList* o = add.skipNull(); o; row++, o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (!setRow(row,YOBJECT(NamedList,ns),ns->name()))
ok = false;
}
}
return ok;
}
bool CustomTable::delTableRow(const String& item)
{
SafeWidget tbl(this);
int row = getRow(item);
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::delTableRow(%s) found=%d",
name().c_str(),item.c_str(),row);
if (row < 0)
return false;
removeRow(row);
return true;
}
bool CustomTable::setTableRow(const String& item, const NamedList* data)
{
SafeWidget tbl(this);
int row = getRow(item);
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setTableRow(%s,%p) found=%d",
name().c_str(),item.c_str(),data,row);
if (row < 0)
return false;
return setRow(row,data);
}
bool CustomTable::getTableRow(const String& item, NamedList* data)
{
int row = getRow(item);
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::getTableRow(%s,%p) found=%d",
name().c_str(),item.c_str(),data,row);
if (row < 0)
return false;
if (!data)
return true;
int n = columnCount();
for (int i = 1; i < n; i++) {
String name;
bool checkable = false;
QTableWidgetItem* h = getColumnId(name,checkable,i);
if (!(h && name))
continue;
QTableWidgetItem* it = QTableWidget::item(row,i);
if (!it)
continue;
NamedString* ns = new NamedString(name);
QtClient::getUtf8(*ns,it->text());
data->setParam(ns);
if (checkable)
data->setParam("check:" + name,String::boolText(it->checkState() == Qt::Checked));
}
return true;
}
bool CustomTable::clearTable()
{
setRowCount(0);
return true;
}
// Set the selected entry
bool CustomTable::setSelect(const String& item)
{
if (!item)
return true;
int row = getRow(item);
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setSelect(%s) found=%d",
name().c_str(),item.c_str(),row);
if (row < 0)
return false;
setCurrentCell(row,1);
return true;
}
bool CustomTable::getSelect(String& item)
{
int row = currentRow();
QTableWidgetItem* it = 0;
if (row >= 0) {
it = QTableWidget::item(row,0);
if (it)
QtClient::getUtf8(item,it->text());
}
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::getSelect() found=(%d,%s)",
name().c_str(),row,item.c_str());
return it != 0;
}
// Find a table row by its item id
int CustomTable::getRow(const String& item)
{
const QString tmp = QtClient::setUtf8(item);
for (int i = 0; i < rowCount(); i++) {
QTableWidgetItem* it = this->item(i,0);
if (it && it->text() == tmp)
return i;
}
return -1;
}
// Find a table row id by its row index
bool CustomTable::getId(String& item, int row)
{
QTableWidgetItem* it = this->item(row,0);
if (it)
QtClient::getUtf8(item,it->text());
return it != 0;
}
// Find a column by its label. Return -1 if not found
QTableWidgetItem* CustomTable::getColumnId(String& id, bool& checkable, int col)
{
QTableWidgetItem* it = horizontalHeaderItem(col);
if (!it)
return 0;
QVariant var = it->data(ColumnId);
if (var.type() == QVariant::String)
QtClient::getUtf8(id,var.toString());
else {
QtClient::getUtf8(id,it->text());
id.toLower();
}
var = it->data(ColumnItemCheckable);
checkable = var.toBool();
return it;
}
// Find a column by its label. Return -1 if not found
int CustomTable::getColumn(const QString& name, bool hidden, bool caseInsensitive)
{
static QString ht("hidden:");
QString what = name;
if (hidden)
what.insert(0,ht);
Qt::CaseSensitivity cs = caseInsensitive ? Qt::CaseInsensitive : Qt::CaseSensitive;
int n = columnCount();
for (int i = 0; i < n; i++) {
QTableWidgetItem* it = horizontalHeaderItem(i);
if (!it)
continue;
QVariant var = it->data(ColumnId);
if (var.type() == QVariant::String) {
if (0 == var.toString().compare(what,cs))
return i;
}
else if (0 == it->text().compare(what,cs))
return i;
}
return -1;
}
// (de)activate enter key press action
void CustomTable::setEnterPressNotify(bool value)
{
QAction* act = qFindChild<QAction*>(this,m_enterKeyActionName);
if (act) {
if (!value) {
QWidget::removeAction(act);
QtClient::deleteLater(act);
}
return;
}
if (!value)
return;
act = new QAction("",this);
act->setObjectName(m_enterKeyActionName);
act->setShortcut(QKeySequence(Qt::Key_Return));
act->setShortcutContext(Qt::WidgetShortcut);
act->setProperty("_yate_autoconnect",QVariant(false));
QWidget::addAction(act);
QtClient::connectObjects(act,SIGNAL(triggered()),this,SLOT(actionTriggered()));
}
// Retrieve table columns widths
QString CustomTable::getColWidths()
{
String widths;
int n = columnCount();
for (int i = 0; i < n; i++)
widths.append(String(columnWidth(i)),",",true);
return QtClient::setUtf8(widths);
}
// Set the table columns widths string
void CustomTable::setColWidths(QString value)
{
QHeaderView* hdr = horizontalHeader();
bool skipLast = hdr && hdr->stretchLastSection();
QStringList list = value.split(',');
for (int i = 0; i < list.size(); i++) {
if (skipLast && i == columnCount() - 1)
break;
bool ok = true;
int w = list[i].toInt(&ok);
if (ok && w >= 0)
setColumnWidth(i,w);
}
}
// Retrieve table sorting
QString CustomTable::getSorting()
{
String sorting;
if (isSortingEnabled()) {
QHeaderView* h = horizontalHeader();
int col = h ? h->sortIndicatorSection() : -1;
if (col >= 0)
sorting << col << "," <<
String::boolText(Qt::AscendingOrder == h->sortIndicatorOrder());
}
return QtClient::setUtf8(sorting);
}
// Set the table sorting
void CustomTable::setSorting(QString value)
{
QStringList list = value.split(',');
if (list.size() < 2)
return;
bool ok = true;
int col = list[0].toInt(&ok);
if (ok && col >= 0 && col < columnCount()) {
String tmp;
QtClient::getUtf8(tmp,list[1]);
sortItems(col,tmp.toBoolean(true) ? Qt::AscendingOrder : Qt::DescendingOrder);
}
}
// Setup a row
bool CustomTable::setRow(int row, const NamedList* data, const String& item)
{
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setRow(%d,%p,%s)",
name().c_str(),row,data,item.c_str());
m_changing = true;
int n = columnCount();
// First init
if (item) {
// Set row id
setItem(row,0,new QTableWidgetItem(QtClient::setUtf8(item)));
// Set row height
if (m_rowHeight > 0)
QTableWidget::setRowHeight(row,m_rowHeight);
// Set checkable columns
for (int i = 1; i < n; i++) {
String name;
bool checkable = false;
getColumnId(name,checkable,i);
if (!checkable)
continue;
QTableWidgetItem* it = QTableWidget::item(row,i);
if (!it) {
it = new QTableWidgetItem;
setItem(row,i,it);
}
it->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
it->setCheckState(Qt::Unchecked);
}
}
if (!data) {
m_changing = false;
return true;
}
for (int i = 1; i < n; i++) {
String name;
bool checkable = false;
getColumnId(name,checkable,i);
if (!name)
continue;
String* text = data->getParam(name);
String* img = data->getParam(name + "_image");
String* check = checkable ? data->getParam("check:" + name) : 0;
if (!(text || img || check))
continue;
QTableWidgetItem* it = QTableWidget::item(row,i);
if (!it) {
it = new QTableWidgetItem;
setItem(row,i,it);
if (!checkable)
it->setFlags(it->flags() & ~Qt::ItemFlags(Qt::ItemIsUserCheckable));
else {
it->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
it->setCheckState(Qt::Unchecked);
}
}
if (text)
it->setText(QtClient::setUtf8(*text));
if (check)
it->setCheckState(check->toBoolean() ? Qt::Checked : Qt::Unchecked);
if (img)
it->setIcon(QIcon(QtClient::setUtf8(*img)));
}
m_changing = false;
return true;
}
// Handle item cell content changes
void CustomTable::onCellChanged(int row, int col)
{
if (m_changing || row < 0 || !m_notifyItemChanged)
return;
String item;
getId(item,row);
if (item)
triggerAction(item,"listitemchanged",this);
}
void CustomTable::contextMenuEvent(QContextMenuEvent* e)
{
int yMax = rowCount() * rowHeight(0);
if (yMax < e->y())
return;
if (m_contextMenu)
m_contextMenu->exec(e->globalPos());
}
// Catch a mouse press event
// Disable selection change signal on right button events
void CustomTable::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::RightButton && !m_notifySelChgOnRClick) {
int row = rowAt(event->y());
if (row >= 0 && row != currentRow()) {
// Disconnect and re-connect only if connected
QtWindow* wnd = 0;
QVariant var = property("dynamicNoItemSelChanged");
if (!var.toBool())
wnd = QtClient::parentWindow(this);
if (wnd)
disconnect(this,SIGNAL(itemSelectionChanged()),
wnd,SLOT(selectionChanged()));
setCurrentCell(row,1);
if (wnd)
QtClient::connectObjects(this,SIGNAL(itemSelectionChanged()),
wnd,SLOT(selectionChanged()));
event->accept();
}
return;
}
QTableWidget::mousePressEvent(event);
}
// Slot for triggered signals received from actions added to the table
void CustomTable::actionTriggered()
{
if (!sender() || currentRow() < 0)
return;
if (sender()->objectName() == m_enterKeyActionName)
onAction(this);
}
// Set filter (hide not matching items)
void CustomTable::setFilter(const String& value)
{
DDebug(ClientDriver::self(),DebugAll,"CustomTable(%s)::setFilter(%s)",
name().c_str(),value.c_str());
SafeWidget tbl(this);
QString tmp = QtClient::setUtf8(value);
if (tmp == m_filterValue)
return;
m_filterValue = tmp;
// Match rows and show or hide them
int rows = rowCount();
int cols = columnCount();
for (int row = 0; row < rows; row++)
for (int col = 0; col < cols; col++)
if (updateFilter(row,col))
break;
}
// Check if the current filter matches a row. Show it if matched, hide it otherwise.
bool CustomTable::updateFilter(int row, int col)
{
bool hide = !rowFilterMatch(row,col);
if (hide == isRowHidden(row))
return false;
setRowHidden(row,hide);
return true;
}
// Check if the current filter matches a row
bool CustomTable::rowFilterMatch(int row, int col)
{
for (int i = m_filterBy.size() - 1; i >= 0; i--) {
QTableWidgetItem* hdr = horizontalHeaderItem(col);
if (!hdr || hdr->text() != m_filterBy[i])
continue;
QTableWidgetItem* it = item(row,col);
if (it && it->text().contains(m_filterValue,Qt::CaseInsensitive))
return true;
}
return false;
}
/*
* CustomTableFactory
*/
// Build CustomTable
void* CustomTableFactory::create(const String& type, const char* name, NamedList* params)
{
if (!params)
return 0;
QWidget* parentWidget = 0;
String* wndname = params->getParam("parentwindow");
if (!TelEngine::null(wndname)) {
String* wName = params->getParam("parentwidget");
QtWindow* wnd = static_cast<QtWindow*>(Client::self()->getWindow(*wndname));
if (wnd && !TelEngine::null(wName))
parentWidget = qFindChild<QWidget*>(wnd,QtClient::setUtf8(*wName));
}
if (type == "CustomTable")
return new CustomTable(name,*params,parentWidget);
return 0;
}
}; // anonymous namespace
#include "customtable.moc"
/* vi: set ts=8 sw=4 sts=4 noet: */