# -*- coding: utf-8 -*-
# Copyright: SCLE SFE
# Contributor: Julien Pagès <j.parkouss@gmail.com>
#
# This software is a computer program whose purpose is to test graphical
# applications written with the QT framework (http://qt.digia.com/).
#
# This software is governed by the CeCILL v2.1 license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL v2.1 license and that you accept its terms.
"""
Definition of widgets and models useable in funq.
"""
from funq.tools import wait_for
from funq.errors import FunqError
import json
class TreeItem(object): # pylint: disable=R0903
"""
Defines an abstract item that contains subitems
"""
client = None
items = None
@classmethod
def create(cls, client, data):
"""
Allow to create a TreeItem from a dico data decoded from json.
"""
self = cls()
self.client = client
for k, v in data.iteritems():
if k != 'items':
setattr(self, k, v)
self.items = [cls.create(client, d) for d in data.get('items', []) ]
return self
class TreeItems(object):
"""
Abstract class to manipulate data that contains :class:`TreeItem`. Used
by modelitems and graphicsitems.
"""
client = None
items = None
ITEM_CLASS = TreeItem
@classmethod
def create(cls, client, data):
"""
Allow to create an instance of the class given some data coming from
decoded json.
"""
self = cls()
self.client = client
self.items = [cls.ITEM_CLASS.create(client, v1) for v1 in data['items']]
return self
def iter(self):
"""
Allow to iterate on every items recursively.
Example::
for item in items.iter():
print item
"""
items = self.items
while items:
item = items.pop(0)
items = item.items + items
yield item
class WidgetMetaClass(type):
"""
Saves a dict of accessible classes to handle inheritance of Widgets.
"""
cpp_classes = {}
def __new__(mcs, name, bases, attrs):
cls = super(WidgetMetaClass, mcs).__new__(mcs, name, bases, attrs)
qt_name = getattr(cls, 'CPP_CLASS', None)
if qt_name:
mcs.cpp_classes[qt_name] = cls
return cls
[docs]class ModelItem(TreeItem):
"""
Allow to manipulate a modelitem in a QAbstractModelItem or derived.
:var viewid: ID of the view attached to the model containing this item
[type: long]
:var row: item row number [type: int]
:var column: item column number [type: int]
:var value: item text value [type: unicode]
:var check_state: item text value of the check state, or None
:var itempath: Internal ID to localize this item [type: str ou None]
:var items: list of subitems [type: :class:`ModelItem`]
"""
viewid = None
row = None
column = None
itempath = None
check_state = None
def _action(self, itemaction):
""" Send the 'model_item_action' action """
self.client.send_command('model_item_action',
oid=self.viewid,
itemaction=itemaction,
row=self.row, column=self.column,
itempath=self.itempath)
[docs] def is_checkable(self):
"""Returns True if the item is checkable"""
return self.check_state is not None
[docs] def is_checked(self):
"""Returns True if the item is checked"""
return self.check_state == 'checked'
[docs] def select(self):
"""
Select this item.
"""
self._action("select")
[docs] def edit(self):
"""
Edit this item.
"""
self._action("edit")
[docs] def click(self):
"""
Click on this item.
"""
self._action("click")
[docs] def dclick(self):
"""
Double click on this item.
"""
self._action("doubleclick")
[docs]class ModelItems(TreeItems):
"""
Allow to manipulate all modelitems in a QAbstractModelItem or derived.
:var items: list of :class:`ModelItem`
"""
ITEM_CLASS = ModelItem
[docs] def item_by_named_path(self, named_path, match_column=0, sep='/', column=0):
"""
Returns the item (:class:`ModelItem`) that match the arborescence
defined by `named_path` and in the given column.
.. note::
The arguments are the same as for :meth:`row_by_named_path`, with
the addition of `column`.
:param column: the column of the desired item
"""
items = self.row_by_named_path(named_path,
match_column=match_column,
sep=sep)
if items:
return items[column]
[docs] def row_by_named_path(self, named_path, match_column=0, sep='/'):
"""
Returns the item list of :class:`ModelItem` that match the arborescence
defined by `named_path`, or None if the path does not exists.
.. important::
Use unicode characters in `named_path` to match elements with non-ascii
characters.
Example::
model_items.row_by_named_path([u'TG/CBO/AP (AUT 1)',
u'Paramètres tranche',
u'TG',
u'DANGER'])
:param named_path: path for the interesting ModelIndex. May be
defined with a list of str or with a single str
that will be splitted on `sep`.
:param match_column: column used to check`named_path` is a string.
"""
if isinstance(named_path, (list, tuple)):
parts = list(named_path)
else:
parts = named_path.split(sep)
item = self
while item and parts:
next_item = None
part = parts.pop(0)
for item_ in item.items:
if match_column == item_.column and item_.value == part:
# on a trouvé l'item que l'on veut
# si c'est le dernier, ramassons toutes les colonnes
if not parts:
row = [ it for it in item.items
if it.row == item_.row ]
return sorted(row, key=lambda it: it.column)
else:
next_item = item_
item = next_item
return None
[docs]class AbstractItemView(Widget):
"""
Specific Widget to manipulate QAbstractItemView or derived.
"""
CPP_CLASS = 'QAbstractItemView'
editor_class_names = ('QLineEdit', 'QComboBox', 'QSpinBox',
'QDoubleSpinBox')
[docs] def model_items(self):
"""
Returns an instance of :class:`ModelItems` based on the model
associated to the view.
"""
data = self.client.send_command('model_items', oid=self.oid)
return ModelItems.create(self.client, data)
[docs] def current_editor(self, editor_class_name=None):
"""
Returns the editor actually opened on this view. One item must be
in editing mode, by using :meth:`ModelItem.dclick` or
:meth:`ModelItem.edit` for example.
Currently these editor types are handled:
'QLineEdit', 'QComboBox', 'QSpinBox' and 'QDoubleSpinBox'.
:param editor_class_name: name of the editor type. If None, every
type of editor will be tested (this may
actually be very slow)
"""
qt_path = '::qt_scrollarea_viewport::%s'
if editor_class_name:
return self.client.widget(path=self.path
+ qt_path % editor_class_name)
for editor_class_name in self.editor_class_names:
try:
return self.client.widget(path=self.path
+ qt_path % editor_class_name)
except FunqError:
pass
raise FunqError("MissingEditor", 'Unable to find an editor.'
' Possible editors: %s' % repr(self.editor_class_names))
[docs]class TabBar(Widget):
"""
Allow to manipulate a QTabBar Widget.
"""
CPP_CLASS = "QTabBar"
[docs] def tab_texts(self):
"""
Returns the list of texts in tabbar.
"""
data = self.client.send_command('tabbar_list', oid=self.oid)
return data["tabtexts"]
[docs] def set_current_tab(self, tab_index_or_name):
"""
Define the current tab given an index or a tab text.
"""
tabnames = self.tab_texts()
if isinstance(tab_index_or_name, int):
index = tab_index_or_name
if index < 0 or index >= len(tabnames):
raise ValueError("Invalid tab Index %d" % index)
else:
index = tabnames.index(tab_index_or_name)
self.set_property('currentIndex', index)
[docs]class GItem(TreeItem):
"""
Allow to manipulate a QGraphicsItem.
:var viewid: ID of the view attached to the model containing this item
[type: long]
:var stackpath: Internal gitem ID, based on stackIndex and parent items
[type: str]
:var objectname: value of the "objectName" property if it inherits
from QObject. [type: unicode or None]
:var classes: list of names of class inheritance if it inherits from QObject.
[type: list(str) or None]
:var items: list of subitems [type: :class:`GItem`]
"""
viewid = None
stackpath = None
objectname = None
classes = None
[docs] def is_qobject(self):
""" Returns True if this GItem inherits QObject """
return self.objectname != None
[docs] def properties(self):
"""
Return the properties of the GItem. The GItem must inherits from
QObject.
"""
return self.client.send_command('gitem_properties',
oid=self.viewid,
stackpath=self.stackpath)
def _action(self, itemaction):
""" Send the command 'model_gitem_action' """
self.client.send_command('model_gitem_action',
oid=self.viewid,
itemaction=itemaction,
stackpath=self.stackpath)
[docs] def click(self):
"""
Click on this gitem.
"""
self._action("click")
[docs] def dclick(self):
"""
Double click on this gitem.
"""
self._action("doubleclick")
[docs]class GItems(TreeItems):
"""
Allow to manipulate a group of QGraphicsItems.
:var items: list of :class:`GItem` that are on top of the scene
(and not subitems)
"""
ITEM_CLASS = GItem
[docs]class GraphicsView(Widget):
"""
Allow to manipulate an instance of QGraphicsView.
"""
CPP_CLASS = 'QGraphicsView'
[docs] def gitems(self):
"""
Returns an instance of :class:`GItems`, that will contains every items
of this QGraphicsView.
"""
data = self.client.send_command('graphicsitems', oid=self.oid)
return GItems.create(self.client, data)
[docs] def dump_gitems(self, stream='gitems.json'):
"""
Write in a file the list of graphics items.
"""
data = self.client.send_command('graphicsitems', oid=self.oid)
if isinstance(stream, basestring):
stream = open(stream, 'w')
json.dump(data,
stream, sort_keys=True, indent=4, separators=(',', ': '))
[docs]class ComboBox(Widget):
"""
Allow to manipulate a QCombobox.
"""
CPP_CLASS = 'QComboBox'
[docs] def model_items(self):
"""
Returns the items (:class:`ModelItems`) associated to this combobox.
"""
# création et affichage de QComboBoxListView
self.click()
# recuperation de ce widget QComboBoxListView
internal_qt_name = '::QComboBoxPrivateContainer::QComboBoxListView'
combo_edit_view = self.client.widget(path=self.path + internal_qt_name)
model_items = combo_edit_view.model_items()
# on cache la QComboBoxListView
#combo_edit_view.set_property('visible', False)
combo_edit_view.click() # Permet de fermer la combobox proprement
return model_items
[docs] def set_current_text(self, text):
"""
Define the text of the combobox, ensuring that it is a possible value.
"""
if not isinstance(text, basestring):
raise TypeError('the text parameter must be a string'
' - got %s' % type(text))
model_items = self.model_items()
column = self.properties()['modelColumn']
index = -1
for item in model_items.items:
if column == int(item.column) and item.value == text:
index = int(item.row)
break
assert index > -1, ("Le texte `%s` n'est pas dans la combobox `%s`"
% (text, self.path))
self.set_property('currentIndex', index)