diff --git a/.idea/pyforms.iml b/.idea/pyforms.iml deleted file mode 100644 index 5554030..0000000 --- a/.idea/pyforms.iml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 53a74ef..1655be8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Important](https://img.shields.io/badge/Important-Note-red.svg "Screen") If you find this project useful, please, do not forget to ![star it](https://raw.githubusercontent.com/UmSenhorQualquer/pyforms/v1.0.beta/docs/imgs/start.png?raw=true "Screen") it. -![New version available](https://img.shields.io/badge/New%20version%20available-2.0-green.svg "Screen") +![New version available](https://img.shields.io/badge/New%20version%20available-3.0-green.svg "Screen") @@ -15,7 +15,7 @@ Pyforms is a Python 2.7.x and 3.x cross-enviroment framework to develop GUI appl ### It offers: * A Python layer of Desktop forms, based on PyQt, OpenGL and other libraries. * A Python layer that allow applications to run on Desktop GUI, Web and terminal without requiring code modifications. -* A group of rules and methodologies that help the developer maintaining his code short, clean, reusable and readable. +* A group of rules and methodologies that help the developer maintain short, clean, reusable and readable code. ![Diagram](docs/pyforms.png?raw=true "Screen") @@ -35,6 +35,7 @@ Example of an application running in the Desktop, Web and Terminal enviroments: ## Examples of applications developed in Pyforms * [Python Video Annotator](https://github.com/UmSenhorQualquer/pythonVideoAnnotator) * [3D tracking analyser](https://github.com/UmSenhorQualquer/3D-tracking-analyser) +* [PyBpod](http://pybpod.readthedocs.io) ## Documentation @@ -82,7 +83,7 @@ class ComputerVisionAlgorithm(BaseWidget): #Define the event that will be called when the run button is processed self._runbutton.value = self.__runEvent #Define the event called before showing the image in the player - self._player.processFrame = self.__processFrame + self._player.process_frame_event = self.__process_frame #Define the organization of the Form Controls self._formset = [ @@ -99,7 +100,7 @@ class ComputerVisionAlgorithm(BaseWidget): """ self._player.value = self._videofile.value - def __processFrame(self, frame): + def __process_frame(self, frame): """ Do some processing to the frame and return the result frame """ diff --git a/build_settings/win/build.bat b/build_settings/win/build.bat deleted file mode 100644 index 13a4b27..0000000 --- a/build_settings/win/build.bat +++ /dev/null @@ -1,31 +0,0 @@ -@echo off - -setlocal enableextensions enabledelayedexpansion - -rem set /A PYTHON_VERSION=2 - -IF /I "%PYTHON_VERSION%" EQU "2" ( - set "WINPYDIR=C:\Users\swp\Python\WinPython-32bit-2.7.10.3\python-2.7.10" - set "WINPYVER=2.7.10.3" -) ELSE ( - set "WINPYDIR=C:\WinPython\WinPython-64bit-3.5.3.0Qt5\python-3.5.3.amd64" - set "WINPYVER=3.5.3.0Qt5" -) - -set "HOME=%WINPYDIR%\..\settings" -set "WINPYARCH=WIN32" - -set "PATH=%WINPYDIR%\Lib\site-packages\PyQt4;%WINPYDIR%\;%WINPYDIR%\DLLs;%WINPYDIR%\Scripts;%WINPYDIR%\..\tools;" - -rem keep nbextensions in Winpython directory, rather then %APPDATA% default -set "JUPYTER_DATA_DIR=%WINPYDIR%\..\settings" - -set PROJECTNAME="pyforms" - -python --version - -pip uninstall -y %PROJECTNAME% -pip install . -pip show %PROJECTNAME% - -python setup.py sdist --formats=zip \ No newline at end of file diff --git a/docs/api-documentation/basewidget.md b/docs/api-documentation/basewidget.md index 40918b1..a7ccf34 100644 --- a/docs/api-documentation/basewidget.md +++ b/docs/api-documentation/basewidget.md @@ -37,9 +37,9 @@ class SimpleExample(BaseWidget): ### \_\_init\_\_(title='Untitled', parent_win=None, win_flag=None) -**title** - Title of the window. -**parent_win** - Parent widget. If the win_flag is None and the parent parameters is set, then the flag_win will be QtCore.Qt.Dialog. -**win_flag** - Window type flag. Type of Qt.WindowType. +**title** - Title of the window. +**parent_win** - Parent widget. If the win_flag is None and the parent parameters is set, then the flag_win will be QtCore.Qt.Dialog. +**win_flag** - Window type flag. Type of Qt.WindowType. ## **Functions** *************************** diff --git a/docs/api-documentation/controls.md b/docs/api-documentation/controls.md index 477fc12..e9b296c 100644 --- a/docs/api-documentation/controls.md +++ b/docs/api-documentation/controls.md @@ -613,6 +613,13 @@ Returns a list of graphs objects. The control may be used to select a file. +### **Constructer** +*************************** + +#### \_\_init\_\_(label='', default='', helptext='', use_save_dialog=False) + +**use_save_dialog** - By default the control will select a file to be opened. If this flab is True The control will open a file to be created or updated. + ### **Functions** *************************** @@ -754,7 +761,7 @@ Returns the a specific cell from the QTableWidget. #### data_changed_event(row, col, item) -Called when any of list the content is updated. +Called when any of content of the list is updated. #### item_selection_changed_event() @@ -775,6 +782,10 @@ Called on item item double click. ### **Properties** *************************** +#### autoscroll + +Returns and sets the autoscroll flag. If True the ControlList will position the scroll automatically to a position where the new new added item will be visible. + #### horizontal_headers Get and set the horizontal headers in the table list. @@ -820,6 +831,42 @@ Returns and sets the readonly flag for the control. +## ControlMatplotlib +*************************** + +![Docs updated](https://img.shields.io/badge/UNITARY%20TESTS-OK-green.svg "Screen") + +![Control image](https://raw.githubusercontent.com/UmSenhorQualquer/pyforms/master/tutorials/Controls4Docs/ControlMatplotlib.png?raw=true "Screen") + +### **Functions** +*************************** + +#### draw() + +Draw the graph. + +### **Properties** +*************************** + +#### value + +Sets and gets a function called to draw the graph. This functions receives a matplotlib figure as parameter. + + +Usage: +```python + +def on_draw(figure: + x = range(len(self.value)) + + axes = figure.add_subplot(111, projection='3d') + axes.clear(); + pts = axes.scatter(x, x, x, c=x) + figure.colorbar(pts) + + +obj.value = on_draw +``` @@ -1210,6 +1257,11 @@ Append text to the bottom. Returns and sets the readonly flag for the control. +#### autoscroll + +Returns and sets the autoscroll flag. If True the ControlTextArea will scroll always the text to the bottom everytime a new text is added. + + ## ControlToolBox *************************** diff --git a/docs/api-documentation/settings.md b/docs/api-documentation/settings.md index d61cf7c..4424bd7 100644 --- a/docs/api-documentation/settings.md +++ b/docs/api-documentation/settings.md @@ -1,6 +1,6 @@ # Settings -Pyforms is using the pysettings library to manage it settings. +Pyforms is using the pyforms.utils.settings_manager library to manage it settings. Here it is described some of the settings of the library. diff --git a/docs/getting-started/multiple-windows.md b/docs/getting-started/multiple-windows.md index 7212031..12c124a 100644 --- a/docs/getting-started/multiple-windows.md +++ b/docs/getting-started/multiple-windows.md @@ -67,8 +67,8 @@ This window should inherit from the BaseWidget and Person classes. ```python import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton from Person import Person class PersonWindow(Person, BaseWidget): @@ -111,7 +111,7 @@ This window should inherit from the BaseWidget and People classes. ```python import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlList +from pyforms.controls import ControlList from People import People from PersonWindow import PersonWindow from AddMenuFuntionality import AddMenuFuntionality @@ -179,7 +179,7 @@ The application will look like: Instead of opening a new window everytime we want to add a new Person, we will change the Application to open the PersonWindow inside the PeopleWindow. For this we will use the ControlEmptyWidget. ```python -from pyforms.Controls import ControlEmptyWidget +from pyforms.controls import ControlEmptyWidget ... def __init__(self): @@ -205,7 +205,7 @@ from pyforms.Controls import ControlEmptyWidget A DockWidget works like the EmptyWidget but can be detached or moved around the sides of the main Window. ```python -from pyforms.Controls import ControlDockWidget +from pyforms.controls import ControlDockWidget ... def __init__(self): diff --git a/docs/getting-started/run-terminal.md b/docs/getting-started/run-terminal.md index 1d89a8a..38d7fd3 100644 --- a/docs/getting-started/run-terminal.md +++ b/docs/getting-started/run-terminal.md @@ -7,8 +7,8 @@ The next code produces this Window: ```python import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton from Person import Person class PersonWindow(Person, BaseWidget): diff --git a/docs/getting-started/the-basic.md b/docs/getting-started/the-basic.md index a6a9013..fb0b632 100644 --- a/docs/getting-started/the-basic.md +++ b/docs/getting-started/the-basic.md @@ -16,8 +16,8 @@ Import the pyforms library, the BaseWidget and the Controls classes that you wil ```python import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton ``` ### Create your application class. @@ -70,8 +70,8 @@ The final code should look like: ```python import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton class SimpleExample1(BaseWidget): diff --git a/docs/index.md b/docs/index.md index 62b5a5f..b5f5f46 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,9 +14,9 @@ Pyforms is a Python 2.7.x and 3.x cross-enviroment framework to develop GUI appl ### It offers * A Python layer of Desktop forms, based on PyQt, OpenGL and other libraries. * A Python layer that allow applications to run on Desktop GUI, Web and terminal without requiring code modifications. -* A group of rules and methodologies that help the developer maintaining his code short, clean, reusable and readable. +* A group of rules and methodologies that help the developer maintain short, clean, reusable and readable code. -![Diagram](https://raw.githubusercontent.com/UmSenhorQualquer/pyforms/v1.0.beta/docs/pyforms.png?raw=true "Screen") +![Diagram](https://raw.githubusercontent.com/UmSenhorQualquer/pyforms/v3.0/docs/pyforms.png?raw=true "Screen") Example of an application running in the Desktop, Web and Terminal enviroments: @@ -28,35 +28,4 @@ Example of an application running in the Desktop, Web and Terminal enviroments: Run in the terminal: ```bash pip install pyforms -``` - -### Requirements - -* [setuptools] (https://pypi.python.org/pypi/setuptools) -* [Python 2.7](https://www.python.org/download/releases/2.7/) -* [pysettings](https://github.com/UmSenhorQualquer/pysettings.git) -* [PyQt4](http://www.riverbankcomputing.co.uk/software/pyqt/download) -* [PyOpenGL](http://pyopengl.sourceforge.net/) [Optional - Only used by some Controls] -* [VisVis](https://code.google.com/p/visvis/) [Optional - Only used by some Controls] -* [Numpy](http://www.numpy.org/) [Optional - Only used by some Controls] -* [Python opencv](http://opencv.org/) [Optional - Only used by some Controls] - - -### Ubuntu 14 - -* setuptools: ```sudo apt-get install python-setuptools``` -* pysettings: ```sudo pip install git+https://github.com/UmSenhorQualquer/pysettings.git``` -* Opengl: ```sudo apt-get install python-opengl``` -* OpenCV: ```sudo apt-get install python-opencv``` -* PyQt4: ```sudo apt-get install pyqt4-dev-tools python-qt4``` -* PyQt4 OpenGL Widget [Optional]: ```sudo apt-get install python-qt4-gl``` -* Pyforms: ```sudo pip install pyforms``` -* VisVis: ```sudo pip install visvis``` - -### Mac OSx - -* Install python and its tools using [Homebrew] (http://brew.sh) -* [Scientific Python on Mac OS X 10.9+ with homebrew | Jörn's Blog](https://joernhees.de/blog/2014/02/25/scientific-python-on-mac-os-x-10-9-with-homebrew/) -* [Installing scientific Python on Mac OS X | Lowin Data Company](http://www.lowindata.com/2013/installing-scientific-python-on-mac-os-x/) -* run ```sudo python setup.py install``` - +``` \ No newline at end of file diff --git a/pyforms/Controls.py b/pyforms/Controls.py deleted file mode 100644 index 9c33ed1..0000000 --- a/pyforms/Controls.py +++ /dev/null @@ -1,108 +0,0 @@ -from pyforms import conf -import logging, traceback - -logger = logging.getLogger(__file__) - -if conf.PYFORMS_MODE in ['GUI', 'GUI-OPENCSP']: - - from pyforms.gui.Controls.ControlBase import ControlBase - from pyforms.gui.Controls.ControlText import ControlText - from pyforms.gui.Controls.ControlBoundingSlider import ControlBoundingSlider - from pyforms.gui.Controls.ControlButton import ControlButton - from pyforms.gui.Controls.ControlCheckBoxList import ControlCheckBoxList - from pyforms.gui.Controls.ControlCheckBox import ControlCheckBox - from pyforms.gui.Controls.ControlCombo import ControlCombo - from pyforms.gui.Controls.ControlDir import ControlDir - from pyforms.gui.Controls.ControlDockWidget import ControlDockWidget - from pyforms.gui.Controls.ControlEmptyWidget import ControlEmptyWidget - from pyforms.gui.Controls.ControlFile import ControlFile - from pyforms.gui.Controls.ControlFilesTree import ControlFilesTree - from pyforms.gui.Controls.ControlLabel import ControlLabel - from pyforms.gui.Controls.ControlList import ControlList - from pyforms.gui.Controls.ControlMdiArea import ControlMdiArea - from pyforms.gui.Controls.ControlNumber import ControlNumber - - if conf.PYFORMS_MATPLOTLIB_ENABLED: - try: - from pyforms.gui.Controls.ControlMatplotlib import ControlMatplotlib - except: - logger.warning("Matplot lib not installed or not working properly") - logger.warning(traceback.format_exc()) - - if conf.PYFORMS_WEB_ENABLED: - try: - from pyforms.gui.Controls.ControlWeb import ControlWeb - except: - logger.warning("QtWebKit lib not installed or not working properly") - logger.warning(traceback.format_exc()) - - if conf.PYFORMS_GL_ENABLED: - try: - from pyforms.gui.Controls.ControlOpenGL import ControlOpenGL - from pyforms.gui.Controls.ControlImage import ControlImage - from pyforms.gui.Controls.ControlPlayer.ControlPlayer import ControlPlayer - except: - logger.warning("GL widgets or Opencv not installed") - logger.warning(traceback.format_exc()) - - from pyforms.gui.Controls.ControlProgress import ControlProgress - from pyforms.gui.Controls.ControlSlider import ControlSlider - from pyforms.gui.Controls.ControlTextArea import ControlTextArea - from pyforms.gui.Controls.ControlToolBox import ControlToolBox - from pyforms.gui.Controls.ControlTree import ControlTree - from pyforms.gui.Controls.ControlTreeView import ControlTreeView - - if conf.PYFORMS_VISVIS_ENABLED: - try: - from pyforms.gui.Controls.ControlVisVis import ControlVisVis - from pyforms.gui.Controls.ControlVisVisVolume import ControlVisVisVolume - except: - logger.warning("VisVis not installed") - #logger.warning(traceback.format_exc()) - - from pyforms.gui.Controls.ControlEventTimeline.ControlEventTimeline import ControlEventTimeline - from pyforms.gui.Controls.ControlEventsGraph.ControlEventsGraph import ControlEventsGraph - - try: - from pyforms.gui.Controls.ControlCodeEditor import ControlCodeEditor - except: - logger.debug("QScintilla2 not installed") - logger.debug(traceback.format_exc()) - - -elif conf.PYFORMS_MODE in ['TERMINAL']: - - from pyforms.terminal.Controls.ControlBase import ControlBase - from pyforms.terminal.Controls.ControlText import ControlText - from pyforms.terminal.Controls.ControlButton import ControlButton - from pyforms.terminal.Controls.ControlCheckBox import ControlCheckBox - from pyforms.terminal.Controls.ControlCombo import ControlCombo - from pyforms.terminal.Controls.ControlFile import ControlFile - from pyforms.terminal.Controls.ControlDir import ControlDir - from pyforms.terminal.Controls.ControlImage import ControlImage - from pyforms.terminal.Controls.ControlSlider import ControlSlider - from pyforms.terminal.Controls.ControlPlayer import ControlPlayer - from pyforms.terminal.Controls.ControlNumber import ControlNumber - from pyforms.terminal.Controls.ControlProgress import ControlProgress - -elif conf.PYFORMS_MODE in ['WEB']: - - from pyforms_web.web.Controls.ControlBase import ControlBase - from pyforms_web.web.Controls.ControlText import ControlText - from pyforms_web.web.Controls.ControlBoundingSlider import ControlBoundingSlider - from pyforms_web.web.Controls.ControlDate import ControlDate - from pyforms_web.web.Controls.ControlButton import ControlButton - from pyforms_web.web.Controls.ControlCheckBox import ControlCheckBox - from pyforms_web.web.Controls.ControlCombo import ControlCombo - from pyforms_web.web.Controls.ControlDir import ControlDir - from pyforms_web.web.Controls.ControlFile import ControlFile - from pyforms_web.web.Controls.ControlImage import ControlImage - from pyforms_web.web.Controls.ControlSlider import ControlSlider - from pyforms_web.web.Controls.ControlPlayer import ControlPlayer - from pyforms_web.web.Controls.ControlProgress import ControlProgress - from pyforms_web.web.Controls.ControlVisVis import ControlVisVis - from pyforms_web.web.Controls.ControlList import ControlList - from pyforms_web.web.Controls.ControlLabel import ControlLabel - from pyforms_web.web.Controls.ControlTimeout import ControlTimeout - from pyforms_web.web.Controls.ControlEmptyWidget import ControlEmptyWidget - from pyforms_web.web.Controls.ControlWorkflow import ControlWorkflow diff --git a/pyforms/__init__.py b/pyforms/__init__.py index 0c4aa63..ca64213 100644 --- a/pyforms/__init__.py +++ b/pyforms/__init__.py @@ -1,40 +1,38 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -import logging -import loggingbootstrap -from pysettings import conf -conf += 'pyforms.settings' +from pyforms.utils.settings_manager import conf -__author__ = "Ricardo Ribeiro" -__credits__ = ["Ricardo Ribeiro"] -__license__ = "MIT" -__version__ = '2.0.5' -__maintainer__ = ["Ricardo Ribeiro", "Carlos Mão de Ferro"] -__email__ = ["ricardojvr@gmail.com", "cajomferro@gmail.com"] -__status__ = "Production" +conf += 'pyforms.settings' -logger = logging.getLogger(__name__) +__author__ = "Ricardo Ribeiro" +__credits__ = ["Ricardo Ribeiro"] +__license__ = "MIT" +__version__ = '3.0.1' +__maintainer__ = ["Ricardo Ribeiro", "Carlos Mão de Ferro"] +__email__ = ["ricardojvr@gmail.com", "cajomferro@gmail.com"] +__status__ = "Production" if conf.PYFORMS_MODE in ['GUI', 'GUI-OPENCSP']: - from pyforms.gui import Controls - from pyforms.gui.BaseWidget import BaseWidget - - if conf.PYFORMS_MODE in ['GUI-OPENCSP']: - from pyforms.gui.appmanager import start_app - else: - from pyforms.gui.standaloneManager import start_app + from pyforms.gui import controls + from pyforms.gui.basewidget import BaseWidget + from pyforms.gui.appmanager import start_app + from pyforms.gui.basewidget import vsplitter, hsplitter elif conf.PYFORMS_MODE in ['TERMINAL']: - from pyforms.terminal import Controls - from pyforms.terminal.BaseWidget import BaseWidget + from pyforms.terminal import controls + from pyforms.terminal.basewidget import BaseWidget from pyforms.terminal.appmanager import start_app elif conf.PYFORMS_MODE in ['WEB']: - from pyforms_web.web import Controls - from pyforms_web.web.BaseWidget import BaseWidget + from pyforms_web.web import controls + from pyforms_web.web.basewidget import BaseWidget, no_columns, segment from pyforms_web.web.appmanager import start_app + + from pyforms_web.web.django_pyforms.model_admin import ModelAdmin + from pyforms_web.web.django_pyforms.model_admin import ViewFormAdmin + from pyforms_web.web.django_pyforms.model_admin import EditFormAdmin diff --git a/pyforms/controls.py b/pyforms/controls.py new file mode 100644 index 0000000..b2d355a --- /dev/null +++ b/pyforms/controls.py @@ -0,0 +1,109 @@ +from pyforms import conf + +if conf.PYFORMS_MODE=='GUI': + import logging, traceback; logger=logging.getLogger(__file__) + + from pyforms.gui.controls.ControlBase import ControlBase + from pyforms.gui.controls.ControlText import ControlText + from pyforms.gui.controls.ControlBoundingSlider import ControlBoundingSlider + from pyforms.gui.controls.ControlButton import ControlButton + from pyforms.gui.controls.ControlToolButton import ControlToolButton + from pyforms.gui.controls.ControlCheckBoxList import ControlCheckBoxList + from pyforms.gui.controls.ControlCheckBox import ControlCheckBox + from pyforms.gui.controls.ControlCombo import ControlCombo + from pyforms.gui.controls.ControlDir import ControlDir + from pyforms.gui.controls.ControlDockWidget import ControlDockWidget + from pyforms.gui.controls.ControlEmptyWidget import ControlEmptyWidget + from pyforms.gui.controls.ControlFile import ControlFile + from pyforms.gui.controls.ControlFilesTree import ControlFilesTree + from pyforms.gui.controls.ControlLabel import ControlLabel + from pyforms.gui.controls.ControlList import ControlList + from pyforms.gui.controls.ControlMdiArea import ControlMdiArea + from pyforms.gui.controls.ControlNumber import ControlNumber + from pyforms.gui.controls.ControlProgress import ControlProgress + from pyforms.gui.controls.ControlSlider import ControlSlider + from pyforms.gui.controls.ControlTextArea import ControlTextArea + from pyforms.gui.controls.ControlToolBox import ControlToolBox + from pyforms.gui.controls.ControlTableView import ControlTableView + from pyforms.gui.controls.ControlTree import ControlTree + from pyforms.gui.controls.ControlTreeView import ControlTreeView + from pyforms.gui.controls.control_event_timeline.ControlEventTimeline import ControlEventTimeline + from pyforms.gui.controls.control_events_graph.ControlEventsGraph import ControlEventsGraph + + if conf.PYFORMS_MATPLOTLIB_ENABLED: + try: + from pyforms.gui.controls.ControlMatplotlib import ControlMatplotlib + except Exception as e: + logger.warning("Matplot lib not installed or not working properly") + logger.error(e, exc_info=True) + + if conf.PYFORMS_WEB_ENABLED: + try: + from pyforms.gui.controls.ControlWeb import ControlWeb + except Exception as e: + logger.warning("QtWebKit lib not installed or not working properly") + logger.error(e, exc_info=True) + + if conf.PYFORMS_GL_ENABLED: + try: + from pyforms.gui.controls.ControlOpenGL import ControlOpenGL + from pyforms.gui.controls.ControlImage import ControlImage + from pyforms.gui.controls.control_player.ControlPlayer import ControlPlayer + except Exception as e: + logger.warning("GL widgets or Opencv is not installed") + logger.error(e, exc_info=True) + + if conf.PYFORMS_VISVIS_ENABLED: + try: + from pyforms.gui.controls.ControlVisVis import ControlVisVis + from pyforms.gui.controls.ControlVisVisVolume import ControlVisVisVolume + except Exception as e: + logger.warning("VisVis not installed") + logger.error(e, exc_info=True) + + if conf.PYFORMS_QSCINTILLA_ENABLED: + try: + from pyforms.gui.controls.ControlCodeEditor import ControlCodeEditor + except Exception as e: + logger.warning("QScintilla2 not installed") + logger.error(e, exc_info=True) + + +elif conf.PYFORMS_MODE=='TERMINAL': + + from pyforms.terminal.controls.ControlBase import ControlBase + from pyforms.terminal.controls.ControlBoundingSlider import ControlBoundingSlider + from pyforms.terminal.controls.ControlText import ControlText + from pyforms.terminal.controls.ControlButton import ControlButton + from pyforms.terminal.controls.ControlCheckBox import ControlCheckBox + from pyforms.terminal.controls.ControlCombo import ControlCombo + from pyforms.terminal.controls.ControlFile import ControlFile + from pyforms.terminal.controls.ControlDir import ControlDir + from pyforms.terminal.controls.ControlImage import ControlImage + from pyforms.terminal.controls.ControlSlider import ControlSlider + from pyforms.terminal.controls.ControlPlayer import ControlPlayer + from pyforms.terminal.controls.ControlNumber import ControlNumber + from pyforms.terminal.controls.ControlProgress import ControlProgress + +elif conf.PYFORMS_MODE=='WEB': + + from pyforms_web.web.controls.ControlBase import ControlBase + from pyforms_web.web.controls.ControlText import ControlText + from pyforms_web.web.controls.ControlBoundingSlider import ControlBoundingSlider + from pyforms_web.web.controls.ControlDate import ControlDate + from pyforms_web.web.controls.ControlButton import ControlButton + from pyforms_web.web.controls.ControlCheckBox import ControlCheckBox + from pyforms_web.web.controls.ControlCombo import ControlCombo + from pyforms_web.web.controls.ControlDir import ControlDir + from pyforms_web.web.controls.ControlFile import ControlFile + from pyforms_web.web.controls.ControlImage import ControlImage + from pyforms_web.web.controls.ControlSlider import ControlSlider + from pyforms_web.web.controls.ControlPlayer import ControlPlayer + from pyforms_web.web.controls.ControlProgress import ControlProgress + from pyforms_web.web.controls.ControlVisVis import ControlVisVis + from pyforms_web.web.controls.ControlList import ControlList + from pyforms_web.web.controls.ControlLabel import ControlLabel + from pyforms_web.web.controls.ControlTimeout import ControlTimeout + from pyforms_web.web.controls.ControlEmptyWidget import ControlEmptyWidget + from pyforms_web.web.controls.ControlWorkflow import ControlWorkflow + from pyforms_web.web.controls.ControlPieChart import ControlPieChart diff --git a/pyforms/dialogs.py b/pyforms/dialogs.py index 84239d7..1d7c5f3 100644 --- a/pyforms/dialogs.py +++ b/pyforms/dialogs.py @@ -1,4 +1,8 @@ from pyforms import conf -if conf.PYFORMS_MODE in ['GUI','GUI-OPENCSP']: - from pyforms.gui.dialogs.csv_parser import CsvParserDialog \ No newline at end of file +if conf.PYFORMS_MODE=='GUI': + from pyforms.gui.dialogs.csv_parser import CsvParserDialog + + +elif conf.PYFORMS_MODE in ['TERMINAL']: + class CsvParserDialog(object): pass \ No newline at end of file diff --git a/pyforms/gui/.DS_Store b/pyforms/gui/.DS_Store new file mode 100644 index 0000000..e4da2b0 Binary files /dev/null and b/pyforms/gui/.DS_Store differ diff --git a/pyforms/gui/BaseWidget.py b/pyforms/gui/BaseWidget.py deleted file mode 100644 index 49678a1..0000000 --- a/pyforms/gui/BaseWidget.py +++ /dev/null @@ -1,492 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - - -import os -import json -import subprocess -import time, sys -from datetime import datetime, timedelta - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QFrame - from PyQt5.QtWidgets import QVBoxLayout - from PyQt5.QtWidgets import QTabWidget - from PyQt5.QtWidgets import QSplitter - from PyQt5.QtWidgets import QHBoxLayout - from PyQt5.QtWidgets import QSpacerItem - from PyQt5.QtWidgets import QSizePolicy - from PyQt5.QtWidgets import QLabel - from PyQt5.QtGui import QFont - from PyQt5.QtWidgets import QFileDialog - from PyQt5.QtWidgets import QApplication - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QFrame - from PyQt4.QtGui import QVBoxLayout - from PyQt4.QtGui import QTabWidget - from PyQt4.QtGui import QSplitter - from PyQt4.QtGui import QHBoxLayout - from PyQt4.QtGui import QSpacerItem - from PyQt4.QtGui import QSizePolicy - from PyQt4.QtGui import QLabel - from PyQt4.QtGui import QFont - from PyQt4.QtGui import QFileDialog - from PyQt4.QtGui import QApplication - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlBase import ControlBase -from pyforms.gui.Controls.ControlProgress import ControlProgress - - -class BaseWidget(QFrame): - """ - The class implements the most basic widget or window. - """ - - def __init__(self, title='Untitled', parent_win=None, win_flag=None): - if parent_win is not None and win_flag is None: win_flag = QtCore.Qt.Dialog - - QFrame.__init__(self) if parent_win is None else QFrame.__init__(self, parent_win, win_flag) - - # self.setObjectName(self.__class__.__name__) - - - layout = QVBoxLayout() - self.setLayout(layout) - - if conf.PYFORMS_USE_QT5: - layout.setContentsMargins(0,0,0,0) - else: - layout.setMargin(0) - - self.title = title - self.has_progress = False - - self._mainmenu = [] - self._splitters = [] - self._tabs = [] - self._formset = None - self._formLoaded = False - self.uid = id(self) - - self.setAccessibleName('BaseWidget') - - ########################################################################## - ############ FUNCTIONS ################################################## - ########################################################################## - - - def init_form(self): - """ - Generate the module Form - """ - if not self._formLoaded: - - if self.has_progress: - self._progress = ControlProgress("Progress", 0, 100) - self._progress.hide() - if self._formset != None: - self._formset += ['_progress'] - - if self._formset is not None: - control = self.generate_panel(self._formset) - self.layout().addWidget(control) - else: - allparams = self.controls - for key, param in allparams.items(): - param.parent = self - param.name = key - self.layout().addWidget(param.form) - self._formLoaded = True - - def set_margin(self, margin): - if conf.PYFORMS_USE_QT5: - self.layout().setContentsMargins(margin,margin,margin,margin) - else: - self.layout().setMargin(margin) - - - def generate_tabs(self, formsetdict): - """ - Generate QTabWidget for the module form - @param formset: Tab form configuration - @type formset: dict - """ - tabs = QTabWidget(self) - for key, item in sorted(formsetdict.items()): - ctrl = self.generate_panel(item) - tabs.addTab(ctrl, key[key.find(':') + 1:]) - return tabs - - def generate_panel(self, formset): - """ - Generate a panel for the module form with all the controls - formset format example: [('_video', '_arenas', '_run'), {"Player":['_threshold', "_player", "=", "_results", "_query"], "Background image":[(' ', '_selectBackground', '_paintBackground'), '_image']}, "_progress"] - tuple: will display the controls in the same horizontal line - list: will display the controls in the same vertical line - dict: will display the controls in a tab widget - '||': will plit the controls in a horizontal line - '=': will plit the controls in a vertical line - @param formset: Form configuration - @type formset: list - """ - control = None - if '=' in formset: - control = QSplitter(QtCore.Qt.Vertical) - tmp = list(formset) - index = tmp.index('=') - firstPanel = self.generate_panel(formset[0:index]) - secondPanel = self.generate_panel(formset[index + 1:]) - control.addWidget(firstPanel) - control.addWidget(secondPanel) - self._splitters.append(control) - return control - elif '||' in formset: - control = QSplitter(QtCore.Qt.Horizontal) - tmp = list(formset) - rindex = lindex = index = tmp.index('||') - rindex -= 1 - rindex += 2 - if isinstance(formset[lindex - 1], int): - lindex = lindex - 1 - if len(formset) > rindex and isinstance(formset[index + 1], int): - rindex += 1 - firstPanel = self.generate_panel(formset[0:lindex]) - secondPanel = self.generate_panel(formset[rindex:]) - if isinstance(formset[index - 1], int): - firstPanel.setMaximumWidth(formset[index - 1]) - if isinstance(formset[index + 1], int): - secondPanel.setMaximumWidth(formset[index + 1]) - control.addWidget(firstPanel) - control.addWidget(secondPanel) - self._splitters.append(control) - return control - control = QFrame(self) - layout = None - if type(formset) is tuple: - layout = QHBoxLayout() - for row in formset: - if isinstance(row, (list, tuple)): - panel = self.generate_panel(row) - layout.addWidget(panel) - elif row == " ": - spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - layout.addItem(spacer) - elif type(row) is dict: - c = self.generate_tabs(row) - layout.addWidget(c) - self._tabs.append(c) - else: - param = self.controls.get(row, None) - if param is None: - label = QLabel() - label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) - # layout.addWidget( label ) - - if row.startswith('info:'): - label.setText(row[5:]) - font = QFont() - font.setPointSize(10) - label.setFont(font) - label.setAccessibleName('info') - elif row.startswith('h1:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(17) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h1') - elif row.startswith('h2:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(16) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h2') - elif row.startswith('h3:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(15) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h3') - elif row.startswith('h4:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(14) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h4') - elif row.startswith('h5:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(12) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h5') - else: - label.setText(row) - font = QFont() - font.setPointSize(10) - label.setFont(font) - label.setAccessibleName('msg') - label.setToolTip(label.text()) - layout.addWidget(label) - else: - param.parent = self - param.name = row - layout.addWidget(param.form) - elif type(formset) is list: - layout = QVBoxLayout() - for row in formset: - if isinstance(row, (list, tuple)): - panel = self.generate_panel(row) - layout.addWidget(panel) - elif row == " ": - spacer = QSpacerItem( - 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) - layout.addItem(spacer) - elif type(row) is dict: - c = self.generate_tabs(row) - layout.addWidget(c) - self._tabs.append(c) - else: - param = self.controls.get(row, None) - if param is None: - label = QLabel() - label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) - label.resize(30, 30) - # layout.addWidget( label ) - - if row.startswith('info:'): - label.setText(row[5:]) - font = QFont() - font.setPointSize(10) - label.setFont(font) - label.setAccessibleName('info') - elif row.startswith('h1:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(17) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h1') - elif row.startswith('h2:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(16) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h2') - elif row.startswith('h3:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(15) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h3') - elif row.startswith('h4:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(14) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h4') - elif row.startswith('h5:'): - label.setText(row[3:]) - font = QFont() - font.setPointSize(12) - font.setBold(True) - label.setFont(font) - label.setAccessibleName('h5') - else: - label.setText(row) - font = QFont() - font.setPointSize(10) - label.setFont(font) - label.setAccessibleName('msg') - - label.setToolTip(label.text()) - - layout.addWidget(label) - else: - param.parent = self - param.name = row - layout.addWidget(param.form) - - if conf.PYFORMS_USE_QT5: - layout.setContentsMargins(0,0,0,0) - else: - layout.setMargin(0) - - control.setLayout(layout) - return control - - def show(self): - """ - It shows the - """ - self.init_form() - super(BaseWidget, self).show() - - def save_form(self, data={}, path=None): - allparams = self.controls - - if hasattr(self, 'load_order'): - for name in self.load_order: - param = allparams[name] - data[name] = {} - param.save_form(data[name]) - else: - for name, param in allparams.items(): - data[name] = {} - param.save_form(data[name]) - return data - - def load_form(self, data, path=None): - allparams = self.controls - - if hasattr(self, 'load_order'): - for name in self.load_order: - param = allparams[name] - if name in data: - param.load_form(data[name]) - else: - for name, param in allparams.items(): - if name in data: - param.load_form(data[name]) - # self.init_form() - - def save_window(self): - allparams = self.controls - data = {} - self.save_form(data) - - filename = QFileDialog.getSaveFileName(self, 'Select file') - with open(filename, 'w') as output_file: json.dump(data, output_file) - - def load_form_filename(self, filename): - with open(filename, 'r') as pkl_file: - project_data = json.load(pkl_file) - data = dict(project_data) - self.load_form(data) - - def load_window(self): - filename = QFileDialog.getOpenFileNames(self, 'Select file') - self.load_form_filename(str(filename[0])) - - ########################################################################## - ############ EVENTS ###################################################### - ########################################################################## - - def before_close_event(self): - """ - Do something before closing widget - Note that the window will be closed anyway - """ - pass - - ########################################################################## - ############ Properties ################################################## - ########################################################################## - - @property - def form_has_loaded(self): - return self._formLoaded - - @property - def controls(self): - """ - Return all the form controls from the the module - """ - result = {} - for name, var in vars(self).items(): - try: - if isinstance(var, ControlBase): - result[name] = var - except: - pass - return result - - @property - def form(self): - return self - - @property - def title(self): - return self.windowTitle() - - @title.setter - def title(self, value): - self.setWindowTitle(value) - - @property - def mainmenu(self): - return self._mainmenu - - @mainmenu.setter - def mainmenu(self, value): - self._mainmenu = value - - @property - def formset(self): - return self._formset - - @formset.setter - def formset(self, value): - self._formset = value - - @property - def uid(self): - return self._uid - - @uid.setter - def uid(self, value): - self._uid = value - - @property - def max_progress(self): - return self._progress.max - - @max_progress.setter - def max_progress(self, value): - self._progress.max = value - - @property - def min_progress(self): - return self._progress.min - - @min_progress.setter - def min_progress(self, value): - self._progress.min = value - - @property - def progress(self): - return self._progress.value - - @progress.setter - def progress(self, value): - self._progress.value = value - QApplication.processEvents() - - if value == self.max_progress: self._progress.hide() - if value == self.min_progress: self._progress.show() - - @property - def visible(self): - return self.isVisible() - - @visible.setter - def visible(self, value): - self.show() if value else self.hide() - - ########################################################################## - ############ PRIVATE FUNCTIONS ########################################### - ########################################################################## - def closeEvent(self, event): - self.before_close_event() - super(BaseWidget, self).closeEvent(event) diff --git a/pyforms/gui/Controls/ControlBase.py b/pyforms/gui/Controls/ControlBase.py deleted file mode 100755 index 413da38..0000000 --- a/pyforms/gui/Controls/ControlBase.py +++ /dev/null @@ -1,243 +0,0 @@ -# !/usr/bin/python -# -*- coding: utf-8 -*- - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QMenu - from PyQt5.QtWidgets import QAction - from PyQt5.QtGui import QIcon - from PyQt5.QtGui import QKeySequence - from PyQt5 import QtCore - from PyQt5 import uic -else: - from PyQt4.QtGui import Qmenu - from PyQt4.QtGui import QAction - from PyQt4.QtGui import QIcon - from PyQt4.QtGui import QKeySequence - from PyQt4 import QtCore - from PyQt4 import uic - - - -class ControlBase(object): - """ - This class represents the most basic control that can exist - A Control is a Widget or a group of widgets that can be reused from application to application - - @undocumented: __repr__ - """ - - def __init__(self, label='', default=None, helptext=None): - self._help = helptext - self._value = default - self._form = None # Qt widget - self._parent = None # Parent window - self._label = label # Label - self._popup_menu = None - - self.init_form() - - - def init_form(self): - """ - Load Control and initiate the events - """ - if self.help: self.form.setToolTip(self.help) - - - def __repr__(self): return str(self._value) - - ########################################################################## - ############ Funcions #################################################### - ########################################################################## - - def load_form(self, data, path=None): - """ - Load a value from the dict variable - @param data: dictionary with the value of the Control - """ - if 'value' in data: - self.value = data['value'] - - def save_form(self, data, path=None): - """ - Save a value to dict variable - @param data: dictionary with to where the value of the Control will be added - """ - if self.value: - data['value'] = self.value - return data - - def show(self): - """ - Show the control - """ - if self.form is None: - return - self.form.show() - - def hide(self): - """ - Hide the control - """ - if self.form is None: - return - self.form.hide() - - - def __create_popup_menu(self): - if not self._popup_menu: - self.form.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.form.customContextMenuRequested.connect(self._open_popup_menu) - self._popup_menu = QMenu(self.parent) - self._popup_menu.aboutToShow.connect( self.about_to_show_contextmenu_event) - - def add_popup_submenu(self, label, submenu=None): - self.__create_popup_menu() - menu = submenu if submenu else self._popup_menu - submenu = QMenu(label, menu) - menu.addMenu(submenu) - return submenu - - def add_popup_menu_option(self, label, function_action=None, key=None, icon=None, submenu=None): - """ - Add an option to the Control popup menu - @param label: label of the option. - @param function_action: function called when the option is selected. - @param key: shortcut key - @param icon: icon - """ - self.__create_popup_menu() - - menu = submenu if submenu else self._popup_menu - - if label == "-": - return menu.addSeparator() - else: - action = QAction(label, self.form) - if icon is not None: - action.setIconVisibleInMenu(True) - action.setIcon(icon if isinstance(icon, QIcon) else QIcon(icon) ) - if key != None: - action.setShortcut(QKeySequence(key)) - if function_action: - action.triggered.connect(function_action) - menu.addAction(action) - return action - - - ########################################################################## - ############ Events ###################################################### - ########################################################################## - - def changed_event(self): - """ - Function called when ever the Control value is changed - """ - return True - - def about_to_show_contextmenu_event(self): - """ - Function called before open the Control popup menu - """ - pass - - def _open_popup_menu(self, position): - if self._popup_menu: - self._popup_menu.exec_(self.form.mapToGlobal(position)) - - ########################################################################## - ############ Properties ################################################## - ########################################################################## - - ########################################################################## - # Set the Control enabled or disabled - - @property - def enabled(self): - return self.form.isEnabled() - - @enabled.setter - def enabled(self, value): - """@type value: boolean""" - self.form.setEnabled(value) - - ########################################################################## - # Return or update the value of the Control - - @property - def value(self): - return self._value - - @value.setter - def value(self, value): - """ - This property return and set what the control should manage or store. - @type value: string - """ - oldvalue = self._value - self._value = value - if oldvalue != value: - self.changed_event() - - @property - def visible(self): return self.form.isVisible() - - @visible.setter - def visible(self, value): - self.show() if value else self.hide() - - @property - def name(self): return self.form.objectName() - - @name.setter - def name(self, value): - """ - This property return and set the name of the control - @type value: string - """ - self.form.setObjectName(value) - - ########################################################################## - # Return or update the label of the Control - - @property - def label(self): - return self._label - - @label.setter - def label(self, value): - """ - Label of the control, if applies - @type value: string - """ - self._label = value - - ########################################################################## - # Return the QT widget - - @property - def form(self): - """ - Returns the Widget of the control. - This property will be deprecated in a future version. - """ - return self._form - - ########################################################################## - # Parent window - - @property - def parent(self): return self._parent - - @parent.setter - def parent(self, value): - """ - Returns or set the parent basewidget where the Control is - @type value: BaseWidget - """ - self._parent = value - - @property - def help(self): return self._help if self._help else '' diff --git a/pyforms/gui/Controls/ControlBoundingSlider.py b/pyforms/gui/Controls/ControlBoundingSlider.py deleted file mode 100755 index fdd6749..0000000 --- a/pyforms/gui/Controls/ControlBoundingSlider.py +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget - from PyQt5.QtWidgets import QVBoxLayout - from PyQt5.QtWidgets import QHBoxLayout - from PyQt5.QtWidgets import QSpinBox - from PyQt5.QtGui import QPainter - from PyQt5.QtGui import QFont - from PyQt5.QtGui import QColor - from PyQt5.QtGui import QCursor - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QWidget - from PyQt4.QtGui import QVBoxLayout - from PyQt4.QtGui import QHBoxLayout - from PyQt4.QtGui import QPainter - from PyQt4.QtGui import QFont - from PyQt4.QtGui import QColor - from PyQt4.QtGui import QCursor - from PyQt4.QtGui import QSpinBox - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlBase import ControlBase - - -class GaugeWidgetVertical(QWidget): - def __init__(self, *args, **kwargs): - QWidget.__init__(self, *args, **kwargs) - - self.setMouseTracking(True) - self.setMinimumHeight(30) - self.setMinimumWidth(30) - self.setMaximumWidth(50) - - self._lower = 0 - self._higher = 100 - self._minVal = 0 - self._maxVal = 75 - self._lastMouseY = None - self._moving = False - self._resizingBottom = False - self._resizingTop = False - self._scale = 1.0 - self._use_float = False - - def changed_event(self): - pass - - def paintEvent(self, e): - # call the base implementation to paint normal interface - super(GaugeWidgetVertical, self).paintEvent(e) - draw = QPainter() - draw.begin(self) - - window_with = self.width() - 1 - diff = (self._higher - self._lower) * self.scale - try: - self._step = float(self.height()) / float(diff) - except ZeroDivisionError: - self._step = 0 - y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale - y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale - - draw.setOpacity(1.0) - draw.setBrush(QtCore.Qt.NoBrush) - draw.setPen(QColor(200, 200, 255)) - - for i in range(self.height() / 5, (self.height() - self.height() / 5) + 1, self.height() / 5): - draw.drawLine(0, i, window_with, i) - - draw.setBrush(QColor(33, 133, 208)) - draw.setPen(QColor(33, 133, 208)) - draw.setOpacity(0.7) - draw.drawRect(0, y_start, window_with, y_end - y_start) - - draw.setFont(QFont('Decorative', 8)) - draw.setPen(QColor(30, 30, 30)) - draw.drawText(3, self.height() - 3, str(self._lower) if self._use_float else str(int(round(self._lower)))) - draw.drawText(3, 10, str(self._higher) if self._use_float else str(int(round(self._higher)))) - - draw.drawText(3, y_start + 11, str(self._minVal) if self._use_float else str(int(round(self._minVal)))) - draw.drawText(3, y_end - 4, str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) - - draw.end() - - def mousePressEvent(self, event): - self._moving = False - self._resizingBottom = False - self._resizingTop = False - - y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale - y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale - - if event.buttons() == QtCore.Qt.LeftButton: - if y_start > event.y() > y_end: - self._moving = True - elif event.y() < y_end: - self._resizingTop = True - elif y_start < event.y(): - self._resizingBottom = True - - def mouseReleaseEvent(self, event): - self._moving = False - self._resizingBottom = False - self._resizingTop = False - - def mouseMoveEvent(self, event): - super(GaugeWidgetVertical, self).mouseMoveEvent(event) - - y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale - y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale - - if y_start < event.y() or event.y() < y_end: - self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) - else: - self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) - - if event.buttons() == QtCore.Qt.LeftButton: - if self._lastMouseY != None: - diff = self._lastMouseY - event.y() - if diff != 0: - if self._moving: - new_min = self._minVal * self.scale + diff * 1 / self._step - new_max = self._maxVal * self.scale + diff * 1 / self._step - if new_min < new_max: - self._minVal = new_min / self.scale - self._maxVal = new_max / self.scale - elif self._resizingTop: - new_max = self._maxVal * self.scale + diff * 1 / self._step - if self._minVal < (new_max / self.scale): self._maxVal = new_max / self.scale - elif self._resizingBottom: - new_min = self._minVal * self.scale + diff * 1 / self._step - if (new_min / self.scale) < self._maxVal: self._minVal = new_min / self.scale - - self.repaint() - - self.changed_event() - - self._lastMouseY = event.y() - - def mouseReleaseEvent(self, event): - self._lastMouseY = None - - @property - def scale(self): - return self._scale - - @scale.setter - def scale(self, value): - self._scale = value - - -class GaugeWidgetHorizontal(GaugeWidgetVertical): - def __init__(self, *args, **kwargs): - super(GaugeWidgetHorizontal, self).__init__(*args, **kwargs) - - self.setMaximumWidth(1000000) - self.setMinimumHeight(20) - self.setMaximumHeight(20) - - def paintEvent(self, e): - # call the base implementation to paint normal interface - QWidget.paintEvent(self, e); - draw = QPainter(); - draw.begin(self) - - h = self.height() - 1 - diff = (self._higher - self._lower) * self.scale - - try: - self._step = float(self.width()) / float(diff) - except ZeroDivisionError: - self._step = 0 - x_start = (self._minVal - self._lower) * self._step * self.scale - x_end = (self._maxVal - self._lower) * self._step * self.scale - - draw.setOpacity(1.0) - draw.setBrush(QtCore.Qt.NoBrush) - draw.setPen(QColor(200, 200, 255)) - - for i in range( int(self.width()/5), int(self.width()-self.width()/5) + 1, int(self.width()/5) ): - draw.drawLine(i, 0, i, h) - - draw.setBrush(QColor(238, 238, 238)) - draw.setPen(QColor(238, 238, 238)) - draw.drawRoundedRect(0, 2, self.width(), h - 4, 3, 3) - - draw.setBrush(QColor(33, 133, 208)) - draw.setPen(QColor(33, 133, 208)) - draw.drawRoundedRect(int(round(x_start)), 2, int(round(x_end - x_start)), h - 4, 3, 3) - # draw.setOpacity(1.0) - draw.setFont(QFont('Decorative', 8)) - draw.setPen(QColor(80, 80, 80)) - - str(self._maxVal) if self._use_float else str(int(round(self._maxVal))) - - boundtext = draw.boundingRect(QtCore.QRectF(), - str(self._higher) if self._use_float else str(int(round(self._higher)))) - draw.drawText(self.width() - boundtext.width(), 14, - str(self._higher) if self._use_float else str(int(round(self._higher)))) - draw.drawText(0, 14, str(self._lower) if self._use_float else str(int(round(self._lower)))) - - draw.setPen(QColor(255, 255, 255)) - boundtext = draw.boundingRect(QtCore.QRectF(), - str(self._minVal) if self._use_float else str(int(round(self._minVal)))) - draw.drawText(x_start + 2, 14, str(self._minVal) if self._use_float else str(int(round(self._minVal)))) - boundtext = draw.boundingRect(QtCore.QRectF(), - str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) - draw.drawText(x_end - boundtext.width(), 14, - str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) - - draw.end() - - def mousePressEvent(self, event): - self._moving = False - self._resizingLeft = False - self._resizingRight = False - - x_start = (self._minVal - self._lower) * self._step * self.scale - x_end = (self._maxVal - self._lower) * self._step * self.scale - - if event.buttons() == QtCore.Qt.LeftButton: - if x_start < event.x() < x_end: - self._moving = True - elif event.x() > x_end: - self._resizingLeft = True - elif x_start > event.x(): - self._resizingRight = True - - def mouseMoveEvent(self, event): - super(GaugeWidgetVertical, self).mouseMoveEvent(event) - - x_start = (self._minVal - self._lower) * self._step * self.scale - x_end = (self._maxVal - self._lower) * self._step * self.scale - - # set the cursors - if x_start >= event.x() or event.x() >= x_end: - self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) - else: - self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) - - if event.buttons() == QtCore.Qt.LeftButton: - if self._lastMouseY != None: - diff = self._lastMouseY - event.x() - if diff != 0: - if self._moving: - new_min = self._minVal * self.scale - diff * 1 / self._step - new_max = self._maxVal * self.scale - diff * 1 / self._step - if new_min < new_max: - self._minVal = new_min / self.scale - self._maxVal = new_max / self.scale - elif self._resizingLeft: - new_max = self._maxVal * self.scale - diff * 1 / self._step - if self._minVal < (new_max / self.scale): self._maxVal = new_max / self.scale - elif self._resizingRight: - new_min = self._minVal * self.scale - diff * 1 / self._step - if (new_min / self.scale) < self._maxVal: self._minVal = new_min / self.scale - - self.repaint() - self.changed_event() - - self._lastMouseY = event.x() - - -class ControlBoundingSlider(ControlBase): - def __init__(self, label="", default=[20, 40], min=0, max=100, horizontal=False, helptext=None, - show_spinboxes=True): - self._horizontal = horizontal - self._show_spinboxes = show_spinboxes - ControlBase.__init__(self, label, default, helptext=helptext) - - self.min = min - self.max = max - self.value = default - self.__update() - - def init_form(self): - self._boundingbox = GaugeWidgetHorizontal() if self._horizontal else GaugeWidgetVertical() - self._boundingbox.changed_event = self.__update - - if self._show_spinboxes: - self._form = hwidget = QWidget() - if self._horizontal: - hlayout = QHBoxLayout() - else: - hlayout = QVBoxLayout() - - - if conf.PYFORMS_USE_QT5: - hlayout.setContentsMargins(0,0,0,0) - else: - hlayout.setMargin(0) - - - hwidget.setLayout(hlayout) - self._min_spinbox = QSpinBox() - self._min_spinbox.valueChanged.connect(self.__min_spinbox_changed) - self._min_spinbox.setMaximumWidth(95) - - self._max_spinbox = QSpinBox() - self._max_spinbox.valueChanged.connect(self.__max_spinbox_changed) - self._max_spinbox.setMaximumWidth(95) - - if self._horizontal: - hlayout.addWidget(self._min_spinbox) - else: - hlayout.addWidget(self._max_spinbox) - hlayout.addWidget(self._boundingbox) - if self._horizontal: - hlayout.addWidget(self._max_spinbox) - else: - hlayout.addWidget(self._min_spinbox) - - else: - self._form = self._boundingbox - - super(ControlBoundingSlider, self).init_form() - - def __max_spinbox_changed(self, value): - if value < self._boundingbox._minVal: return - if hasattr(self, '_is_updating_spinboxes'): return - self.scale = self.__find_scale_factor(value) - self._boundingbox._maxVal = value - self._boundingbox.repaint() - self.changed_event() - - def __min_spinbox_changed(self, value): - if value > self._boundingbox._maxVal: return - if hasattr(self, '_is_updating_spinboxes'): return - self.scale = self.__find_scale_factor(value) - self._boundingbox._minVal = value - self._boundingbox.repaint() - self.changed_event() - - def __update(self): - l, h = self._boundingbox._minVal, self._boundingbox._maxVal - self._is_updating_spinboxes = True - self._min_spinbox.setValue(l) - self._max_spinbox.setValue(h) - del self._is_updating_spinboxes - self.changed_event() - - def changed_event(self): - pass - - def __find_scale_factor(self, value): - scale = 1.0 - new_value = value - while abs(new_value) < 0.0: - scale *= 10.0 - new_value = value * scale - return scale - - ########################################################################## - ############ Properties ################################################## - ########################################################################## - - @property - def value(self): - return self._boundingbox._minVal, self._boundingbox._maxVal - - @value.setter - def value(self, value): - ControlBase.value.fset(self, value) - self.scale = self.__find_scale_factor(value[0]) - self._boundingbox._minVal, self._boundingbox._maxVal = value[0], value[1] - if hasattr(self, '_min_spinbox'): self._min_spinbox.setValue(value[0]) - if hasattr(self, '_max_spinbox'): self._max_spinbox.setValue(value[1]) - self._boundingbox.repaint() - - @property - def min(self): - return self._boundingbox._lower - - @min.setter - def min(self, value): - self._boundingbox._lower = value - self._boundingbox.repaint() - if hasattr(self, '_min_spinbox'): self._min_spinbox.setMinimum(value) - if hasattr(self, '_max_spinbox'): self._max_spinbox.setMinimum(value) - - @property - def max(self): - return self._boundingbox._higher - - @max.setter - def max(self, value): - self._boundingbox._higher = value - self._boundingbox.repaint() - if hasattr(self, '_min_spinbox'): self._min_spinbox.setMaximum(value) - if hasattr(self, '_max_spinbox'): self._max_spinbox.setMaximum(value) - - @property - def scale(self): - return self._boundingbox.scale - - @scale.setter - def scale(self, value): - self._boundingbox.scale = value - - @property - def convert_2_int(self): - return not self._boundingbox._use_float - - @convert_2_int.setter - def convert_2_int(self, value): - self._boundingbox._use_float = not value diff --git a/pyforms/gui/Controls/ControlCheckBoxList.py b/pyforms/gui/Controls/ControlCheckBoxList.py deleted file mode 100755 index 127b833..0000000 --- a/pyforms/gui/Controls/ControlCheckBoxList.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from pysettings import conf - -import pyforms.utils.tools as tools - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QListWidgetItem - from PyQt5 import uic - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QListWidgetItem - from PyQt4 import uic - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlBase import ControlBase - - -class ControlCheckBoxList(ControlBase): - def init_form(self): - control_path = tools.getFileInSameDirectory(__file__, "tree.ui") - self._form = uic.loadUi(control_path) - - self._form.label.setText(self._label) - - self._form.listWidget.itemChanged.connect(self.item_changed) - - self._form.listWidget.itemSelectionChanged.connect(self.__itemSelectionChanged) - - if self.help: self.form.setToolTip(self.help) - - def item_changed(self, item): - self.changed_event() - - def __add__(self, val): - if isinstance(val, (tuple, list)): - item = QListWidgetItem(str(val[0])) - item.value = val[0] - if val[1]: - item.setCheckState(QtCore.Qt.Checked) - else: - item.setCheckState(QtCore.Qt.Unchecked) - else: - item = QListWidgetItem(str(val)) - item.value = val - - self._form.listWidget.addItem(item) - return self - - def __sub__(self, other): - if isinstance(other, int): - if other < 0: - indexToRemove = self._form.listWidget.currentRow() - else: - indexToRemove = other - self._form.listWidget.takeItem(indexToRemove) - else: - for row in range(self.count): - item = self._form.listWidget.item(row) - if item != None and hasattr(item, 'value') and item.value == other: - self._form.listWidget.takeItem(row) - return self - - def clear(self): - self._form.listWidget.clear() - - def refresh(self): - for row in range(self.count): - item = self._form.listWidget.item(row) - if hasattr(item, 'value'): item.setText(str(item.value)) - - ############################################################################ - ############ Events ######################################################## - ############################################################################ - - def __itemSelectionChanged(self): - self.selection_changed_event() - - def selection_changed_event(self): - pass - - ############################################################################ - ############ Properties #################################################### - ############################################################################ - - @property - def count(self): - return self._form.listWidget.count() - - @property - def checked_indexes(self): - results = [] - for row in range(self.count): - item = self._form.listWidget.item(row) - if item != None and item.checkState() == QtCore.Qt.Checked: results.append(row) - return results - - @property - def value(self): - results = [] - for row in range(self.count): - item = self._form.listWidget.item(row) - if item != None and item.checkState() == QtCore.Qt.Checked: - results.append(item.value if hasattr(item, 'value') else str(item.text())) - return results - - @value.setter - def value(self, value): - self.clear() - for row in value: self += row - - @property - def selected_row_index(self): - return self.form.listWidget.currentRow() - - @property - def items(self): - results = [] - for row in range(self.count): - item = self._form.listWidget.item(row) - results.append( - [item.value if hasattr(item, 'value') else str(item.text()), item.checkState() == QtCore.Qt.Checked]) - return results diff --git a/pyforms/gui/Controls/ControlEventTimeline/ControlEventTimeline.py b/pyforms/gui/Controls/ControlEventTimeline/ControlEventTimeline.py deleted file mode 100755 index 16174d4..0000000 --- a/pyforms/gui/Controls/ControlEventTimeline/ControlEventTimeline.py +++ /dev/null @@ -1,502 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import csv -import os - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QScrollArea, QColorDialog, QFileDialog, QMessageBox, QPushButton, QLabel, \ - QSlider, QHBoxLayout, QVBoxLayout - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QWidget, QScrollArea, QColorDialog, QFileDialog, QMessageBox, QPushButton, QLabel, QSlider, \ - QHBoxLayout, QVBoxLayout - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlEventTimeline.TimelineChart import TimelineChart -from pyforms.gui.Controls.ControlBase import ControlBase -from pyforms.gui.Controls.ControlEventTimeline.TimelineWidget import TimelineWidget -from pyforms.gui.Controls.ControlEventTimeline.TimelinePopupWindow import TimelinePopupWindow -from pyforms.gui.Controls.ControlEventTimeline.import_window import ImportWindow - -from pyforms.gui.Controls.ControlEventTimeline.graphs_graph2event import Graph2Event -from pyforms.gui.Controls.ControlEventTimeline.graphs_properties import GraphsProperties -from pyforms.gui.Controls.ControlEventTimeline.graphs_eventsgenerator import GraphsEventsGenerator - - - -class ControlEventTimeline(ControlBase, QWidget): - """ - Timeline events editor - """ - - def __init__(self, label="", default=0, max=100): - QWidget.__init__(self) - ControlBase.__init__(self, label, default) - self._max = 100 - self._graphs_prop_win = GraphsProperties(self._time, self) - self._graphsgenerator_win = GraphsEventsGenerator(self._time) - self._graph2event_win = Graph2Event(self._time) - - # Popup menus that only show when clicking on a TIMELINEDELTA object - self._deltaLockAction = self.add_popup_menu_option("Lock", self.__lockSelected, key='L') - self._deltaColorAction = self.add_popup_menu_option("Pick a color", self.__pickColor) - self._deltaRemoveAction = self.add_popup_menu_option("Remove", self.__removeSelected, key='Delete') - self._deltaActions = [self._deltaLockAction, self._deltaColorAction, self._deltaRemoveAction] - for action in self._deltaActions: - action.setVisible(False) - - self.add_popup_menu_option("-") - - self.add_popup_menu_option("Graphs", self.show_graphs_properties, icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) - self.add_popup_menu_option("Apply a function to the graphs", self.__generate_graphs_events, - icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) - self.add_popup_menu_option("Convert graph to events", self.__graph2event_event, - icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) - - self.add_popup_menu_option("-") - - # General righ click popup menus - self.add_popup_menu_option("Rows", self.__setLinePropertiesEvent, - icon=conf.PYFORMS_ICON_EVENTTIMELINE_REMOVE) - self.add_popup_menu_option("-") - - - - self.add_popup_menu_option("-") - - self.add_popup_menu_option("Auto adjust rows", self.__auto_adjust_tracks_evt, - icon=conf.PYFORMS_ICON_EVENTTIMELINE_REFRESH) - self.add_popup_menu_option("Add a row", self.__add_track_2_bottom_evt, - icon=conf.PYFORMS_ICON_EVENTTIMELINE_ADD) - - self.add_popup_menu_option("-") - - clean_menu = self.add_popup_submenu('Clean') - - self.add_popup_menu_option('The current row', function_action=self.__cleanLine, submenu=clean_menu) - self.add_popup_menu_option('-') - self.add_popup_menu_option('All graphs', function_action=self.__cleanCharts, submenu=clean_menu) - self.add_popup_menu_option('-') - self.add_popup_menu_option('Everything', function_action=self.clean, submenu=clean_menu) - - def init_form(self): - # Get the current path of the file - rootPath = os.path.dirname(__file__) - - vlayout = QVBoxLayout() - hlayout = QHBoxLayout() - - if conf.PYFORMS_USE_QT5: - hlayout.setContentsMargins(0,0,0,0) - vlayout.setContentsMargins(0,0,0,0) - else: - hlayout.setMargin(0) - vlayout.setMargin(0) - - self.setLayout(vlayout) - - # Add scroll area - scrollarea = QScrollArea() - scrollarea.setMinimumHeight(140) - scrollarea.setWidgetResizable(True) - scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent - scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent - vlayout.addWidget(scrollarea) - # vlayout.setContentsMargins(5, 5, 5, 5) - - # The timeline widget - widget = TimelineWidget(self) - widget._scroll = scrollarea - # widget.setMinimumHeight(1000) - scrollarea.setWidget(widget) - - # TODO Options buttons - # btn_1 = QtGui.QPushButton("?") - # btn_2 = QtGui.QPushButton("?") - # vlayout_options = QtGui.QVBoxLayout() - # vlayout_options.addWidget(btn_1) - # vlayout_options.addWidget(btn_2) - # hlayout.addLayout(vlayout_options) - # hlayout.addWidget(btn_1) - # hlayout.addWidget(btn_2) - - # Timeline zoom slider - slider = QSlider(QtCore.Qt.Horizontal) - slider.setFocusPolicy(QtCore.Qt.NoFocus) - slider.setMinimum(1) - slider.setMaximum(100) - slider.setValue(10) - slider.setPageStep(1) - slider.setTickPosition(QSlider.NoTicks) # TicksBothSides - slider.valueChanged.connect(self.__scaleSliderChange) - - slider_label_zoom_in = QLabel() - slider_label_zoom_out = QLabel() - slider_label_zoom_in.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) - slider_label_zoom_out.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) - - self._zoomLabel = QLabel("100%") - hlayout.addWidget(self._zoomLabel) - hlayout.addWidget(slider_label_zoom_out) - hlayout.addWidget(slider) - hlayout.addWidget(slider_label_zoom_in) - # hlayout.setContentsMargins(5, 0, 5, 5) - # Import/Export Buttons - btn_import = QPushButton("Import") - - btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) - btn_import.clicked.connect(self.__import) - btn_export = QPushButton("Export") - - btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) - btn_export.clicked.connect(self.__export) - # importexport_vlayout = QtGui.QVBoxLayout() - # importexport_vlayout.adimdWidget(btn_import) - # importexport_vlayout.addWidget(btn_export) - # hlayout.addLayout(importexport_vlayout) - hlayout.addWidget(btn_import) - hlayout.addWidget(btn_export) - - vlayout.addLayout(hlayout) - - self._time = widget - self._scrollArea = scrollarea - - ########################################################################## - #### HELPERS/PUBLIC FUNCTIONS ############################################ - ########################################################################## - - def __add__(self, other): - if isinstance(other, TimelineChart): - self._graphs_prop_win += other - self._graphsgenerator_win += other - self._graph2event_win += other - return self - - def __sub__(self, other): - if isinstance(other, int): - self._graphs_prop_win -= other - self._graphsgenerator_win -= other - self._graph2event_win -= other - return self - - def rename_graph(self, graph_index, newname): - self._graphs_prop_win.rename_graph(graph_index, newname) - self._graphsgenerator_win.rename_graph(graph_index, newname) - self._graph2event_win.rename_graph(graph_index, newname) - - def add_period(self, value, row=0, color=None): - """ - - :param value: - :param row: - :param color: - :return: - """ - self._time.addPeriod(value, track, color) - self._time.repaint() - - def add_graph(self, name, data): - """ - - :param name: - :param data: - :return: - """ - self._time.add_chart(name, data) - - def import_graph(self, filename, frame_col=0, val_col=1): - """ - - :param filename: - :param frame_col: - :param val_col: - :return: - """ - self.__import() - self._import_window.import_chart(filename, frame_col, val_col) - - def import_graph_file(self, filename, separator=';', ignore_rows=0): - """ - - :param filename: - :param separator: - :param ignore_rows: - :return: - """ - csvfile = open(filename, 'U') - spamreader = csv.reader(csvfile, delimiter=separator) - for i in range(ignore_rows): next(spamreader, None) - chart = self._time.importchart_csv(spamreader) - chart.name = os.path.basename(filename).replace('.csv', '').replace('.CSV', '') - csvfile.close() - - def show_graphs_properties(self): - """ - - """ - self._graphs_prop_win.show() - self._time.repaint() - - def import_csv(self, csvfile): - """ - - :param csvfile: - """ - # If there are annotation in the timeline, show a warning - if len(self._time._tracks) > 0: # dict returns True if not empty - message = ["You are about to import new data. ", - "If you proceed, current annotations will be erased. ", - "Make sure to export current annotations first to save.", - "\n", - "Are you sure you want to proceed?"] - reply = QMessageBox.question(self, - "Warning!", - "".join(message), - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) - - if reply != QMessageBox.Yes: - return - - self._time.import_csv(csvfile) - - def export_csv_file(self, filename): - with open(filename, 'w') as csvfile: - spamwriter = csv.writer(csvfile, dialect='excel') - self._time.export_csv(spamwriter) - - def import_csv_file(self, filename): - with open(filename, 'r') as csvfile: - spamreader = csv.reader(csvfile, dialect='excel') - self._time.import_csv(spamreader) - - ########################################################################## - #### EVENTS ############################################################## - ########################################################################## - - def mouse_moveover_timeline_event(self, event): - self._graphs_prop_win.mouse_moveover_timeline_event(event) - - - @property - def pointer_changed_event(self): - return self._time._pointer.moveEvent - - @pointer_changed_event.setter - def pointer_changed_event(self, value): - self._time._pointer.moveEvent = value - - def __auto_adjust_tracks_evt(self): - for i in range(len(self._time.tracks) - 1, -1, -1): - track = self._time.tracks[i] - if len(track) == 0: - self._time.remove_track(track) - else: - break - - def __add_track_2_bottom_evt(self): - self._time.addTrack() - - def __remove_track_from_bottom_evt(self): - if len(self._time.tracks) > 0: - track = self._time.tracks[-1] - if len(track) == 0: - self._time.remove_track(track) - - def __generate_graphs_events(self): - self._graphsgenerator_win.show() - - def __graph2event_event(self): - self._graph2event_win.show() - - ########################################################################## - #### PROPERTIES ########################################################## - ########################################################################## - - @property - def value(self): - return self._time.position - - @value.setter - def value(self, value): - ControlBase.value.fset(self, value) - self._time.position = value - - @property - def max(self): - return self._time.minimumWidth() - - @max.setter - def max(self, value): - self._max = value - self._time.setMinimumWidth(value) - self.repaint() - - @property - def form(self): - return self - - @property - def rows(self): - return self._time.tracks - - @property - def graphs(self): return self._time.graphs - - ########################################################################## - #### PRIVATE FUNCTIONS ################################################### - ########################################################################## - - def about_to_show_contextmenu_event(self): - for action in self._deltaActions: - action.setVisible( - True) if self._time._selected is not None else action.setVisible(False) - - - - def __setLinePropertiesEvent(self): - """ - This controls makes possible the edition of a track in the - timeline, based on the position of the mouse. - - Updates: - - Track label - - Track default color - """ - current_track = self._time.current_mouseover_track - parent = self._time - - # Tracks info dict and index - i = current_track - - # Save current default color to override with selected track color - timeline_default_color = parent.color - try: - parent.color = self._time._tracks[current_track].color - except Exception as e: - error_message = ("You tried to edit an empty track.", - "\n", - "Initialize it by creating an event first.") - QMessageBox.warning( - parent, "Attention!", "".join(error_message)) - return e - - # Create dialog - dialog = TimelinePopupWindow(parent, i) - dialog.setModal(True) # to disable main application window - - # If dialog is accepted, update dict info - if dialog._ui.exec_() == dialog.Accepted: - # Update label - if dialog.behavior is not None: - self._time._tracks[i].title = dialog.behavior - - # Update color - if self._time._tracks[i].color != dialog.color: - for delta in self._time._tracks[i].periods: - delta.color = dialog.color - self._time._tracks[i].color = dialog.color - self._time.repaint() - else: - pass - - # Restore timeline default color - parent.color = timeline_default_color - - def __lockSelected(self): - self._time.lockSelected() - - def __removeSelected(self): - self._time.removeSelected() - - def __import(self): - """Import annotations from a file.""" - if not hasattr(self, '_import_window'): self._import_window = ImportWindow(self) - self._import_window.show() - - def __export(self): - """Export annotations to a file.""" - - filename, ffilter = QFileDialog.getSaveFileName(parent=self, - caption="Export annotations file", - directory="untitled.csv", - filter="CSV Files (*.csv);;CSV Matrix Files (*.csv)", - options=QFileDialog.DontUseNativeDialog) - - filename = str(filename) - ffilter = str(ffilter) - if filename != "": - with open(filename, 'w') as csvfile: - spamwriter = csv.writer(csvfile, dialect='excel') - if ffilter == 'CSV Files (*.csv)': - self._time.export_csv(spamwriter) - elif ffilter == 'CSV Matrix Files (*.csv)': - self._time.export_2_csv_matrix(spamwriter) - - def __export_2_csv_matrix(self): - QMessageBox.warning( - self, "Important!", 'Please note that this file cannot be imported after.') - - filename = QFileDialog.getSaveFileName(parent=self, - caption="Export matrix file", - directory="untitled.csv", - filter="CSV Files (*.csv)", - options=QFileDialog.DontUseNativeDialog) - if filename != "": - with open(filename, 'w') as csvfile: - spamwriter = csv.writer(csvfile, dialect='excel') - self._time.export_2_csv_matrix(spamwriter) - - def __cleanLine(self): - reply = QMessageBox.question(self, 'Confirm', - "Are you sure you want to clean all the events on this track?", - QMessageBox.Yes | - QMessageBox.No, QMessageBox.No) - if reply == QMessageBox.Yes: - self._time.cleanLine(self._time.current_mouseover_track) - - def __cleanCharts(self): - reply = QMessageBox.question(self, 'Confirm', - "Are you sure you want to clean all the charts?", QMessageBox.Yes | - QMessageBox.No, QMessageBox.No) - if reply == QMessageBox.Yes: - self._time.cleanCharts() - - def clean(self): - reply = QMessageBox.question(self, 'Confirm', - "Are you sure you want to clean everything?", QMessageBox.Yes | - QMessageBox.No, QMessageBox.No) - if reply == QMessageBox.Yes: - self._time.clean() - - def __pickColor(self): - self._time.color = QColorDialog.getColor(self._time.color) - if self._time._selected != None: - self._time._selected.color = self._time.color - self._time.repaint() - - def __scaleSliderChange(self, value): - scale = 0.1 * value - self._time.setMinimumWidth(scale * self._max) - self._time.scale = scale - self._zoomLabel.setText(str(value * 10).zfill(3) + "%") - - def __scrollAreaKeyReleaseEvent(self, event): - modifiers = int(event.modifiers()) - self._time.keyReleaseEvent(event) - if modifiers is not QtCore.Qt.ControlModifier and \ - modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ - modifiers is not QtCore.Qt.ShiftModifier: - QScrollArea.keyReleaseEvent(self._scrollArea, event) - - def __scrollAreaKeyPressEvent(self, event): - modifiers = int(event.modifiers()) - if modifiers is not QtCore.Qt.ControlModifier and \ - modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ - modifiers is not QtCore.Qt.ShiftModifier: - QScrollArea.keyPressEvent(self._scrollArea, event) diff --git a/pyforms/gui/Controls/ControlEventTimeline/graphs_eventsgenerator.py b/pyforms/gui/Controls/ControlEventTimeline/graphs_eventsgenerator.py deleted file mode 100644 index 9a55b45..0000000 --- a/pyforms/gui/Controls/ControlEventTimeline/graphs_eventsgenerator.py +++ /dev/null @@ -1,128 +0,0 @@ -import csv, sys -from pyforms import BaseWidget -from pyforms.Controls import ControlProgress -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlList -from pyforms.Controls import ControlNumber -from pyforms.Controls import ControlSlider -from pyforms.Controls import ControlTextArea -from pyforms.Controls import ControlText -from pyforms.Controls import ControlNumber -from pyforms.Controls import ControlLabel - -import time -import datetime - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QMessageBox -else: - from PyQt4.QtGui import QMessageBox - - - -class GraphsEventsGenerator(BaseWidget): - - def __init__(self, timeline=None): - super(GraphsEventsGenerator, self).__init__('Apply a function to the graph values', parent_win=timeline) - self.setContentsMargins(10, 10, 10, 10) - self._timeline = timeline - - - # Definition of the forms fields - self._graphs_list = ControlList('Graphs list (try double click)') - self._equation = ControlTextArea('Equation') - self._graphname = ControlText('Graph name') - self._genevts_btn = ControlButton('Generate graph') - - - self._formset = [ - ( - ['_graphs_list'], - '||', - [ - '_graphname', - '_equation', - '_genevts_btn', - ] - ), - ] - - self._graphs_list.cell_double_clicked_event = self.__cell_double_clicked_evt - self._graphs_list.readonly = True - self._graphs_list.select_entire_row = True - self._genevts_btn.value = self.__generage_events_evt - - - - def __add__(self, other): - self._graphs_list += [other.name] - return self - - def __sub__(self, other): - self._graphs_list -= other - return self - - def rename_graph(self, graph_index, newname): - self._graphs_list.set_value(graph_index, 0, newname) - - @property - def graphs(self): - return self._timeline._charts - - def show(self): - super(GraphsEventsGenerator, self).show() - - if len(self._graphname.value.strip())==0: - self._graphname.value = "generated-graph-{0}".format( len(self._timeline.graphs)) - - - - def __cell_double_clicked_evt(self, row, column): - if len(self._equation.value.strip())==0: - self._equation.value += '[{0}]'.format( self._graphs_list.value[row][column] ) - else: - self._equation.value += ' and [{0}]'.format( self._graphs_list.value[row][column] ) - - - def __generage_events_evt(self): - if len(self._graphname.value.strip())==0: - QMessageBox.warning( - self, "Attention!", 'The graph name cannot be empty') - return - - if len(self._equation.value.strip())==0: - QMessageBox.warning( - self, "Attention!", 'The equation cannot be empty') - return - - max_frame = 0 - equation = self._equation.value - for i, values in enumerate(self._graphs_list.value): - graphname = '[{0}]'.format(values[0]) - - if graphname in equation: max_frame = max(max_frame, len(self.graphs[i])) - - equation = equation.replace(graphname, 'graphs[{0}][i]'.format(i) ) - - graphs = self.graphs - data = [] - try: - for i in range(max_frame): - data.append( (i, eval(equation)) ) - self._timeline.add_chart(self._graphname.value, data) - - self._graphname.value = "generated-graph-{0}".format( len(self._timeline.graphs)) - except Exception as e: - QMessageBox.warning( self, "Error!", str(e) ) - - -################################################################################################################## -################################################################################################################## -################################################################################################################## - -# Execute the application -if __name__ == "__main__": - import pyforms - pyforms.start_app(GraphsEventsGenerator, geometry=(0, 0, 600, 400)) diff --git a/pyforms/gui/Controls/ControlEventTimeline/import_window.py b/pyforms/gui/Controls/ControlEventTimeline/import_window.py deleted file mode 100644 index b76c53b..0000000 --- a/pyforms/gui/Controls/ControlEventTimeline/import_window.py +++ /dev/null @@ -1,163 +0,0 @@ -import csv -from pyforms import BaseWidget -from pyforms.Controls import ControlProgress -from pyforms.Controls import ControlCombo -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlEmptyWidget -from pyforms.Controls import ControlFile -from pyforms.Controls import ControlNumber - - -import time -import datetime - - -class BonsaiImportFileDlg(BaseWidget): - - def __init__(self, timeline=None): - super(BonsaiImportFileDlg, self).__init__('Import file') - - self._file = ControlFile('File to import') - self._fps = ControlNumber('Video FPS', 30) - - self._formset = [('_fps', '_file')] - - #self._file.value = '/home/ricardo/Desktop/rat1e2.csv' - - -class ImportWindow(BaseWidget): - - def __init__(self, timeline=None): - super(ImportWindow, self).__init__('Import file', parent_win=timeline) - self.setContentsMargins(10, 10, 10, 10) - self._timeline = timeline - - # Definition of the forms fields - self._filetype = ControlCombo('Please select the type of file you would like to import:') - self._importButton = ControlButton('Import') - self._panel = ControlEmptyWidget('Panel') - self._file = ControlFile('File to import') - - self._panel.value = self._file - self._filetype.add_item('Events file', 0) - self._filetype.add_item('Graph file', 1) - self._filetype.add_item('Bonsai events file', 2) - - self._formset = [ - ('_filetype', ' '), - '_panel', - (' ', '_importButton'), ' '] - - self._filetype.changed_event = self.__fileTypeChanged - self._importButton.value = self.__importData - - from pyforms.gui.dialogs.csv_parser import CsvParserDialog - self._graphCsvParserDlg = CsvParserDialog() - self._graphCsvParserDlg.xField.label = "Value column" - self._graphCsvParserDlg.yField.hide() - self._graphCsvParserDlg.zField.hide() - self._graphCsvParserDlg.loadButton.hide() - - self._bonsaiImportDlg = BonsaiImportFileDlg() - - def __fileTypeChanged(self): - if self._filetype.value == 0: - self._panel.value = self._file - - elif self._filetype.value == 1: - self._panel.value = self._graphCsvParserDlg - - elif self._filetype.value == 2: - self._panel.value = self._bonsaiImportDlg - - def __importData(self): - if self._filetype.value == 0: - separator = ',' - with open(self._file.value, 'rU') as csvfile: - line = csvfile.readline() - if ";" in line: - separator = ';' - with open(self._file.value, 'rU') as csvfile: - csvfile = csv.reader(csvfile, delimiter=separator) - self._timeline._time.import_csv(csvfile) - - elif self._filetype.value == 1: - self._timeline._time.importchart_csv(self._graphCsvParserDlg) - self._timeline.show_graphs_properties() - - elif self._filetype.value == 2: - with open(self._bonsaiImportDlg._file.value, 'rU') as csvfile: - values = [] - pointEventValues = [] - csvfile = csv.reader(csvfile, delimiter=' ') - for row in csvfile: # strip Start/End word from all events names which are not PointEven - try: - timestr = row[1].rstrip('0') - cvttime = datetime.datetime.strptime(timestr, "%H:%M:%S.%f") - except: - timestr = row[1] - cvttime = datetime.datetime.strptime(timestr, "%H:%M:%S") - - seconds = (cvttime - datetime.datetime(1900, 1, 1)).total_seconds() - frame = int(round(self._bonsaiImportDlg._fps.value * seconds)) - - if row[2] == "PointEvent": - eventtype = row[0] - pointEventValues.append([eventtype, frame, row[2]]) - else: - if row[0].startswith('Start'): - eventtype = row[0][len('Start'):] # strip Start word from the beginning - else: - eventtype = row[0][len('End'):] # strip End word from the beginning - values.append([eventtype, frame, row[2]]) - - values = sorted(values, key=lambda x: (x[0].capitalize(), x[1])) - pointEventValues = sorted(pointEventValues, key=lambda x: (x[0].capitalize(), x[1])) - ntracks = len(set([x[0] for x in values])) + 1 - - # collapse - events = [] - eventsTypes = {} # Events names - currentTrack = 0 - for index in range(0, len(pointEventValues)): - pointEventValue = pointEventValues[index] - eventsTypes[pointEventValue[0]] = currentTrack - self._timeline.addPeriod([pointEventValue[1], pointEventValue[1] + 50, pointEventValue[0]], track=currentTrack) - - currentTrack = 1 - - for index in range(0, len(values), 2): - row0 = values[index] - row1 = values[index + 1] - - if row0[0] not in eventsTypes: - eventsTypes[row0[0]] = currentTrack - track = currentTrack - currentTrack += 1 - else: - track = eventsTypes[row0[0]] - - self._timeline.addPeriod([row0[1], row1[1], row0[0]], track=track) - - self._timeline.repaint() - self._timeline._time.repaint() - self.close() # pylint: disable=no-member - - - def import_chart(self, filename, frame_col=0, val_col=1): - self._filetype.value = 1 - self._graphCsvParserDlg.filename = filename - self._graphCsvParserDlg.frameColumn = frame_col - self._graphCsvParserDlg.xColumn = val_col - - - - -################################################################################################################## -################################################################################################################## -################################################################################################################## - -# Execute the application -if __name__ == "__main__": - import pyforms - pyforms.start_app(ImportWindow, geometry=(0, 0, 600, 400)) diff --git a/pyforms/gui/Controls/ControlList.py b/pyforms/gui/Controls/ControlList.py deleted file mode 100755 index c6c59bd..0000000 --- a/pyforms/gui/Controls/ControlList.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -""" pyforms.gui.Controls.ControlList - -""" -import logging -import os - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget - from PyQt5.QtWidgets import QTableWidgetItem - from PyQt5.QtWidgets import QAbstractItemView - from PyQt5.QtGui import QIcon - from PyQt5 import uic - from PyQt5 import QtCore - from PyQt5.QtCore import Qt - -else: - from PyQt4.QtGui import QWidget - from PyQt4.QtGui import QTableWidgetItem - from PyQt4.QtGui import QAbstractItemView - from PyQt4.QtGui import QIcon - from PyQt4 import uic - from PyQt4 import QtCore - from PyQt4.QtCore import Qt - -from pyforms.gui.BaseWidget import BaseWidget -from pyforms.gui.Controls.ControlBase import ControlBase - -logger = logging.getLogger(__name__) - - -class ControlList(ControlBase, QWidget): - """ This class represents a wrapper to the table widget - It allows to implement a list view - """ - - CELL_VALUE_BEFORE_CHANGE = None # store value when cell is double clicked - - def __init__(self, label="", default="", add_function=None, - remove_function=None): - QWidget.__init__(self) - - self._plusFunction = add_function - self._minusFunction = remove_function - ControlBase.__init__(self, label, default) - - ########################################################################## - ############ FUNCTIONS ################################################### - ########################################################################## - - def init_form(self): - plusFunction = self._plusFunction - minusFunction = self._minusFunction - - # Get the current path of the file - rootPath = os.path.dirname(__file__) - # Load the UI for the self instance - uic.loadUi(os.path.join(rootPath, "list.ui"), self) - - self.label = self._label - - self.tableWidget.currentCellChanged.connect( - self.tableWidgetCellChanged) - self.tableWidget.currentItemChanged.connect( - self.tableWidgetItemChanged) - self.tableWidget.itemSelectionChanged.connect( - self.tableWidgetItemSelectionChanged) - self.tableWidget.cellDoubleClicked.connect(self.tableWidgetCellDoubleClicked) - self.tableWidget.model().dataChanged.connect(self._dataChangedEvent) - - if plusFunction is None and minusFunction is None: - self.bottomBar.hide() - elif plusFunction is None: - self.plusButton.hide() - self.minusButton.pressed.connect(minusFunction) - elif minusFunction is None: - self.minusButton.hide() - self.plusButton.pressed.connect(plusFunction) - else: - self.plusButton.pressed.connect(plusFunction) - self.minusButton.pressed.connect(minusFunction) - - def __repr__(self): - return "ControlList " + str(self._value) - - def clear(self, headers=False): - - for row in range(self.rows_count): - columns = [] - for column in range(self.columns_count): - v = self.get_value(column, row) - if isinstance(v, BaseWidget): - v.destroy() - - if headers: - self.tableWidget.clear() - self.tableWidget.setColumnCount(3) - self.tableWidget.setRowCount(0) - else: - self.tableWidget.clearContents() - self.tableWidget.setRowCount(0) - - def save_form(self, data, path=None): - if self.value: - rows = [] - for row in range(self.rows_count): - columns = [] - for column in range(self.columns_count): - v = self.get_value(column, row) - if isinstance(v, BaseWidget): - columns.append(v.save({})) - else: - columns.append(str(v)) - rows.append(columns) - data['value'] = rows - return data - - def load_form(self, data, path=None): - if self.value: - rows = data['value'] - for row in range(len(rows)): - for column in range(len(rows[row])): - v = self.get_value(column, row) - if isinstance(v, BaseWidget): - v.load(rows[row][column]) - else: - self.set_value(column, row, rows[row][column]) - elif 'value' in data.keys(): - self.value = data['value'] - - def __add__(self, other): - - row = self.tableWidget.rowCount() - - self.tableWidget.insertRow(row) - if self.tableWidget.currentColumn() < len(other): - self.tableWidget.setColumnCount(len(other)) - - for column, e in enumerate(other): self.set_value(column, row, e) - - self.tableWidget.resizeColumnsToContents() - return self - - def __sub__(self, other): - - if isinstance(other, int): - if other < 0: - indexToRemove = self.tableWidget.currentRow() - else: - indexToRemove = other - - self.tableWidget.removeRow(indexToRemove) - return self - - def set_value(self, column, row, value): - if isinstance(value, QWidget): - self.tableWidget.setCellWidget(row, column, value) - value.show() - self.tableWidget.setRowHeight(row, value.height()) - else: - args = [value] if not hasattr(value, 'icon') else [QIcon(value.icon), value] - item = QTableWidgetItem() - item.setData(Qt.EditRole, *args) - self.tableWidget.setItem(row, column, item) - - def get_value(self, column, row): - try: - return str(self.tableWidget.item(row, column).text()) - except AttributeError as err: - return self.tableWidget.cellWidget(row, column) - except AttributeError as err: - return '' - - def resize_rows_contents(self): - self.tableWidget.resizeRowsToContents() - - def get_currentrow_value(self): - currentRow = self.tableWidget.currentRow() - if not currentRow < 0: - return self.value[currentRow] - else: - return [] - - def get_cell(self, column, row): - return self.tableWidget.item(row, column) - - def set_sorting_enabled(self, value): - """ - Enable or disable columns sorting - - :param bool value: True to enable sorting, False otherwise - """ - self.tableWidget.setSortingEnabled(value) - - ########################################################################## - ############ EVENTS ###################################################### - ########################################################################## - - def data_changed_event(self, row, col, item): - pass - - def item_selection_changed_event(self): - pass - - def current_cell_changed_event(self, next_row, next_col, previous_row, previous_col): - pass - - def current_item_changed_event(self, current, previous): - pass - - def cell_double_clicked_event(self, row, column): - pass - - ########################################################################## - ############ PROPERTIES ################################################## - ########################################################################## - - - @property - def horizontal_headers(self): - return self._horizontalHeaders - - @horizontal_headers.setter - def horizontal_headers(self, horizontal_headers): - """Set horizontal headers in the table list.""" - - self._horizontalHeaders = horizontal_headers - - self.tableWidget.setColumnCount(len(horizontal_headers)) - self.tableWidget.horizontalHeader().setVisible(True) - - for idx, header in enumerate(horizontal_headers): - item = QTableWidgetItem() - item.setText(header) - self.tableWidget.setHorizontalHeaderItem(idx, item) - - @property - def word_wrap(self): - return self.tableWidget.wordWrap() - - @word_wrap.setter - def word_wrap(self, value): - self.tableWidget.setWordWrap(value) - - @property - def readonly(self): - return self.tableWidget.editTriggers() - - @readonly.setter - def readonly(self, value): - if value: - self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) - else: - self.tableWidget.setEditTriggers(QAbstractItemView.AllEditTriggers) - - @property - def select_entire_row(self): - return self.tableWidget.selectionBehavior() - - @select_entire_row.setter - def select_entire_row(self, value): - if value: - self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) - else: - self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectItems) - - @property - def rows_count(self): - return self.tableWidget.rowCount() - - @property - def columns_count(self): - return self.tableWidget.columnCount() - - def __len__(self): - return self.rows_count - - @property - def value(self): - if hasattr(self, 'tableWidget'): - results = [] - for row in range(self.tableWidget.rowCount()): - r = [] - for col in range(self.tableWidget.columnCount()): - try: - r.append(self.get_value(col, row)) - except Exception as err: - logger.debug(str(err)) - r.append("") - results.append(r) - return results - return self._value - - @value.setter - def value(self, value): - self.clear() - for row in value: self += row - - # TODO: implement += on self.value? I want to add a list of tuples to - # self.value - - @property - def selected_rows_indexes(self): - result = [] - for index in self.tableWidget.selectedIndexes(): - result.append(index.row()) - return list(set(result)) - - @property - def selected_row_index(self): - indexes = self.selected_rows_indexes - if len(indexes) > 0: - return indexes[0] - else: - return None - - @property - def label(self): - return self.labelWidget.getText() - - @label.setter - def label(self, value): - if value != '': - self.labelWidget.setText(value) - else: - self.labelWidget.hide() - - @property - def form(self): - return self - - @property - def icon_size(self): - return self.tableWidget.iconSize() - - @icon_size.setter - def icon_size(self, value): - if isinstance(value, (tuple, list)): - self.tableWidget.setIconSize(QtCore.QSize(*value)) - else: - self.tableWidget.setIconSize(QtCore.QSize(value, value)) - - ########################################################################## - ############ PRIVATE FUNCTIONS ########################################### - ########################################################################## - - def _dataChangedEvent(self, item): - self.data_changed_event(item.row(), item.column(), self.tableWidget.model().data(item)) - self.changed_event() - - def tableWidgetCellChanged(self, nextRow, nextCol, previousRow, - previousCol): - self.current_cell_changed_event(nextRow, nextCol, previousRow, previousCol) - self.changed_event() - - def tableWidgetItemChanged(self, current, previous): - self.current_item_changed_event(current, previous) - self.changed_event() - - def tableWidgetItemSelectionChanged(self): - self.item_selection_changed_event() - - def tableWidgetCellDoubleClicked(self, row, column): - """ - (From PyQt) This signal is emitted whenever a cell in the table is double clicked. - The row and column specified is the cell that was double clicked. - - Besides firing this signal, we save the current value, in case the user needs to know the old value. - :param row: - :param column: - :return: - """ - self.CELL_VALUE_BEFORE_CHANGE = self.get_value(column, row) - logger.debug("Cell double clicked. Stored value: %s", self.CELL_VALUE_BEFORE_CHANGE) - self.cell_double_clicked_event(row, column) - - def empty_signal(self, *args, **kwargs): - """ - Use this function if you want to disconnect a signal temporarily - """ - pass diff --git a/pyforms/gui/Controls/ControlOpenGL.py b/pyforms/gui/Controls/ControlOpenGL.py deleted file mode 100644 index c12b840..0000000 --- a/pyforms/gui/Controls/ControlOpenGL.py +++ /dev/null @@ -1,255 +0,0 @@ -# !/usr/bin/python -# -*- coding: utf-8 -*- - -import logging - -from pysettings import conf - -logger = logging.getLogger(__name__) - -from pyforms.gui.Controls.ControlBase import ControlBase - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QSizePolicy - - try: - from OpenGL.GL import * - from OpenGL.GLU import * - from PyQt5.QtOpenGL import QGLWidget - except: - logger.debug("No OpenGL library available") - -else: - from PyQt4.QtGui import QSizePolicy - - try: - from OpenGL.GL import * - from OpenGL.GLU import * - from PyQt4.QtOpenGL import QGLWidget - except: - logger.debug("No OpenGL library available") - - -class OpenglGLWidget(QGLWidget): - def __init__(self, parent=None): - QGLWidget.__init__(self, parent) - - self._zoom = 1.0 - self._scene = None - self._rotation = [0, 0, 0] - - self._mouseLeftDown = False - self._mouseRightDown = False - - self._mouseGLPosition = [0, 0, 0] - self._lastMouseGLPosition = [0, 0, 0] - - self._mousePosition = [0, 0] # Current mouse position - self._lastMousePosition = [0, 0] # Last mouse position - - self._mouseStartDragPoint = None - self._clear_color = None - - self.setMinimumHeight(100) - self.setMinimumWidth(100) - self.setMouseTracking(True) - self.setAcceptDrops(True) - - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding); - - def initializeGL(self): - glClearDepth(1.0) - if self._clear_color: glClearColor(*self._clear_color) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE) - - glEnable(GL_POINT_SMOOTH); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_POLYGON_SMOOTH); - glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); - - def resizeGL(self, width, height): - """ - - :param width: - :param height: - :return: - """ - glViewport(0, 0, width, height) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(65.0, float(width) / float(height), 0.01, 800.0) - - def paintGL(self): - """ - - """ - if self._clear_color: - glBlendFunc(GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glClearColor(*self._clear_color) - else: - glBlendFunc(GL_SRC_ALPHA, GL_ONE) - glClearColor(0, 0, 0, 1) - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - - glEnable(GL_BLEND) - - glScalef(1, -1, -1) - - glTranslatef(0, 0, self._zoom) - glRotatef(self._rotation[0], 1, 0, 0) - glRotatef(self._rotation[1], 0, 1, 0) - glRotatef(self._rotation[2], 0, 0, 1) - - if self._scene != None: self._scene.DrawGLScene() - - if self.mouseDown: - # Get mouse position - modelview, projection = glGetDoublev(GL_MODELVIEW_MATRIX), glGetDoublev(GL_PROJECTION_MATRIX) - viewport = glGetIntegerv(GL_VIEWPORT) - winX, winY = float(self._mousePosition[0]), float(viewport[3] - self._mousePosition[1]) - winZ = glReadPixels(winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT) - self._mouseGLPosition = gluUnProject(winX, winY, winZ[0][0], modelview, projection, viewport) - - glDisable(GL_BLEND) - - def __updateMouse(self, event, pressed=None): - self._mousePosition[0] = event.x() - self._mousePosition[1] = event.y() - - if pressed != None: - if event.button() == 2: self._mouseRightDown = pressed - if event.button() == 1: self._mouseLeftDown = pressed - - def wheelEvent(self, event): - """ - - :param event: - """ - if event.delta() < 0: - self._zoom += 1 - else: - self._zoom -= 1 - - self.repaint() - - def mouseReleaseEvent(self, event): - """ - - :param event: - """ - self.__updateMouse(event) - - if self._mouseRightDown: - self._lastMouseGLPosition = self._mouseGLPosition - self._mouseRightDown = False - - if self._mouseLeftDown: - self.onEndDrag(self._mouseStartDragPoint, self._mouseGLPosition) - self._mouseLeftDown = False - - def mousePressEvent(self, event): - """ - - :param event: - """ - QGLWidget.mousePressEvent(self, event) - self.__updateMouse(event, pressed=True) - self.repaint() - - if self._mouseLeftDown: self._mouseStartDragPoint = self._mouseGLPosition - if self._mouseRightDown: self._lastMouseGLPosition = self._mouseGLPosition - - self.onPress(event.button(), self._mousePosition, self._mouseGLPosition) - - def mouseMoveEvent(self, event): - """ - - :param event: - """ - QGLWidget.mouseMoveEvent(self, event) - self.__updateMouse(event) - self.repaint() - - self.onMove((self._mouseGLPosition[0], self._mouseGLPosition[1])) - - if self.mouseDown: - if self._mouseLeftDown: - # p = self._mouseGLPosition[0] - self._x, self._mouseGLPosition[1] + self._y - # p = self._mouseGLPosition[0], self._mouseGLPosition[1] - self.onDrag(self._lastMousePosition, self._mousePosition) - - self._lastMousePosition = list(self._mousePosition) - - def onMove(self, point): - pass - - def onPress(self, button, point, glpoint=None): - pass - - def onDrag(self, startPoint, endPoint, startGLPoint=None, endGLPoint=None): - """ - - :param startPoint: - :param endPoint: - :param startGLPoint: - :param endGLPoint: - """ - movX, movY = endPoint[0] - startPoint[0], endPoint[1] - startPoint[1] - self._rotation[2] -= float(movX) * 0.15 - self._rotation[0] += float(movY) * 0.15 - - def onEndDrag(self, startPoint, endPoint, startGLPoint=None, endGLPoint=None): - pass - - ############################################################################## - ###### Properties ############################################################ - ############################################################################## - - @property - def scene(self): - return self._scene - - @scene.setter - def scene(self, value): - self._scene = value - - @property - def mouseDown(self): - return self._mouseLeftDown or self._mouseRightDown - - def resetZoomAndRotation(self): - self._zoom, self._rotation = 0.0, [0, 0, 0] - - -class ControlOpenGL(ControlBase): - def init_form(self): self._form = OpenglGLWidget() - - def repaint(self): self._form.repaint() - - def reset_zoom_and_rotation(self): self._form.resetZoomAndRotation() - - @property - def value(self): return self._form.scene - - @value.setter - def value(self, value): self._form.scene = value; self._form.repaint() - - @property - def clear_color(self): self._form._clear_color - - @clear_color.setter - def clear_color(self, value): - self._form._clear_color = value; - self._form.repaint() - - @property - def width(self): return self._form.width() - - @property - def height(self): return self._form.height() diff --git a/pyforms/gui/Controls/ControlPlayer/VideoGLWidget.py b/pyforms/gui/Controls/ControlPlayer/VideoGLWidget.py deleted file mode 100644 index 9804e51..0000000 --- a/pyforms/gui/Controls/ControlPlayer/VideoGLWidget.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -""" pyforms.gui.Controls.ControlPlayer.VideoGLWidget - -""" - -from pyforms.gui.Controls.ControlPlayer.AbstractGLWidget import AbstractGLWidget - - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtOpenGL import QGLWidget -else: - from PyQt4.QtOpenGL import QGLWidget - -class VideoGLWidget(AbstractGLWidget, QGLWidget): pass \ No newline at end of file diff --git a/pyforms/gui/Controls/ControlTextArea.py b/pyforms/gui/Controls/ControlTextArea.py deleted file mode 100755 index 0af7adc..0000000 --- a/pyforms/gui/Controls/ControlTextArea.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import pyforms.utils.tools as tools -from pysettings import conf -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic -else: - from PyQt4 import uic -from pyforms.gui.Controls.ControlBase import ControlBase - -__author__ = "Ricardo Ribeiro" -__credits__ = ["Ricardo Ribeiro"] -__license__ = "MIT" -__version__ = "0.0" -__maintainer__ = "Ricardo Ribeiro" -__email__ = "ricardojvr@gmail.com" -__status__ = "Development" - - -class ControlTextArea(ControlBase): - - def init_form(self): - control_path = tools.getFileInSameDirectory(__file__, "textArea.ui") - self._form = uic.loadUi(control_path) - self._form.label.setText(self._label) - if self._value: - self._form.plainTextEdit.setPlainText(str(self._value)) - - if not self._label or len(self._label)==0: - self.form.label.hide() - - super(ControlTextArea, self).init_form() - self.form.plainTextEdit.textChanged.connect(self.finishEditing) - - def __add__(self, other): - self._form.plainTextEdit.appendPlainText(str(other)) - return self - - def finishEditing(self): - """Function called when the lineEdit widget is edited""" - self.changed_event() - - - @property - def value(self): - return self._form.plainTextEdit.toPlainText() - - @value.setter - def value(self, value): - self._form.plainTextEdit.setPlainText(str(value)) - - @property - def readonly(self): - return self._form.plainTextEdit.isReadOnly() - - @readonly.setter - def readonly(self, value): - self._form.plainTextEdit.setReadOnly(value) diff --git a/pyforms/gui/Controls/ControlTreeView.py b/pyforms/gui/Controls/ControlTreeView.py deleted file mode 100644 index 1ac261c..0000000 --- a/pyforms/gui/Controls/ControlTreeView.py +++ /dev/null @@ -1,133 +0,0 @@ -# !/usr/bin/python -# -*- coding: utf-8 -*- - -from pysettings import conf - -from pyforms.gui.Controls.ControlBase import ControlBase - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QTreeView, QAbstractItemView - from PyQt5.QtGui import QStandardItem, QStandardItemModel - -else: - from PyQt4.QtGui import QTreeView, QAbstractItemView, QStandardItem, QStandardItemModel - - -class ControlTreeView(ControlBase, QTreeView): - def __init__(self, title='untitled'): - QTreeView.__init__(self) - ControlBase.__init__(self, title) - - def init_form(self): - self.setSelectionBehavior(QAbstractItemView.SelectRows) - self.header().hide() - self.setUniformRowHeights(True) - self.setDragDropMode(QAbstractItemView.InternalMove) - self.setDragEnabled(True) - self.setAcceptDrops(True) - - self.setModel(QStandardItemModel()) - self.model().itemChanged.connect(self.__item_changed_event) - - self.selectionChanged = self.selectionChanged - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # populate data - """ - for i in range(3): - parent1 = QtGui.QStandardItem('Family {}. Some long status text for sp'.format(i)) - for j in range(3): - child1 = QtGui.QStandardItem('Child {}'.format(i*3+j)) - parent1.appendRow(child1) - model().appendRow(parent1) - # span container columns - view.setFirstColumnSpanned(i, view.rootIndex(), True) - """ - - def __item_changed_event(self, item): - self.item_changed_event(item) - - def item_changed_event(self, item): - pass - - def item_selection_changed_event(self): - pass - - def selectionChanged(self, selected, deselected): - super(QTreeView, self.form).selectionChanged(selected, deselected) - self.item_selection_changed_event() - - @property - def mouseSelectedRowsIndexes(self): - result = [] - for index in self.form.selectedIndexes(): - result.append(index.row()) - return list(set(result)) - - @property - def selected_row_index(self): - indexes = self.mouseSelectedRowsIndexes - if len(indexes) > 0: - return indexes[0] - else: - return None - - @property - def selectedItem(self): - for index in self.form.selectedIndexes(): - item = index.model().itemFromIndex(index) - return item - else: - return None - - @property - def cells(self): - results = [] - for row in range(self._model().rowCount()): - r = [] - for col in range(self._model().columnCount()): - r.append(self._model().item(row, col)) - if len(r) > 0: results.append(r) - # print r, '---' - - return results - - def __add__(self, other): - if isinstance(other, TreeItem): - self._model().invisibleRootItem().appendRow(other) - - elif isinstance(other, list): - for x in other: - item = QStandardItem(x) - self._model().appendRow(item) - else: - item = QStandardItem(other) - self._model().appendRow(item) - - self.form.setFirstColumnSpanned(self._model().rowCount() - 1, self.form.rootIndex(), True) - return self - - def __sub__(self, other): - if isinstance(other, int): - if other < 0: - indexToRemove = self.selected_row_index - else: - indexToRemove = other - self.model().removeRow(indexToRemove) - return self - - @property - def value(self): - return self.form.model().invisibleRootItem() - return self.recursivelyReadRoot(root) - - @value.setter - def value(self, value): - for row in value: self += row - - def getAllSceneObjects(self): - return self._model().getChildrens() - - @property - def form(self): - return self diff --git a/pyforms/gui/Controls/ControlWeb.py b/pyforms/gui/Controls/ControlWeb.py deleted file mode 100644 index afe3247..0000000 --- a/pyforms/gui/Controls/ControlWeb.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -__author__ = "Ricardo Ribeiro" -__credits__ = ["Ricardo Ribeiro"] -__license__ = "MIT" -__version__ = "0.0" -__maintainer__ = "Ricardo Ribeiro" -__email__ = "ricardojvr@gmail.com" -__status__ = "Development" - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView - from PyQt5.QtCore import QUrl -else: - from PyQt4.QtCore import QUrl - from PyQt4.QtWebKit import QWebView - -from pyforms.gui.Controls.ControlBase import ControlBase - - -class ControlWeb(ControlBase, QWebView): - def __init__(self, label=""): - QWebView.__init__(self) - ControlBase.__init__(self, label) - - ############################################################################ - ############ Properties #################################################### - ############################################################################ - - @property - def value(self): return ControlBase.value.fget(self) - - @value.setter - def value(self, value): - ControlBase.value.fset(self, value) - QWebView.load(self, QUrl(value)) - - @property - def form(self): - return self diff --git a/pyforms/gui/Controls/comboInput.ui b/pyforms/gui/Controls/comboInput.ui deleted file mode 100755 index 7c06100..0000000 --- a/pyforms/gui/Controls/comboInput.ui +++ /dev/null @@ -1,53 +0,0 @@ - - - Form - - - - 0 - 0 - 169 - 38 - - - - - 0 - 38 - - - - - 16777215 - 38 - - - - Form - - - - 0 - - - - - TextLabel - - - - - - - - 0 - 0 - - - - - - - - - diff --git a/pyforms/gui/Controls/dbquery.ui b/pyforms/gui/Controls/dbquery.ui deleted file mode 100755 index 67f380f..0000000 --- a/pyforms/gui/Controls/dbquery.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - Form - - - - 0 - 0 - 471 - 168 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 0 - - - 0 - - - - - QAbstractItemView::SelectRows - - - - - - - - diff --git a/pyforms/gui/Controls/list.ui b/pyforms/gui/Controls/list.ui deleted file mode 100755 index 3e2ffdf..0000000 --- a/pyforms/gui/Controls/list.ui +++ /dev/null @@ -1,131 +0,0 @@ - - - Form - - - - 0 - 0 - 471 - 168 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 0 - - - 0 - - - - - TextLabel - - - Qt::AlignCenter - - - - - - - false - - - Qt::NoPen - - - false - - - 100 - - - false - - - true - - - false - - - false - - - false - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16 - 16 - - - - - - - - - - - - - 16 - 16 - - - - + - - - - - - - - - - - diff --git a/pyforms/gui/Controls/list_with_headers.ui b/pyforms/gui/Controls/list_with_headers.ui deleted file mode 100644 index 42ad4fd..0000000 --- a/pyforms/gui/Controls/list_with_headers.ui +++ /dev/null @@ -1,147 +0,0 @@ - - - Form - - - - 0 - 0 - 471 - 168 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 0 - - - 0 - - - - - TextLabel - - - Qt::AlignCenter - - - - - - - false - - - Qt::NoPen - - - true - - - 100 - - - false - - - true - - - false - - - false - - - false - - - - column1 - - - - - column2 - - - - - - - - - 0 - - - 0 - - - - - - 16 - 16 - - - - + - - - - - - - - 16 - 16 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - diff --git a/pyforms/gui/Controls/table.ui b/pyforms/gui/Controls/table.ui deleted file mode 100644 index debb4f1..0000000 --- a/pyforms/gui/Controls/table.ui +++ /dev/null @@ -1,113 +0,0 @@ - - - Form - - - - 0 - 0 - 471 - 168 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 0 - - - 0 - - - - - TextLabel - - - Qt::AlignCenter - - - - - - - false - - - - - - - - 0 - - - 0 - - - - - - 16 - 16 - - - - + - - - - - - - - 16 - 16 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - diff --git a/pyforms/gui/appmanager.py b/pyforms/gui/appmanager.py index 768ca5a..4ad5c7c 100644 --- a/pyforms/gui/appmanager.py +++ b/pyforms/gui/appmanager.py @@ -1,68 +1,210 @@ -import sys, glob, os -import logging -import traceback -from PyQt4 import uic -from PyQt4 import QtGui, QtCore -import pyforms.utils.tools as tools - -class Container(object): - def __init__(self, ClassObject): - - self.logger = logging.getLogger('pyforms') - - self._algorithm = ClassObject() - self._algorithm.init_form() - form_path = os.path.join(tools.get_object_class_path( Container ), 'mainWindow.ui') - self._form = uic.loadUi( form_path ) - self._form.verticalLayout.addWidget( self._algorithm ) - self._form.verticalLayout.setMargin(10) - self._form.verticalLayout.setSpacing(0) - - if conf.PYFORMS_USE_QT5: - self._form.verticalLayout.setContentsMargins(10,10,10,10) - else: - self._form.verticalLayout.setMargin(10) - - self._form.actionExit.triggered.connect( self.actionExit_triggered ) - self._form.show() - self._form.setWindowTitle( self._algorithm.title ) - self._form.actionStop.setEnabled(False) - self._form.actionStop.triggered.connect( self.actionStop_triggered ) - - try: - getattr(self._algorithm, 'execute') - self._form.actionRun.triggered.connect( self.actionRun_triggered ) - except: - self._form.menuAlgorithm.setEnabled(False) - self._form.actionRun.setEnabled(False) - self._form.menuAlgorithm.setVisible(False) - self._form.actionRun.setVisible(False) - - - def actionExit_triggered(self): exit() - def actionRun_triggered(self): - self._algorithm.stop = False - self._form.actionRun.setEnabled(False) - self._form.actionStop.setEnabled(True) - try: - self._algorithm.execute() - except Exception as err: - self.logger.warning("Error", exc_info=True) - self.actionStop_triggered() - - def actionStop_triggered(self): - self._algorithm.stop = True - self._form.actionRun.setEnabled(True) - self._form.actionStop.setEnabled(False) - - - -def start_app(ClassObject): - from pysettings import conf - - app = QtGui.QApplication(sys.argv) - - conf += 'pyforms.gui.settings' - - container = Container(ClassObject) - app.exec_() \ No newline at end of file +#!/usr/bin/python +# -*- coding: utf-8 -*- +from pyforms.gui.controls.ControlDockWidget import ControlDockWidget +from AnyQt.QtWidgets import QMainWindow, QDockWidget, QAction, QApplication, QToolBar +from AnyQt import QtCore, _api +from AnyQt.QtGui import QIcon + +from pyforms.utils.settings_manager import conf +import sys, os, platform, logging + +logger = logging.getLogger(__name__) + +class StandAloneContainer(QMainWindow): + def __init__(self, ClassObject): + super(QMainWindow, self).__init__() + + w = ClassObject() + w.app_main_window = self + self._widget = w + + if len(w.mainmenu) > 0: + w._mainmenu = self.__initMainMenu(w.mainmenu) + + w.init_form() + + if _api.USED_API == _api.QT_API_PYQT5: + self.layout().setContentsMargins(conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN) + elif _api.USED_API == _api.QT_API_PYQT4: + self.layout().setMargin(conf.PYFORMS_MAINWINDOW_MARGIN) + + self.setCentralWidget(w) + self.setWindowTitle(w.title) + + docks = {} + for name, item in w.controls.items(): + if isinstance(item, ControlDockWidget): + if item.side not in docks: + docks[item.side] = [] + docks[item.side].append((name, item)) + + #Construct the toolbar + if len(w.toolbar)>0: + tb = QToolBar(self) + self.addToolBar(tb) + for item in w.toolbar: + if isinstance(item, str) and item in ['|','-']: + tb.addSeparator() + else: + tb.addWidget(item.form) + + + for key, widgets in docks.items(): + side = QtCore.Qt.RightDockWidgetArea + if key == 'left': + side = QtCore.Qt.LeftDockWidgetArea + elif key == 'right': + side = QtCore.Qt.RightDockWidgetArea + elif key == 'top': + side = QtCore.Qt.TopDockWidgetArea + elif key == 'bottom': + side = QtCore.Qt.BottomDockWidgetArea + else: + side = QtCore.Qt.LeftDockWidgetArea + + if isinstance(widgets, list): + widgets = sorted(widgets, key=lambda x: x[1].order) + + for name, widget in widgets: + dock = QDockWidget(self) + dock.setFeatures( + QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable) + dock.setObjectName(name) + + if _api.USED_API == _api.QT_API_PYQT5: + dock.setContentsMargins(0,0,0,0) + widget.form.layout().setContentsMargins(widget.margin,widget.margin,widget.margin,widget.margin) + elif _api.USED_API == _api.QT_API_PYQT4: + dock.setMargin(0) + widget.form.layout().setMargin(widget.margin) + + # print dock.objectName(),1 + dock.setWidget(widget.form) + dock.setWindowTitle(widget.label) + widget.dock = dock + if not widget._show: dock.hide() + + self.addDockWidget(side, dock) + else: + dock = QDockWidget(self) + dock.setFeatures( + QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable) + # dock.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) + + if _api.USED_API == _api.QT_API_PYQT5: + widget.form.layout().setContentsMargins(widget.margin,widget.margin,widget.margin,widget.margin) + elif _api.USED_API == _api.QT_API_PYQT5: + widget.form.layout().setMargin(widget.margin) + + # print dock.objectName(), 2 + dock.setObjectName(name) + dock.setWidget(widget.form) + self.addDockWidget(side, dock) + dock.setWindowTitle(widget.label) + widget.dock = dock + if not widget._show: dock.hide() + + if conf.PYFORMS_STYLESHEET: + stylesheet_files = [conf.PYFORMS_STYLESHEET] + + p = platform.system() + if p == 'Windows' and conf.PYFORMS_STYLESHEET_WINDOWS: + stylesheet_files.append(conf.PYFORMS_STYLESHEET_WINDOWS) + elif p == 'Darwin' and conf.PYFORMS_STYLESHEET_DARWIN: + stylesheet_files.append(conf.PYFORMS_STYLESHEET_DARWIN) + elif p == 'Linux' and conf.PYFORMS_STYLESHEET_LINUX: + stylesheet_files.append(conf.PYFORMS_STYLESHEET_LINUX) + + logger.debug('Import stylesheets: {0}'.format(stylesheet_files)) + self.loadStyleSheetFile(stylesheet_files) + + + def closeEvent(self, event): + self._widget.closeEvent(event) + + def __initMainMenu(self, options, keys={}): + menubar = self.menuBar() + for menuIndex, m in enumerate(options): + for key, menus in m.items(): + menu = menubar.addMenu(key) + for subMenuIndex, m1 in enumerate(menus): + if isinstance(m1, str) and m1 == "-": + menu.addSeparator() + else: + for text, func in m1.items(): + if text != 'icon': + action = QAction(text, self) + if 'icon' in m1.keys(): + action.setIconVisibleInMenu(True) + action.setIcon(QIcon(m1['icon'])) + if func: + action.triggered.connect(func) + menu.addAction(action) + options[menuIndex][key][subMenuIndex][text] = action + break + return options + + def loadStyleSheetFile(self, files): + content = '' + for filename in files: + infile = open(filename, 'r') + content += infile.read() + '\n' + infile.close() + + self.setStyleSheet(content) + + +def execute_test_file(myapp): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("test_file", help="File with the tests script") + args = parser.parse_args() + + with open(args.test_file) as f: + global_vars = globals() + local_vars = locals() + code = compile(f.read(), args.test_file, 'exec') + exec(code, global_vars, local_vars) + + +def start_app(ClassObject, geometry=None, stylesheet=None): + from pyforms.utils.settings_manager import conf + + app = QApplication(sys.argv) + + conf += 'pyforms.gui.settings' + + mainwindow = StandAloneContainer(ClassObject) + + myapp = mainwindow.centralWidget() + + if geometry is not None: + mainwindow.show() + mainwindow.setGeometry(*geometry) + else: + mainwindow.showMaximized() + + if conf.PYFORMS_QUALITY_TESTS_PATH is not None: + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--test", help="File with the tests script") + args = parser.parse_args() + + if args.test: + TEST_PATH = os.path.join(conf.PYFORMS_QUALITY_TESTS_PATH, args.test) + TEST_FILE_PATH = os.path.join(TEST_PATH, args.test + '.py') + DATA_PATH = os.path.join(TEST_PATH, 'data', sys.platform) + INPUT_DATA_PATH = os.path.join(DATA_PATH, 'input-data') + OUTPUT_DATA_PATH = os.path.join(DATA_PATH, 'output-data') + EXPECTED_DATA_PATH = os.path.join(DATA_PATH, 'expected-data') + + with open(TEST_FILE_PATH) as f: + global_vars = {} # globals() + local_vars = locals() + code = compile(f.read(), TEST_FILE_PATH, 'exec') + + exec(code, global_vars, local_vars) + + if stylesheet: app.setStyleSheet(stylesheet) + app.exec_() + return myapp diff --git a/pyforms/gui/basewidget.py b/pyforms/gui/basewidget.py new file mode 100644 index 0000000..c098193 --- /dev/null +++ b/pyforms/gui/basewidget.py @@ -0,0 +1,602 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import json, AnyQt + +from pyforms.utils.settings_manager import conf + +from AnyQt.QtWidgets import QFrame +from AnyQt.QtWidgets import QVBoxLayout +from AnyQt.QtWidgets import QTabWidget +from AnyQt.QtWidgets import QSplitter +from AnyQt.QtWidgets import QHBoxLayout +from AnyQt.QtWidgets import QSpacerItem +from AnyQt.QtWidgets import QSizePolicy +from AnyQt.QtWidgets import QLabel +from AnyQt.QtGui import QFont +from AnyQt.QtWidgets import QFileDialog +from AnyQt.QtWidgets import QApplication +from AnyQt import QtCore, _api + +from pyforms.gui.controls.ControlBase import ControlBase + +from AnyQt.QtWidgets import QMessageBox, QInputDialog + +class vsplitter(object): + def __init__(self, *args, **kwargs): + self.items = args + self.left_width = kwargs.get('left_width', 1) + self.right_width = kwargs.get('right_width', 1) + def __getitem__(self,index): return self.items[index] + def __setitem__(self,index,value): self.items[index] = value + def __len__(self): return len(self.items) + def __iter__(self): + self._index = -1; return self + def __next__(self): + self._index += 1 + if self._index>=len(self.items): raise StopIteration + return self.items[self._index] + +class hsplitter(object): + def __init__(self, *args, **kwargs): self.items = args + def __getitem__(self,index): return self.items[index] + def __setitem__(self,index,value): self.items[index] = value + def __len__(self): return len(self.items) + def __iter__(self): + self._index = -1; return self + def __next__(self): + self._index += 1 + if self._index>=len(self.items): raise StopIteration + return self.items[self._index] + + +class BaseWidget(QFrame): + """ + The class implements the most basic widget or window. + """ + + def __init__(self, *args, **kwargs): + title = kwargs.get('title', args[0] if len(args)>0 else '') + + parent_win = kwargs.get('parent_win', kwargs.get('parent_widget', None)) + win_flag = kwargs.get('win_flag', None) + + self._parent_widget = parent_win + + if parent_win is not None and win_flag is None: win_flag = QtCore.Qt.Dialog + + QFrame.__init__(self) if parent_win is None else QFrame.__init__(self, parent_win, win_flag) + + layout = QVBoxLayout() + self.setLayout(layout) + + if _api.USED_API == _api.QT_API_PYQT5: + layout.setContentsMargins(0,0,0,0) + elif _api.USED_API == _api.QT_API_PYQT4: + layout.setMargin(0) + + self.title = title + + self.title = title + self.has_progress = False + + self.toolbar = [] + self._mainmenu = [] + self._splitters = [] + self._tabs = [] + self._formset = None + self._formLoaded = False + self.uid = id(self) + + self.setAccessibleName('BaseWidget') + + ########################################################################## + ############ FUNCTIONS ################################################## + ########################################################################## + + def init_form(self): + """ + Generate the module Form + """ + if not self._formLoaded: + + if self._formset is not None: + control = self.generate_panel(self._formset) + self.layout().addWidget(control) + else: + allparams = self.controls + for key, param in allparams.items(): + param.parent = self + param.name = key + self.layout().addWidget(param.form) + self._formLoaded = True + + def set_margin(self, margin): + if _api.USED_API == _api.QT_API_PYQT5: + self.layout().setContentsMargins(margin,margin,margin,margin) + elif _api.USED_API == _api.QT_API_PYQT4: + self.layout().setMargin(margin) + + + def generate_tabs(self, formsetdict): + """ + Generate QTabWidget for the module form + @param formset: Tab form configuration + @type formset: dict + """ + tabs = QTabWidget(self) + for key, item in sorted(formsetdict.items()): + ctrl = self.generate_panel(item) + tabs.addTab(ctrl, key[key.find(':') + 1:]) + return tabs + + def generate_panel(self, formset): + """ + Generate a panel for the module form with all the controls + formset format example: [('_video', '_arenas', '_run'), {"Player":['_threshold', "_player", "=", "_results", "_query"], "Background image":[(' ', '_selectBackground', '_paintBackground'), '_image']}, "_progress"] + tuple: will display the controls in the same horizontal line + list: will display the controls in the same vertical line + dict: will display the controls in a tab widget + '||': will plit the controls in a horizontal line + '=': will plit the controls in a vertical line + @param formset: Form configuration + @type formset: list + """ + control = None + if '=' in formset or isinstance( formset, hsplitter ): + control = QSplitter(QtCore.Qt.Vertical) + index = list(formset).index('=') + first_panel = self.generate_panel(formset[0:index]) + second_panel = self.generate_panel(formset[index+1:]) + control.addWidget(first_panel) + control.addWidget(second_panel) + self._splitters.append(control) + return control + elif '||' in formset or isinstance( formset, vsplitter ): + control = QSplitter(QtCore.Qt.Horizontal) + index = list(formset).index('||') + first_panel = self.generate_panel(formset[0:index]) + second_panel = self.generate_panel(formset[index+1:]) + control.addWidget(first_panel) + control.addWidget(second_panel) + + if isinstance( formset, vsplitter ): + sizes = [formset.left_width, formset.right_width] + control.setSizes(sizes) + self._splitters.append(control) + return control + control = QFrame(self) + layout = None + if type(formset) is tuple: + layout = QHBoxLayout() + for row in formset: + if isinstance(row, (list, tuple, vsplitter, hsplitter) ): + panel = self.generate_panel(row) + layout.addWidget(panel) + elif row == " ": + spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + layout.addItem(spacer) + elif type(row) is dict: + c = self.generate_tabs(row) + layout.addWidget(c) + self._tabs.append(c) + else: + param = self.controls.get(row, None) + if param is None: + label = QLabel() + label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) + # layout.addWidget( label ) + + if row.startswith('info:'): + label.setText(row[5:]) + font = QFont() + font.setPointSize(10) + label.setFont(font) + label.setAccessibleName('info') + elif row.startswith('h1:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(17) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h1') + elif row.startswith('h2:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(16) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h2') + elif row.startswith('h3:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(15) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h3') + elif row.startswith('h4:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(14) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h4') + elif row.startswith('h5:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(12) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h5') + else: + label.setText(row) + font = QFont() + font.setPointSize(10) + label.setFont(font) + label.setAccessibleName('msg') + label.setToolTip(label.text()) + layout.addWidget(label) + else: + param.parent = self + param.name = row + layout.addWidget(param.form) + elif type(formset) is list: + layout = QVBoxLayout() + for row in formset: + if isinstance(row, (list, tuple, vsplitter, hsplitter) ): + panel = self.generate_panel(row) + layout.addWidget(panel) + elif row == " ": + spacer = QSpacerItem( + 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + layout.addItem(spacer) + elif type(row) is dict: + c = self.generate_tabs(row) + layout.addWidget(c) + self._tabs.append(c) + else: + param = self.controls.get(row, None) + if param is None: + label = QLabel() + label.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) + label.resize(30, 30) + # layout.addWidget( label ) + + if row.startswith('info:'): + label.setText(row[5:]) + font = QFont() + font.setPointSize(10) + label.setFont(font) + label.setAccessibleName('info') + elif row.startswith('h1:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(17) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h1') + elif row.startswith('h2:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(16) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h2') + elif row.startswith('h3:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(15) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h3') + elif row.startswith('h4:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(14) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h4') + elif row.startswith('h5:'): + label.setText(row[3:]) + font = QFont() + font.setPointSize(12) + font.setBold(True) + label.setFont(font) + label.setAccessibleName('h5') + else: + label.setText(row) + font = QFont() + font.setPointSize(10) + label.setFont(font) + label.setAccessibleName('msg') + + label.setToolTip(label.text()) + + layout.addWidget(label) + else: + param.parent = self + param.name = row + layout.addWidget(param.form) + + if _api.USED_API == _api.QT_API_PYQT5: + layout.setContentsMargins(0,0,0,0) + elif _api.USED_API == _api.QT_API_PYQT4: + layout.setMargin(0) + + control.setLayout(layout) + return control + + def show(self): + self.init_form() + super(BaseWidget, self).show() + + def save_form(self, data={}, path=None): + allparams = self.controls + + if hasattr(self, 'load_order'): + for name in self.load_order: + param = allparams[name] + data[name] = {} + param.save_form(data[name]) + else: + for name, param in allparams.items(): + data[name] = {} + param.save_form(data[name]) + return data + + def load_form(self, data, path=None): + allparams = self.controls + + if hasattr(self, 'load_order'): + for name in self.load_order: + param = allparams[name] + if name in data: + param.load_form(data[name]) + else: + for name, param in allparams.items(): + if name in data: + param.load_form(data[name]) + # self.init_form() + + def save_window(self): + allparams = self.controls + data = {} + self.save_form(data) + + filename, _ = QFileDialog.getSaveFileName(self, 'Select file') + with open(filename, 'w') as output_file: json.dump(data, output_file) + + def load_form_filename(self, filename): + with open(filename, 'r') as pkl_file: + project_data = json.load(pkl_file) + data = dict(project_data) + self.load_form(data) + + def load_window(self): + filename, _ = QFileDialog.getOpenFileNames(self, 'Select file') + self.load_form_filename(str(filename[0])) + + def close(self): + super(BaseWidget, self).close() + + + def input_text(self, msg, title='', default=None): + text, ok = QInputDialog.getText(self, title, msg, text=default) + if ok: + return str(text) + else: + return None + + def input_double(self, msg, title='', default=0, min=-2147483647, max=2147483647, decimals=1): + text, ok = QInputDialog.getDouble(self, title, msg, value=default, min=min, max=max, decimals=decimals) + if ok: + return float(text) + else: + return None + + def input_int(self, msg, title='', default=0, min=-2147483647, max=2147483647): + text, ok = QInputDialog.getInt(self, title, msg, value=default, min=min, max=max) + if ok: + return int(text) + else: + return None + + + def question(self, msg, title=None, buttons=['no', 'yes']): + btns = None + for btn in buttons: + if btn.lower()=='cancel': + b = QMessageBox.Cancel + elif btn.lower()=='no': + b = QMessageBox.No + elif btn.lower()=='yes': + b = QMessageBox.Yes + + if btns is None: + btns=b + else: btns |= b + + m = QMessageBox(QMessageBox.Question, title, msg, btns) + reply = m.exec() + + if reply==QMessageBox.Cancel: return 'cancel' + elif reply==QMessageBox.No: return 'no' + elif reply==QMessageBox.Yes: return 'yes' + + return None + + def message(self, msg, title=None, msg_type=None): + if msg_type=='success': + m = QMessageBox(QMessageBox.NoIcon, title, msg) + elif msg_type=='info': + m = QMessageBox(QMessageBox.Information, title, msg) + elif msg_type=='warning': + m = QMessageBox(QMessageBox.Warning, title, msg) + elif msg_type=='error': + m = QMessageBox(QMessageBox.Critical, title, msg) + elif msg_type=='about': + m = QMessageBox(QMessageBox.Question, title, msg) + elif msg_type=='aboutQt': + m = QMessageBox(QMessageBox.Question, title, msg) + else: + m = QMessageBox(QMessageBox.NoIcon, title, msg) + + m.exec() + + def success(self, msg, title=None): self.message(msg, title, msg_type='success') + def info(self, msg, title=None): self.message(msg, title, msg_type='info') + def warning(self, msg, title=None): self.message(msg, title, msg_type='warning'); + def alert(self, msg, title=None): self.message(msg, title, msg_type='error') + def critical(self, msg, title=None): self.message(msg, title, msg_type='error') + def about(self, msg, title=None): self.message(msg, title, msg_type='about') + def aboutQt(self, msg, title=None): self.message(msg, title, msg_type='aboutQt') + + def message_popup(self, msg, title='', buttons=None, handler=None, msg_type='success'): + pass + def success_popup(self, msg, title='', buttons=None, handler=None): + return self.message_popup(msg, title, buttons, handler, msg_type='success') + def info_popup(self, msg, title='', buttons=None, handler=None): + return self.message_popup(msg, title, buttons, handler, msg_type='info') + def warning_popup(self, msg, title='', buttons=None, handler=None): + return self.message_popup(msg, title, buttons, handler, msg_type='warning') + def alert_popup(self, msg, title='', buttons=None, handler=None): + return self.message_popup(msg, title, buttons, handler, msg_type='alert') + + + + + ########################################################################## + ############ GUI functions ############################################### + ########################################################################## + + def set_margin(self, margin): + if AnyQt.USED_API=='pyqt5': + self.layout().setContentsMargins(margin,margin,margin,margin) + else: + self.layout().setMargin(margin) + + ########################################################################## + ############ EVENTS ###################################################### + ########################################################################## + + def before_close_event(self): + """ + Do something before closing widget + Note that the window will be closed anyway + """ + pass + + ########################################################################## + ############ Properties ################################################## + ########################################################################## + + @property + def controls(self): + """ + Return all the form controls from the the module + """ + result = {} + for name, var in vars(self).items(): + try: + if isinstance(var, ControlBase): + result[name] = var + except: + pass + return result + + ############################################################################ + ############ GUI Properties ################################################ + ############################################################################ + + + @property + def form_has_loaded(self): + return self._formLoaded + + + @property + def form(self): + return self + + @property + def visible(self): + return self.isVisible() + + @property + def mainmenu(self): + return self._mainmenu + @mainmenu.setter + def mainmenu(self, value): + self._mainmenu = value + + + @property + def controls(self): + """ + Return all the form controls from the the module + """ + result = {} + for name, var in vars(self).items(): + try: + if isinstance(var, ControlBase): + result[name] = var + except: + pass + return result + + ############################################################################ + ############ GUI Properties ################################################ + ############################################################################ + + + @property + def form_has_loaded(self): + return self._formLoaded + + @property + def parent_widget(self): + return self._parent_widget + + @property + def form(self): + return self + + @property + def title(self): + return self.windowTitle() + + @title.setter + def title(self, value): + self.setWindowTitle(value) + + + @property + def formset(self): + return self._formset + + @formset.setter + def formset(self, value): + self._formset = value + + @property + def uid(self): + return self._uid + + @uid.setter + def uid(self, value): + self._uid = value + + + @property + def visible(self): + return self.isVisible() + + + + ########################################################################## + ############ PRIVATE FUNCTIONS ########################################### + ########################################################################## + + def closeEvent(self, event): + self.before_close_event() + super(BaseWidget, self).closeEvent(event) diff --git a/pyforms/gui/controls/ControlBase.py b/pyforms/gui/controls/ControlBase.py new file mode 100644 index 0000000..edc3053 --- /dev/null +++ b/pyforms/gui/controls/ControlBase.py @@ -0,0 +1,280 @@ +# !/usr/bin/python +# -*- coding: utf-8 -*- + +from pyforms.utils.settings_manager import conf + +from AnyQt import QtCore, uic +from AnyQt.QtWidgets import QMenu, QAction +from AnyQt.QtGui import QIcon, QKeySequence + +class ControlBase(object): + """ + This class represents the most basic control that can exist + A Control is a Widget or a group of widgets that can be reused from application to application + + @undocumented: __repr__ + """ + + def __init__(self, *args, **kwargs): + self._form = None # Qt widget + self._parent = None # Parent window + self._popup_menu = None + + self._help = kwargs.get('helptext', None) + self._value = kwargs.get('default', None) + self._label = kwargs.get('label', args[0] if len(args)>0 else '') + """ + self._visible = kwargs.get('visible', True) + self._error = kwargs.get('error', False) + self._css = kwargs.get('css', None) + self._enabled = kwargs.get('enabled', True) + self._readonly = kwargs.get('readonly', False) + self._label_visible = kwargs.get('label_visible', True) + """ + + self.init_form() + + self.changed_event = kwargs.get('changed_event', self.changed_event) + self.enabled = kwargs.get('enabled', True) + self.readonly = kwargs.get('readonly', False) + if not kwargs.get('visible', True): self.hide() + + + def __repr__(self): return str(self._value) + + ########################################################################## + ############ Funcions #################################################### + ########################################################################## + + def init_form(self): + """ + Load Control and initiate the events + """ + if self.help: self.form.setToolTip(self.help) + + + + def load_form(self, data, path=None): + """ + Load a value from the dict variable + @param data: dictionary with the value of the Control + """ + if 'value' in data: + self.value = data['value'] + + def save_form(self, data, path=None): + """ + Save a value to dict variable + @param data: dictionary with to where the value of the Control will be added + """ + data['value'] = self.value + return data + + def show(self): + """ + Show the control + """ + if self.form is None: + return + elif self.form==self: + super(ControlBase,self).show() + else: + self.form.show() + + def hide(self): + """ + Hide the control + """ + if self.form is None: + return + elif self.form==self: + super(ControlBase,self).hide() + else: + self.form.hide() + + + def __create_popup_menu(self): + if not self._popup_menu: + self.form.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.form.customContextMenuRequested.connect(self._open_popup_menu) + self._popup_menu = QMenu(self.parent) + self._popup_menu.aboutToShow.connect( self.about_to_show_contextmenu_event) + + def add_popup_submenu(self, label, submenu=None): + self.__create_popup_menu() + menu = submenu if submenu else self._popup_menu + submenu = QMenu(label, menu) + menu.addMenu(submenu) + return submenu + + def add_popup_menu_option(self, label, function_action=None, key=None, icon=None, submenu=None): + """ + Add an option to the Control popup menu + @param label: label of the option. + @param function_action: function called when the option is selected. + @param key: shortcut key + @param icon: icon + """ + self.__create_popup_menu() + + menu = submenu if submenu else self._popup_menu + + if label == "-": + return menu.addSeparator() + else: + action = QAction(label, self.form) + if icon is not None: + action.setIconVisibleInMenu(True) + action.setIcon(icon if isinstance(icon, QIcon) else QIcon(icon) ) + if key != None: + action.setShortcut(QKeySequence(key)) + if function_action: + action.triggered.connect(function_action) + menu.addAction(action) + return action + + + ########################################################################## + ############ Events ###################################################### + ########################################################################## + + def changed_event(self): + """ + Function called when ever the Control value is changed + """ + return True + + def about_to_show_contextmenu_event(self): + """ + Function called before open the Control popup menu + """ + pass + + def _open_popup_menu(self, position): + if self._popup_menu: + self._popup_menu.exec_(self.form.mapToGlobal(position)) + + ########################################################################## + ############ Properties ################################################## + ########################################################################## + + ########################################################################## + # Set the Control enabled or disabled + + @property + def enabled(self): + return self.form.isEnabled() + + @enabled.setter + def enabled(self, value): + """@type value: boolean""" + self.form.setEnabled(value) + + ########################################################################## + # Return or update the value of the Control + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + """ + This property return and set what the control should manage or store. + @type value: string + """ + oldvalue = self._value + self._value = value + if oldvalue != value: + self.changed_event() + + + @property + def name(self): return self.form.objectName() + + @name.setter + def name(self, value): + """ + This property return and set the name of the control + @type value: string + """ + self.form.setObjectName(value) + + ########################################################################## + # Return or update the label of the Control + + @property + def label(self): + return self._label + + @label.setter + def label(self, value): + """ + Label of the control, if applies + @type value: string + """ + self._label = value + + ########################################################################## + # Parent window + + @property + def parent(self): return self._parent + + @parent.setter + def parent(self, value): + """ + Returns or set the parent basewidget where the Control is + @type value: BaseWidget + """ + self._parent = value + + @property + def visible(self): return self.form.isVisible() + + @property + def help(self): return self._help if self._help else '' + + @property + def error(self): return None + + @error.setter + def error(self, value): + pass + + @property + def label_visible(self): return None + + @label_visible.setter + def label_visible(self, value): + pass + + @property + def readonly(self): return None + + @readonly.setter + def readonly(self, value): + pass + + @property + def css(self): return None + + @css.setter + def css(self, value): + pass + + + ########################################################################## + ############ Properties just for the GUI version ######################### + ########################################################################## + + ########################################################################## + # Return the QT widget + + @property + def form(self): + """ + Returns the Widget of the control. + This property will be deprecated in a future version. + """ + return self._form diff --git a/pyforms/gui/controls/ControlBoundingSlider.py b/pyforms/gui/controls/ControlBoundingSlider.py new file mode 100644 index 0000000..fc051a7 --- /dev/null +++ b/pyforms/gui/controls/ControlBoundingSlider.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from pyforms.utils.settings_manager import conf + +from AnyQt import QtCore, _api +from AnyQt.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSpinBox +from AnyQt.QtGui import QFont, QColor, QCursor, QPainter + +from pyforms.gui.controls.ControlBase import ControlBase + + +class GaugeWidgetVertical(QWidget): + def __init__(self, *args, **kwargs): + QWidget.__init__(self, *args, **kwargs) + + self.setMouseTracking(True) + self.setMinimumHeight(30) + self.setMinimumWidth(30) + self.setMaximumWidth(50) + + self._step = 0 + self._lower = 0 + self._higher = 100 + self._minVal = 0 + self._maxVal = 75 + self._lastMouseY = None + self._moving = False + self._resizingBottom = False + self._resizingTop = False + self._scale = 1.0 + self._use_float = False + + def changed_event(self): + pass + + def paintEvent(self, e): + # call the base implementation to paint normal interface + super(GaugeWidgetVertical, self).paintEvent(e) + draw = QPainter() + draw.begin(self) + + window_with = self.width() - 1 + diff = (self._higher - self._lower) * self.scale + try: + self._step = float(self.height()) / float(diff) + except ZeroDivisionError: + self._step = 0 + y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale + y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale + + draw.setOpacity(1.0) + draw.setBrush(QtCore.Qt.NoBrush) + draw.setPen(QColor(200, 200, 255)) + + b = int(self.height() / 5) + e = int(self.height() - (self.height() / 5) + 1) + s = int(self.height() / 5) + + for i in range( b, e, s ): + draw.drawLine(0, i, window_with, i) + + draw.setBrush(QColor(33, 133, 208)) + draw.setPen(QColor(33, 133, 208)) + draw.setOpacity(0.7) + draw.drawRect(0, y_start, window_with, y_end - y_start) + + draw.setFont(QFont('Decorative', 8)) + draw.setPen(QColor(30, 30, 30)) + draw.drawText(3, self.height() - 3, str(self._lower) if self._use_float else str(int(round(self._lower)))) + draw.drawText(3, 10, str(self._higher) if self._use_float else str(int(round(self._higher)))) + + draw.drawText(3, y_start + 11, str(self._minVal) if self._use_float else str(int(round(self._minVal)))) + draw.drawText(3, y_end - 4, str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) + + draw.end() + + def mousePressEvent(self, event): + self._moving = False + self._resizingBottom = False + self._resizingTop = False + + y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale + y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale + + if event.buttons() == QtCore.Qt.LeftButton: + if y_start > event.y() > y_end: + self._moving = True + elif event.y() < y_end: + self._resizingTop = True + elif y_start < event.y(): + self._resizingBottom = True + + def mouseReleaseEvent(self, event): + self._moving = False + self._resizingBottom = False + self._resizingTop = False + + def mouseMoveEvent(self, event): + super(GaugeWidgetVertical, self).mouseMoveEvent(event) + + y_start = self.height() - (self._minVal - self._lower) * self._step * self.scale + y_end = self.height() - (self._maxVal - self._lower) * self._step * self.scale + + if y_start < event.y() or event.y() < y_end: + self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) + else: + self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) + + if event.buttons() == QtCore.Qt.LeftButton: + if self._lastMouseY != None: + diff = self._lastMouseY - event.y() + if diff != 0: + if self._moving: + new_min = self._minVal * self.scale + diff * 1 / self._step + new_max = self._maxVal * self.scale + diff * 1 / self._step + if new_min < new_max: + self._minVal = new_min / self.scale + self._maxVal = new_max / self.scale + elif self._resizingTop: + new_max = self._maxVal * self.scale + diff * 1 / self._step + if self._minVal < (new_max / self.scale): self._maxVal = new_max / self.scale + elif self._resizingBottom: + new_min = self._minVal * self.scale + diff * 1 / self._step + if (new_min / self.scale) < self._maxVal: self._minVal = new_min / self.scale + + self.repaint() + + self.changed_event() + + self._lastMouseY = event.y() + + def mouseReleaseEvent(self, event): + self._lastMouseY = None + + @property + def scale(self): + return self._scale + + @scale.setter + def scale(self, value): + self._scale = value + + +class GaugeWidgetHorizontal(GaugeWidgetVertical): + def __init__(self, *args, **kwargs): + super(GaugeWidgetHorizontal, self).__init__(*args, **kwargs) + + self.setMaximumWidth(1000000) + self.setMinimumHeight(20) + self.setMaximumHeight(20) + + def paintEvent(self, e): + # call the base implementation to paint normal interface + QWidget.paintEvent(self, e); + draw = QPainter(); + draw.begin(self) + + h = self.height() - 1 + diff = (self._higher - self._lower) * self.scale + + try: + self._step = float(self.width()) / float(diff) + except ZeroDivisionError: + self._step = 0 + x_start = (self._minVal - self._lower) * self._step * self.scale + x_end = (self._maxVal - self._lower) * self._step * self.scale + + draw.setOpacity(1.0) + draw.setBrush(QtCore.Qt.NoBrush) + draw.setPen(QColor(200, 200, 255)) + + for i in range( int(self.width()/5), int(self.width()-self.width()/5) + 1, int(self.width()/5) ): + draw.drawLine(i, 0, i, h) + + draw.setBrush(QColor(238, 238, 238)) + draw.setPen(QColor(238, 238, 238)) + draw.drawRoundedRect(0, 2, self.width(), h - 4, 3, 3) + + draw.setBrush(QColor(33, 133, 208)) + draw.setPen(QColor(33, 133, 208)) + draw.drawRoundedRect(int(round(x_start)), 2, int(round(x_end - x_start)), h - 4, 3, 3) + # draw.setOpacity(1.0) + draw.setFont(QFont('Decorative', 8)) + draw.setPen(QColor(80, 80, 80)) + + str(self._maxVal) if self._use_float else str(int(round(self._maxVal))) + + boundtext = draw.boundingRect(QtCore.QRectF(), + str(self._higher) if self._use_float else str(int(round(self._higher)))) + draw.drawText(self.width() - boundtext.width(), 14, + str(self._higher) if self._use_float else str(int(round(self._higher)))) + draw.drawText(0, 14, str(self._lower) if self._use_float else str(int(round(self._lower)))) + + draw.setPen(QColor(255, 255, 255)) + boundtext = draw.boundingRect(QtCore.QRectF(), + str(self._minVal) if self._use_float else str(int(round(self._minVal)))) + draw.drawText(x_start + 2, 14, str(self._minVal) if self._use_float else str(int(round(self._minVal)))) + boundtext = draw.boundingRect(QtCore.QRectF(), + str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) + draw.drawText(x_end - boundtext.width(), 14, + str(self._maxVal) if self._use_float else str(int(round(self._maxVal)))) + + draw.end() + + def mousePressEvent(self, event): + self._moving = False + self._resizingLeft = False + self._resizingRight = False + + x_start = (self._minVal - self._lower) * self._step * self.scale + x_end = (self._maxVal - self._lower) * self._step * self.scale + + if event.buttons() == QtCore.Qt.LeftButton: + if x_start < event.x() < x_end: + self._moving = True + elif event.x() > x_end: + self._resizingLeft = True + elif x_start > event.x(): + self._resizingRight = True + + def mouseMoveEvent(self, event): + super(GaugeWidgetVertical, self).mouseMoveEvent(event) + + x_start = (self._minVal - self._lower) * self._step * self.scale + x_end = (self._maxVal - self._lower) * self._step * self.scale + + # set the cursors + if x_start >= event.x() or event.x() >= x_end: + self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) + else: + self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) + + if event.buttons() == QtCore.Qt.LeftButton: + if self._lastMouseY != None: + diff = self._lastMouseY - event.x() + if diff != 0: + if self._moving: + new_min = self._minVal * self.scale - diff * 1 / self._step + new_max = self._maxVal * self.scale - diff * 1 / self._step + if new_min < new_max: + self._minVal = new_min / self.scale + self._maxVal = new_max / self.scale + elif self._resizingLeft: + new_max = self._maxVal * self.scale - diff * 1 / self._step + if self._minVal < (new_max / self.scale): self._maxVal = new_max / self.scale + elif self._resizingRight: + new_min = self._minVal * self.scale - diff * 1 / self._step + if (new_min / self.scale) < self._maxVal: self._minVal = new_min / self.scale + + self.repaint() + self.changed_event() + + self._lastMouseY = event.x() + + +class ControlBoundingSlider(ControlBase): + def __init__(self, *args, **kwargs): + self._horizontal = kwargs.get('horizontal', True) + self._show_spinboxes = kwargs.get('show_spinboxes', True) + ControlBase.__init__(self, *args, **kwargs) + + self.min = kwargs.get('min', kwargs.get('minimum', 0)) + self.max = kwargs.get('max', kwargs.get('maximum', 100)) + self.value = kwargs.get('default', [10,20]) + self.__update() + + def init_form(self): + self._boundingbox = GaugeWidgetHorizontal() if self._horizontal else GaugeWidgetVertical() + self._boundingbox.changed_event = self.__update + + if self._show_spinboxes: + self._form = hwidget = QWidget() + if self._horizontal: + hlayout = QHBoxLayout() + else: + hlayout = QVBoxLayout() + + + if _api.USED_API == _api.QT_API_PYQT5: + hlayout.setContentsMargins(0,0,0,0) + elif _api.USED_API == _api.QT_API_PYQT4: + hlayout.setMargin(0) + + + hwidget.setLayout(hlayout) + self._min_spinbox = QSpinBox() + self._min_spinbox.valueChanged.connect(self.__min_spinbox_changed) + self._min_spinbox.setMaximumWidth(95) + + self._max_spinbox = QSpinBox() + self._max_spinbox.valueChanged.connect(self.__max_spinbox_changed) + self._max_spinbox.setMaximumWidth(95) + + if self._horizontal: + hlayout.addWidget(self._min_spinbox) + else: + hlayout.addWidget(self._max_spinbox) + hlayout.addWidget(self._boundingbox) + if self._horizontal: + hlayout.addWidget(self._max_spinbox) + else: + hlayout.addWidget(self._min_spinbox) + + else: + self._form = self._boundingbox + + super(ControlBoundingSlider, self).init_form() + + def __max_spinbox_changed(self, value): + if value < self._boundingbox._minVal: return + if hasattr(self, '_is_updating_spinboxes'): return + self.scale = self.__find_scale_factor(value) + self._boundingbox._maxVal = value + self._boundingbox.repaint() + self.changed_event() + + def __min_spinbox_changed(self, value): + if value > self._boundingbox._maxVal: return + if hasattr(self, '_is_updating_spinboxes'): return + self.scale = self.__find_scale_factor(value) + self._boundingbox._minVal = value + self._boundingbox.repaint() + self.changed_event() + + def __update(self): + l, h = self._boundingbox._minVal, self._boundingbox._maxVal + self._is_updating_spinboxes = True + self._min_spinbox.setValue(l) + self._max_spinbox.setValue(h) + del self._is_updating_spinboxes + self.changed_event() + + def changed_event(self): + pass + + def __find_scale_factor(self, value): + scale = 1.0 + new_value = value + while abs(new_value) < 0.0: + scale *= 10.0 + new_value = value * scale + return scale + + def load_form(self, data, path=None): + """ + Load a value from the dict variable + @param data: dictionary with the value of the Control + """ + + self.convert_2_int = data.get('convert-int', self.convert_2_int) + self.scale = data.get('scale', self.scale) + self.max = data.get('max', self.max) + self.min = data.get('min', self.min) + self.value = data.get('value', self.value) + + + def save_form(self, data, path=None): + """ + Save a value to dict variable + @param data: dictionary with to where the value of the Control will be added + """ + data['value'] = self.value + data['max'] = self.max + data['min'] = self.min + data['scale'] = self.scale + data['convert-int'] = self.convert_2_int + return data + + ########################################################################## + ############ Properties ################################################## + ########################################################################## + + @property + def value(self): + return self._boundingbox._minVal, self._boundingbox._maxVal + + @value.setter + def value(self, value): + self.scale = self.__find_scale_factor(value[0]) + self._boundingbox._minVal, self._boundingbox._maxVal = value[0], value[1] + if hasattr(self, '_min_spinbox'): self._min_spinbox.setValue(value[0]) + if hasattr(self, '_max_spinbox'): self._max_spinbox.setValue(value[1]) + ControlBase.value.fset(self, value) + self._boundingbox.repaint() + + @property + def min(self): + return self._boundingbox._lower + + @min.setter + def min(self, value): + self._boundingbox._lower = value + self._boundingbox.repaint() + if hasattr(self, '_min_spinbox'): self._min_spinbox.setMinimum(value) + if hasattr(self, '_max_spinbox'): self._max_spinbox.setMinimum(value) + + @property + def max(self): + return self._boundingbox._higher + + @max.setter + def max(self, value): + self._boundingbox._higher = value + self._boundingbox.repaint() + if hasattr(self, '_min_spinbox'): self._min_spinbox.setMaximum(value) + if hasattr(self, '_max_spinbox'): self._max_spinbox.setMaximum(value) + + @property + def scale(self): + return self._boundingbox.scale + + @scale.setter + def scale(self, value): + self._boundingbox.scale = value + + @property + def convert_2_int(self): + return not self._boundingbox._use_float + + @convert_2_int.setter + def convert_2_int(self, value): + self._boundingbox._use_float = not value diff --git a/pyforms/gui/Controls/ControlButton.py b/pyforms/gui/controls/ControlButton.py old mode 100755 new mode 100644 similarity index 71% rename from pyforms/gui/Controls/ControlButton.py rename to pyforms/gui/controls/ControlButton.py index 29aa486..a16ec7b --- a/pyforms/gui/Controls/ControlButton.py +++ b/pyforms/gui/controls/ControlButton.py @@ -3,26 +3,25 @@ -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QPushButton - from PyQt5.QtWidgets import QSizePolicy - from PyQt5.QtGui import QIcon - from PyQt5 import uic -else: - from PyQt4.QtGui import QPushButton - from PyQt4.QtGui import QSizePolicy - from PyQt4.QtGui import QIcon - from PyQt4 import uic +from AnyQt import uic +from AnyQt.QtWidgets import QPushButton, QSizePolicy +from AnyQt.QtGui import QIcon -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase class ControlButton(ControlBase): - def __init__(self, label='', default=None, checkable=False, helptext=None): - self._checkable = checkable - super(ControlButton, self).__init__(label=label, default=default, helptext=helptext) + def __init__(self, *args, **kwargs): + self._checkable = kwargs.get('checkable', False) + super(ControlButton, self).__init__(*args, **kwargs) + + default = kwargs.get('default', None) + if default: self.value = default + + icon = kwargs.get('icon', None) + if icon: self.icon = icon def init_form(self): self._form = QPushButton() diff --git a/pyforms/gui/Controls/ControlCheckBox.py b/pyforms/gui/controls/ControlCheckBox.py old mode 100755 new mode 100644 similarity index 78% rename from pyforms/gui/Controls/ControlCheckBox.py rename to pyforms/gui/controls/ControlCheckBox.py index 8568b16..df62a48 --- a/pyforms/gui/Controls/ControlCheckBox.py +++ b/pyforms/gui/controls/ControlCheckBox.py @@ -1,19 +1,13 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf -import pyforms.utils.tools as tools - -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic - from PyQt5 import QtCore +from AnyQt import uic, QtCore -else: - from PyQt4 import uic - from PyQt4 import QtCore -from pyforms.gui.Controls.ControlBase import ControlBase +import pyforms.utils.tools as tools +from pyforms.gui.controls.ControlBase import ControlBase class ControlCheckBox(ControlBase): @@ -35,7 +29,7 @@ def __checkedToggle(self): def load_form(self, data, path=None): if 'value' in data: - self._form.checkBox.setChecked(data['value'] == 'True') + self._form.checkBox.setChecked(data['value'] == 'True' or data['value'] == True) def save_form(self, data, path=None): data['value'] = str(self._form.checkBox.isChecked()) diff --git a/pyforms/gui/controls/ControlCheckBoxList.py b/pyforms/gui/controls/ControlCheckBoxList.py new file mode 100644 index 0000000..8cfa367 --- /dev/null +++ b/pyforms/gui/controls/ControlCheckBoxList.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from pyforms.utils.settings_manager import conf + +import pyforms.utils.tools as tools + +from AnyQt import uic, QtCore +from AnyQt.QtWidgets import QListWidgetItem + +from pyforms.gui.controls.ControlBase import ControlBase + + +class ControlCheckBoxList(ControlBase): + + def init_form(self): + control_path = tools.getFileInSameDirectory(__file__, "tree.ui") + self._form = uic.loadUi(control_path) + + self._form.label.setText(self._label) + + self._form.listWidget.itemChanged.connect(self.item_changed) + + self._form.listWidget.itemSelectionChanged.connect(self.__itemSelectionChanged) + + if self.help: self.form.setToolTip(self.help) + + + + + def save_form(self, data={}, path=None): + results = [] + + for row in range(self.count): + item = self._form.listWidget.item(row) + if item != None and item.checkState() == QtCore.Qt.Checked: + results.append(item.text()) + data['selected'] = results + + return data + + def load_form(self, data, path=None): + results = data['selected'] + + for row in range(self.count): + item = self._form.listWidget.item(row) + if item != None and item.text() in results: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + + + + def item_changed(self, item): + self.changed_event() + + def __add__(self, val): + if isinstance(val, (tuple, list)): + item = QListWidgetItem(str(val[0])) + item.value = val[0] + if val[1]: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + else: + item = QListWidgetItem(str(val)) + item.value = val + + self._form.listWidget.addItem(item) + return self + + def __sub__(self, other): + if isinstance(other, int): + if other < 0: + indexToRemove = self._form.listWidget.currentRow() + else: + indexToRemove = other + self._form.listWidget.takeItem(indexToRemove) + else: + for row in range(self.count): + item = self._form.listWidget.item(row) + if item != None and hasattr(item, 'value') and item.value == other: + self._form.listWidget.takeItem(row) + return self + + def clear(self): + self._form.listWidget.clear() + + def refresh(self): + for row in range(self.count): + item = self._form.listWidget.item(row) + if hasattr(item, 'value'): item.setText(str(item.value)) + + ############################################################################ + ############ Events ######################################################## + ############################################################################ + + def __itemSelectionChanged(self): + self.selection_changed_event() + + def selection_changed_event(self): + pass + + ############################################################################ + ############ Properties #################################################### + ############################################################################ + + @property + def count(self): + return self._form.listWidget.count() + + @property + def checked_indexes(self): + results = [] + for row in range(self.count): + item = self._form.listWidget.item(row) + if item != None and item.checkState() == QtCore.Qt.Checked: results.append(row) + return results + + @property + def value(self): + results = [] + for row in range(self.count): + item = self._form.listWidget.item(row) + if item != None and item.checkState() == QtCore.Qt.Checked: + results.append(item.value if hasattr(item, 'value') else str(item.text())) + return results + + @value.setter + def value(self, value): + self.clear() + for row in value: self += row + self.changed_event() + + @property + def selected_row_index(self): + return self.form.listWidget.currentRow() + + @property + def items(self): + results = [] + for row in range(self.count): + item = self._form.listWidget.item(row) + results.append( + [item.value if hasattr(item, 'value') else str(item.text()), item.checkState() == QtCore.Qt.Checked]) + return results diff --git a/pyforms/gui/Controls/ControlCodeEditor.py b/pyforms/gui/controls/ControlCodeEditor.py old mode 100755 new mode 100644 similarity index 76% rename from pyforms/gui/Controls/ControlCodeEditor.py rename to pyforms/gui/controls/ControlCodeEditor.py index 1a2c2a1..2b2c883 --- a/pyforms/gui/Controls/ControlCodeEditor.py +++ b/pyforms/gui/controls/ControlCodeEditor.py @@ -3,22 +3,21 @@ import logging -from pysettings import conf +from AnyQt import _api +from pyforms.utils.settings_manager import conf -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase import pyforms.utils.tools as tools -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QApplication - from PyQt5.QtGui import QFontMetrics, QColor, QIcon, QFont - from PyQt5.Qsci import QsciScintilla, QsciLexerPython - from PyQt5 import uic - from PyQt5 import QtCore -else: - from PyQt4.QtGui import QApplication, QFontMetrics, QColor, QIcon, QFont - from PyQt4.Qsci import QsciScintilla, QsciLexerPython - from PyQt4 import uic - from PyQt4 import QtCore +from AnyQt import QtCore, uic +from AnyQt.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSpinBox +from AnyQt.QtGui import QFontMetrics, QColor, QIcon, QFont + +if _api.USED_API == _api.QT_API_PYQT5: + from PyQt5.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs +elif _api.USED_API == _api.QT_API_PYQT4: + from PyQt4.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs + logger = logging.getLogger(__name__) @@ -30,18 +29,19 @@ class ControlCodeEditor(ControlBase): ARROW_MARKER_NUM = 8 - def __init__(self, label='', default='', helptext='', read_only=True): + def __init__(self, *args, **kwargs): """ :param label: :param default: :param helptext: """ - self._read_only = read_only - - super(ControlCodeEditor, self).__init__(label, default, helptext) - + self._read_only = kwargs.get('readonly', False) self._changed_func = None + super(ControlCodeEditor, self).__init__(*args, **kwargs) + + self.discart_event = kwargs.get('discart_event', self.discart_event) + def init_form(self): """ @@ -51,14 +51,19 @@ def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "code_editor.ui") self._form = uic.loadUi(control_path) - self._code_editor = self._form.code_editor + self._code_editor = self._form.code_editor self._save_button = self._form.save_button + self._discart_button = self._form.discart_button + self._save_button.clicked[bool].connect(self.on_save_changes) + self._discart_button.clicked[bool].connect(self.on_discart_changes) - if not self._read_only: + + if self._read_only: self._code_editor.setReadOnly(True) self._save_button.setVisible(False) + self._discart_button.setVisible(False) self.form.font_size.addItem('9') self.form.font_size.addItem('10') @@ -75,6 +80,7 @@ def init_form(self): self.form.font_size.currentIndexChanged.connect(self.__font_size_index_changed) self.form.save_button.setIcon(QIcon(conf.PYFORMS_ICON_CODEEDITOR_SAVE)) + self.form.discart_button.setIcon(QIcon(conf.PYFORMS_ICON_CODEEDITOR_DISCART)) self.lexer = QsciLexerPython @@ -141,6 +147,18 @@ def _load_code_editor_settings(self): # Don't want to see the horizontal scrollbar at all # Use raw message to Scintilla here (all messages are documented here: http://www.scintilla.org/ScintillaDoc.html) self._code_editor.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) + + self._lexer_obj = lexer + self.qsci_api = QsciAPIs(self._lexer_obj) + ## Add autocompletion strings + self.qsci_api.add("aLongString") + self.qsci_api.add("aLongerString") + self.qsci_api.add("aDifferentString") + self.qsci_api.add("sOmethingElse") + ## Compile the api for use in the lexer + self.qsci_api.prepare() + self._code_editor.setAutoCompletionThreshold(1) + self._code_editor.setAutoCompletionSource(QsciScintilla.AcsAll) # not too small # self._code_editor.setMinimumSize(600, 450) @@ -168,15 +186,30 @@ def on_modification_changed(self): """ On modification change, re-enable save button """ - if self._changed_func: self._save_button.setEnabled(True) + if self._changed_func: + self._save_button.setEnabled(True) + self._discart_button.setEnabled(True) def on_save_changes(self): """ On button save clicked, save changes made on the code editor to file """ - if self.changed_event() and self._changed_func: + if self.changed_event(): self._code_editor.setModified(False) self._save_button.setEnabled(False) + self._discart_button.setEnabled(False) + + def on_discart_changes(self): + + if self.discart_event(): + self._code_editor.setModified(False) + self._save_button.setEnabled(False) + self._discart_button.setEnabled(False) + + def discart_event(self): + + return True + def _key_pressed(self, event): """ @@ -223,11 +256,11 @@ def value(self): @value.setter def value(self, value): - ControlBase.value.fset(self, value) - if len(value) > 0: + if value is not None: self._code_editor.setText(str(value)) self._code_editor.setModified(False) self._save_button.setEnabled(False) + self._discart_button.setEnabled(False) @property def changed_event(self): diff --git a/pyforms/gui/Controls/ControlCombo.py b/pyforms/gui/controls/ControlCombo.py old mode 100755 new mode 100644 similarity index 64% rename from pyforms/gui/Controls/ControlCombo.py rename to pyforms/gui/controls/ControlCombo.py index df9860a..254edd0 --- a/pyforms/gui/Controls/ControlCombo.py +++ b/pyforms/gui/controls/ControlCombo.py @@ -4,51 +4,71 @@ import pyforms.utils.tools as tools -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic -else: - from PyQt4 import uic +from AnyQt.QtWidgets import QLabel, QWidget, QComboBox, QHBoxLayout, QSizePolicy -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase -class ControlCombo(ControlBase): +class ControlCombo(ControlBase, QWidget): """This class represents a wrapper to the combo box""" + def __init__(self, *args, **kwargs): + QWidget.__init__(self) + ControlBase.__init__(self, *args, **kwargs) + ########################################################################## ############ Functions ################################################### ########################################################################## def init_form(self): - control_path = tools.getFileInSameDirectory(__file__, "comboInput.ui") - self._form = uic.loadUi(control_path) - - self._form.comboLabel.setAccessibleName('ControlCombo-label') - self._form.comboBox.currentIndexChanged.connect(self._currentIndexChanged) - self._form.comboBox.activated.connect(self._activated) - self._form.comboBox.highlighted.connect(self._highlighted) - self._form.comboBox.editTextChanged.connect(self._editTextChanged) + self._layout = QHBoxLayout() + self._combo = QComboBox(self.form) + + if self._label is not None: + self._combolabel = QLabel(self.form) + self._layout.addWidget( self._combolabel ) + self._combolabel.setAccessibleName('ControlCombo-label') + self.label = self._label + else: + self._combolabel = None + + self._layout.addWidget( self._combo ) + self.form.setLayout( self._layout ) + + + self._combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self._layout.setContentsMargins(0,0,0,0) + self.form.setContentsMargins(0,0,0,0) + self.form.setMinimumHeight(38) + self.form.setMaximumHeight(38) + self.form.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + + self._combo.currentIndexChanged.connect(self._currentIndexChanged) + self._combo.activated.connect(self._activated) + self._combo.highlighted.connect(self._highlighted) + self._combo.editTextChanged.connect(self._editTextChanged) + self._items = {} self._addingItem = False - self.label = self._label + def clear(self): self._items = {} self._value = None - self._form.comboBox.clear() + self._combo.clear() def add_item(self, label, value=None): self._addingItem = True if value is not None: if not (value in self._items.values()): - self._form.comboBox.addItem(label) + self._combo.addItem(label) else: if not (label in self._items.keys()): - self._form.comboBox.addItem(label) + self._combo.addItem(label) firstValue = False if self._items == {}: @@ -77,10 +97,23 @@ def get_item_index_by_name(self, item_name): :param item_name: item name in combo box :type item_name: string """ - return self._form.comboBox.findText(item_name) + return self._combo.findText(item_name) def count(self): - return self._form.comboBox.count() + return self._combo.count() + + def show(self): + """ + Show the control + """ + QWidget.show(self) + + def hide(self): + """ + Hide the control + """ + QWidget.hide(self) + ########################################################################## ############ Events ###################################################### @@ -110,13 +143,21 @@ def edittext_changed_event(self, text): ############ PROPERTIES ################################################## ########################################################################## + @property + def form(self): + return self + @property def current_index(self): - return self._form.comboBox.currentIndex() + return self._combo.currentIndex() @current_index.setter def current_index(self, value): - self._form.comboBox.setCurrentIndex(value) + self._combo.setCurrentIndex(value) + + @property + def values(self): + return self._items.values() @property def items(self): @@ -130,15 +171,15 @@ def value(self): def value(self, value): for key, val in self.items: if value == val: - index = self._form.comboBox.findText(key) - self._form.comboBox.setCurrentIndex(index) + index = self._combo.findText(key) + self._combo.setCurrentIndex(index) if self._value != value: self.changed_event() self._value = val @property def text(self): - return str(self._form.comboBox.currentText()) + return str(self._combo.currentText()) @text.setter def text(self, value): @@ -149,7 +190,10 @@ def text(self, value): @property def label(self): - return self._form.comboLabel.text() + if self._combolabel: + return self._combolabel.text() + else: + return None @label.setter def label(self, value): @@ -157,7 +201,9 @@ def label(self, value): Label of the control, if applies @type value: string """ - self._form.comboLabel.setText(value) + if self._combolabel: + self._combolabel.setText(value) + ########################################################################## ############ Private functions ########################################### @@ -165,7 +211,7 @@ def label(self, value): def _activated(self, index): if not self._addingItem: - item = self._form.comboBox.currentText() + item = self._combo.currentText() if len(item) >= 1: ControlBase.value.fset(self, self._items[str(item)]) self.activated_event(index) @@ -182,7 +228,7 @@ def _editTextChanged(self, text): def _currentIndexChanged(self, index): if not self._addingItem: - item = self._form.comboBox.currentText() + item = self._combo.currentText() if len(item) >= 1: ControlBase.value.fset(self, self._items[str(item)]) self.current_index_changed_event(index) diff --git a/pyforms/gui/Controls/ControlDir.py b/pyforms/gui/controls/ControlDir.py old mode 100755 new mode 100644 similarity index 72% rename from pyforms/gui/Controls/ControlDir.py rename to pyforms/gui/controls/ControlDir.py index a4df416..8091a12 --- a/pyforms/gui/Controls/ControlDir.py +++ b/pyforms/gui/controls/ControlDir.py @@ -1,26 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf -from pyforms.gui.Controls.ControlText import ControlText +from pyforms.gui.controls.ControlText import ControlText import pyforms.utils.tools as tools -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QFileDialog - from PyQt5 import uic -else: - from PyQt4.QtGui import QFileDialog - from PyQt4 import uic +from AnyQt import uic +from AnyQt.QtWidgets import QFileDialog class ControlDir(ControlText): + def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "fileInput.ui") self._form = uic.loadUi(control_path) self._form.label.setText(self._label) + self._form.lineEdit.setText(self._value) def click(self): value = str(QFileDialog.getExistingDirectory(self._form, 'Choose a directory', self.value)) diff --git a/pyforms/gui/Controls/ControlDockWidget.py b/pyforms/gui/controls/ControlDockWidget.py similarity index 74% rename from pyforms/gui/Controls/ControlDockWidget.py rename to pyforms/gui/controls/ControlDockWidget.py index fd7fb35..35c2b48 100644 --- a/pyforms/gui/Controls/ControlDockWidget.py +++ b/pyforms/gui/controls/ControlDockWidget.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pyforms.gui.Controls.ControlEmptyWidget import ControlEmptyWidget +from pyforms.gui.controls.ControlEmptyWidget import ControlEmptyWidget class ControlDockWidget(ControlEmptyWidget): @@ -11,11 +11,13 @@ class ControlDockWidget(ControlEmptyWidget): SIDE_BOTTOM = 'bottom' SIDE_DETACHED = 'detached' - def __init__(self, label='', default=None, side='left', order=0, margin=0): - ControlEmptyWidget.__init__(self, label) - self.side = side - self.order = order - self.margin = margin + def __init__(self, *args, **kwargs): + ControlEmptyWidget.__init__(self, *args, **kwargs) + self.side = kwargs.get('side', 'left') + self.order = kwargs.get('order',0) + self.margin = kwargs.get('margin', 0) + + default = kwargs.get('default', None) if default is not None: self.value = default self._show = True diff --git a/pyforms/gui/Controls/ControlEmptyWidget.py b/pyforms/gui/controls/ControlEmptyWidget.py old mode 100755 new mode 100644 similarity index 72% rename from pyforms/gui/Controls/ControlEmptyWidget.py rename to pyforms/gui/controls/ControlEmptyWidget.py index a144a5a..7fb92c1 --- a/pyforms/gui/Controls/ControlEmptyWidget.py +++ b/pyforms/gui/controls/ControlEmptyWidget.py @@ -1,34 +1,29 @@ #!/usr/bifn/python # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget - from PyQt5.QtWidgets import QVBoxLayout -else: - from PyQt4.QtGui import QWidget - from PyQt4.QtGui import QVBoxLayout - -from pyforms.gui.Controls.ControlBase import ControlBase -from pyforms.gui.BaseWidget import BaseWidget +from AnyQt import _api +from AnyQt.QtWidgets import QWidget, QVBoxLayout +from pyforms.gui.controls.ControlBase import ControlBase +from pyforms.gui.basewidget import BaseWidget class ControlEmptyWidget(ControlBase, QWidget): - def __init__(self, label='', default=None): + def __init__(self, *args, **kwargs): QWidget.__init__(self) layout = QVBoxLayout() - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: layout.setContentsMargins(0,0,0,0) - else: + elif _api.USED_API == _api.QT_API_PYQT4: layout.setMargin(0) self.form.setLayout(layout) - ControlBase.__init__(self, label) - self.value = default + ControlBase.__init__(self, *args, **kwargs) + self.value = kwargs.get('default', None) def init_form(self): pass @@ -55,9 +50,16 @@ def value(self, value): if isinstance(value, list): for w in value: self.form.layout().addWidget(w.form) + if not hasattr(w, 'about_to_be_shown'): + w.about_to_be_shown = True + w.show() + del w.about_to_be_shown else: self.form.layout().addWidget(value.form) - + if not hasattr(value, 'about_to_be_shown'): + value.about_to_be_shown = True + value.show() + del value.about_to_be_shown # The init_form should be called only for the BaseWidget if isinstance(value, BaseWidget) and not value._formLoaded: diff --git a/pyforms/gui/Controls/ControlFile.py b/pyforms/gui/controls/ControlFile.py old mode 100755 new mode 100644 similarity index 53% rename from pyforms/gui/Controls/ControlFile.py rename to pyforms/gui/controls/ControlFile.py index 31039d2..f892f81 --- a/pyforms/gui/Controls/ControlFile.py +++ b/pyforms/gui/controls/ControlFile.py @@ -1,22 +1,23 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -from pyforms.gui.Controls.ControlText import ControlText +from pyforms.utils.settings_manager import conf +from pyforms.gui.controls.ControlText import ControlText import pyforms.utils.tools as tools -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QFileDialog - from PyQt5 import uic -else: - from PyQt4.QtGui import QFileDialog - from PyQt4 import uic +from AnyQt import uic, _api +from AnyQt.QtWidgets import QFileDialog class ControlFile(ControlText): + + def __init__(self, *args, **kwargs): + + super(ControlFile, self).__init__(*args, **kwargs) + self.use_save_dialog = kwargs.get('use_save_dialog', False) + def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "fileInput.ui") self._form = uic.loadUi(control_path) @@ -30,11 +31,16 @@ def finishEditing(self): self.changed_event() def click(self): - value = QFileDialog.getOpenFileName(self.parent, self._label, self.value) + + if self.use_save_dialog: + value, _ = QFileDialog.getSaveFileName(self.parent, self._label, self.value) + else: + value = QFileDialog.getOpenFileName(self.parent, self._label, self.value) + - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: value = value[0] - else: + elif _api.USED_API == _api.QT_API_PYQT4: value = str(value) if value and len(value)>0: self.value = value diff --git a/pyforms/gui/Controls/ControlFilesTree.py b/pyforms/gui/controls/ControlFilesTree.py old mode 100755 new mode 100644 similarity index 59% rename from pyforms/gui/Controls/ControlFilesTree.py rename to pyforms/gui/controls/ControlFilesTree.py index 3f5f899..1e6fa3e --- a/pyforms/gui/Controls/ControlFilesTree.py +++ b/pyforms/gui/controls/ControlFilesTree.py @@ -2,20 +2,12 @@ # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf -from pyforms.gui.Controls.ControlBase import ControlBase - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QTreeView - from PyQt5.QtWidgets import QFileSystemModel - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QTreeView - from PyQt4.QtGui import QFileSystemModel - from PyQt4 import QtCore +from pyforms.gui.controls.ControlBase import ControlBase +from AnyQt import QtCore +from AnyQt.QtWidgets import QTreeView, QFileSystemModel class ControlFilesTree(ControlBase): def init_form(self): diff --git a/pyforms/gui/Controls/ControlImage.py b/pyforms/gui/controls/ControlImage.py old mode 100755 new mode 100644 similarity index 69% rename from pyforms/gui/Controls/ControlImage.py rename to pyforms/gui/controls/ControlImage.py index a7a93da..f669b4b --- a/pyforms/gui/Controls/ControlImage.py +++ b/pyforms/gui/controls/ControlImage.py @@ -3,11 +3,11 @@ import logging -from pysettings import conf +from pyforms.utils.settings_manager import conf logger = logging.getLogger(__name__) -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase from pyforms.utils import tools try: @@ -15,34 +15,33 @@ except: logger.debug("OpenCV not available") -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic +import OpenGL.GL as GL +import OpenGL.GLU as GLU + +from AnyQt import QtCore, QtOpenGL, uic +from AnyQt.QtWidgets import QWidget +from AnyQt import _api + +if _api.USED_API == _api.QT_API_PYQT5: try: - import OpenGL.GL as GL - import OpenGL.GLU as GLU - from PyQt5.QtOpenGL import QGLWidget + from AnyQt.QtOpenGL import QGLWidget except: logger.debug("No OpenGL library available") import platform - if platform.system() == 'Darwin': - from pyforms.gui.Controls.ControlPlayer.VideoQt5GLWidget import VideoQt5GLWidget as VideoGLWidget + from pyforms.gui.controls.control_player.VideoQt5GLWidget import VideoQt5GLWidget as VideoGLWidget else: - from pyforms.gui.Controls.ControlPlayer.VideoGLWidget import VideoGLWidget - -else: - from PyQt4 import uic + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget +elif _api.USED_API == _api.QT_API_PYQT4: try: - import OpenGL.GL as GL - import OpenGL.GLU as GLU from PyQt4.QtOpenGL import QGLWidget except: logger.debug("No OpenGL library available") - from pyforms.gui.Controls.ControlPlayer.VideoGLWidget import VideoGLWidget + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget import numpy as np diff --git a/pyforms/gui/Controls/ControlLabel.py b/pyforms/gui/controls/ControlLabel.py old mode 100755 new mode 100644 similarity index 75% rename from pyforms/gui/Controls/ControlLabel.py rename to pyforms/gui/controls/ControlLabel.py index 3d30273..669877b --- a/pyforms/gui/Controls/ControlLabel.py +++ b/pyforms/gui/controls/ControlLabel.py @@ -1,17 +1,14 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" pyforms.gui.Controls.ControlLabel""" +""" pyforms.gui.controls.ControlLabel""" import pyforms.utils.tools as tools -from pysettings import conf -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic -else: - from PyQt4 import uic +from pyforms.utils.settings_manager import conf +from AnyQt import uic -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase __author__ = "Carlos Mão de Ferro" __copyright__ = "" @@ -25,9 +22,6 @@ class ControlLabel(ControlBase): - def __init__(self, label=''): - super(ControlLabel, self).__init__(label=label, default=label) - def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "label.ui") self._form = uic.loadUi(control_path) diff --git a/pyforms/gui/controls/ControlList.py b/pyforms/gui/controls/ControlList.py new file mode 100644 index 0000000..0968a42 --- /dev/null +++ b/pyforms/gui/controls/ControlList.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" pyforms.gui.controls.ControlList + +""" +import logging +import os + +from pyforms.utils.settings_manager import conf + +from AnyQt import QtCore, uic +from AnyQt.QtWidgets import QTableWidgetItem, QWidget, QAbstractItemView +from AnyQt.QtGui import QIcon + +from pyforms.gui.basewidget import BaseWidget +from pyforms.gui.controls.ControlBase import ControlBase + +logger = logging.getLogger(__name__) + + +class ControlList(ControlBase, QWidget): + """ This class represents a wrapper to the table widget + It allows to implement a list view + """ + + CELL_VALUE_BEFORE_CHANGE = None # store value when cell is double clicked + + def __init__(self, *args, **kwargs): + QWidget.__init__(self) + + self._plusFunction = kwargs.get('add_function', None) + self._minusFunction = kwargs.get('remove_function', None) + ControlBase.__init__(self, *args, **kwargs) + + self.autoscroll = kwargs.get('autoscroll', True) + self.resizecolumns = kwargs.get('resizecolumns', True) + self.select_entire_row = kwargs.get('select_entire_row', False) + self.horizontal_headers = kwargs.get('horizontal_headers', None) + + ########################################################################## + ############ FUNCTIONS ################################################### + ########################################################################## + + def init_form(self): + plusFunction = self._plusFunction + minusFunction = self._minusFunction + + # Get the current path of the file + rootPath = os.path.dirname(__file__) + # Load the UI for the self instance + uic.loadUi(os.path.join(rootPath, "list.ui"), self) + + self.label = self._label + + self.tableWidget.currentCellChanged.connect(self.tableWidgetCellChanged) + self.tableWidget.currentItemChanged.connect(self.tableWidgetItemChanged) + self.tableWidget.itemSelectionChanged.connect(self.tableWidgetItemSelectionChanged) + self.tableWidget.cellDoubleClicked.connect(self.tableWidgetCellDoubleClicked) + self.tableWidget.model().dataChanged.connect(self._dataChangedEvent) + + + self.tableWidget.horizontalHeader().setVisible(False) + + if plusFunction is None and minusFunction is None: + self.plusButton.hide() + self.minusButton.hide() + elif plusFunction is None: + self.plusButton.hide() + self.minusButton.pressed.connect(minusFunction) + elif minusFunction is None: + self.minusButton.hide() + self.plusButton.pressed.connect(plusFunction) + else: + self.plusButton.pressed.connect(plusFunction) + self.minusButton.pressed.connect(minusFunction) + + + + def __repr__(self): + return "ControlList " + str(self._value) + + def clear(self, headers=False): + + for row in range(self.rows_count): + columns = [] + for column in range(self.columns_count): + v = self.get_value(column, row) + if isinstance(v, BaseWidget): + v.destroy() + + if headers: + self.tableWidget.clear() + self.tableWidget.setColumnCount(3) + self.tableWidget.setRowCount(0) + else: + self.tableWidget.clearContents() + self.tableWidget.setRowCount(0) + + def save_form(self, data, path=None): + if self.value: + rows = [] + for row in range(self.rows_count): + columns = [] + for column in range(self.columns_count): + v = self.get_value(column, row) + if isinstance(v, BaseWidget): + columns.append(v.save_form({}, path)) + else: + columns.append(str(v)) + rows.append(columns) + data['value'] = rows + return data + + def load_form(self, data, path=None): + if self.value: + rows = data['value'] + for row in range(len(rows)): + for column in range(len(rows[row])): + v = self.get_value(column, row) + if isinstance(v, BaseWidget): + v.load_form(rows[row][column], path) + else: + self.set_value(column, row, rows[row][column]) + elif 'value' in data.keys(): + self.value = data['value'] + + def __add__(self, other): + row_index = self.tableWidget.rowCount() + + self.tableWidget.insertRow(row_index) + + #increase the number of columns if necessary #### + if self.tableWidget.currentColumn() < len(other): + self.tableWidget.setColumnCount(len(other)) + ################################################# + + for column, e in enumerate(other): self.set_value(column, row_index, e) + + #Auto resize the columns if the flag is activated ################ + if self.resizecolumns: + self.tableWidget.resizeColumnsToContents() + ################################################################## + + # auto scroll the list to the new inserted item ################## + if self.autoscroll: + self.tableWidget.scrollToItem( self.get_cell(0,row_index) ) + ################################################################## + return self + + def __sub__(self, other): + + if isinstance(other, int): + if other < 0: + indexToRemove = self.tableWidget.currentRow() + else: + indexToRemove = other + + self.tableWidget.removeRow(indexToRemove) + return self + + def set_value(self, column, row, value): + if isinstance(value, QWidget): + self.tableWidget.setCellWidget(row, column, value) + value.show() + self.tableWidget.setRowHeight(row, value.height()) + elif isinstance(value, ControlBase): + self.tableWidget.setCellWidget(row, column, value.form) + value.show() + self.tableWidget.setRowHeight(row, value.form.height()) + else: + args = [value] if not hasattr(value, 'icon') else [QIcon(value.icon), value] + item = QTableWidgetItem() + item.setData(QtCore.Qt.EditRole, *args) + self.tableWidget.setItem(row, column, item) + + def get_value(self, column, row): + try: + return str(self.tableWidget.item(row, column).text()) + except AttributeError as err: + return self.tableWidget.cellWidget(row, column) + except AttributeError as err: + return '' + + def resize_rows_contents(self): + self.tableWidget.resizeRowsToContents() + + def get_currentrow_value(self): + currentRow = self.tableWidget.currentRow() + if not currentRow < 0: + return self.value[currentRow] + else: + return [] + + def get_cell(self, column, row): + return self.tableWidget.item(row, column) + + def set_sorting_enabled(self, value): + """ + Enable or disable columns sorting + + :param bool value: True to enable sorting, False otherwise + """ + self.tableWidget.setSortingEnabled(value) + + ########################################################################## + ############ EVENTS ###################################################### + ########################################################################## + + def data_changed_event(self, row, col, item): + pass + + def item_selection_changed_event(self): + pass + + def current_cell_changed_event(self, next_row, next_col, previous_row, previous_col): + pass + + def current_item_changed_event(self, current, previous): + pass + + def cell_double_clicked_event(self, row, column): + pass + + ########################################################################## + ############ PROPERTIES ################################################## + ########################################################################## + + + @property + def horizontal_headers(self): + return self._horizontalHeaders + + @horizontal_headers.setter + def horizontal_headers(self, horizontal_headers): + """Set horizontal headers in the table list.""" + if horizontal_headers is None: + self._horizontalHeaders = horizontal_headers + self.tableWidget.horizontalHeader().setVisible(False) + return + + + self._horizontalHeaders = horizontal_headers + + self.tableWidget.setColumnCount(len(horizontal_headers)) + self.tableWidget.horizontalHeader().setVisible(True) + + for idx, header in enumerate(horizontal_headers): + item = QTableWidgetItem() + item.setText(header) + self.tableWidget.setHorizontalHeaderItem(idx, item) + + @property + def word_wrap(self): + return self.tableWidget.wordWrap() + + @word_wrap.setter + def word_wrap(self, value): + self.tableWidget.setWordWrap(value) + + @property + def readonly(self): + return self.tableWidget.editTriggers() + + @readonly.setter + def readonly(self, value): + if value: + self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) + else: + self.tableWidget.setEditTriggers(QAbstractItemView.AllEditTriggers) + + @property + def select_entire_row(self): + return self.tableWidget.selectionBehavior() + + @select_entire_row.setter + def select_entire_row(self, value): + if value: + self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows) + else: + self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectItems) + + @property + def rows_count(self): + return self.tableWidget.rowCount() + + @property + def columns_count(self): + return self.tableWidget.columnCount() + + def __len__(self): + return self.rows_count + + @property + def value(self): + if hasattr(self, 'tableWidget'): + results = [] + for row in range(self.tableWidget.rowCount()): + r = [] + for col in range(self.tableWidget.columnCount()): + try: + r.append(self.get_value(col, row)) + except Exception as err: + logger.debug(str(err)) + r.append("") + results.append(r) + return results + return self._value + + @value.setter + def value(self, value): + self.clear() + for row in value: self += row + + # TODO: implement += on self.value? I want to add a list of tuples to + # self.value + + @property + def selected_rows_indexes(self): + result = [] + for index in self.tableWidget.selectedIndexes(): + result.append(index.row()) + return list(set(result)) + + @property + def selected_row_index(self): + indexes = self.selected_rows_indexes + if len(indexes) > 0: + return indexes[0] + else: + return None + + @property + def label(self): + return self.labelWidget.getText() + + @label.setter + def label(self, value): + if value.strip() != '': + self.labelWidget.setText(value) + else: + self.labelWidget.hide() + + @property + def form(self): + return self + + @property + def icon_size(self): + return self.tableWidget.iconSize() + + @icon_size.setter + def icon_size(self, value): + if isinstance(value, (tuple, list)): + self.tableWidget.setIconSize(QtCore.QSize(*value)) + else: + self.tableWidget.setIconSize(QtCore.QSize(value, value)) + + @property + def autoscroll(self): return self._autoscroll + @autoscroll.setter + def autoscroll(self, value): self._autoscroll = value + + @property + def resizecolumns(self): return self._resizecolumns + @resizecolumns.setter + def resizecolumns(self, value): self._resizecolumns = value + + + ########################################################################## + ############ PRIVATE FUNCTIONS ########################################### + ########################################################################## + + def _dataChangedEvent(self, item): + self.data_changed_event(item.row(), item.column(), self.tableWidget.model().data(item)) + self.changed_event() + + def tableWidgetCellChanged(self, nextRow, nextCol, previousRow, + previousCol): + self.current_cell_changed_event(nextRow, nextCol, previousRow, previousCol) + self.changed_event() + + def tableWidgetItemChanged(self, current, previous): + self.current_item_changed_event(current, previous) + self.changed_event() + + def tableWidgetItemSelectionChanged(self): + self.item_selection_changed_event() + + def tableWidgetCellDoubleClicked(self, row, column): + """ + (From PyQt) This signal is emitted whenever a cell in the table is double clicked. + The row and column specified is the cell that was double clicked. + + Besides firing this signal, we save the current value, in case the user needs to know the old value. + :param row: + :param column: + :return: + """ + self.CELL_VALUE_BEFORE_CHANGE = self.get_value(column, row) + logger.debug("Cell double clicked. Stored value: %s", self.CELL_VALUE_BEFORE_CHANGE) + self.cell_double_clicked_event(row, column) + + def empty_signal(self, *args, **kwargs): + """ + Use this function if you want to disconnect a signal temporarily + """ + pass diff --git a/pyforms/gui/Controls/ControlMatplotlib.py b/pyforms/gui/controls/ControlMatplotlib.py similarity index 82% rename from pyforms/gui/Controls/ControlMatplotlib.py rename to pyforms/gui/controls/ControlMatplotlib.py index f83d8d5..13a7ed2 100644 --- a/pyforms/gui/Controls/ControlMatplotlib.py +++ b/pyforms/gui/controls/ControlMatplotlib.py @@ -10,21 +10,19 @@ __status__ = "Development" -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QVBoxLayout -else: - from PyQt4.QtGui import QWidget, QVBoxLayout +from AnyQt.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout +from pyforms.gui.controls.ControlBase import ControlBase -from pyforms.gui.Controls.ControlBase import ControlBase +from AnyQt import _api -if conf.PYFORMS_USE_QT5: +if _api.USED_API == _api.QT_API_PYQT5: from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -else: +elif _api.USED_API == _api.QT_API_PYQT4: from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar @@ -35,9 +33,9 @@ class ControlMatplotlib(ControlBase, QWidget): - def __init__(self, label = ""): + def __init__(self, *args, **kwargs): QWidget.__init__(self) - ControlBase.__init__(self, label) + ControlBase.__init__(self, *args, **kwargs) def init_form(self): @@ -83,11 +81,6 @@ def on_draw(self, figure): ############ Properties #################################################### ############################################################################ - @property - def axes(self): return self._axes - @axes.setter - def axes(self, value): self._axes = value - @property def fig(self): return self._fig @fig.setter diff --git a/pyforms/gui/Controls/ControlMdiArea.py b/pyforms/gui/controls/ControlMdiArea.py similarity index 90% rename from pyforms/gui/Controls/ControlMdiArea.py rename to pyforms/gui/controls/ControlMdiArea.py index 6011c5e..ba45418 100644 --- a/pyforms/gui/Controls/ControlMdiArea.py +++ b/pyforms/gui/controls/ControlMdiArea.py @@ -3,17 +3,11 @@ import logging -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QMdiArea - from PyQt5.QtWidgets import QMdiSubWindow +from AnyQt.QtWidgets import QMdiArea, QMdiSubWindow -else: - from PyQt4.QtGui import QMdiArea - from PyQt4.QtGui import QMdiSubWindow - -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase logger = logging.getLogger(__name__) @@ -24,10 +18,11 @@ class ControlMdiArea(ControlBase, QMdiArea): an area in which MDI windows are displayed. """ - def __init__(self, label=""): + def __init__(self, *args, **kwargs): QMdiArea.__init__(self) - ControlBase.__init__(self, label) + ControlBase.__init__(self, *args, **kwargs) self._showCloseButton = True + self._openWindows = [] self.logger = logging.getLogger(__name__) diff --git a/pyforms/gui/Controls/ControlNumber.py b/pyforms/gui/controls/ControlNumber.py similarity index 79% rename from pyforms/gui/Controls/ControlNumber.py rename to pyforms/gui/controls/ControlNumber.py index 3d9e6f2..5f29b31 100644 --- a/pyforms/gui/Controls/ControlNumber.py +++ b/pyforms/gui/controls/ControlNumber.py @@ -2,24 +2,22 @@ # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf import pyforms.utils.tools as tools -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic +from AnyQt import uic -else: - from PyQt4 import uic - -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase class ControlNumber(ControlBase): - def __init__(self, label="", default=0, minimum=0, maximum=100): - self._min = minimum - self._max = maximum - ControlBase.__init__(self, label, default) + def __init__(self, *args, **kwargs): + self._min = kwargs.get('minimum', 0) + self._max = kwargs.get('maximum', 100) + if 'default' not in kwargs: kwargs['default'] = 0 + ControlBase.__init__(self, *args, **kwargs) + self.decimals = kwargs.get('decimals', 0) def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "number.ui") @@ -30,7 +28,7 @@ def init_form(self): self.value = self._value self.form.label.setAccessibleName('ControlNumber-label') self.form.spinBox.valueChanged.connect(self.update_event) - self.form.spinBox.setDecimals(0) + def update_event(self, value): self._updateSlider = False @@ -53,7 +51,7 @@ def value(self): return self._value @value.setter - def value(self, value): + def value(self, value): self.form.spinBox.setValue(value) ControlBase.value.fset(self, value) diff --git a/pyforms/gui/controls/ControlOpenGL.py b/pyforms/gui/controls/ControlOpenGL.py new file mode 100644 index 0000000..dcb6bd4 --- /dev/null +++ b/pyforms/gui/controls/ControlOpenGL.py @@ -0,0 +1,276 @@ +# !/usr/bin/python +# -*- coding: utf-8 -*- + +import logging + +from pyforms.utils.settings_manager import conf + +logger = logging.getLogger(__name__) + +from pyforms.gui.controls.ControlBase import ControlBase + +from AnyQt.QtWidgets import QSizePolicy + +from AnyQt import _api + +from OpenGL.GL import * +from OpenGL.GLU import * + +from AnyQt import _api +if _api.USED_API == _api.QT_API_PYQT5: + try: + from AnyQt.QtOpenGL import QGLWidget + except: + logger.debug("No OpenGL library available") + + import platform + if platform.system() == 'Darwin': + from pyforms.gui.controls.control_player.VideoQt5GLWidget import VideoQt5GLWidget as VideoGLWidget + else: + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget + +elif _api.USED_API == _api.QT_API_PYQT4: + try: + from PyQt4.QtOpenGL import QGLWidget + except: + logger.debug("No OpenGL library available") + + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget + + +class OpenglGLWidget(QGLWidget): + def __init__(self, parent=None): + QGLWidget.__init__(self, parent) + + self._zoom = 1.0 + self._scene = None + self._rotation = [0, 0, 0] + + self._mouseLeftDown = False + self._mouseRightDown = False + + self._mouseGLPosition = [0, 0, 0] + self._lastMouseGLPosition = [0, 0, 0] + + self._mousePosition = [0, 0] # Current mouse position + self._lastMousePosition = [0, 0] # Last mouse position + + self._mouseStartDragPoint = None + self._clear_color = None + + self.setMinimumHeight(100) + self.setMinimumWidth(100) + self.setMouseTracking(True) + self.setAcceptDrops(True) + + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding); + + def initializeGL(self): + glClearDepth(1.0) + if self._clear_color: glClearColor(*self._clear_color) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE) + + glEnable(GL_POINT_SMOOTH); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POLYGON_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + + def resizeGL(self, width, height): + """ + + :param width: + :param height: + :return: + """ + + glViewport(0, 0, width, height) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + + if height!=0: + ratio = float(width) / float(height) + else: + ratio = 1 + gluPerspective(65.0, ratio, 0.01, 800.0) + + def paintGL(self): + """ + + """ + if self._clear_color: + glBlendFunc(GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glClearColor(*self._clear_color) + else: + glBlendFunc(GL_SRC_ALPHA, GL_ONE) + glClearColor(0, 0, 0, 1) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + + glEnable(GL_BLEND) + + glScalef(1, -1, -1) + + glTranslatef(0, 0, self._zoom) + glRotatef(self._rotation[0], 1, 0, 0) + glRotatef(self._rotation[1], 0, 1, 0) + glRotatef(self._rotation[2], 0, 0, 1) + + if self._scene != None: self._scene.DrawGLScene() + + if self.mouseDown: + # Get mouse position + modelview, projection = glGetDoublev(GL_MODELVIEW_MATRIX), glGetDoublev(GL_PROJECTION_MATRIX) + viewport = glGetIntegerv(GL_VIEWPORT) + winX, winY = float(self._mousePosition[0]), float(viewport[3] - self._mousePosition[1]) + winZ = glReadPixels(winX, winY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT) + self._mouseGLPosition = gluUnProject(winX, winY, winZ[0][0], modelview, projection, viewport) + + glDisable(GL_BLEND) + + def __updateMouse(self, event, pressed=None): + self._mousePosition[0] = event.x() + self._mousePosition[1] = event.y() + + if pressed != None: + if event.button() == 2: self._mouseRightDown = pressed + if event.button() == 1: self._mouseLeftDown = pressed + + def wheelEvent(self, event): + """ + + :param event: + """ + if _api.USED_API == _api.QT_API_PYQT5: + p = event.angleDelta() + delta = p.y() + elif _api.USED_API == _api.QT_API_PYQT4: + delta = event.delta() + + + if delta < 0: + self._zoom += 1 + else: + self._zoom -= 1 + + self.repaint() + + def mouseReleaseEvent(self, event): + """ + + :param event: + """ + self.__updateMouse(event) + + if self._mouseRightDown: + self._lastMouseGLPosition = self._mouseGLPosition + self._mouseRightDown = False + + if self._mouseLeftDown: + self.onEndDrag(self._mouseStartDragPoint, self._mouseGLPosition) + self._mouseLeftDown = False + + def mousePressEvent(self, event): + """ + + :param event: + """ + QGLWidget.mousePressEvent(self, event) + self.__updateMouse(event, pressed=True) + self.repaint() + + if self._mouseLeftDown: self._mouseStartDragPoint = self._mouseGLPosition + if self._mouseRightDown: self._lastMouseGLPosition = self._mouseGLPosition + + self.onPress(event.button(), self._mousePosition, self._mouseGLPosition) + + def mouseMoveEvent(self, event): + """ + + :param event: + """ + QGLWidget.mouseMoveEvent(self, event) + self.__updateMouse(event) + self.repaint() + + self.onMove((self._mouseGLPosition[0], self._mouseGLPosition[1])) + + if self.mouseDown: + if self._mouseLeftDown: + # p = self._mouseGLPosition[0] - self._x, self._mouseGLPosition[1] + self._y + # p = self._mouseGLPosition[0], self._mouseGLPosition[1] + self.onDrag(self._lastMousePosition, self._mousePosition) + + self._lastMousePosition = list(self._mousePosition) + + def onMove(self, point): + pass + + def onPress(self, button, point, glpoint=None): + pass + + def onDrag(self, startPoint, endPoint, startGLPoint=None, endGLPoint=None): + """ + + :param startPoint: + :param endPoint: + :param startGLPoint: + :param endGLPoint: + """ + movX, movY = endPoint[0] - startPoint[0], endPoint[1] - startPoint[1] + self._rotation[2] -= float(movX) * 0.15 + self._rotation[0] += float(movY) * 0.15 + + def onEndDrag(self, startPoint, endPoint, startGLPoint=None, endGLPoint=None): + pass + + ############################################################################## + ###### Properties ############################################################ + ############################################################################## + + @property + def scene(self): + return self._scene + + @scene.setter + def scene(self, value): + self._scene = value + + @property + def mouseDown(self): + return self._mouseLeftDown or self._mouseRightDown + + def resetZoomAndRotation(self): + self._zoom, self._rotation = 0.0, [0, 0, 0] + + +class ControlOpenGL(ControlBase): + def init_form(self): self._form = OpenglGLWidget() + + def repaint(self): self._form.repaint() + + def reset_zoom_and_rotation(self): self._form.resetZoomAndRotation() + + @property + def value(self): return self._form.scene + + @value.setter + def value(self, value): self._form.scene = value; self._form.repaint() + + @property + def clear_color(self): self._form._clear_color + + @clear_color.setter + def clear_color(self, value): + self._form._clear_color = value; + self._form.repaint() + + @property + def width(self): return self._form.width() + + @property + def height(self): return self._form.height() diff --git a/pyforms/gui/Controls/ControlProgress.py b/pyforms/gui/controls/ControlProgress.py old mode 100755 new mode 100644 similarity index 77% rename from pyforms/gui/Controls/ControlProgress.py rename to pyforms/gui/controls/ControlProgress.py index a3d2a9a..d4dc776 --- a/pyforms/gui/Controls/ControlProgress.py +++ b/pyforms/gui/controls/ControlProgress.py @@ -2,27 +2,20 @@ # -*- coding: utf-8 -*- import os - -from pyforms.gui.Controls.ControlBase import ControlBase - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic - from PyQt5.QtWidgets import QApplication - -else: - from PyQt4 import uic - from PyQt4.QtGui import QApplication - +from pyforms.gui.controls.ControlBase import ControlBase +from AnyQt import uic +from AnyQt.QtWidgets import QApplication class ControlProgress(ControlBase): - def __init__(self, label="%p%", default=0, min=0, max=100): + + def __init__(self, *args, **kwargs): self._updateSlider = True - self._min = min - self._max = max + self._min = kwargs.get('minimum', 0) + self._max = kwargs.get('maximum', 100) + if 'default' not in kwargs: kwargs['default'] = 0 + ControlBase.__init__(self, *args, **kwargs) - ControlBase.__init__(self, label, default) + def init_form(self): module_path = os.path.abspath(os.path.dirname(__file__)) diff --git a/pyforms/gui/Controls/ControlSlider.py b/pyforms/gui/controls/ControlSlider.py old mode 100755 new mode 100644 similarity index 78% rename from pyforms/gui/Controls/ControlSlider.py rename to pyforms/gui/controls/ControlSlider.py index 43c8ae7..4d0b3d1 --- a/pyforms/gui/Controls/ControlSlider.py +++ b/pyforms/gui/controls/ControlSlider.py @@ -1,29 +1,26 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - import pyforms.utils.tools as tools -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic - -else: - from PyQt4 import uic - -from pyforms.gui.Controls.ControlBase import ControlBase +from AnyQt import uic +from pyforms.gui.controls.ControlBase import ControlBase class ControlSlider(ControlBase): - def __init__(self, label="", default=0, min=0, max=100): - self._updateSlider = True - self._min = min - self._max = max - ControlBase.__init__(self, label, default) - self._form.value.setText(str(default)) + def __init__(self, *args, **kwargs): + self._updateSlider = True + self._min = kwargs.get('minimum', 0) + self._max = kwargs.get('maximum', 100) + if 'default' not in kwargs: kwargs['default'] = 0 + ControlBase.__init__(self, *args, **kwargs) + + self._form.value.setText(str(kwargs['default'])) self._form.horizontalSlider.valueChanged.connect(self.valueChanged) + + def init_form(self): control_path = tools.getFileInSameDirectory(__file__, "sliderInput.ui") self._form = uic.loadUi(control_path) @@ -43,7 +40,7 @@ def load_form(self, data, path=None): if 'value' in data: self.value = int(data['value']) def save_form(self, data, path=None): - if self.value: data['value'] = self.value + data['value'] = self.value @property def value(self): diff --git a/pyforms/gui/controls/ControlTableView.py b/pyforms/gui/controls/ControlTableView.py new file mode 100644 index 0000000..0946a0f --- /dev/null +++ b/pyforms/gui/controls/ControlTableView.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" pyforms.gui.controls.ControlList + +""" +import logging +import os + +from pyforms.utils.settings_manager import conf + +from AnyQt import QtCore, uic +from AnyQt.QtWidgets import QTableView, QAbstractItemView +from AnyQt.QtGui import QIcon + +from pyforms.gui.basewidget import BaseWidget +from pyforms.gui.controls.ControlBase import ControlBase + +logger = logging.getLogger(__name__) + + +class ControlTableView(ControlBase, QTableView): + """ This class represents a wrapper to the table widget + It allows to implement a list view + """ + + CELL_VALUE_BEFORE_CHANGE = None # store value when cell is double clicked + + def __init__(self, *args, **kwargs): + QTableView.__init__(self) + ControlBase.__init__(self, *args, **kwargs) + + if kwargs.get('select_entire_row', False): + self.setSelectionBehavior(QAbstractItemView.SelectRows) + + + ########################################################################## + ############ EVENTS ###################################################### + ########################################################################## + + + ########################################################################## + ############ PROPERTIES ################################################## + ########################################################################## + + + + @property + def value(self): + return self.model() + + @value.setter + def value(self, value): + self.setModel(value) + + + + + @property + def form(self): + return self + + + ########################################################################## + ############ PRIVATE FUNCTIONS ########################################### + ########################################################################## diff --git a/pyforms/gui/Controls/ControlText.py b/pyforms/gui/controls/ControlText.py old mode 100755 new mode 100644 similarity index 87% rename from pyforms/gui/Controls/ControlText.py rename to pyforms/gui/controls/ControlText.py index 45b4516..610c2b5 --- a/pyforms/gui/Controls/ControlText.py +++ b/pyforms/gui/controls/ControlText.py @@ -10,18 +10,10 @@ @status: Development @lastEditedBy: Carlos Mão de Ferro (carlos.maodeferro@neuro.fchampalimaud.org) ''' - -from pyforms.gui.Controls.ControlBase import ControlBase import pyforms.utils.tools as tools - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QLineEdit - from PyQt5 import uic -else: - from PyQt4.QtGui import QLineEdit - from PyQt4 import uic +from pyforms.gui.controls.ControlBase import ControlBase +from AnyQt.QtWidgets import QLineEdit +from AnyQt import uic class ControlText(ControlBase): diff --git a/pyforms/gui/controls/ControlTextArea.py b/pyforms/gui/controls/ControlTextArea.py new file mode 100644 index 0000000..cc1cd46 --- /dev/null +++ b/pyforms/gui/controls/ControlTextArea.py @@ -0,0 +1,71 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +__author__ = "Ricardo Ribeiro" +__credits__ = ["Ricardo Ribeiro"] +__license__ = "MIT" +__version__ = "0.0" +__maintainer__ = "Ricardo Ribeiro" +__email__ = "ricardojvr@gmail.com" +__status__ = "Development" + +import pyforms.utils.tools as tools +from AnyQt import uic, QtGui +from pyforms.gui.controls.ControlBase import ControlBase + +class ControlTextArea(ControlBase): + + def __init__(self, *args, **kwargs): + ControlBase.__init__(self, *args, **kwargs) + self.autoscroll = kwargs.get('autoscroll', False) + + + + def init_form(self): + control_path = tools.getFileInSameDirectory(__file__, "textArea.ui") + self._form = uic.loadUi(control_path) + self._form.label.setText(self._label) + if self._value: + self._form.plainTextEdit.setPlainText(str(self._value)) + + if not self._label or len(self._label)==0: + self.form.label.hide() + + super(ControlTextArea, self).init_form() + self.form.plainTextEdit.textChanged.connect(self.finishEditing) + + def __add__(self, other): + self._form.plainTextEdit.appendPlainText(str(other)) + + # if activated the text field is autoscrolled to the bottom + if self.autoscroll: + self._form.plainTextEdit.moveCursor(QtGui.QTextCursor.End) + + return self + + def finishEditing(self): + """Function called when the lineEdit widget is edited""" + self.changed_event() + + + @property + def value(self): + return self._form.plainTextEdit.toPlainText() + + @value.setter + def value(self, value): + self._form.plainTextEdit.setPlainText(str(value)) + + @property + def readonly(self): + return self._form.plainTextEdit.isReadOnly() + + @readonly.setter + def readonly(self, value): + self._form.plainTextEdit.setReadOnly(value) + + + @property + def autoscroll(self): return self._autoscroll + @autoscroll.setter + def autoscroll(self, value): self._autoscroll = value diff --git a/pyforms/gui/Controls/ControlToolBox.py b/pyforms/gui/controls/ControlToolBox.py similarity index 72% rename from pyforms/gui/Controls/ControlToolBox.py rename to pyforms/gui/controls/ControlToolBox.py index 87ecbfb..4562d9d 100644 --- a/pyforms/gui/Controls/ControlToolBox.py +++ b/pyforms/gui/controls/ControlToolBox.py @@ -1,21 +1,15 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf +from pyforms.utils.settings_manager import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QToolBox - from PyQt5.QtWidgets import QFrame - from PyQt5.QtWidgets import QVBoxLayout - from PyQt5.QtWidgets import QHBoxLayout +from AnyQt import _api +from AnyQt.QtWidgets import QToolBox +from AnyQt.QtWidgets import QFrame +from AnyQt.QtWidgets import QVBoxLayout +from AnyQt.QtWidgets import QHBoxLayout -else: - from PyQt4.QtGui import QToolBox - from PyQt4.QtGui import QFrame - from PyQt4.QtGui import QVBoxLayout - from PyQt4.QtGui import QHBoxLayout - -from pyforms.gui.Controls.ControlBase import ControlBase +from pyforms.gui.controls.ControlBase import ControlBase class ControlToolBox(ControlBase): @@ -39,9 +33,9 @@ def value(self, value): widget = QFrame(self.form); layout = QVBoxLayout(); - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: layout.setContentsMargins(0,0,0,0) - else: + elif _api.USED_API == _api.QT_API_PYQT4: layout.setMargin(0) widget.setLayout(layout) @@ -51,9 +45,9 @@ def value(self, value): hwidget = QFrame(self.form); hlayout = QHBoxLayout(); - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: hlayout.setContentsMargins(0,0,0,0) - else: + elif _api.USED_API == _api.QT_API_PYQT4: hlayout.setMargin(0) hwidget.setLayout(hlayout) diff --git a/pyforms/gui/controls/ControlToolButton.py b/pyforms/gui/controls/ControlToolButton.py new file mode 100644 index 0000000..0fd102c --- /dev/null +++ b/pyforms/gui/controls/ControlToolButton.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + + +from pyforms.utils.settings_manager import conf + +from AnyQt.QtCore import Qt +from AnyQt import uic +from AnyQt.QtWidgets import QToolButton, QSizePolicy +from AnyQt.QtGui import QIcon + +from pyforms.gui.controls.ControlBase import ControlBase + + +class ControlToolButton(ControlBase): + def __init__(self, *args, **kwargs): + self._checkable = kwargs.get('checkable', False) + self._maxheight = kwargs.get('maxheight', None) + self._maxwidth = kwargs.get('maxwidth', None) + super(ControlToolButton, self).__init__(*args, **kwargs) + + default = kwargs.get('default', None) + if default: self.value = default + + icon = kwargs.get('icon', None) + if icon: self.icon = icon + + + + def init_form(self): + self._form = QToolButton() + if self._maxwidth: self._form.setMaximumWidth(self._maxwidth) + if self._maxheight: self._form.setMaximumHeight(self._maxheight) + self._form.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + self._form.setCheckable(self._checkable) + self.label = self._label + self._form.setToolTip(self.help) + + def click(self): + self._form.click() + + def load_form(self, data, path=None): + pass + + def save_form(self, data, path=None): + pass + + ########################################################################## + + @property + def label(self): + return ControlBase.label.fget(self) + + @label.setter + def label(self, value): + ControlBase.label.fset(self, value) + if value is None: + self._form.setToolButtonStyle(Qt.ToolButtonIconOnly) + else: + self._form.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + self._form.setText(self._label) + + @property + def icon(self): + return self._form.icon() + + @icon.setter + def icon(self, value): + if isinstance(value, (str, bytes)): + self._form.setIcon(QIcon(value)) + else: + self._form.setIcon(value) + + ########################################################################## + + @property + def value(self): + return None + + @value.setter + def value(self, value): + try: + self._form.clicked.disconnect() # ignore previous signals if any + except TypeError as err: + # http://stackoverflow.com/questions/21586643/pyqt-widget-connect-and-disconnect + pass + self._form.clicked[bool].connect(value) + + @property + def checked(self): + return self._form.isChecked() + + @checked.setter + def checked(self, value): + self._form.setChecked(value) diff --git a/pyforms/gui/Controls/ControlTree.py b/pyforms/gui/controls/ControlTree.py similarity index 94% rename from pyforms/gui/Controls/ControlTree.py rename to pyforms/gui/controls/ControlTree.py index e5c00a2..07ce514 100644 --- a/pyforms/gui/Controls/ControlTree.py +++ b/pyforms/gui/controls/ControlTree.py @@ -1,26 +1,17 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -from pyforms.gui.Controls.ControlBase import ControlBase - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QTreeView, QAbstractItemView, QAction - from PyQt5.QtGui import QIcon, QKeySequence - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QTreeWidget, QTreeWidgetItem, QTreeView, QAbstractItemView, QAction, QKeySequence, QIcon - from PyQt4 import QtCore - +from pyforms.gui.controls.ControlBase import ControlBase +from AnyQt.QtWidgets import QTreeWidget, QTreeWidgetItem, QTreeView, QAbstractItemView, QAction +from AnyQt.QtGui import QIcon, QKeySequence +from AnyQt import QtCore class ControlTree(ControlBase, QTreeWidget): """This class represents a wrapper to the QTreeWidget""" - def __init__(self, label='', default=''): + def __init__(self, *args, **kwargs): QTreeWidget.__init__(self) - ControlBase.__init__(self, label, default) + ControlBase.__init__(self, *args, **kwargs) def init_form(self): self.setSelectionBehavior(QAbstractItemView.SelectRows) diff --git a/pyforms/gui/controls/ControlTreeView.py b/pyforms/gui/controls/ControlTreeView.py new file mode 100644 index 0000000..aec05bb --- /dev/null +++ b/pyforms/gui/controls/ControlTreeView.py @@ -0,0 +1,83 @@ +# !/usr/bin/python +# -*- coding: utf-8 -*- +from pyforms.gui.controls.ControlBase import ControlBase + +from AnyQt.QtWidgets import QTreeView, QAbstractItemView +from AnyQt.QtGui import QStandardItem, QStandardItemModel + + +class ControlTreeView(ControlBase, QTreeView): + + default_width = None + + def __init__(self, *args, **kwargs): + QTreeView.__init__(self) + ControlBase.__init__(self, *args, **kwargs) + + self.item_selection_changed_event = kwargs.get('item_selection_changed_event', self.item_selection_changed_event) + self.item_double_clicked_event = kwargs.get('item_double_clicked_event', self.item_double_clicked_event) + + def init_form(self): + #self.header().hide() + self.setSelectionBehavior(QAbstractItemView.SelectRows) + self.setUniformRowHeights(True) + #self.setDragDropMode(QAbstractItemView.InternalMove) + #self.setDragEnabled(True) + #self.setAcceptDrops(True) + + self.value = QStandardItemModel() if self._value is None else self._value + + #self.selectionChanged = self.__selectionChanged + self.mouseDoubleClickEvent = self.__itemDoubleClicked + + self.selectionModel().selectionChanged.connect(self.__selectionChanged) + + def item_selection_changed_event(self, selected, deselected): + pass + + def item_double_clicked_event(self, evt): + pass + + ################################################### + ## OTHER FUNCTIONS ################################ + ################################################### + + def __itemDoubleClicked(self, evt): + self.item_double_clicked_event(evt) + + def __selectionChanged(self, selected, deselected): + #def selectionChanged(self, selected, deselected): + self.item_selection_changed_event(selected, deselected) + #super(QTreeView, self.form).selectionChanged(selected, deselected) + + ################################################### + ## PROPERTIES ##################################### + ################################################### + + @property + def selected_row_index(self): + for index in self.selectedIndexes(): + return index.row() + return None + + @property + def selected_item(self): + for index in self.selectedIndexes(): + res = [] + while index.isValid(): + print( index.data() ) + res.append( index.data() ) + index = index.parent() + return list(reversed(res)) + return None + + @property + def value(self): + return self.model() + + @value.setter + def value(self, value): self.setModel(value) + + + @property + def form(self): return self diff --git a/pyforms/gui/Controls/ControlVisVis.py b/pyforms/gui/controls/ControlVisVis.py old mode 100755 new mode 100644 similarity index 80% rename from pyforms/gui/Controls/ControlVisVis.py rename to pyforms/gui/controls/ControlVisVis.py index a0d9577..5efad6b --- a/pyforms/gui/Controls/ControlVisVis.py +++ b/pyforms/gui/controls/ControlVisVis.py @@ -9,41 +9,33 @@ __email__ = "ricardojvr@gmail.com" __status__ = "Development" +import visvis as vv, numpy as np -import pyforms.utils.tools as tools -from pyforms.gui.Controls.ControlBase import ControlBase - -from visvis import Point, Pointset -import visvis as vv -import numpy as np - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy -else: - from PyQt4.QtGui import QWidget, QVBoxLayout, QSizePolicy +from visvis import Point, Pointset +from pyforms.gui.controls.ControlBase import ControlBase +from AnyQt import _api +from AnyQt.QtWidgets import QWidget, QVBoxLayout, QSizePolicy class ControlVisVis(ControlBase): def init_form(self): - self._form = QtGui.QWidget() - layout = QtGui.QVBoxLayout() + self._form = QWidget() + layout = QVBoxLayout() - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: layout.setContentsMargins(0,0,0,0) else: layout.setMargin(0) self._form.setLayout( layout ) - self._app = vv.use('pyqt4') + self._app = vv.use('pyqt5') self._app.Create() Figure = self._app.GetFigureClass() self._fig = Figure(self._form) - policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) widget = self._fig._widget widget.setSizePolicy(policy) diff --git a/pyforms/gui/Controls/ControlVisVisVolume.py b/pyforms/gui/controls/ControlVisVisVolume.py old mode 100755 new mode 100644 similarity index 80% rename from pyforms/gui/Controls/ControlVisVisVolume.py rename to pyforms/gui/controls/ControlVisVisVolume.py index d7709ee..723fc35 --- a/pyforms/gui/Controls/ControlVisVisVolume.py +++ b/pyforms/gui/controls/ControlVisVisVolume.py @@ -9,33 +9,24 @@ __email__ = "ricardojvr@gmail.com" __status__ = "Development" -import pyforms.utils.tools as tools -from pyforms.gui.Controls.ControlBase import ControlBase - -from visvis import Point, Pointset -import visvis as vv -import numpy as np - -from pysettings import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QVBoxLayout, QSizePolicy -else: - from PyQt4.QtGui import QWidget, QVBoxLayout, QSizePolicy - +import visvis as vv, numpy as np +from pyforms.gui.controls.ControlBase import ControlBase +from visvis import Point, Pointset +from AnyQt import _api +from AnyQt.QtWidgets import QWidget, QVBoxLayout, QSizePolicy class ControlVisVisVolume(ControlBase): def init_form(self): self._form = QWidget() layout = QVBoxLayout() - layout.setMargin(0) - - if conf.PYFORMS_USE_QT5: + + if _api.USED_API == _api.QT_API_PYQT5: layout.setContentsMargins(0,0,0,0) - else: + elif _api.USED_API == _api.QT_API_PYQT4: layout.setMargin(0) self._form.setLayout(layout) - self._app = vv.use('pyqt4') + self._app = vv.use('pyqt5') self._app.Create() self._first = True diff --git a/pyforms/gui/controls/ControlWeb.py b/pyforms/gui/controls/ControlWeb.py new file mode 100644 index 0000000..a8415a3 --- /dev/null +++ b/pyforms/gui/controls/ControlWeb.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +__author__ = "Ricardo Ribeiro" +__credits__ = ["Ricardo Ribeiro"] +__license__ = "MIT" +__version__ = "0.0" +__maintainer__ = "Ricardo Ribeiro" +__email__ = "ricardojvr@gmail.com" +__status__ = "Development" + +from AnyQt.QtWebEngineWidgets import QWebEngineView as QWebView +from AnyQt.QtCore import QUrl +from pyforms.gui.controls.ControlBase import ControlBase + + +class ControlWeb(ControlBase, QWebView): + def __init__(self, *args, **kwargs): + QWebView.__init__(self) + if 'load_finnished_event' in kwargs: + self.load_finnished_event=kwargs['load_finnished_event'] + self.loadFinished.connect(self.__load_finnished_evt) + ControlBase.__init__(self, *args, **kwargs) + + + def init_form(self): + """ + Load Control and initiate the events + """ + ControlBase.init_form(self) + if self._value: QWebView.load(self, QUrl(self._value)) + if self.help: self.form.setToolTip(self.help) + ############################################################################ + ############ Properties #################################################### + ############################################################################ + + def __load_finnished_evt(self, ok): + self.load_finnished_event(ok) + + def load_finnished_event(self,ok): + pass + + @property + def value(self): return ControlBase.value.fget(self) + + @value.setter + def value(self, value): + ControlBase.value.fset(self, value) + QWebView.load(self, QUrl(value)) + + @property + def form(self): + return self diff --git a/pyforms/gui/Controls/__init__.py b/pyforms/gui/controls/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/__init__.py rename to pyforms/gui/controls/__init__.py diff --git a/pyforms/gui/Controls/button.ui b/pyforms/gui/controls/button.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/button.ui rename to pyforms/gui/controls/button.ui diff --git a/pyforms/gui/Controls/checkbox.ui b/pyforms/gui/controls/checkbox.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/checkbox.ui rename to pyforms/gui/controls/checkbox.ui diff --git a/pyforms/gui/Controls/code_editor.ui b/pyforms/gui/controls/code_editor.ui similarity index 55% rename from pyforms/gui/Controls/code_editor.ui rename to pyforms/gui/controls/code_editor.ui index 95f18bd..bf51fd5 100644 --- a/pyforms/gui/Controls/code_editor.ui +++ b/pyforms/gui/controls/code_editor.ui @@ -14,11 +14,23 @@ Form - + + 0 + + + 0 + + + 0 + + 0 + + 2 + @@ -50,19 +62,57 @@ - - - false + + + Qt::Horizontal + + + QSizePolicy::Maximum - - - 0 - 0 - + + + 40 + 10 + + + + + + + + false Save + + QToolButton::InstantPopup + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + false + + + Discart + + + QToolButton::InstantPopup + + + Qt::ToolButtonTextBesideIcon + + + true + diff --git a/pyforms/gui/controls/control_event_timeline/ControlEventTimeline.py b/pyforms/gui/controls/control_event_timeline/ControlEventTimeline.py new file mode 100644 index 0000000..b1e7f41 --- /dev/null +++ b/pyforms/gui/controls/control_event_timeline/ControlEventTimeline.py @@ -0,0 +1,499 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import csv +import os + +from pyforms.utils.settings_manager import conf + + +from AnyQt.QtWidgets import QWidget, QScrollArea, QColorDialog, QFileDialog, QMessageBox, QPushButton, QLabel, QSlider, QHBoxLayout, QVBoxLayout +from AnyQt import QtCore, _api + +from pyforms.gui.controls.control_event_timeline.TimelineChart import TimelineChart +from pyforms.gui.controls.ControlBase import ControlBase +from pyforms.gui.controls.control_event_timeline.TimelineWidget import TimelineWidget +from pyforms.gui.controls.control_event_timeline.TimelinePopupWindow import TimelinePopupWindow +from pyforms.gui.controls.control_event_timeline.import_window import ImportWindow + +from pyforms.gui.controls.control_event_timeline.graphs_graph2event import Graph2Event +from pyforms.gui.controls.control_event_timeline.graphs_properties import GraphsProperties +from pyforms.gui.controls.control_event_timeline.graphs_eventsgenerator import GraphsEventsGenerator + + + +class ControlEventTimeline(ControlBase, QWidget): + """ + Timeline events editor + """ + + def __init__(self, label="", default=0, max=100): + QWidget.__init__(self) + ControlBase.__init__(self, label, default) + self._max = 100 + self._graphs_prop_win = GraphsProperties(self._time, self) + self._graphsgenerator_win = GraphsEventsGenerator(self._time) + self._graph2event_win = Graph2Event(self._time) + + # Popup menus that only show when clicking on a TIMELINEDELTA object + self._deltaLockAction = self.add_popup_menu_option("Lock", self.__lockSelected, key='L') + self._deltaColorAction = self.add_popup_menu_option("Pick a color", self.__pickColor) + self._deltaRemoveAction = self.add_popup_menu_option("Remove", self.__removeSelected, key='Delete') + self._deltaActions = [self._deltaLockAction, self._deltaColorAction, self._deltaRemoveAction] + for action in self._deltaActions: + action.setVisible(False) + + self.add_popup_menu_option("-") + + self.add_popup_menu_option("Graphs", self.show_graphs_properties, icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) + self.add_popup_menu_option("Apply a function to the graphs", self.__generate_graphs_events, + icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) + self.add_popup_menu_option("Convert graph to events", self.__graph2event_event, + icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) + + self.add_popup_menu_option("-") + + # General righ click popup menus + self.add_popup_menu_option("Rows", self.__setLinePropertiesEvent, + icon=conf.PYFORMS_ICON_EVENTTIMELINE_REMOVE) + self.add_popup_menu_option("-") + + + + self.add_popup_menu_option("-") + + self.add_popup_menu_option("Auto adjust rows", self.__auto_adjust_tracks_evt, + icon=conf.PYFORMS_ICON_EVENTTIMELINE_REFRESH) + self.add_popup_menu_option("Add a row", self.__add_track_2_bottom_evt, + icon=conf.PYFORMS_ICON_EVENTTIMELINE_ADD) + + self.add_popup_menu_option("-") + + clean_menu = self.add_popup_submenu('Clean') + + self.add_popup_menu_option('The current row', function_action=self.__cleanLine, submenu=clean_menu) + self.add_popup_menu_option('-') + self.add_popup_menu_option('All graphs', function_action=self.__cleanCharts, submenu=clean_menu) + self.add_popup_menu_option('-') + self.add_popup_menu_option('Everything', function_action=self.clean, submenu=clean_menu) + + def init_form(self): + # Get the current path of the file + rootPath = os.path.dirname(__file__) + + vlayout = QVBoxLayout() + hlayout = QHBoxLayout() + + if _api.USED_API == _api.QT_API_PYQT5: + hlayout.setContentsMargins(0,0,0,0) + vlayout.setContentsMargins(0,0,0,0) + elif _api.USED_API == _api.QT_API_PYQT4: + hlayout.setMargin(0) + vlayout.setMargin(0) + + + + + self.setLayout(vlayout) + + # Add scroll area + scrollarea = QScrollArea() + scrollarea.setMinimumHeight(140) + scrollarea.setWidgetResizable(True) + scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent + scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent + vlayout.addWidget(scrollarea) + # vlayout.setContentsMargins(5, 5, 5, 5) + + # The timeline widget + widget = TimelineWidget(self) + widget._scroll = scrollarea + # widget.setMinimumHeight(1000) + scrollarea.setWidget(widget) + + # TODO Options buttons + # btn_1 = QtGui.QPushButton("?") + # btn_2 = QtGui.QPushButton("?") + # vlayout_options = QtGui.QVBoxLayout() + # vlayout_options.addWidget(btn_1) + # vlayout_options.addWidget(btn_2) + # hlayout.addLayout(vlayout_options) + # hlayout.addWidget(btn_1) + # hlayout.addWidget(btn_2) + + # Timeline zoom slider + slider = QSlider(QtCore.Qt.Horizontal) + slider.setFocusPolicy(QtCore.Qt.NoFocus) + slider.setMinimum(1) + slider.setMaximum(100) + slider.setValue(10) + slider.setPageStep(1) + slider.setTickPosition(QSlider.NoTicks) # TicksBothSides + slider.valueChanged.connect(self.__scaleSliderChange) + + slider_label_zoom_in = QLabel() + slider_label_zoom_out = QLabel() + slider_label_zoom_in.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) + slider_label_zoom_out.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) + + self._zoomLabel = QLabel("100%") + hlayout.addWidget(self._zoomLabel) + hlayout.addWidget(slider_label_zoom_out) + hlayout.addWidget(slider) + hlayout.addWidget(slider_label_zoom_in) + # hlayout.setContentsMargins(5, 0, 5, 5) + # Import/Export Buttons + btn_import = QPushButton("Import") + + btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) + btn_import.clicked.connect(self.__import) + btn_export = QPushButton("Export") + + btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) + btn_export.clicked.connect(self.__export) + # importexport_vlayout = QtGui.QVBoxLayout() + # importexport_vlayout.adimdWidget(btn_import) + # importexport_vlayout.addWidget(btn_export) + # hlayout.addLayout(importexport_vlayout) + hlayout.addWidget(btn_import) + hlayout.addWidget(btn_export) + + vlayout.addLayout(hlayout) + + self._time = widget + self._scrollArea = scrollarea + + ########################################################################## + #### HELPERS/PUBLIC FUNCTIONS ############################################ + ########################################################################## + + def __add__(self, other): + if isinstance(other, TimelineChart): + self._graphs_prop_win += other + self._graphsgenerator_win += other + self._graph2event_win += other + return self + + def __sub__(self, other): + if isinstance(other, int): + self._graphs_prop_win -= other + self._graphsgenerator_win -= other + self._graph2event_win -= other + return self + + def rename_graph(self, graph_index, newname): + self._graphs_prop_win.rename_graph(graph_index, newname) + self._graphsgenerator_win.rename_graph(graph_index, newname) + self._graph2event_win.rename_graph(graph_index, newname) + + def add_period(self, value, row=0, color=None): + """ + + :param value: + :param row: + :param color: + :return: + """ + self._time.add_period(value, row, color) + self._time.repaint() + + def add_graph(self, name, data): + """ + + :param name: + :param data: + :return: + """ + self._time.add_chart(name, data) + + def import_graph(self, filename, frame_col=0, val_col=1): + """ + + :param filename: + :param frame_col: + :param val_col: + :return: + """ + self.__import() + self._import_window.import_chart(filename, frame_col, val_col) + + def import_graph_file(self, filename, separator=';', ignore_rows=0): + """ + + :param filename: + :param separator: + :param ignore_rows: + :return: + """ + csvfile = open(filename, 'U') + spamreader = csv.reader(csvfile, delimiter=separator) + for i in range(ignore_rows): next(spamreader, None) + chart = self._time.importchart_csv(spamreader) + chart.name = os.path.basename(filename).replace('.csv', '').replace('.CSV', '') + csvfile.close() + + def show_graphs_properties(self): + """ + + """ + self._graphs_prop_win.show() + self._time.repaint() + + def import_csv(self, csvfile): + """ + + :param csvfile: + """ + # If there are annotation in the timeline, show a warning + if len(self._time._tracks) > 0: # dict returns True if not empty + message = ["You are about to import new data. ", + "If you proceed, current annotations will be erased. ", + "Make sure to export current annotations first to save.", + "\n", + "Are you sure you want to proceed?"] + reply = QMessageBox.question(self, + "Warning!", + "".join(message), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + + if reply != QMessageBox.Yes: + return + + self._time.import_csv(csvfile) + + def export_csv_file(self, filename): + with open(filename, 'w') as csvfile: + spamwriter = csv.writer(csvfile, dialect='excel') + self._time.export_csv(spamwriter) + + def import_csv_file(self, filename): + with open(filename, 'r') as csvfile: + spamreader = csv.reader(csvfile, dialect='excel') + self._time.import_csv(spamreader) + + ########################################################################## + #### EVENTS ############################################################## + ########################################################################## + + def mouse_moveover_timeline_event(self, event): + self._graphs_prop_win.mouse_moveover_timeline_event(event) + + + @property + def pointer_changed_event(self): + return self._time._pointer.moveEvent + + @pointer_changed_event.setter + def pointer_changed_event(self, value): + self._time._pointer.moveEvent = value + + def __auto_adjust_tracks_evt(self): + for i in range(len(self._time.tracks) - 1, -1, -1): + track = self._time.tracks[i] + if len(track) == 0: + self._time.remove_track(track) + else: + break + + def __add_track_2_bottom_evt(self): + self._time.add_track() + + def __remove_track_from_bottom_evt(self): + if len(self._time.tracks) > 0: + track = self._time.tracks[-1] + if len(track) == 0: + self._time.remove_track(track) + + def __generate_graphs_events(self): + self._graphsgenerator_win.show() + + def __graph2event_event(self): + self._graph2event_win.show() + + ########################################################################## + #### PROPERTIES ########################################################## + ########################################################################## + + @property + def value(self): + return self._time.position + + @value.setter + def value(self, value): + ControlBase.value.fset(self, value) + self._time.position = value + + @property + def max(self): + return self._time.minimumWidth() + + @max.setter + def max(self, value): + self._max = value + self._time.setMinimumWidth(value) + self.repaint() + + @property + def form(self): + return self + + @property + def rows(self): + return self._time.tracks + + @property + def graphs(self): return self._time.graphs + + ########################################################################## + #### PRIVATE FUNCTIONS ################################################### + ########################################################################## + + def about_to_show_contextmenu_event(self): + for action in self._deltaActions: + action.setVisible( + True) if self._time._selected is not None else action.setVisible(False) + + + + def __setLinePropertiesEvent(self): + """ + This controls makes possible the edition of a track in the + timeline, based on the position of the mouse. + + Updates: + - Track label + - Track default color + """ + current_track = self._time.current_mouseover_track + parent = self._time + + # Tracks info dict and index + i = current_track + + # Save current default color to override with selected track color + timeline_default_color = parent.color + try: + parent.color = self._time._tracks[current_track].color + except Exception as e: + error_message = ("You tried to edit an empty track.", + "\n", + "Initialize it by creating an event first.") + QMessageBox.warning( + parent, "Attention!", "".join(error_message)) + return e + + # Create dialog + dialog = TimelinePopupWindow(parent, i) + dialog.setModal(True) # to disable main application window + + # If dialog is accepted, update dict info + if dialog._ui.exec_() == dialog.Accepted: + # Update label + if dialog.behavior is not None: + self._time._tracks[i].title = dialog.behavior + + # Update color + if self._time._tracks[i].color != dialog.color: + for delta in self._time._tracks[i].periods: + delta.color = dialog.color + self._time._tracks[i].color = dialog.color + self._time.repaint() + else: + pass + + # Restore timeline default color + parent.color = timeline_default_color + + def __lockSelected(self): + self._time.lockSelected() + + def __removeSelected(self): + self._time.removeSelected() + + def __import(self): + """Import annotations from a file.""" + if not hasattr(self, '_import_window'): self._import_window = ImportWindow(self) + self._import_window.show() + + def __export(self): + """Export annotations to a file.""" + + filename, ffilter = QFileDialog.getSaveFileName(parent=self, + caption="Export annotations file", + directory="untitled.csv", + filter="CSV Files (*.csv);;CSV Matrix Files (*.csv)", + options=QFileDialog.DontUseNativeDialog) + + filename = str(filename) + ffilter = str(ffilter) + if filename != "": + with open(filename, 'w') as csvfile: + spamwriter = csv.writer(csvfile, dialect='excel') + if ffilter == 'CSV Files (*.csv)': + self._time.export_csv(spamwriter) + elif ffilter == 'CSV Matrix Files (*.csv)': + self._time.export_2_csv_matrix(spamwriter) + + def __export_2_csv_matrix(self): + QMessageBox.warning( + self, "Important!", 'Please note that this file cannot be imported after.') + + filename, _ = QFileDialog.getSaveFileName(parent=self, + caption="Export matrix file", + directory="untitled.csv", + filter="CSV Files (*.csv)", + options=QFileDialog.DontUseNativeDialog) + if filename != "": + with open(filename, 'w') as csvfile: + spamwriter = csv.writer(csvfile, dialect='excel') + self._time.export_2_csv_matrix(spamwriter) + + def __cleanLine(self): + reply = QMessageBox.question(self, 'Confirm', + "Are you sure you want to clean all the events on this track?", + QMessageBox.Yes | + QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + self._time.cleanLine(self._time.current_mouseover_track) + + def __cleanCharts(self): + reply = QMessageBox.question(self, 'Confirm', + "Are you sure you want to clean all the charts?", QMessageBox.Yes | + QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + self._time.cleanCharts() + + def clean(self): + reply = QMessageBox.question(self, 'Confirm', + "Are you sure you want to clean everything?", QMessageBox.Yes | + QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + self._time.clean() + + def __pickColor(self): + self._time.color = QColorDialog.getColor(self._time.color) + if self._time._selected != None: + self._time._selected.color = self._time.color + self._time.repaint() + + def __scaleSliderChange(self, value): + scale = 0.1 * value + self._time.setMinimumWidth(scale * self._max) + self._time.scale = scale + self._zoomLabel.setText(str(value * 10).zfill(3) + "%") + + def __scrollAreaKeyReleaseEvent(self, event): + modifiers = int(event.modifiers()) + self._time.keyReleaseEvent(event) + if modifiers is not QtCore.Qt.ControlModifier and \ + modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ + modifiers is not QtCore.Qt.ShiftModifier: + QScrollArea.keyReleaseEvent(self._scrollArea, event) + + def __scrollAreaKeyPressEvent(self, event): + modifiers = int(event.modifiers()) + if modifiers is not QtCore.Qt.ControlModifier and \ + modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ + modifiers is not QtCore.Qt.ShiftModifier: + QScrollArea.keyPressEvent(self._scrollArea, event) diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelineChart.py b/pyforms/gui/controls/control_event_timeline/TimelineChart.py similarity index 89% rename from pyforms/gui/Controls/ControlEventTimeline/TimelineChart.py rename to pyforms/gui/controls/control_event_timeline/TimelineChart.py index 319cb19..a5cf8cf 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/TimelineChart.py +++ b/pyforms/gui/controls/control_event_timeline/TimelineChart.py @@ -1,13 +1,6 @@ # !/usr/bin/python # -*- coding: utf-8 -*- - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - -else: - from PyQt4.QtGui import QColor +from AnyQt.QtGui import QColor class TimelineChart(object): @@ -124,21 +117,21 @@ def draw(self, painter, left, right, top, bottom): painter.setPen(self._color) painter.setOpacity(0.7) - fov_height = (bottom - top) * self._zoom - start = self._widget.x2frame(left) - end = self._widget.x2frame(right) - end = len(self) if end > len(self) else end - diff_max_min = (self._graph_max - self._graph_min) + fov_height = (bottom - top) * self.zoom #calculate the height visible + start = self._widget.x2frame(left) #calculate the start frame to draw + end = self._widget.x2frame(right) #calculate the end frame to draw + end = len(self) if end > len(self) else end #check if the end frame his higher than the available data + diff_max_min = (self._graph_max - self._graph_min) #calculate the difference bettween the lower and higher value top = (-self._graph_min if self._graph_min > 0 else abs(self._graph_min)) * self._zoom if diff_max_min <= 0: diff_max_min = 1 - last_coordenate = None + last_coordenate = None last_real_x_coord = None for i, y in enumerate(self._data[start:end]): - if y: + if y is not None: x = i + start if y == None: continue y = self._top + ((top + y) * fov_height) // diff_max_min diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelineDelta.py b/pyforms/gui/controls/control_event_timeline/TimelineDelta.py old mode 100755 new mode 100644 similarity index 91% rename from pyforms/gui/Controls/ControlEventTimeline/TimelineDelta.py rename to pyforms/gui/controls/control_event_timeline/TimelineDelta.py index 1ac8f5d..202f2f0 --- a/pyforms/gui/Controls/ControlEventTimeline/TimelineDelta.py +++ b/pyforms/gui/controls/control_event_timeline/TimelineDelta.py @@ -1,19 +1,13 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf +from AnyQt.QtWidgets import QInputDialog +from AnyQt.QtGui import QColor -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QInputDialog - from PyQt5.QtGui import QColor - -else: - from PyQt4.QtGui import QColor, QInputDialog - -from pyforms.gui.Controls.ControlEventTimeline.Track import Track +from pyforms.gui.controls.control_event_timeline.Track import Track from pyforms import BaseWidget -from pyforms.Controls import ControlText, ControlNumber, ControlButton +from pyforms.controls import ControlText, ControlNumber, ControlButton class DeltaEditWindow(BaseWidget): @@ -21,14 +15,11 @@ class DeltaEditWindow(BaseWidget): def __init__(self, parent_win, label, begin, end): BaseWidget.__init__(self, 'Edit frame', parent_win=parent_win) - if conf.PYFORMS_USE_QT5: - self.layout().setContentsMargins(5,5,5,5) - else: - self.layout().setMargin(5) + self.set_margin(5) - self._label = ControlText('Label', label) - self._begin = ControlNumber('Begin', begin, 0, 100000000000000) - self._end = ControlNumber('End', end, 0, 100000000000000) + self._label = ControlText('Label', default=label) + self._begin = ControlNumber('Begin', default=begin, minimum=0, maximum=100000000000000) + self._end = ControlNumber('End', default=end, minimum=0, maximum=100000000000000) self._applybtn = ControlButton('Apply') @@ -42,12 +33,16 @@ def __init__(self, parent_win, label, begin, end): self._end.changed_event = self.__end_changed_event def __begin_changed_event(self): - if self._begin.value>=self._end.value: + if not hasattr(self, '_updating') and self._begin.value>=self._end.value: + self._updating = True self._begin.value = self._end.value-1 + del self._updating def __end_changed_event(self): - if self._end.value<=self._begin.value: + if not hasattr(self, '_updating') and self._end.value<=self._begin.value: + self._updating = True self._end.value = self._begin.value+1 + del self._updating @property @@ -109,7 +104,7 @@ def checkNumberOfTracks(self): """ if self.track >= (self._parent.numberoftracks - 1): for i in range(self._parent.numberoftracks - 1, self.track + 1): - self._parent.addTrack() + self._parent.add_track() def collide(self, x, y): """ @@ -325,7 +320,7 @@ def track(self, value): # Verify if the new track exists. In case not create it self._top = Track.whichTop(value) - if self.track >= len(self._parent._tracks): self._parent.addTrack() + if self.track >= len(self._parent._tracks): self._parent.add_track() # if do not exists in the track add it if self not in self._parent._tracks[self.track].periods: self._parent._tracks[self.track].periods.append(self) diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelinePointer.py b/pyforms/gui/controls/control_event_timeline/TimelinePointer.py old mode 100755 new mode 100644 similarity index 92% rename from pyforms/gui/Controls/ControlEventTimeline/TimelinePointer.py rename to pyforms/gui/controls/control_event_timeline/TimelinePointer.py index 22d7c9d..ab9f6c4 --- a/pyforms/gui/Controls/ControlEventTimeline/TimelinePointer.py +++ b/pyforms/gui/controls/control_event_timeline/TimelinePointer.py @@ -1,15 +1,8 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QColor - from PyQt4 import QtCore +from AnyQt.QtGui import QColor +from AnyQt import QtCore class TimelinePointer(object): diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelinePopupWindow.py b/pyforms/gui/controls/control_event_timeline/TimelinePopupWindow.py similarity index 93% rename from pyforms/gui/Controls/ControlEventTimeline/TimelinePopupWindow.py rename to pyforms/gui/controls/control_event_timeline/TimelinePopupWindow.py index 863d453..42d8587 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/TimelinePopupWindow.py +++ b/pyforms/gui/controls/control_event_timeline/TimelinePopupWindow.py @@ -1,17 +1,13 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QDialog, QInputDialog, QColorDialog - from PyQt5.QtGui import QColor, QPixmap, QFont, QPainter - from PyQt5 import uic - from PyQt5 import QtCore -else: - from PyQt4.QtGui import QDialog, QInputDialog, QColor, QPixmap, QColorDialog, QPainter, QFont - from PyQt4 import uic - from PyQt4 import QtCore +from pyforms.utils.settings_manager import conf + +from AnyQt.QtWidgets import QDialog, QInputDialog, QColorDialog +from AnyQt.QtGui import QColor, QPixmap, QFont, QPainter +from AnyQt import uic +from AnyQt import QtCore + import pyforms.utils.tools as tools diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelinePopupWindow.ui b/pyforms/gui/controls/control_event_timeline/TimelinePopupWindow.ui similarity index 100% rename from pyforms/gui/Controls/ControlEventTimeline/TimelinePopupWindow.ui rename to pyforms/gui/controls/control_event_timeline/TimelinePopupWindow.ui diff --git a/pyforms/gui/Controls/ControlEventTimeline/TimelineWidget.py b/pyforms/gui/controls/control_event_timeline/TimelineWidget.py old mode 100755 new mode 100644 similarity index 94% rename from pyforms/gui/Controls/ControlEventTimeline/TimelineWidget.py rename to pyforms/gui/controls/control_event_timeline/TimelineWidget.py index 5e98ad0..157d61a --- a/pyforms/gui/Controls/ControlEventTimeline/TimelineWidget.py +++ b/pyforms/gui/controls/control_event_timeline/TimelineWidget.py @@ -1,21 +1,15 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QMessageBox - from PyQt5.QtGui import QColor, QPainter, QFont, QCursor - from PyQt5 import QtCore +from AnyQt.QtWidgets import QWidget, QMessageBox +from AnyQt.QtGui import QColor, QPainter, QFont, QCursor +from AnyQt import QtCore -else: - from PyQt4.QtGui import QWidget, QColor, QMessageBox, QPainter, QFont, QCursor - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlEventTimeline.Track import Track -from pyforms.gui.Controls.ControlEventTimeline.TimelinePointer import TimelinePointer -from pyforms.gui.Controls.ControlEventTimeline.TimelineDelta import TimelineDelta -from pyforms.gui.Controls.ControlEventTimeline.TimelineChart import TimelineChart +from pyforms.gui.controls.control_event_timeline.Track import Track +from pyforms.gui.controls.control_event_timeline.TimelinePointer import TimelinePointer +from pyforms.gui.controls.control_event_timeline.TimelineDelta import TimelineDelta +from pyforms.gui.controls.control_event_timeline.TimelineChart import TimelineChart class TimelineWidget(QWidget): @@ -194,10 +188,10 @@ def import_csv(self, csvfileobject): for row in csvfileobject: if len(row) == 0: continue if row[0] == "T": - track = self.addTrack() + track = self.add_track() track.properties = row elif row[0] == "P": - period = self.addPeriod([0, 1, '-']) + period = self.add_period([0, 1, '-']) period.properties = row def export_csv(self, csvfileobject): @@ -275,16 +269,16 @@ def cleanLine(self, track_index=None): QMessageBox.about(self, "Error", "You must select a timebar first") return - def addTrack(self): + def add_track(self): t = Track(parent=self) self._tracks.append(t) self.setMinimumHeight(Track.whichTop(len(self._tracks))) return t - def addPeriod(self, value, track=0, color=None): + def add_period(self, value, row=0, color=None): """Adds an annotated interval.""" begin, end, title = value - period = TimelineDelta(begin, end, title=title, parent=self, top=Track.whichTop(track)) + period = TimelineDelta(begin, end, title=title, parent=self, top=Track.whichTop(row)) self._tracks[period.track].periods.append(period) return period @@ -429,7 +423,7 @@ def keyReleaseEvent(self, event): comment = "" if end > start: - self.addPeriod((start, end, comment), self._selected_track) + self.add_period((start, end, comment), self._selected_track) self.repaint() self._creating_event = False else: @@ -568,7 +562,7 @@ def numberoftracks(self, value): # self._n_tracks = value if value < len(self._tracks): for i in range(value, self._tracks + 1): - self.addTrack() + self.add_track() y = value * 34 + 20 if y + 40 > self.height(): self.setMinimumHeight(y + 40) diff --git a/pyforms/gui/Controls/ControlEventTimeline/Track.py b/pyforms/gui/controls/control_event_timeline/Track.py similarity index 92% rename from pyforms/gui/Controls/ControlEventTimeline/Track.py rename to pyforms/gui/controls/control_event_timeline/Track.py index 16b188e..c44e187 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/Track.py +++ b/pyforms/gui/controls/control_event_timeline/Track.py @@ -1,16 +1,8 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QColor - from PyQt4 import QtCore - +from AnyQt.QtGui import QColor +from AnyQt import QtCore class Track(object): """ diff --git a/pyforms/gui/Controls/ControlEventTimeline/__init__.py b/pyforms/gui/controls/control_event_timeline/__init__.py similarity index 89% rename from pyforms/gui/Controls/ControlEventTimeline/__init__.py rename to pyforms/gui/controls/control_event_timeline/__init__.py index 5a0c3bb..956e9c5 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/__init__.py +++ b/pyforms/gui/controls/control_event_timeline/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" pyforms.gui.Controls.ControlEventTimeline.__init__ +""" pyforms.gui.controls.ControlEventTimeline.__init__ """ diff --git a/pyforms/gui/controls/control_event_timeline/graphs_eventsgenerator.py b/pyforms/gui/controls/control_event_timeline/graphs_eventsgenerator.py new file mode 100644 index 0000000..5aac17e --- /dev/null +++ b/pyforms/gui/controls/control_event_timeline/graphs_eventsgenerator.py @@ -0,0 +1,131 @@ +import csv, sys +from pyforms import BaseWidget +from pyforms.controls import ControlProgress +from pyforms.controls import ControlButton +from pyforms.controls import ControlList +from pyforms.controls import ControlNumber +from pyforms.controls import ControlSlider +from pyforms.controls import ControlTextArea +from pyforms.controls import ControlText +from pyforms.controls import ControlNumber +from pyforms.controls import ControlLabel +import traceback +import time +import datetime + +from pyforms.utils.settings_manager import conf +from AnyQt.QtWidgets import QMessageBox + + + +class GraphsEventsGenerator(BaseWidget): + + def __init__(self, timeline=None): + super(GraphsEventsGenerator, self).__init__('Apply a function to the graph values', parent_win=timeline) + self.setContentsMargins(10, 10, 10, 10) + self._timeline = timeline + + + # Definition of the forms fields + self._graphs_list = ControlList('Graphs list (try double click)', readonly=False, select_entire_row=True) + self._equation = ControlTextArea('Equation') + self._graphname = ControlText('Graph name') + self._genevts_btn = ControlButton('Generate graph') + + + self._formset = [ + ( + ['_graphs_list'], + '||', + [ + '_graphname', + '_equation', + '_genevts_btn', + ] + ), + ] + + self._graphs_list.cell_double_clicked_event = self.__cell_double_clicked_evt + self._graphs_list.readonly = True + self._graphs_list.select_entire_row = True + self._genevts_btn.value = self.__generage_events_evt + + + + def __add__(self, other): + self._graphs_list += [other.name] + return self + + def __sub__(self, other): + self._graphs_list -= other + return self + + def rename_graph(self, graph_index, newname): + self._graphs_list.set_value(graph_index, 0, newname) + + @property + def graphs(self): + return self._timeline._charts + + def show(self): + super(GraphsEventsGenerator, self).show() + + if len(self._graphname.value.strip())==0: + self._graphname.value = "generated-graph-{0}".format( len(self._timeline.graphs)) + + + + def __cell_double_clicked_evt(self, row, column): + if len(self._equation.value.strip())==0: + self._equation.value += '[{0}]'.format( self._graphs_list.value[row][column] ) + else: + self._equation.value += ' and [{0}]'.format( self._graphs_list.value[row][column] ) + + + def __generage_events_evt(self): + if len(self._graphname.value.strip())==0: + QMessageBox.warning( + self, "Attention!", 'The graph name cannot be empty') + return + + if len(self._equation.value.strip())==0: + QMessageBox.warning( + self, "Attention!", 'The equation cannot be empty') + return + + max_frame = 0 + equation = self._equation.value + for i, values in enumerate(self._graphs_list.value): + graphname = '[{0}]'.format(values[0]) + + if graphname in equation: max_frame = max(max_frame, len(self.graphs[i])) + + equation = equation.replace(graphname, 'graphs[{0}][i]'.format(i) ) + + graphs = self.graphs + data = [] + try: + for i in range(max_frame): + + try: + value = eval(equation) + except: + value = None + + data.append( (i, value) ) + self._timeline.add_chart(self._graphname.value, data) + + self._graphname.value = "generated-graph-{0}".format( len(self._timeline.graphs)) + except Exception as e: + traceback.print_exc() + QMessageBox.warning( self, "Error!", str(e) ) + + +################################################################################################################## +################################################################################################################## +################################################################################################################## + +# Execute the application +if __name__ == "__main__": + import pyforms + pyforms.start_app(GraphsEventsGenerator, geometry=(0, 0, 600, 400)) diff --git a/pyforms/gui/Controls/ControlEventTimeline/graphs_graph2event.py b/pyforms/gui/controls/control_event_timeline/graphs_graph2event.py similarity index 84% rename from pyforms/gui/Controls/ControlEventTimeline/graphs_graph2event.py rename to pyforms/gui/controls/control_event_timeline/graphs_graph2event.py index afc2793..d5f219b 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/graphs_graph2event.py +++ b/pyforms/gui/controls/control_event_timeline/graphs_graph2event.py @@ -1,26 +1,14 @@ -import csv, sys -from pyforms import BaseWidget -from pyforms.Controls import ControlProgress -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlList -from pyforms.Controls import ControlNumber -from pyforms.Controls import ControlSlider -from pyforms.Controls import ControlTextArea -from pyforms.Controls import ControlText -from pyforms.Controls import ControlNumber -from pyforms.Controls import ControlLabel - -import time -import datetime - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QMessageBox -else: - from PyQt4.QtGui import QMessageBox - - +from pyforms import BaseWidget +from pyforms.controls import ControlProgress +from pyforms.controls import ControlButton +from pyforms.controls import ControlList +from pyforms.controls import ControlNumber +from pyforms.controls import ControlSlider +from pyforms.controls import ControlTextArea +from pyforms.controls import ControlText +from pyforms.controls import ControlNumber +from pyforms.controls import ControlLabel +from AnyQt.QtWidgets import QMessageBox class Graph2Event(BaseWidget): diff --git a/pyforms/gui/Controls/ControlEventTimeline/graphs_properties.py b/pyforms/gui/controls/control_event_timeline/graphs_properties.py similarity index 81% rename from pyforms/gui/Controls/ControlEventTimeline/graphs_properties.py rename to pyforms/gui/controls/control_event_timeline/graphs_properties.py index 0fe89e8..ba8de0a 100644 --- a/pyforms/gui/Controls/ControlEventTimeline/graphs_properties.py +++ b/pyforms/gui/controls/control_event_timeline/graphs_properties.py @@ -1,12 +1,16 @@ import csv, time, datetime, decimal, logging, sys, numpy as np from pyforms import BaseWidget -from pyforms.Controls import ControlProgress -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlList -from pyforms.Controls import ControlNumber -from pyforms.Controls import ControlSlider -from pyforms.Controls import ControlText -from pyforms.Controls import ControlLabel +from pyforms.controls import ControlProgress +from pyforms.controls import ControlButton +from pyforms.controls import ControlList +from pyforms.controls import ControlNumber +from pyforms.controls import ControlSlider +from pyforms.controls import ControlText +from pyforms.controls import ControlLabel +from pyforms.utils.settings_manager import conf + +if conf.PYFORMS_MODE=='GUI': + from AnyQt.QtWidgets import QColorDialog logger = logging.getLogger(__name__) @@ -24,12 +28,13 @@ def __init__(self, timelineWidget=None, parent_win=None): # Definition of the forms fields self._graphs_list = ControlList('Graphs list') self._name = ControlText('Name') - self._min_value = ControlNumber('Min', 0, -sys.float_info.max, sys.float_info.max) - self._max_value = ControlNumber('Max', 0, -sys.float_info.max, sys.float_info.max) - self._values_zoom = ControlSlider('Amplitude', 100, 60, 400) - self._values_top = ControlNumber('Bottom', 0, -1000, 1000) + self._min_value = ControlNumber('Min', default=0, minimum=-sys.float_info.max, maximum=sys.float_info.max) + self._max_value = ControlNumber('Max', default=0, minimum=-sys.float_info.max, maximum=sys.float_info.max) + self._values_zoom = ControlSlider('Amplitude', default=100, minimum=60, maximum=400) + self._values_top = ControlNumber('Bottom', default=0, minimum=-1000, maximum=1000) self._remove_graph_btn = ControlButton('Remove graph') self._value = ControlLabel() + self._pickcolor = ControlButton('Pick color', default=self.__pickcolor_evt) self._graphs_list.readonly = True @@ -41,7 +46,7 @@ def __init__(self, timelineWidget=None, parent_win=None): ' ', '_name', ('_min_value', '_max_value', ' '), - ('_values_top', ' '), + ('_values_top', '_pickcolor'), '_values_zoom', 'info:Choose one graph and move the mouse over \nthe timeline to visualize the coordenates.', '_value' @@ -66,6 +71,7 @@ def __init__(self, timelineWidget=None, parent_win=None): self._values_zoom.enabled = False self._values_top.enabled = False self._remove_graph_btn.enabled = False + self._pickcolor.enabled = False self._remove_graph_btn.value = self.__remove_chart @@ -97,6 +103,13 @@ def show(self): super(GraphsProperties, self).show() self._loaded = False + def __pickcolor_evt(self): + color = QColorDialog.getColor( + self._current_selected_graph._color, + self, 'Pick a color for the graph') + self._current_selected_graph._color = color + self._timeline.repaint() + def __remove_chart(self): index = self._graphs_list.selected_row_index @@ -110,6 +123,7 @@ def __remove_chart(self): self._values_zoom.enabled = False self._values_top.enabled = False self._remove_graph_btn.enabled = False + self._pickcolor.enabled = False self._timeline.repaint() self._mainwindow -= index @@ -143,6 +157,7 @@ def __graphs_list_selection_changed(self): self._values_zoom.enabled = True self._values_top.enabled = True self._remove_graph_btn.enabled = True + self._pickcolor.enabled = True self._current_selected_graph = graph del self._updating_properties diff --git a/pyforms/gui/controls/control_event_timeline/import_window.py b/pyforms/gui/controls/control_event_timeline/import_window.py new file mode 100644 index 0000000..accc474 --- /dev/null +++ b/pyforms/gui/controls/control_event_timeline/import_window.py @@ -0,0 +1,275 @@ +import csv +from pyforms import BaseWidget +from pyforms.controls import ControlProgress +from pyforms.controls import ControlCombo +from pyforms.controls import ControlButton +from pyforms.controls import ControlEmptyWidget +from pyforms.controls import ControlFile +from pyforms.controls import ControlNumber + + +import time +import datetime +import dateutil.parser + +import logging + +logger=logging.getLogger(__file__) + +class BonsaiImportFileDlg(BaseWidget): + + def __init__(self, timeline=None): + super(BonsaiImportFileDlg, self).__init__('Import file') + + self._file = ControlFile('File to import') + self._fps = ControlNumber('Video FPS', default=30) + self._startframe = ControlNumber('Initial frame', visible=False, maximum=100000) + + + self._formset = [('_startframe','_fps', '_file')] + + #self._file.value = '/home/ricardo/Desktop/rat1e2.csv' + + +class ImportWindow(BaseWidget): + + def __init__(self, timeline=None): + super(ImportWindow, self).__init__('Import file', parent_win=timeline) + self.setContentsMargins(10, 10, 10, 10) + self._timeline = timeline + + # Definition of the forms fields + self._filetype = ControlCombo('Please select the type of file you would like to import:') + self._importButton = ControlButton('Import') + self._panel = ControlEmptyWidget('Panel') + self._file = ControlFile('File to import') + + self._panel.value = self._file + self._filetype.add_item('Events file', 0) + self._filetype.add_item('Graph file', 1) + self._filetype.add_item('Bonsai events file', 2) + self._filetype.add_item('Bonsai events file (old format)', 3) + + self._formset = [ + ('_filetype', ' '), + '_panel', + (' ', '_importButton'), + ' ' + ] + + self._filetype.changed_event = self.__fileTypeChanged + self._importButton.value = self.__importData + + from pyforms.gui.dialogs.csv_parser import CsvParserDialog + self._graphCsvParserDlg = CsvParserDialog() + self._graphCsvParserDlg.xField.label = "Value column" + self._graphCsvParserDlg.yField.hide() + self._graphCsvParserDlg.zField.hide() + self._graphCsvParserDlg.loadButton.hide() + + self._bonsai_import_dlg = BonsaiImportFileDlg() + + def __fileTypeChanged(self): + if self._filetype.value == 0: + self._panel.value = self._file + + elif self._filetype.value == 1: + self._panel.value = self._graphCsvParserDlg + + elif self._filetype.value == 2: + self._panel.value = self._bonsai_import_dlg + self._bonsai_import_dlg._startframe.hide() + + elif self._filetype.value == 3: + self._panel.value = self._bonsai_import_dlg + self._bonsai_import_dlg._startframe.show() + + + + + + + + + + + + + + def __importData(self): + if self._filetype.value == 0: + separator = ',' + with open(self._file.value, 'rU') as csvfile: + line = csvfile.readline() + if ";" in line: + separator = ';' + with open(self._file.value, 'rU') as csvfile: + csvfile = csv.reader(csvfile, delimiter=separator) + self._timeline._time.import_csv(csvfile) + + elif self._filetype.value == 1: + self._timeline._time.importchart_csv(self._graphCsvParserDlg) + self._timeline.show_graphs_properties() + + elif self._filetype.value == 2: + self.__import_bonsai_events() + elif self._filetype.value == 3: + self.__import_bonsai_events_oldformat() + + self._timeline.repaint() + self._timeline._time.repaint() + self.close() # pylint: disable=no-member + + + def import_chart(self, filename, frame_col=0, val_col=1): + self._filetype.value = 1 + self._graphCsvParserDlg.filename = filename + self._graphCsvParserDlg.frameColumn = frame_col + self._graphCsvParserDlg.xColumn = val_col + + + + def __import_bonsai_events(self): + + with open(self._bonsai_import_dlg._file.value, 'rU') as csvfile: + values = [] + pointEventValues = [] + csvfile = csv.reader(csvfile, delimiter=' ') + for row in csvfile: # strip Start/End word from all events names which are not PointEven + + try: + timestr = row[1].rstrip('0') + cvttime = datetime.datetime.strptime(timestr, "%H:%M:%S.%f") + except: + timestr = row[1] + try: + print(timestr) + cvttime = datetime.datetime.strptime(timestr, "%H:%M:%S") + except: + timestr = timestr.replace('T', ' ') + print(timestr) + cvttime = dateutil.parser.parse(timestr) + + seconds = (cvttime - datetime.datetime(1900, 1, 1)).total_seconds() + frame = int(round(self._bonsai_import_dlg._fps.value * seconds)) + + if row[2] == "PointEvent": + eventtype = row[0] + pointEventValues.append([eventtype, frame, row[2]]) + else: + if row[0].startswith('Start'): + eventtype = row[0][len('Start'):] # strip Start word from the beginning + else: + eventtype = row[0][len('End'):] # strip End word from the beginning + values.append([eventtype, frame, row[2]]) + + values = sorted(values, key=lambda x: (x[0].capitalize(), x[1])) + pointEventValues = sorted(pointEventValues, key=lambda x: (x[0].capitalize(), x[1])) + ntracks = len(set([x[0] for x in values])) + 1 + + # collapse + events = [] + eventsTypes = {} # Events names + currentTrack = 0 + for index in range(0, len(pointEventValues)): + pointEventValue = pointEventValues[index] + eventsTypes[pointEventValue[0]] = currentTrack + self._timeline.add_period([pointEventValue[1], pointEventValue[1] + 50, pointEventValue[0]], row=currentTrack) + + currentTrack = 1 + + for index in range(0, len(values), 2): + row0 = values[index] + row1 = values[index + 1] + + if row0[0] not in eventsTypes: + eventsTypes[row0[0]] = currentTrack + track = currentTrack + currentTrack += 1 + else: + track = eventsTypes[row0[0]] + + self._timeline.add_period([row0[1], row1[1], row0[0]], row=track) + + + + + def __import_bonsai_events_oldformat(self): + + try: + with open(self._bonsai_import_dlg._file.value, 'rU') as csvfile: + windows_events = [] + points_events = [] + first_date = None + + initial_frame = int(self._bonsai_import_dlg._startframe.value) + + open_windows = {} + for row in csvfile: + row = row[:-1] #remove the newline character + + if row.endswith('WindowClosing'): + split = row.rfind(' ') + #eventtype = row[-split:] + eventtype = 'end' + timestr = row[split-33:split] + eventname = row[3:split-33].strip() + elif row.endswith('PointEvent'): + split = row.rfind(' ') + eventtype = row[split+1:] + timestr = row[split-33:split] + eventname = row[:split-33] + else: + eventtype = 'start' + timestr = row[-33:] + eventname = row[5:-33].strip() + + cvttime = dateutil.parser.parse(timestr.replace('T', ' ')) + cvttime = cvttime.replace(tzinfo=None) + if first_date is None: first_date = cvttime + + seconds = (cvttime - first_date).total_seconds() + frame = int(round(self._bonsai_import_dlg._fps.value * seconds)) + initial_frame + + if eventtype=='PointEvent': + points_events.append([eventtype, frame, eventname]) + else: + if eventtype=='start': + open_windows[eventname] = frame + elif eventtype=='end': + begin_frame = open_windows[eventname] + del open_windows[eventname] + windows_events.append( [eventtype, (begin_frame, frame) , eventname] ) + + + windows_events = sorted(windows_events, key=lambda x: (x[0].capitalize(), x[2], x[1])) + points_events = sorted(points_events, key=lambda x: (x[0].capitalize(), x[1])) + ntracks = len(set([x[0][1] for x in windows_events])) + 1 + + # collapse + events = [] + events_types = {} # Events names + current_track = 0 + for index in range(0, len(points_events)): + eventtype, frame, eventname = points_events[index] + events_types[eventname] = current_track + self._timeline.add_period([frame, frame+2, eventname], row=current_track) + + current_track = 1 + + for event in windows_events: + eventtype, (frame_begin, frame_end), eventname = event + + if eventname not in events_types: + events_types[eventname] = current_track + track = current_track + current_track += 1 + else: + track = events_types[eventname] + + self._timeline.add_period([frame_begin, frame_end, eventname], row=track) + + + except Exception as e: + self.warning("An error occurred when trying to import the file. Please check the logs.") + logger.error(e, exc_info=True) \ No newline at end of file diff --git a/pyforms/gui/Controls/ControlEventsGraph/ControlEventsGraph.py b/pyforms/gui/controls/control_events_graph/ControlEventsGraph.py old mode 100755 new mode 100644 similarity index 88% rename from pyforms/gui/Controls/ControlEventsGraph/ControlEventsGraph.py rename to pyforms/gui/controls/control_events_graph/ControlEventsGraph.py index 072503a..04938ca --- a/pyforms/gui/Controls/ControlEventsGraph/ControlEventsGraph.py +++ b/pyforms/gui/controls/control_events_graph/ControlEventsGraph.py @@ -2,20 +2,10 @@ # -*- coding: utf-8 -*- import csv - -from pysettings import conf - -from pyforms.gui.Controls.ControlBase import ControlBase -from pyforms.gui.Controls.ControlEventsGraph.EventsWidget import EventsWidget - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QScrollBar, QFileDialog - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QWidget, QVBoxLayout, QScrollArea, QScrollBar, QFileDialog - from PyQt4 import QtCore - +from pyforms.gui.controls.ControlBase import ControlBase +from pyforms.gui.controls.control_events_graph.EventsWidget import EventsWidget +from AnyQt.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QScrollBar, QFileDialog +from AnyQt import QtCore, _api class ControlEventsGraph(ControlBase, QWidget): """ @@ -38,9 +28,9 @@ def __init__(self, label="", default=0, min=0, max=100, **kwargs): def init_form(self): vlayout = QVBoxLayout() - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: vlayout.setContentsMargins(0,0,0,0) - else: + elif _api.USED_API == _api.QT_API_PYQT4: vlayout.setMargin(0) self.setLayout(vlayout) @@ -95,7 +85,7 @@ def get_export_filename(self): return "untitled.csv" def __export(self): """Export annotations to a file.""" - filename = QFileDialog.getSaveFileName(parent=self, + filename, _ = QFileDialog.getSaveFileName(parent=self, caption="Export annotations file", directory=self.get_export_filename(), filter="CSV Files (*.csv)", diff --git a/pyforms/gui/Controls/ControlEventsGraph/Event.py b/pyforms/gui/controls/control_events_graph/Event.py similarity index 92% rename from pyforms/gui/Controls/ControlEventsGraph/Event.py rename to pyforms/gui/controls/control_events_graph/Event.py index a8c577a..38af3c0 100644 --- a/pyforms/gui/Controls/ControlEventsGraph/Event.py +++ b/pyforms/gui/controls/control_events_graph/Event.py @@ -1,15 +1,8 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - -else: - from PyQt4.QtGui import QColor - -from pyforms.gui.Controls.ControlEventsGraph.Track import Track +from AnyQt.QtGui import QColor +from pyforms.gui.controls.control_events_graph.Track import Track class Event(object): diff --git a/pyforms/gui/Controls/ControlEventsGraph/EventsWidget.py b/pyforms/gui/controls/control_events_graph/EventsWidget.py old mode 100755 new mode 100644 similarity index 94% rename from pyforms/gui/Controls/ControlEventsGraph/EventsWidget.py rename to pyforms/gui/controls/control_events_graph/EventsWidget.py index f5d82f4..77a012f --- a/pyforms/gui/Controls/ControlEventsGraph/EventsWidget.py +++ b/pyforms/gui/controls/control_events_graph/EventsWidget.py @@ -2,21 +2,14 @@ # -*- coding: utf-8 -*- import sys +from pyforms.utils.settings_manager import conf +from AnyQt.QtWidgets import QWidget +from AnyQt.QtGui import QColor, QPainter, QFont +from AnyQt import QtCore -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QWidget - from PyQt5.QtGui import QColor, QPainter, QFont - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QWidget, QColor, QPainter, QFont - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlEventsGraph.Track import Track -from pyforms.gui.Controls.ControlEventsGraph.Pointer import Pointer -from pyforms.gui.Controls.ControlEventsGraph.Event import Event +from pyforms.gui.controls.control_events_graph.Track import Track +from pyforms.gui.controls.control_events_graph.Pointer import Pointer +from pyforms.gui.controls.control_events_graph.Event import Event class EventsWidget(QWidget): @@ -53,7 +46,7 @@ def __init__(self, scroll): #### HELPERS/FUNCTIONS ################################################### ########################################################################## - def clean(self): + def clear(self): """ Clean all events """ diff --git a/pyforms/gui/Controls/ControlEventsGraph/Pointer.py b/pyforms/gui/controls/control_events_graph/Pointer.py similarity index 91% rename from pyforms/gui/Controls/ControlEventsGraph/Pointer.py rename to pyforms/gui/controls/control_events_graph/Pointer.py index 771713c..548357f 100644 --- a/pyforms/gui/Controls/ControlEventsGraph/Pointer.py +++ b/pyforms/gui/controls/control_events_graph/Pointer.py @@ -1,16 +1,8 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - from PyQt5 import QtCore - -else: - from PyQt4.QtGui import QColor - from PyQt4 import QtCore - +from AnyQt.QtGui import QColor +from AnyQt import QtCore class Pointer(object): """ diff --git a/pyforms/gui/Controls/ControlEventsGraph/Track.py b/pyforms/gui/controls/control_events_graph/Track.py similarity index 94% rename from pyforms/gui/Controls/ControlEventsGraph/Track.py rename to pyforms/gui/controls/control_events_graph/Track.py index 028266a..2435321 100644 --- a/pyforms/gui/Controls/ControlEventsGraph/Track.py +++ b/pyforms/gui/controls/control_events_graph/Track.py @@ -1,16 +1,8 @@ # !/usr/bin/python # -*- coding: utf-8 -*- -from pysettings import conf - import bisect - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtGui import QColor - -else: - from PyQt4.QtGui import QColor - +from AnyQt.QtGui import QColor class Track(object): """ diff --git a/pyforms/gui/Controls/ControlEventsGraph/__init__.py b/pyforms/gui/controls/control_events_graph/__init__.py similarity index 88% rename from pyforms/gui/Controls/ControlEventsGraph/__init__.py rename to pyforms/gui/controls/control_events_graph/__init__.py index 161f236..b6df673 100644 --- a/pyforms/gui/Controls/ControlEventsGraph/__init__.py +++ b/pyforms/gui/controls/control_events_graph/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" pyforms.gui.Controls.ControlEventTimeline.__init__ +""" pyforms.gui.controls.ControlEventTimeline.__init__ """ diff --git a/pyforms/gui/Controls/ControlPlayer/AbstractGLWidget.py b/pyforms/gui/controls/control_player/AbstractGLWidget.py similarity index 93% rename from pyforms/gui/Controls/ControlPlayer/AbstractGLWidget.py rename to pyforms/gui/controls/control_player/AbstractGLWidget.py index 493509b..3d391b5 100644 --- a/pyforms/gui/Controls/ControlPlayer/AbstractGLWidget.py +++ b/pyforms/gui/controls/control_player/AbstractGLWidget.py @@ -1,38 +1,25 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" pyforms.gui.Controls.ControlPlayer.VideoGLWidget +""" pyforms.gui.controls.ControlPlayer.VideoGLWidget """ +import logging, cv2 -from pysettings import conf -import logging, math, pyforms.utils.tools as tools, time +from pyforms.utils.settings_manager import conf +from AnyQt import QtGui, QtCore, _api +from AnyQt.QtWidgets import QApplication -if conf.PYFORMS_USE_QT5: - from PyQt5 import QtGui - from PyQt5 import QtCore - from PyQt5.QtWidgets import QApplication -else: - from PyQt4 import QtGui - from PyQt4 import QtCore - from PyQt4.QtGui import QApplication - -import OpenGL.GL as GL +import OpenGL.GL as GL import OpenGL.GLU as GLU -from pysettings import conf -try: - import cv2 -except: - print("Warning: was not possible to import cv2 in ControlPlayer") - -__author__ = "Ricardo Ribeiro" -__credits__ = ["Ricardo Ribeiro"] -__license__ = "MIT" -__version__ = "0.0" -__maintainer__ = "Ricardo Ribeiro" -__email__ = "ricardojvr@gmail.com" -__status__ = "Development" +__author__ = "Ricardo Ribeiro" +__credits__ = ["Ricardo Ribeiro"] +__license__ = "MIT" +__version__ = "0.0" +__maintainer__ = "Ricardo Ribeiro" +__email__ = "ricardojvr@gmail.com" +__status__ = "Development" class MouseEvent: @@ -40,6 +27,7 @@ def __init__(self, event): self.x = event.x() self.y = event.y() self.button = event.button() + self.event = event class AbstractGLWidget(object): @@ -350,10 +338,10 @@ def wheelEvent(self, event): self._mouseX = event.x() self._mouseY = event.y() - if conf.PYFORMS_USE_QT5: + if _api.USED_API == _api.QT_API_PYQT5: p = event.angleDelta() delta = p.y() - else: + elif _api.USED_API == _api.QT_API_PYQT4: delta = event.delta() zoom_factor = delta / float(1500) @@ -455,39 +443,39 @@ def keyReleaseEvent(self, event): if event.key() == QtCore.Qt.Key_M: self._move_img = False if event.key() == QtCore.Qt.Key_1: - self._control.speed = 1 + self._control.next_frame_step = 1 self.show_tmp_msg('Speed: 1x') if event.key() == QtCore.Qt.Key_2: - self._control.speed = 2 + self._control.next_frame_step = 2 self.show_tmp_msg('Speed: 2x') if event.key() == QtCore.Qt.Key_3: - self._control.speed = 3 + self._control.next_frame_step = 3 self.show_tmp_msg('Speed: 3x') if event.key() == QtCore.Qt.Key_4: - self._control.speed = 4 + self._control.next_frame_step = 4 self.show_tmp_msg('Speed: 4x') if event.key() == QtCore.Qt.Key_5: - self._control.speed = 5 + self._control.next_frame_step = 5 self.show_tmp_msg('Speed: 5x') if event.key() == QtCore.Qt.Key_6: - self._control.speed = 6 + self._control.next_frame_step = 6 self.show_tmp_msg('Speed: 6x') if event.key() == QtCore.Qt.Key_7: - self._control.speed = 7 + self._control.next_frame_step = 7 self.show_tmp_msg('Speed: 7x') if event.key() == QtCore.Qt.Key_8: - self._control.speed = 8 + self._control.next_frame_step = 8 self.show_tmp_msg('Speed: 8x') if event.key() == QtCore.Qt.Key_9: - self._control.speed = 9 + self._control.next_frame_step = 9 self.show_tmp_msg('Speed: 9x') diff --git a/pyforms/gui/Controls/ControlPlayer/ControlPlayer.py b/pyforms/gui/controls/control_player/ControlPlayer.py similarity index 90% rename from pyforms/gui/Controls/ControlPlayer/ControlPlayer.py rename to pyforms/gui/controls/control_player/ControlPlayer.py index 466d76f..8f2492a 100755 --- a/pyforms/gui/Controls/ControlPlayer/ControlPlayer.py +++ b/pyforms/gui/controls/control_player/ControlPlayer.py @@ -1,56 +1,37 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" pyforms.gui.Controls.ControlPlayer.ControlPlayer +""" pyforms.gui.controls.ControlPlayer.ControlPlayer """ +__author__ = "Ricardo Ribeiro" +__credits__ = ["Ricardo Ribeiro"] +__license__ = "MIT" +__version__ = "0.0" +__maintainer__ = "Ricardo Ribeiro" +__email__ = "ricardojvr@gmail.com" +__status__ = "Development" -import math -import os -import logging +import logging, platform, os, math, cv2 -from pysettings import conf -if conf.PYFORMS_USE_QT5: - from PyQt5 import uic - from PyQt5 import QtCore - from PyQt5.QtWidgets import QFrame - from PyQt5.QtWidgets import QApplication +from pyforms.utils.settings_manager import conf +from AnyQt import uic, _api +from AnyQt import QtCore +from AnyQt.QtWidgets import QFrame +from AnyQt.QtWidgets import QApplication +from pyforms.gui.controls.ControlBase import ControlBase +if _api.USED_API == _api.QT_API_PYQT5: import platform - if platform.system() == 'Darwin': - from pyforms.gui.Controls.ControlPlayer.VideoQt5GLWidget import VideoQt5GLWidget as VideoGLWidget + from pyforms.gui.controls.control_player.VideoQt5GLWidget import VideoQt5GLWidget as VideoGLWidget else: - from pyforms.gui.Controls.ControlPlayer.VideoGLWidget import VideoGLWidget - -else: - from PyQt4.QtGui import QFrame - from PyQt4 import uic - from PyQt4 import QtCore - from PyQt4.QtGui import QApplication - from pyforms.gui.Controls.ControlPlayer.VideoGLWidget import VideoGLWidget - -from pyforms.gui.Controls.ControlBase import ControlBase -import pyforms.utils.tools as tools -from pysettings import conf - -try: - import cv2 -except: - print("Warning: was not possible to import cv2 in ControlPlayer") - - + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget +elif _api.USED_API == _api.QT_API_PYQT4: + from pyforms.gui.controls.control_player.VideoGLWidget import VideoGLWidget -__author__ = "Ricardo Ribeiro" -__credits__ = ["Ricardo Ribeiro"] -__license__ = "MIT" -__version__ = "0.0" -__maintainer__ = "Ricardo Ribeiro" -__email__ = "ricardojvr@gmail.com" -__status__ = "Development" - class ControlPlayer(ControlBase, QFrame): @@ -78,8 +59,6 @@ def init_form(self): self.videoPlay.setIcon(conf.PYFORMS_ICON_VIDEOPLAYER_PAUSE_PLAY) - - self._videoWidget = VideoGLWidget() self._videoWidget._control = self self.videoLayout.addWidget(self._videoWidget) @@ -227,6 +206,7 @@ def value(self): return ControlBase.value.fget(self) @value.setter def value(self, value): + self.form.setUpdatesEnabled(False) if value is None: self.stop() self.videoControl.setEnabled(False) @@ -254,6 +234,7 @@ def value(self, value): self.videoControl.setEnabled(True) self.refresh() + self.form.setUpdatesEnabled(True) ########################################################################## @@ -273,6 +254,8 @@ def __rotateZ(self): def update_frame(self): + self.form.setUpdatesEnabled(False) + if not self.visible: self.stop() if self.value is None: @@ -298,6 +281,9 @@ def update_frame(self): if self._updateVideoFrame: self.videoFrames.setValue(currentFrame) + self.form.setUpdatesEnabled(True) + + def videoPlay_clicked(self): """Slot for Play/Pause functionality.""" diff --git a/pyforms/gui/controls/control_player/VideoGLWidget.py b/pyforms/gui/controls/control_player/VideoGLWidget.py new file mode 100644 index 0000000..1cae8fa --- /dev/null +++ b/pyforms/gui/controls/control_player/VideoGLWidget.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" pyforms.gui.controls.ControlPlayer.VideoGLWidget + +""" + +from pyforms.gui.controls.control_player.AbstractGLWidget import AbstractGLWidget +from AnyQt.QtOpenGL import QGLWidget + +class VideoGLWidget(AbstractGLWidget, QGLWidget): pass \ No newline at end of file diff --git a/pyforms/gui/Controls/ControlPlayer/VideoQt5GLWidget.py b/pyforms/gui/controls/control_player/VideoQt5GLWidget.py similarity index 86% rename from pyforms/gui/Controls/ControlPlayer/VideoQt5GLWidget.py rename to pyforms/gui/controls/control_player/VideoQt5GLWidget.py index 5485d3f..0d16072 100644 --- a/pyforms/gui/Controls/ControlPlayer/VideoQt5GLWidget.py +++ b/pyforms/gui/controls/control_player/VideoQt5GLWidget.py @@ -1,9 +1,9 @@ -from pyforms.gui.Controls.ControlPlayer.AbstractGLWidget import AbstractGLWidget +from pyforms.gui.controls.control_player.AbstractGLWidget import AbstractGLWidget import math -from PyQt5 import QtGui -from PyQt5.QtWidgets import QOpenGLWidget -from PyQt5 import QtCore +from AnyQt import QtGui +from AnyQt.QtWidgets import QOpenGLWidget +from AnyQt import QtCore class VideoQt5GLWidget(AbstractGLWidget, QOpenGLWidget): diff --git a/pyforms/gui/Controls/ControlPlayer/__init__.py b/pyforms/gui/controls/control_player/__init__.py similarity index 100% rename from pyforms/gui/Controls/ControlPlayer/__init__.py rename to pyforms/gui/controls/control_player/__init__.py diff --git a/pyforms/gui/Controls/ControlPlayer/video.ui b/pyforms/gui/controls/control_player/video.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/ControlPlayer/video.ui rename to pyforms/gui/controls/control_player/video.ui diff --git a/pyforms/gui/Controls/fileInput.ui b/pyforms/gui/controls/fileInput.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/fileInput.ui rename to pyforms/gui/controls/fileInput.ui diff --git a/pyforms/gui/Controls/image.ui b/pyforms/gui/controls/image.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/image.ui rename to pyforms/gui/controls/image.ui diff --git a/pyforms/gui/Controls/label.ui b/pyforms/gui/controls/label.ui similarity index 100% rename from pyforms/gui/Controls/label.ui rename to pyforms/gui/controls/label.ui diff --git a/pyforms/gui/controls/list.ui b/pyforms/gui/controls/list.ui new file mode 100644 index 0000000..760ab06 --- /dev/null +++ b/pyforms/gui/controls/list.ui @@ -0,0 +1,219 @@ + + + ControlList + + + + 0 + 0 + 471 + 168 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 28 + + + + TextLabel + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + + 0 + 0 + + + + + 28 + 28 + + + + + 18 + 75 + true + + + + + + + + + 28 + 28 + + + + true + + + + + + + + 0 + 0 + + + + + 28 + 28 + + + + + 18 + 75 + true + + + + - + + + + 28 + 28 + + + + true + + + + + + + + + + false + + + Qt::NoPen + + + false + + + 100 + + + false + + + true + + + false + + + false + + + false + + + + + + + + diff --git a/pyforms/gui/Controls/number.ui b/pyforms/gui/controls/number.ui similarity index 100% rename from pyforms/gui/Controls/number.ui rename to pyforms/gui/controls/number.ui diff --git a/pyforms/gui/Controls/progressInput.ui b/pyforms/gui/controls/progressInput.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/progressInput.ui rename to pyforms/gui/controls/progressInput.ui diff --git a/pyforms/gui/Controls/sliderInput.ui b/pyforms/gui/controls/sliderInput.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/sliderInput.ui rename to pyforms/gui/controls/sliderInput.ui diff --git a/pyforms/gui/controls/table-view.ui b/pyforms/gui/controls/table-view.ui new file mode 100644 index 0000000..3d765ad --- /dev/null +++ b/pyforms/gui/controls/table-view.ui @@ -0,0 +1,198 @@ + + + ControlList + + + + 0 + 0 + 471 + 168 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Form + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 28 + + + + + 16777215 + 28 + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 28 + + + + TextLabel + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 0 + + + + + + + + 0 + 0 + + + + + 28 + 28 + + + + + 18 + 75 + true + + + + + + + + + 28 + 28 + + + + true + + + + + + + + 0 + 0 + + + + + 28 + 28 + + + + + 18 + 75 + true + + + + - + + + + 28 + 28 + + + + true + + + + + + + + + + false + + + Qt::NoPen + + + 0 + + + 100 + + + false + + + true + + + 0 + + + false + + + + + + + + diff --git a/pyforms/gui/Controls/textArea.ui b/pyforms/gui/controls/textArea.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/textArea.ui rename to pyforms/gui/controls/textArea.ui diff --git a/pyforms/gui/Controls/textInput.ui b/pyforms/gui/controls/textInput.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/textInput.ui rename to pyforms/gui/controls/textInput.ui diff --git a/pyforms/gui/Controls/tree.ui b/pyforms/gui/controls/tree.ui old mode 100755 new mode 100644 similarity index 100% rename from pyforms/gui/Controls/tree.ui rename to pyforms/gui/controls/tree.ui diff --git a/pyforms/gui/Controls/uipics/add.png b/pyforms/gui/controls/uipics/add.png similarity index 100% rename from pyforms/gui/Controls/uipics/add.png rename to pyforms/gui/controls/uipics/add.png diff --git a/pyforms/gui/Controls/uipics/film.png b/pyforms/gui/controls/uipics/film.png similarity index 100% rename from pyforms/gui/Controls/uipics/film.png rename to pyforms/gui/controls/uipics/film.png diff --git a/pyforms/gui/Controls/uipics/film_edit.png b/pyforms/gui/controls/uipics/film_edit.png similarity index 100% rename from pyforms/gui/Controls/uipics/film_edit.png rename to pyforms/gui/controls/uipics/film_edit.png diff --git a/pyforms/gui/Controls/uipics/graph.png b/pyforms/gui/controls/uipics/graph.png similarity index 100% rename from pyforms/gui/Controls/uipics/graph.png rename to pyforms/gui/controls/uipics/graph.png diff --git a/pyforms/gui/Controls/uipics/page_white_get.png b/pyforms/gui/controls/uipics/page_white_get.png similarity index 100% rename from pyforms/gui/Controls/uipics/page_white_get.png rename to pyforms/gui/controls/uipics/page_white_get.png diff --git a/pyforms/gui/Controls/uipics/page_white_put.png b/pyforms/gui/controls/uipics/page_white_put.png similarity index 100% rename from pyforms/gui/Controls/uipics/page_white_put.png rename to pyforms/gui/controls/uipics/page_white_put.png diff --git a/pyforms/gui/Controls/uipics/refresh.png b/pyforms/gui/controls/uipics/refresh.png similarity index 100% rename from pyforms/gui/Controls/uipics/refresh.png rename to pyforms/gui/controls/uipics/refresh.png diff --git a/pyforms/gui/Controls/uipics/remove.png b/pyforms/gui/controls/uipics/remove.png similarity index 100% rename from pyforms/gui/Controls/uipics/remove.png rename to pyforms/gui/controls/uipics/remove.png diff --git a/pyforms/gui/Controls/uipics/timeline.png b/pyforms/gui/controls/uipics/timeline.png similarity index 100% rename from pyforms/gui/Controls/uipics/timeline.png rename to pyforms/gui/controls/uipics/timeline.png diff --git a/pyforms/gui/Controls/uipics/zoom_in.png b/pyforms/gui/controls/uipics/zoom_in.png similarity index 100% rename from pyforms/gui/Controls/uipics/zoom_in.png rename to pyforms/gui/controls/uipics/zoom_in.png diff --git a/pyforms/gui/Controls/uipics/zoom_out.png b/pyforms/gui/controls/uipics/zoom_out.png similarity index 100% rename from pyforms/gui/Controls/uipics/zoom_out.png rename to pyforms/gui/controls/uipics/zoom_out.png diff --git a/pyforms/gui/dialogs/csv_parser.py b/pyforms/gui/dialogs/csv_parser.py index 65be78e..cb219c0 100644 --- a/pyforms/gui/dialogs/csv_parser.py +++ b/pyforms/gui/dialogs/csv_parser.py @@ -1,10 +1,10 @@ import csv from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlFile -from pyforms.Controls import ControlList -from pyforms.Controls import ControlNumber +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlFile +from pyforms.controls import ControlList +from pyforms.controls import ControlNumber class CsvParserDialog(BaseWidget): @@ -15,12 +15,12 @@ def __init__(self, parent=None): # Definition of the forms fields self._filename = ControlFile('CSV File') - self._separator = ControlText('Separator', ';') - self._startingrow = ControlNumber('Starting row', 0) - self._frameCol = ControlNumber('Frame column', 0, 0, 100) - self._xCol = ControlNumber('X column', 1, 0, 100) - self._yCol = ControlNumber('Y column', 2, 0, 100) - self._zCol = ControlNumber('Z column', 3, 0, 100) + self._separator = ControlText('Separator', default=';') + self._startingrow = ControlNumber('Starting row', default=0) + self._frameCol = ControlNumber('Frame column', default=0, minumum=0, maximum=100) + self._xCol = ControlNumber('X column', default=1, minumum=0, maximum=100) + self._yCol = ControlNumber('Y column', default=2, minumum=0, maximum=100) + self._zCol = ControlNumber('Z column', default=3, minumum=0, maximum=100) self._filePreview = ControlList('Preview') self._loadButton = ControlButton('Load') @@ -40,10 +40,10 @@ def filename(self, value): self.__refreshPreview() @property - def loadFileEvent(self): return self._loadButton.value + def load_file_event(self): return self._loadButton.value - @loadFileEvent.setter - def loadFileEvent(self, value): self._loadButton.value = value + @load_file_event.setter + def load_file_event(self, value): self._loadButton.value = value @property def separator(self): return self._separator.value @@ -107,7 +107,7 @@ def __next__(self): return self.next() def next(self): if self._spamreader != None: - row = self._spamreader.next() + row = next(self._spamreader) return [row[int(col)] for col in self._cols] else: raise StopIteration() diff --git a/pyforms/gui/dialogs/generic_csv_parser.py b/pyforms/gui/dialogs/generic_csv_parser.py index e2364f6..4aeabce 100644 --- a/pyforms/gui/dialogs/generic_csv_parser.py +++ b/pyforms/gui/dialogs/generic_csv_parser.py @@ -1,90 +1,137 @@ import csv from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlFile -from pyforms.Controls import ControlList -from pyforms.Controls import ControlNumber +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlFile +from pyforms.controls import ControlList +from pyforms.controls import ControlNumber class GenericCsvParserDialog(BaseWidget): + def __init__(self, columns, parent=None): + super(GenericCsvParserDialog, self).__init__('CSV Choose the columns', parent_win=parent) + + self._filename = None + self._columns = columns + self._columns_indexes = [] + self._rownum = 0 + + # Definition of the forms fields + self._filename = ControlFile('CSV File') + self._separator = ControlCombo('Separator', default='auto') + self._startingrow = ControlNumber('Starting row', default=0) + + for index, column in enumerate(columns): + setattr(self, '_col_{0}'.format(index), ControlNumber(column, default=index, minimum=-1, maximum=1000)) + + self._filePreview = ControlList('Preview') + self._loadButton = ControlButton('Load') + + form_row = ['_separator'] + ['_col_{0}'.format(index) for index, column in enumerate(columns)] + ['_loadButton'] + + self._formset = [ + ('_filename', '_startingrow'), + tuple(form_row), + '_filePreview' + ] + self._separator.changed_event = self.__refreshPreview + self._filename.changed_event = self.__refreshPreview + self._startingrow.changed_event = self.__refreshPreview + self._loadButton.value = self.load + + self._load_event = None + + self._separator.add_item('auto', 'auto') + self._separator.add_item(';', ';') + self._separator.add_item(',', ',') + self._separator.add_item('TAB', '\t') + self._separator.add_item('Excel', csv.excel) + self._separator.add_item('Excel TAB', csv.excel_tab) + + @property + def load_file_event(self): + return self._load_event + + @load_file_event.setter + def load_file_event(self, value): + self._load_event = value + + def __iter__(self): + + if self._filename.value != None and self._filename.value != '': + csvfile = open(self._filename.value, 'U') + if self.delimiter in ['auto', csv.excel, csv.excel_tab]: + try: + dialect = csv.Sniffer().sniff(csvfile.read(1024)) + csvfile.seek(0) + self._spamreader = csv.reader(csvfile, dialect) + except: + self._spamreader = None + return self + else: + self._spamreader = csv.reader(csvfile, delimiter=self.delimiter) + for i in range(int(self._startingrow.value)): next(self._spamreader, None) # skip the headers + else: + self._spamreader = None + + return self + + # For compatibility with python 3 + def __next__(self): + return self.next() + + def next(self): + if self._spamreader != None: + row = next(self._spamreader) + res = [] + for col in self._columns_indexes: + if col == -1: + res.append(self._rownum) + else: + if row[col].lower() == 'nan': + res.append('None') + else: + res.append(row[col]) + + self._rownum +=1 + + return res + else: + raise StopIteration() + + def __refreshPreview(self): + if self._filename.value != None and self._filename.value != '': + with open(self._filename.value, 'U') as csvfile: + if self.delimiter in ['auto', csv.excel, csv.excel_tab]: + try: + dialect = csv.Sniffer().sniff(csvfile.read(1024)) + csvfile.seek(0) + spamreader = csv.reader(csvfile, dialect) + except: + self.message('Error when reading the file.') + return + else: + spamreader = csv.reader(csvfile, delimiter=self.delimiter) + + for i in range(int(self._startingrow.value)): next(spamreader, None) # skip the headers + self._filePreview.value = [] + self._filePreview.horizontalHeaders = map(str, range(1000)) + for i, row in enumerate(spamreader): + self._filePreview += row + if i >= 10: + break + + @property + def delimiter(self): + return self._separator.value + + def load(self): + self.__refreshPreview() + self._columns_indexes = [] + for index, column in enumerate(self._columns): + self._columns_indexes.append(int(getattr(self, '_col_{0}'.format(index)).value)) + if self._load_event is not None: self._load_event() - def __init__(self, columns, parent=None): - super(GenericCsvParserDialog, self).__init__('CSV Choose the columns', parent_win = parent) - - self._filename = None - self._columns = columns - self._columns_indexes = [] - - # Definition of the forms fields - self._filename = ControlFile('CSV File') - self._separator = ControlText('Separator', ';') - self._startingrow = ControlNumber('Starting row', 0) - - for index, column in enumerate(columns): - setattr(self, '_col_{0}'.format(index), ControlNumber(column, index, -1, 1000) ) - - self._filePreview = ControlList('Preview') - self._loadButton = ControlButton('Load') - - form_row = ['_separator'] + ['_col_{0}'.format(index) for index, column in enumerate(columns)] + ['_loadButton'] - - self._formset = [ - ('_filename','_startingrow'), - tuple(form_row), - '_filePreview' - ] - self._separator.changed_event = self.__refreshPreview - self._filename.changed_event = self.__refreshPreview - self._startingrow.changed_event = self.__refreshPreview - self._loadButton.value = self.load - - self._load_event = None - - @property - def load_file_event(self): return self._load_event - @load_file_event.setter - def load_file_event(self, value): self._load_event = value - - - def __iter__(self): - if self._filename.value != None and self._filename.value != '': - csvfile = open(self._filename.value, 'U') - self._spamreader = csv.reader(csvfile, delimiter=self._separator.value) - for i in range(int(self._startingrow.value)): next(self._spamreader, None) # skip the headers - else: - self._spamreader = None - return self - - # For compatibility with python 3 - def __next__(self): return self.next() - - def next(self): - if self._spamreader != None: - row = next(self._spamreader) - return [row[col] for col in self._columns_indexes] - else: - raise StopIteration() - - def __refreshPreview(self): - if self._filename.value != None and self._filename.value != '': - with open(self._filename.value, 'U') as csvfile: - spamreader = csv.reader(csvfile, delimiter=self._separator.value) - for i in range(int(self._startingrow.value)): next(spamreader, None) # skip the headers - self._filePreview.value = [] - self._filePreview.horizontalHeaders = map(str, range(1000)) - for i, row in enumerate(spamreader): - self._filePreview += row - if i >= 10: - break - - def load(self): - self.__refreshPreview() - self._columns_indexes = [] - for index, column in enumerate(self._columns): - self._columns_indexes.append( int(getattr(self, '_col_{0}'.format(index) ).value) ) - if self._load_event is not None: self._load_event() - ################################################################################################################## ################################################################################################################## @@ -92,5 +139,6 @@ def load(self): # Execute the application if __name__ == "__main__": - import pyforms - pyforms.start_app(GenericCsvParserDialog, geometry=(0, 0, 600, 400)) + import pyforms + + pyforms.start_app(GenericCsvParserDialog, geometry=(0, 0, 600, 400)) \ No newline at end of file diff --git a/pyforms/gui/mainWindow.ui b/pyforms/gui/mainWindow.ui deleted file mode 100755 index cae4f72..0000000 --- a/pyforms/gui/mainWindow.ui +++ /dev/null @@ -1,76 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 767 - 597 - - - - MainWindow - - - - - 0 - - - 0 - - - - - 0 - - - - - - - - - 0 - 0 - 767 - 25 - - - - - File - - - - - - Algorithm - - - - - - - - - - Quit - - - - - Run - - - - - Stop - - - - - - diff --git a/pyforms/gui/settings.py b/pyforms/gui/settings.py index 89ab5e8..d833613 100644 --- a/pyforms/gui/settings.py +++ b/pyforms/gui/settings.py @@ -2,20 +2,12 @@ # -*- coding: utf-8 -*- import os - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QStyle, qApp - from PyQt5.QtGui import QIcon, QPixmap - -else: - from PyQt4.QtGui import QStyle, qApp, QIcon, QPixmap +from AnyQt.QtGui import QIcon, QPixmap +from AnyQt.QtWidgets import QStyle, qApp def path(filename): - """ - + """ :param filename: :return: """ @@ -29,6 +21,7 @@ def path(filename): state=QIcon.On) PYFORMS_ICON_CODEEDITOR_SAVE = QIcon(qApp.style().standardPixmap(QStyle.SP_DialogSaveButton)) +PYFORMS_ICON_CODEEDITOR_DISCART = QIcon(qApp.style().standardPixmap(QStyle.SP_DialogDiscardButton)) PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN = QPixmap(path(os.path.join("Controls", "uipics", "zoom_in.png"))) PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT = QPixmap(path(os.path.join("Controls", "uipics", "zoom_in.png"))) diff --git a/pyforms/gui/standaloneManager.py b/pyforms/gui/standaloneManager.py deleted file mode 100644 index 4e6ba0a..0000000 --- a/pyforms/gui/standaloneManager.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - - -import sys -import os, types -import inspect, platform -import logging - -from pysettings import conf - -from pysettings import conf - -if conf.PYFORMS_USE_QT5: - from PyQt5.QtWidgets import QMainWindow - from PyQt5.QtWidgets import QDockWidget - from PyQt5.QtWidgets import QAction - from PyQt5.QtWidgets import QApplication - from PyQt5 import QtCore - from PyQt5.QtGui import QIcon -else: - from PyQt4.QtGui import QMainWindow - from PyQt4.QtGui import QDockWidget - from PyQt4.QtGui import QAction - from PyQt4.QtGui import QIcon - from PyQt4.QtGui import QApplication - from PyQt4 import QtCore - -from pyforms.gui.Controls.ControlDockWidget import ControlDockWidget - -logger = logging.getLogger(__name__) - - -class StandAloneContainer(QMainWindow): - def __init__(self, ClassObject): - super(QMainWindow, self).__init__() - - w = ClassObject() - w.app_main_window = self - self._widget = w - - if len(w.mainmenu) > 0: - w._mainmenu = self.__initMainMenu(w.mainmenu) - - w.init_form() - - if conf.PYFORMS_USE_QT5: - self.layout().setContentsMargins(conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN,conf.PYFORMS_MAINWINDOW_MARGIN) - else: - self.layout().setMargin(conf.PYFORMS_MAINWINDOW_MARGIN) - - self.setCentralWidget(w) - self.setWindowTitle(w.title) - - docks = {} - for name, item in w.controls.items(): - if isinstance(item, ControlDockWidget): - if item.side not in docks: - docks[item.side] = [] - docks[item.side].append((name, item)) - - for key, widgets in docks.items(): - side = QtCore.Qt.RightDockWidgetArea - if key == 'left': - side = QtCore.Qt.LeftDockWidgetArea - elif key == 'right': - side = QtCore.Qt.RightDockWidgetArea - elif key == 'top': - side = QtCore.Qt.TopDockWidgetArea - elif key == 'bottom': - side = QtCore.Qt.BottomDockWidgetArea - else: - side = QtCore.Qt.LeftDockWidgetArea - - if isinstance(widgets, list): - widgets = sorted(widgets, key=lambda x: x[1].order) - - for name, widget in widgets: - dock = QDockWidget(self) - dock.setFeatures( - QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable) - dock.setObjectName(name) - - if conf.PYFORMS_USE_QT5: - dock.setContentsMargins(0,0,0,0) - widget.form.layout().setContentsMargins(widget.margin,widget.margin,widget.margin,widget.margin) - else: - dock.setMargin(0) - widget.form.layout().setMargin(widget.margin) - - # print dock.objectName(),1 - dock.setWidget(widget.form) - dock.setWindowTitle(widget.label) - widget.dock = dock - if not widget._show: dock.hide() - - self.addDockWidget(side, dock) - else: - dock = QDockWidget(self) - dock.setFeatures( - QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetMovable) - # dock.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea | QtCore.Qt.RightDockWidgetArea) - - if conf.PYFORMS_USE_QT5: - widget.form.layout().setContentsMargins(widget.margin,widget.margin,widget.margin,widget.margin) - else: - widget.form.layout().setMargin(widget.margin) - - # print dock.objectName(), 2 - dock.setObjectName(name) - dock.setWidget(widget.form) - self.addDockWidget(side, dock) - dock.setWindowTitle(widget.label) - widget.dock = dock - if not widget._show: dock.hide() - - if conf.PYFORMS_STYLESHEET: - stylesheet_files = [conf.PYFORMS_STYLESHEET] - - p = platform.system() - if p == 'Windows' and conf.PYFORMS_STYLESHEET_WINDOWS: - stylesheet_files.append(conf.PYFORMS_STYLESHEET_WINDOWS) - elif p == 'Darwin' and conf.PYFORMS_STYLESHEET_DARWIN: - stylesheet_files.append(conf.PYFORMS_STYLESHEET_DARWIN) - elif p == 'Linux' and conf.PYFORMS_STYLESHEET_LINUX: - stylesheet_files.append(conf.PYFORMS_STYLESHEET_LINUX) - - logger.debug('Import stylesheets: {0}'.format(stylesheet_files)) - self.loadStyleSheetFile(stylesheet_files) - - def closeEvent(self, event): - self._widget.closeEvent(event) - - def __initMainMenu(self, options, keys={}): - menubar = self.menuBar() - for menuIndex, m in enumerate(options): - for key, menus in m.items(): - menu = menubar.addMenu(key) - for subMenuIndex, m1 in enumerate(menus): - if isinstance(m1, str) and m1 == "-": - menu.addSeparator() - else: - for text, func in m1.items(): - if text != 'icon': - action = QAction(text, self) - if 'icon' in m1.keys(): - action.setIconVisibleInMenu(True) - action.setIcon(QIcon(m1['icon'])) - if func: - action.triggered.connect(func) - menu.addAction(action) - options[menuIndex][key][subMenuIndex][text] = action - break - return options - - def loadStyleSheetFile(self, files): - content = '' - for filename in files: - infile = open(filename, 'r') - content += infile.read() + '\n' - infile.close() - - self.setStyleSheet(content) - - -def execute_test_file(myapp): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("test_file", help="File with the tests script") - args = parser.parse_args() - - with open(args.test_file) as f: - global_vars = globals() - local_vars = locals() - code = compile(f.read(), args.test_file, 'exec') - exec(code, global_vars, local_vars) - - -def start_app(ClassObject, geometry=None): - from pysettings import conf - - app = QApplication(sys.argv) - - conf += 'pyforms.gui.settings' - - mainwindow = StandAloneContainer(ClassObject) - - myapp = mainwindow.centralWidget() - - if geometry is not None: - mainwindow.show() - mainwindow.setGeometry(*geometry) - else: - mainwindow.showMaximized() - - if conf.PYFORMS_QUALITY_TESTS_PATH is not None: - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--test", help="File with the tests script") - args = parser.parse_args() - - if args.test: - TEST_PATH = os.path.join(conf.PYFORMS_QUALITY_TESTS_PATH, args.test) - TEST_FILE_PATH = os.path.join(TEST_PATH, args.test + '.py') - DATA_PATH = os.path.join(TEST_PATH, 'data', sys.platform) - INPUT_DATA_PATH = os.path.join(DATA_PATH, 'input-data') - OUTPUT_DATA_PATH = os.path.join(DATA_PATH, 'output-data') - EXPECTED_DATA_PATH = os.path.join(DATA_PATH, 'expected-data') - - with open(TEST_FILE_PATH) as f: - global_vars = {} # globals() - local_vars = locals() - code = compile(f.read(), TEST_FILE_PATH, 'exec') - - exec(code, global_vars, local_vars) - - app.exec_() - return myapp diff --git a/pyforms/settings.py b/pyforms/settings.py index 0441907..f350bca 100644 --- a/pyforms/settings.py +++ b/pyforms/settings.py @@ -2,12 +2,16 @@ # -*- coding: utf-8 -*- -import logging, os +import logging, os, sys + +if 'terminal_mode' in sys.argv: + PYFORMS_MODE = 'TERMINAL' +else: + PYFORMS_MODE = os.environ.get('PYFORMS_MODE', 'GUI') -PYFORMS_MODE = os.environ.get('PYFORMS_MODE', 'GUI') PYFORMS_LOG_FILENAME = 'pyforms.log' -PYFORMS_LOG_HANDLER_FILE_LEVEL = logging.DEBUG +PYFORMS_LOG_HANDLER_FILE_LEVEL = logging.INFO PYFORMS_LOG_HANDLER_CONSOLE_LEVEL = logging.INFO PYFORMS_CONTROL_CODE_EDITOR_DEFAULT_FONT_SIZE = '12' @@ -28,9 +32,8 @@ # See pyforms.utils.plugins_finder.find_class() PYFORMS_SILENT_PLUGINS_FINDER = True -PYFORMS_USE_QT5 = True - -PYFORMS_MATPLOTLIB_ENABLED = True -PYFORMS_WEB_ENABLED = True -PYFORMS_GL_ENABLED = True -PYFORMS_VISVIS_ENABLED = True +PYFORMS_QSCINTILLA_ENABLED = True +PYFORMS_MATPLOTLIB_ENABLED = True +PYFORMS_WEB_ENABLED = True +PYFORMS_GL_ENABLED = True +PYFORMS_VISVIS_ENABLED = True diff --git a/pyforms/terminal/BaseWidget.py b/pyforms/terminal/BaseWidget.py deleted file mode 100755 index 97fc159..0000000 --- a/pyforms/terminal/BaseWidget.py +++ /dev/null @@ -1,185 +0,0 @@ -from pyforms.Controls import ControlFile, ControlSlider, ControlText, ControlCombo, ControlCheckBox, ControlBase, ControlDir, ControlNumber -from datetime import datetime, timedelta -import argparse, uuid, os, shutil, time, sys, subprocess - -try: - import requests -except: - print("No requests lib") - - -class BaseWidget(object): - - def __init__(self, title): - self._parser = argparse.ArgumentParser() - with open('pid.txt', 'w') as f: - f.write( str(os.getpid()) ) - - self._controlsPrefix = '' - self._title = title - self.stop = False - - ############################################################################ - ############ Module functions ############################################# - ############################################################################ - - def init_form(self, parse=True): - result = {} - for fieldname, var in self.controls.items(): - name = var._name - if isinstance(var, ( - ControlFile,ControlSlider,ControlText, - ControlCombo,ControlCheckBox, ControlDir, ControlNumber - ) - ): - self._parser.add_argument("--%s" % name, help=var.label) - - if parse: - - self._parser.add_argument( - "--exec{0}".format(self._controlsPrefix), - default='', - help='Function from the application that should be executed. Use | to separate a list of functions.') - self._args = self._parser.parse_args() - - self.__parse_terminal_parameters() - self.__execute_events() - - - def __parse_terminal_parameters(self): - for fieldname, var in self.controls.items(): - name = var._name - if self._args.__dict__.get(name, None): - - if isinstance(var, ControlFile): - value = self._args.__dict__[name] - if value!=None and (value.startswith('http://') or value.startswith('https://')): - local_filename = value.split('/')[-1] - outputFileName = os.path.join('input', local_filename) - self.__downloadFile(value, outputFileName) - var.value = outputFileName - else: - var.value = value - - if isinstance(var, ControlDir): - value = self._args.__dict__[name] - var.value = value - elif isinstance(var, (ControlText, ControlCombo)): - var.value = self._args.__dict__[name] - elif isinstance(var, ControlCheckBox): - var.value = self._args.__dict__[name]=='True' - elif isinstance(var, (ControlSlider, ControlNumber) ): - var.value = int(self._args.__dict__[name]) - - - - def __execute_events(self): - for function in self._args.__dict__.get("exec{0}".format(self._controlsPrefix), []).split('|'): - if len(function)>0: - getattr(self, function)() - - res = {} - for controlName, control in self.controls.items(): - res[controlName] = {'value': control.value } - with open('out-parameters.txt', 'w') as outfile: - outfile.write( str(res) ) - - - def __downloadFile(self, url, outFilepath): - chunksize = 512*1024 - r = requests.get(url, stream=True) - with open(outFilepath, 'w') as f: - for chunk in r.iter_content(chunk_size=chunksize): - if chunk: f.write(chunk); f.flush(); - - - - def execute(self): pass - - - def start_progress(self, total = 100): - self._total_processing_count = total - self._processing_initial_time = time.time() - self._processing_count = 1 - - def update_progress(self): - div = int(self._total_processing_count/400) - if div==0: div = 1 - if (self._processing_count % div )==0: - self._processing_last_time = time.time() - total_passed_time = self._processing_last_time - self._processing_initial_time - remaining_time = ( (self._total_processing_count * total_passed_time) / self._processing_count ) - total_passed_time - if remaining_time<0: remaining_time = 0 - time_remaining = datetime(1,1,1) + timedelta(seconds=remaining_time ) - time_elapsed = datetime(1,1,1) + timedelta(seconds=(total_passed_time) ) - - values = ( - time_elapsed.day-1, time_elapsed.hour, time_elapsed.minute, time_elapsed.second, - time_remaining.day-1, time_remaining.hour, time_remaining.minute, time_remaining.second, - (float(self._processing_count)/float(self._total_processing_count))*100.0, self._processing_count, self._total_processing_count, - ) - - print("Elapsed: %d:%d:%d:%d; Remaining: %d:%d:%d:%d; Processed %0.2f %% (%d/%d); | \r" % values) - sys.stdout.flush() - - self._processing_count += 1 - - def end_progress(self): - self._processing_count = self._total_processing_count - self.update_progress() - - - - def __savePID(self, pid): - try: - with open('pending_PID.txt', 'w') as f: - f.write(str(pid)) - f.write('\n') - except (IOError) as e: - raise e - - def __savePID(self, pid): - try: - with open('pending_PID.txt', 'w') as f: - f.write(str(pid)) - f.write('\n') - except (IOError) as e: - raise e - - - def executeCommand(self, cmd, cwd=None, env=None): - if cwd!=None: - currentdirectory = os.getcwd() - os.chdir(cwd) - - print(" ".join(cmd)) - proc = subprocess.Popen(cmd) - - if cwd!=None: os.chdir(currentdirectory) - self.__savePID(proc.pid) - proc.wait() - #(output, error) = proc.communicate() - #if error: print 'error: ', error - #print 'output: ', output - return ''#output - - def exec_terminal_cmd(self, args, **kwargs): - print('TERMINAL <<',' '.join(args) ) - sys.stdout.flush() - proc = subprocess.Popen(args, **kwargs) - self.__savePID(proc.pid) - proc.wait() - sys.stdout.flush() - - - @property - def controls(self): - """ - Return all the form controls from the the module - """ - result = {} - for name, var in vars(self).items(): - if isinstance(var, ControlBase): - var._name = self._controlsPrefix+"-"+name if len(self._controlsPrefix)>0 else name - result[name] = var - return result \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlButton.py b/pyforms/terminal/Controls/ControlButton.py deleted file mode 100755 index 6e9835d..0000000 --- a/pyforms/terminal/Controls/ControlButton.py +++ /dev/null @@ -1,10 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlButton(ControlBase): - - @property - def value(self): return None - - @value.setter - def value(self, value): self._value = value - diff --git a/pyforms/terminal/Controls/ControlDir.py b/pyforms/terminal/Controls/ControlDir.py deleted file mode 100644 index 2b1f64e..0000000 --- a/pyforms/terminal/Controls/ControlDir.py +++ /dev/null @@ -1,5 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlDir(ControlBase): - - pass \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlFile.py b/pyforms/terminal/Controls/ControlFile.py deleted file mode 100755 index 4f8aa63..0000000 --- a/pyforms/terminal/Controls/ControlFile.py +++ /dev/null @@ -1,5 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlFile(ControlBase): - - pass \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlPlayer.py b/pyforms/terminal/Controls/ControlPlayer.py deleted file mode 100755 index b47a294..0000000 --- a/pyforms/terminal/Controls/ControlPlayer.py +++ /dev/null @@ -1,116 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase -import numpy - - -import numpy as np -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - - -try: - import cv2 -except: - print( "cv2 not present. ControlPlayer not working") - - -try: - from PIL import Image -except: - print( "PIL not present. ControlPlayer not working") - - -try: - import base64 -except: - print( "base64 not present. ControlPlayer not working") - - - - -class ControlPlayer(ControlBase): - _currentFrame = None - _min = 0 - _max = 0 - _filename = '' - _position = 0 - - def process_frame_event(self, frame): pass - - def play(): pass - - def stop(): pass - - def refresh(): pass - - def update_frame(): pass - - def double_click_event(event, x, y): pass - - def click_event(event, x, y): pass - - def drag_event(start_point, end_point): pass - - def end_drag_event(start_point, end_point): pass - - def key_release_event(event): pass - - def process_frame_event(frame): pass - - def save_Form(self, data): pass - - def load_form(self, data): pass - - @property - def value(self): - result = {'min': self._min, 'max': self._max, 'position': self.video_index, 'frame': '', 'filename': self._filename } - capture = self._value - - _, image = capture.read() - if isinstance(image, numpy.ndarray): - image = self.processFrame(image) - - if isinstance(image, list) or isinstance(image, tuple): - image = groupImage(image, True) - - - if len(image.shape)>2: image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB) - image = Image.fromarray(image) - buff = StringIO.StringIO() - image.save(buff, format="PNG") - content = buff.getvalue() - buff.close() - result['frame'] = base64.b64encode(content) - - return result - - @value.setter - def value(self, value): - if isinstance( value, (str, unicode) ): - ControlBase.value.fset(self, cv2.VideoCapture( value ) ) - self._filename = value - if isinstance( value, dict ): - ControlBase.value.fset(self, cv2.VideoCapture( value['filename'] ) ) - if 'position' in value.keys(): self.video_index = int(value['position']) - self._filename = value['filename'] - self._max = int(self._value.get(7)) - - - - @property - def video_index(self): return self._position - - @video_index.setter - def video_index(self, value): self._position = value - - - - - @property - def image(self): return self._currentFrame - - @image.setter - def image(self, value): pass - - diff --git a/pyforms/terminal/Controls/ControlProgress.py b/pyforms/terminal/Controls/ControlProgress.py deleted file mode 100755 index 16c90fc..0000000 --- a/pyforms/terminal/Controls/ControlProgress.py +++ /dev/null @@ -1,31 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlProgress(ControlBase): - - _min = 0 - _max = 100 - - def __init__(self, label = "%p%", defaultValue = 0, min = 0, max = 100, helptext=None): - self._updateSlider = True - self._min = min - self._max = max - ControlBase.__init__(self, label, defaultValue) - - - @property - def value(self): return self._value - - @value.setter - def value(self, value): self._value = value - - @property - def min(self): return - - @min.setter - def min(self, value): pass - - @property - def max(self): return - - @max.setter - def max(self, value): pass \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlSlider.py b/pyforms/terminal/Controls/ControlSlider.py deleted file mode 100755 index 4df2c46..0000000 --- a/pyforms/terminal/Controls/ControlSlider.py +++ /dev/null @@ -1,26 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlSlider(ControlBase): - - _min = 0 - _max = 100 - - def __init__(self, label = "", helptext=None, defaultValue = 0, min = 0, max = 100): - self._updateSlider = True - self._min = min - self._max = max - - ControlBase.__init__(self, label, defaultValue) - - - @property - def min(self): return self._min - - @min.setter - def min(self, value): self._min = value - - @property - def max(self): return self._max - - @max.setter - def max(self, value): self._max = value \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlText.py b/pyforms/terminal/Controls/ControlText.py deleted file mode 100755 index 1e851cf..0000000 --- a/pyforms/terminal/Controls/ControlText.py +++ /dev/null @@ -1,5 +0,0 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase - -class ControlText(ControlBase):pass - - \ No newline at end of file diff --git a/pyforms/terminal/appmanager.py b/pyforms/terminal/appmanager.py old mode 100755 new mode 100644 index 153b00d..7cd5b4c --- a/pyforms/terminal/appmanager.py +++ b/pyforms/terminal/appmanager.py @@ -1,5 +1,5 @@ from pyforms import BaseWidget -def start_app(ClassObject): - app = ClassObject() - app.init_form() +def start_app(AppClass, **kwargs): + app = AppClass(**kwargs.get('app_args', {})) + app.init_form() diff --git a/pyforms/terminal/basewidget.py b/pyforms/terminal/basewidget.py new file mode 100644 index 0000000..4cdfd9b --- /dev/null +++ b/pyforms/terminal/basewidget.py @@ -0,0 +1,220 @@ +from pyforms.controls import ControlFile, ControlSlider, ControlText, ControlCombo, ControlCheckBox, ControlBase, ControlDir, ControlNumber, ControlBoundingSlider +from datetime import datetime, timedelta +import argparse, uuid, os, shutil, time, sys, subprocess +import simplejson as json + +import logging, traceback; logger=logging.getLogger(__file__) + +try: + import requests +except: + logger.warning("No requests lib") + logger.error(e, exc_info=True) + + +class BaseWidget(object): + + def __init__(self, *args, **kwargs): + self._parser = argparse.ArgumentParser() + self._controlsPrefix = '' + self._title = kwargs.get('title', args[0] if len(args)>0 else '') + self.stop = False + + self._conf = kwargs.get('load', None) + + ############################################################################ + ############ Module functions ############################################# + ############################################################################ + + def set_margin(self, margin):pass + + def init_form(self, parse=True): + result = {} + for fieldname, var in self.controls.items(): + name = var._name + if isinstance(var, ( + ControlFile, ControlSlider, ControlText, + ControlCombo,ControlCheckBox, ControlDir, ControlNumber, ControlBoundingSlider + ) + ): + self._parser.add_argument("--%s" % name, help=var.label) + + if parse: + self._parser.add_argument('terminal_mode', type=str, default='terminal_mode', help='Flag to run pyforms in terminal mode') + self._parser.add_argument( + "--exec{0}".format(self._controlsPrefix), + default='', + help='Function from the application that should be executed. Use | to separate a list of functions.') + self._parser.add_argument( + "--load{0}".format(self._controlsPrefix), + default=None, + help='Load a json file containing the pyforms form configuration.') + self._args = self._parser.parse_args() + + self.__parse_terminal_parameters() + self.__execute_events() + + def load_form(self, data, path=None): + allparams = self.controls + + if hasattr(self, 'load_order'): + for name in self.load_order: + param = allparams[name] + if name in data: + param.load_form(data[name]) + else: + for name, param in allparams.items(): + if name in data: + param.load_form(data[name]) + + + def __parse_terminal_parameters(self): + for fieldname, var in self.controls.items(): + name = var._name + if self._args.__dict__.get(name, None): + + if isinstance(var, ControlFile): + value = self._args.__dict__[name] + if value!=None and (value.startswith('http://') or value.startswith('https://')): + local_filename = value.split('/')[-1] + outputFileName = os.path.join('input', local_filename) + self.__downloadFile(value, outputFileName) + var.value = outputFileName + else: + var.value = value + + if isinstance(var, ControlDir): + value = self._args.__dict__[name] + var.value = value + elif isinstance(var, (ControlText, ControlCombo)): + var.value = self._args.__dict__[name] + elif isinstance(var, ControlCheckBox): + var.value = self._args.__dict__[name]=='True' + elif isinstance(var, (ControlSlider, ControlNumber) ): + var.value = int(self._args.__dict__[name]) + elif isinstance(var, ControlBoundingSlider): + var.value = eval(self._args.__dict__[name]) + + if self._args.load: + print('\n--------- LOADING CONFIG ------------------') + with open(self._args.load) as infile: + data = json.load(infile) + self.load_form(data, os.path.dirname(self._args.load)) + print('--------- END LOADING CONFIG --------------\n') + elif self._conf is not None: + print('\n--------- LOADING DEFAULT CONFIG ------------------') + self.load_form(self._conf, '.') + print('--------- END LOADING DEFAULT CONFIG --------------\n') + + + + def __execute_events(self): + for function in self._args.__dict__.get("exec{0}".format(self._controlsPrefix), []).split('|'): + if len(function)>0: + getattr(self, function)() + + res = {} + for controlName, control in self.controls.items(): + res[controlName] = {'value': control.value } + with open('out-parameters.txt', 'w') as outfile: + outfile.write( str(res) ) + + + def __downloadFile(self, url, outFilepath): + chunksize = 512*1024 + r = requests.get(url, stream=True) + with open(outFilepath, 'w') as f: + for chunk in r.iter_content(chunk_size=chunksize): + if chunk: f.write(chunk); f.flush(); + + + + def execute(self): pass + + + def start_progress(self, total = 100): + self._total_processing_count = total + self._processing_initial_time = time.time() + self._processing_count = 1 + + def update_progress(self): + div = int(self._total_processing_count/400) + if div==0: div = 1 + if (self._processing_count % div )==0: + self._processing_last_time = time.time() + total_passed_time = self._processing_last_time - self._processing_initial_time + remaining_time = ( (self._total_processing_count * total_passed_time) / self._processing_count ) - total_passed_time + if remaining_time<0: remaining_time = 0 + time_remaining = datetime(1,1,1) + timedelta(seconds=remaining_time ) + time_elapsed = datetime(1,1,1) + timedelta(seconds=(total_passed_time) ) + + values = ( + time_elapsed.day-1, time_elapsed.hour, time_elapsed.minute, time_elapsed.second, + time_remaining.day-1, time_remaining.hour, time_remaining.minute, time_remaining.second, + (float(self._processing_count)/float(self._total_processing_count))*100.0, self._processing_count, self._total_processing_count, + ) + + print("Elapsed: %d:%d:%d:%d; Remaining: %d:%d:%d:%d; Processed %0.2f %% (%d/%d); | \r" % values) + sys.stdout.flush() + + self._processing_count += 1 + + def end_progress(self): + self._processing_count = self._total_processing_count + self.update_progress() + + + + def __savePID(self, pid): + try: + with open('pending_PID.txt', 'w') as f: + f.write(str(pid)) + f.write('\n') + except (IOError) as e: + raise e + + def __savePID(self, pid): + try: + with open('pending_PID.txt', 'w') as f: + f.write(str(pid)) + f.write('\n') + except (IOError) as e: + raise e + + + def executeCommand(self, cmd, cwd=None, env=None): + if cwd!=None: + currentdirectory = os.getcwd() + os.chdir(cwd) + + print(" ".join(cmd)) + proc = subprocess.Popen(cmd) + + if cwd!=None: os.chdir(currentdirectory) + self.__savePID(proc.pid) + proc.wait() + #(output, error) = proc.communicate() + #if error: print 'error: ', error + #print 'output: ', output + return ''#output + + def exec_terminal_cmd(self, args, **kwargs): + print('TERMINAL <<',' '.join(args) ) + sys.stdout.flush() + proc = subprocess.Popen(args, **kwargs) + self.__savePID(proc.pid) + proc.wait() + sys.stdout.flush() + + + @property + def controls(self): + """ + Return all the form controls from the the module + """ + result = {} + for name, var in vars(self).items(): + if isinstance(var, ControlBase): + var._name = self._controlsPrefix+"-"+name if len(self._controlsPrefix)>0 else name + result[name] = var + return result \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlBase.py b/pyforms/terminal/controls/ControlBase.py old mode 100755 new mode 100644 similarity index 67% rename from pyforms/terminal/Controls/ControlBase.py rename to pyforms/terminal/controls/ControlBase.py index bb53838..033b160 --- a/pyforms/terminal/Controls/ControlBase.py +++ b/pyforms/terminal/controls/ControlBase.py @@ -7,21 +7,24 @@ class ControlBase(object): _label = None _controlHTML = "" - def __init__(self, label = "", defaultValue = "", helptext=None): + def __init__(self, *args, **kwargs): self._id = uuid.uuid4() - self._value = defaultValue + self._value = kwargs.get('default', None) self._parent = 1 - self._label = label + self._label = kwargs.get('label', args[0] if len(args)>0 else '') def init_form(self): pass def load_form(self, data, path=None): - if 'value' in data: self.value = data['value'] + oldvalue = self.value + self.value = data.get('value', None) + if oldvalue!=self.value: self.changed_event() - def save_form(self, data, path=None): - if self.value: data['value'] = self.value - - def value_updated(self, value): pass + def changed_event(self): + """ + Function called when ever the Control value is changed + """ + return True def show(self):pass @@ -55,7 +58,7 @@ def value(self): return self._value def value(self, value): oldvalue = self._value self._value = value - if oldvalue!=value: self.value_updated(value) + if oldvalue!=value: self.changed_event() ############################################################################ @@ -80,27 +83,4 @@ def parent(self, value): self._parent = value - @property - def maxWidth(self): return -1 - - @maxWidth.setter - def maxWidth(self, value): pass - - @property - def minWidth(self): return -1 - - @minWidth.setter - def minWidth(self, value): pass - - - @property - def maxHeight(self): return -1 - - @maxHeight.setter - def maxHeight(self, value): pass - - @property - def minHeight(self): return -1 - - @minHeight.setter - def minHeight(self, value): pass + \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlBoundingSlider.py b/pyforms/terminal/controls/ControlBoundingSlider.py new file mode 100644 index 0000000..f424808 --- /dev/null +++ b/pyforms/terminal/controls/ControlBoundingSlider.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlBoundingSlider(ControlBase): + + pass \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlButton.py b/pyforms/terminal/controls/ControlButton.py new file mode 100644 index 0000000..01d0130 --- /dev/null +++ b/pyforms/terminal/controls/ControlButton.py @@ -0,0 +1,17 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlButton(ControlBase): + + def __init__(self, *args, **kwargs): + super(ControlButton, self).__init__(*args, **kwargs) + self.checked = False + + @property + def value(self): return None + + @value.setter + def value(self, value): self._value = value + + + + \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlCheckBox.py b/pyforms/terminal/controls/ControlCheckBox.py similarity index 76% rename from pyforms/terminal/Controls/ControlCheckBox.py rename to pyforms/terminal/controls/ControlCheckBox.py index 2c7540d..1e8ada4 100644 --- a/pyforms/terminal/Controls/ControlCheckBox.py +++ b/pyforms/terminal/controls/ControlCheckBox.py @@ -1,5 +1,5 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase +from pyforms.terminal.controls.ControlBase import ControlBase class ControlCheckBox(ControlBase): diff --git a/pyforms/terminal/controls/ControlCheckBoxList.py b/pyforms/terminal/controls/ControlCheckBoxList.py new file mode 100644 index 0000000..e26fd80 --- /dev/null +++ b/pyforms/terminal/controls/ControlCheckBoxList.py @@ -0,0 +1,55 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlCheckBoxList(ControlBase): + + def __init__(self, *args, **kwargs): + if 'default' not in kwargs: kwargs['default'] = [] + super(ControlCheckBoxList, self).__init__(*args, **kwargs) + + def clear(self): + self._value = [] + + def __add__(self, val): + self._value.append(val) + return self + + def __sub__(self, other): + self._value.remove(other) + return self + + def load_form(self, data, path=None): + results = data['selected'] + for row in range(self.count): + item = self._value[row][0] + if item != None and str(item) in results: + self._value[row] = [item, True] + else: + self._value[row] = [item, False] + self.changed_event() + + @property + def count(self): + return len(self._value) + + @property + def selected_row_index(self): + return -1 + + @property + def value(self): + results = [] + for item, checked in self._value: + if checked: + results.append(item) + return results + + @value.setter + def value(self, value): + self.clear() + for row in value: self += row + self.changed_event() + + @property + def items(self): + for item, checked in self._value: + yield (item, checked) \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlCombo.py b/pyforms/terminal/controls/ControlCombo.py old mode 100755 new mode 100644 similarity index 68% rename from pyforms/terminal/Controls/ControlCombo.py rename to pyforms/terminal/controls/ControlCombo.py index b3cb855..7317b80 --- a/pyforms/terminal/Controls/ControlCombo.py +++ b/pyforms/terminal/controls/ControlCombo.py @@ -1,24 +1,22 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase +from pyforms.terminal.controls.ControlBase import ControlBase class ControlCombo(ControlBase): + def __init__(self, *args, **kwargs): + super(ControlCombo, self).__init__(*args, **kwargs) + self._items = None def add_item(self, label, value = None): if self._items==None: self._items={} - self._addingItem = True - #if not (label in self._items.keys()): - # self._form.comboBox.addItem( label ) - - firstValue = False - if self._items=={}: firstValue = True + + first_value = (len(self._items)==0) if value==None: self._items[label] = label else: self._items[label] = value - self._addingItem = False - - if firstValue: self.value = self._items[label] + + if first_value: self.value = self._items[label] def clear(self): @@ -39,7 +37,8 @@ def value(self): return self._value def value(self, value): for key, val in self._items.items(): if value == val: - if self._value!=value: self.value_updated(value) + if self._value!=value: + self.changed_event() self._value = val @property diff --git a/pyforms/terminal/controls/ControlDir.py b/pyforms/terminal/controls/ControlDir.py new file mode 100644 index 0000000..7828cb9 --- /dev/null +++ b/pyforms/terminal/controls/ControlDir.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlDir(ControlBase): + + pass \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlEmptyWidget.py b/pyforms/terminal/controls/ControlEmptyWidget.py new file mode 100644 index 0000000..294d493 --- /dev/null +++ b/pyforms/terminal/controls/ControlEmptyWidget.py @@ -0,0 +1,8 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlEmptyWidget(ControlBase): + + + def load_form(self, data, path=None): + if 'value' in data and self.value is not None and self.value != '': + self.value.load_form(data['value'], path) \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlFile.py b/pyforms/terminal/controls/ControlFile.py new file mode 100644 index 0000000..0da983e --- /dev/null +++ b/pyforms/terminal/controls/ControlFile.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlFile(ControlBase): + + pass \ No newline at end of file diff --git a/pyforms/terminal/Controls/ControlImage.py b/pyforms/terminal/controls/ControlImage.py old mode 100755 new mode 100644 similarity index 71% rename from pyforms/terminal/Controls/ControlImage.py rename to pyforms/terminal/controls/ControlImage.py index a189775..db4914f --- a/pyforms/terminal/Controls/ControlImage.py +++ b/pyforms/terminal/controls/ControlImage.py @@ -1,4 +1,4 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase +from pyforms.terminal.controls.ControlBase import ControlBase class ControlImage(ControlBase): diff --git a/pyforms/terminal/controls/ControlLabel.py b/pyforms/terminal/controls/ControlLabel.py new file mode 100644 index 0000000..61431a8 --- /dev/null +++ b/pyforms/terminal/controls/ControlLabel.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlLabel(ControlBase):pass + + \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlList.py b/pyforms/terminal/controls/ControlList.py new file mode 100644 index 0000000..fd7443e --- /dev/null +++ b/pyforms/terminal/controls/ControlList.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" pyforms.gui.Controls.ControlList + +""" +import logging +from pyforms.terminal.BaseWidget import BaseWidget +from pyforms.terminal.controls.ControlBase import ControlBase + + +logger = logging.getLogger(__name__) + + +class ControlList(ControlBase): + """ This class represents a wrapper to the table widget + It allows to implement a list view + """ + def __init__(self, *args, **kwargs): + if 'default' not in kwargs: kwargs['default'] = [] + super(ControlList, self).__init__(*args, **kwargs) + + + def clear(self, headers=False): + pass + + def save_form(self, data, path=None): + if self.value: + rows = [] + for row in range(self.rows_count): + columns = [] + for column in range(self.columns_count): + v = self.get_value(column, row) + if isinstance(v, BaseWidget): + columns.append(v.save({})) + else: + columns.append(str(v)) + rows.append(columns) + data['value'] = rows + return data + + def load_form(self, data, path=None): + rows = data.get('value') + + if self.value is not None and rows is not None: + for row_index in range(len(self.value)): + for column_index in range(len(self.value[row_index])): + v = self.get_value(column_index, row_index) + + if isinstance(v, BaseWidget): + v.load_form(rows[row_index][column_index], path) + else: + self.value[row_index] = list(self.value[row_index]) + self.value[row_index][column_index] = rows[row_index][column_index] + elif rows is not None: + self.value = rows + + def __add__(self, other): + self._value.append(val) + return self + """ + row_index = self.tableWidget.rowCount() + + self.tableWidget.insertRow(row_index) + + #increase the number of columns if necessary #### + if self.tableWidget.currentColumn() < len(other): + self.tableWidget.setColumnCount(len(other)) + ################################################# + + for column, e in enumerate(other): self.set_value(column, row_index, e) + + self.tableWidget.resizeColumnsToContents() + + # auto scroll the list to the new inserted item ################## + if self.autoscroll: + self.tableWidget.scrollToItem( self.get_cell(0,row_index) ) + ################################################################## + return self + """ + + def __sub__(self, other): + self._value.remove(other) + return self + """ + if isinstance(other, int): + if other < 0: + indexToRemove = self.tableWidget.currentRow() + else: + indexToRemove = other + + self.tableWidget.removeRow(indexToRemove) + return self + """ + + def set_value(self, column, row, value): + self.value[row][column] = value + + def get_value(self, column, row): + return self.value[row][column] + + def resize_rows_contents(self): + pass + + def get_currentrow_value(self): + return None + + def get_cell(self, column, row): + return None + + def set_sorting_enabled(self, value): + pass + + ########################################################################## + ############ EVENTS ###################################################### + ########################################################################## + + def data_changed_event(self, row, col, item): + pass + + def item_selection_changed_event(self): + pass + + def current_cell_changed_event(self, next_row, next_col, previous_row, previous_col): + pass + + def current_item_changed_event(self, current, previous): + pass + + def cell_double_clicked_event(self, row, column): + pass + + ########################################################################## + ############ PROPERTIES ################################################## + ########################################################################## + + + @property + def horizontal_headers(self): + return self._horizontalHeaders + + @horizontal_headers.setter + def horizontal_headers(self, horizontal_headers): + """Set horizontal headers in the table list.""" + + self._horizontalHeaders = horizontal_headers + + + @property + def word_wrap(self): + return True + + @word_wrap.setter + def word_wrap(self, value): + pass + + @property + def readonly(self): + return False + + @readonly.setter + def readonly(self, value): + pass + + @property + def select_entire_row(self): + pass + + @select_entire_row.setter + def select_entire_row(self, value): + pass + + @property + def rows_count(self): + return 0 + + @property + def columns_count(self): + return 0 + + def __len__(self): + return 0 + + + # TODO: implement += on self.value? I want to add a list of tuples to + # self.value + + @property + def selected_rows_indexes(self): + return None + + @property + def selected_row_index(self): + pass + + @property + def icon_size(self): + return 0 + + @icon_size.setter + def icon_size(self, value): + pass + + @property + def autoscroll(self): return False + @autoscroll.setter + def autoscroll(self, value): pass + diff --git a/pyforms/terminal/Controls/ControlNumber.py b/pyforms/terminal/controls/ControlNumber.py similarity index 86% rename from pyforms/terminal/Controls/ControlNumber.py rename to pyforms/terminal/controls/ControlNumber.py index 8ea43b9..58c34ba 100644 --- a/pyforms/terminal/Controls/ControlNumber.py +++ b/pyforms/terminal/controls/ControlNumber.py @@ -1,4 +1,4 @@ -from pyforms.terminal.Controls.ControlBase import ControlBase +from pyforms.terminal.controls.ControlBase import ControlBase class ControlNumber(ControlBase): diff --git a/pyforms/terminal/controls/ControlPlayer.py b/pyforms/terminal/controls/ControlPlayer.py new file mode 100644 index 0000000..183eccd --- /dev/null +++ b/pyforms/terminal/controls/ControlPlayer.py @@ -0,0 +1,146 @@ +from pyforms.terminal.controls.ControlBase import ControlBase +from pyforms.utils.tools import groupImage +import numpy, types + + +import numpy as np +try: + from StringIO import StringIO as BufferClass +except ImportError: + from io import BytesIO as BufferClass + + +try: + import cv2 +except: + print( "cv2 not present. ControlPlayer not working") + + +try: + from PIL import Image +except: + print( "PIL not present. ControlPlayer not working") + + +try: + import base64 +except: + print( "base64 not present. ControlPlayer not working") + + + + +class ControlPlayer(ControlBase): + _currentFrame = None + _min = 0 + _max = 0 + _filename = '' + _position = 0 + + def process_frame_event(self, frame): pass + + def play(self): pass + + def stop(self): pass + + def refresh(self): pass + + def update_frame(self): pass + + def double_click_event(self, event, x, y): pass + + def click_event(self, event, x, y): pass + + def drag_event(self, start_point, end_point): pass + + def end_drag_event(self, start_point, end_point): pass + + def key_release_event(self, event): pass + + def process_frame_event(self, frame): pass + + def save_Form(self, data): pass + + def load_form(self, data): pass + + @property + def value(self): + result = {'min': self._min, 'max': self._max, 'position': self.video_index, 'frame': '', 'filename': self._filename } + capture = self._value + + _, image = capture.read() + if isinstance(image, numpy.ndarray): + image = self.process_frame_event(image) + + if isinstance(image, list) or isinstance(image, tuple): + image = groupImage(image, True) + + + if len(image.shape)>2: image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB) + image = Image.fromarray(image) + buff = BufferClass() + image.save(buff, format="PNG") + content = buff.getvalue() + buff.close() + result['frame'] = base64.b64encode(content) + + return result + + @value.setter + def value(self, value): + if value: + if isinstance( value, str ): + ControlBase.value.fset(self, cv2.VideoCapture( value ) ) + self._filename = value + if isinstance( value, dict ): + ControlBase.value.fset(self, cv2.VideoCapture( value['filename'] ) ) + if 'position' in value.keys(): self.video_index = int(value['position']) + self._filename = value['filename'] + + if isinstance(value,types.BuiltinFunctionType): + ControlBase.value.fset(self, value ) + + self._max = int(self._value.get(7)) + else: + self._filename = None + self._value = None + + + + @property + def video_index(self): return self._position + + @video_index.setter + def video_index(self, value): self._position = value + + + + + @property + def image(self): return self._currentFrame + + @image.setter + def image(self, value): pass + + + + + + @property + def frame_width(self): return self._value.get(3) + + @property + def frame_height(self): return self._value.get(4) + + @property + def max(self): return int(self._value.get(7)) + + @property + def frame(self): return self._currentFrame + + @property + def fps(self): + """ + Return the video frames per second + """ + return self._value.get(5) diff --git a/pyforms/terminal/controls/ControlProgress.py b/pyforms/terminal/controls/ControlProgress.py new file mode 100644 index 0000000..ca5094b --- /dev/null +++ b/pyforms/terminal/controls/ControlProgress.py @@ -0,0 +1,37 @@ +from pyforms.terminal.controls.ControlBase import ControlBase +from sys import stdout + +class ControlProgress(ControlBase): + + _min = 0 + _max = 100 + + def __init__(self, *args, **kwargs): + self._updateSlider = True + self._min = kwargs.get('min', kwargs.get('minimum', 0)) + self._max = kwargs.get('max', kwargs.get('maximum', 100)) + ControlBase.__init__(self, *args, **kwargs) + + + @property + def value(self): return self._value + + @value.setter + def value(self, value): + #diff = self._max-self._min + + if (value % 100 == 0): + stdout.write( '\rprogress {0}/{1}'.format(value-self._min, self._max) ) + self._value = value + + @property + def min(self): return self._min + + @min.setter + def min(self, value): self._min = value + + @property + def max(self): return self._max + + @max.setter + def max(self, value): self._max = value \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlSlider.py b/pyforms/terminal/controls/ControlSlider.py new file mode 100644 index 0000000..ca5de38 --- /dev/null +++ b/pyforms/terminal/controls/ControlSlider.py @@ -0,0 +1,23 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlSlider(ControlBase): + + + def __init__(self, *args, **kwargs): + self._min = kwargs.get('minimum', 0) + self._max = kwargs.get('maximum', 100) + if 'default' not in kwargs: kwargs['default'] = 0 + ControlBase.__init__(self, *args, **kwargs) + + + @property + def min(self): return self._min + + @min.setter + def min(self, value): self._min = value + + @property + def max(self): return self._max + + @max.setter + def max(self, value): self._max = value \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlText.py b/pyforms/terminal/controls/ControlText.py new file mode 100644 index 0000000..c3b76cb --- /dev/null +++ b/pyforms/terminal/controls/ControlText.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlText(ControlBase):pass + + \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlTextArea.py b/pyforms/terminal/controls/ControlTextArea.py new file mode 100644 index 0000000..e1d5d3f --- /dev/null +++ b/pyforms/terminal/controls/ControlTextArea.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlTextArea(ControlBase):pass + + \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlToolBox.py b/pyforms/terminal/controls/ControlToolBox.py new file mode 100644 index 0000000..412d2a6 --- /dev/null +++ b/pyforms/terminal/controls/ControlToolBox.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlToolBox(ControlBase):pass + + \ No newline at end of file diff --git a/pyforms/terminal/controls/ControlTree.py b/pyforms/terminal/controls/ControlTree.py new file mode 100644 index 0000000..a3d50d4 --- /dev/null +++ b/pyforms/terminal/controls/ControlTree.py @@ -0,0 +1,5 @@ +from pyforms.terminal.controls.ControlBase import ControlBase + +class ControlTree(ControlBase):pass + + \ No newline at end of file diff --git a/pyforms/terminal/Controls/__init__.py b/pyforms/terminal/controls/__init__.py similarity index 100% rename from pyforms/terminal/Controls/__init__.py rename to pyforms/terminal/controls/__init__.py diff --git a/pyforms/utils/__init__.py b/pyforms/utils/__init__.py old mode 100755 new mode 100644 diff --git a/pyforms/utils/make_lambda.py b/pyforms/utils/make_lambda.py new file mode 100644 index 0000000..fab247b --- /dev/null +++ b/pyforms/utils/make_lambda.py @@ -0,0 +1,5 @@ + + +def make_lambda_func(func, **kwargs): + """ Auxiliar function for passing parameters to functions """ + return lambda: func(**kwargs) \ No newline at end of file diff --git a/pyforms/utils/plugins_finder.py b/pyforms/utils/plugins_finder.py index 8b0a9ed..67d7605 100644 --- a/pyforms/utils/plugins_finder.py +++ b/pyforms/utils/plugins_finder.py @@ -3,7 +3,7 @@ import re import logging -from pysettings import conf +from pyforms.utils.settings_manager import conf logger = logging.getLogger(__name__) @@ -40,19 +40,21 @@ def find_class(self, class_full_name): class_name = values[-1] module_name = ('.' + '.'.join(values[:-1])) if len(values) > 1 else '' module = __import__(plugin + module_name, fromlist=['']) - class_def = getattr(module, class_name) - res.append(class_def) + try: + class_def = getattr(module, class_name) + res.append(class_def) + except AttributeError: + if not conf.PYFORMS_SILENT_PLUGINS_FINDER: + logger.error('Error importing model {0} {1} {2}'.format(str(plugin), str(package_name), str(class_name)), exc_info=True) + except ImportError: if not conf.PYFORMS_SILENT_PLUGINS_FINDER: - logger.error( - 'Error importing model {0} {1} {2}'.format(str(plugin), str(package_name), str(class_name)), - exc_info=True) - pass + logger.error('Error importing model {0} {1} {2}'.format(str(plugin), str(package_name), str(class_name)), exc_info=True) + except: - if not conf.PYFORMS_SILENT_PLUGINS_FINDER: - logger.error( - 'Error importing model {0} {1} {2}'.format(str(plugin), str(package_name), str(class_name)), - exc_info=True) + logger.error( + 'Error importing model {0} {1} {2}'.format(str(plugin), str(package_name), str(class_name)), + exc_info=True) pass return res diff --git a/pyforms/utils/settings_manager.py b/pyforms/utils/settings_manager.py new file mode 100644 index 0000000..3ede4d2 --- /dev/null +++ b/pyforms/utils/settings_manager.py @@ -0,0 +1,42 @@ + +import logging; logger = logging.getLogger(__name__) + +class SettingsManager(object): + + def __init__(self): + self._modules = [] + self._loaded_modules = {} + + def __load_module(self, module_name): + if module_name in self._loaded_modules.keys(): return self._loaded_modules[module_name] + + modules = module_name.split('.') + moduleclass = __import__( '.'.join(modules[:-1]) , fromlist=[modules[-1]] ) + + module = getattr(moduleclass, modules[-1]) + self._loaded_modules[module_name] = module + + return module + + def __add__(self, other): + if isinstance(other, str): other = self.__load_module(other) + + modules = object.__getattribute__(self, '_modules') + if other not in modules: + logger.info('added settings: {0}'.format(other.__name__)) + modules.append(other) + self._modules = sorted(modules, key=lambda x: (x.SETTINGS_PRIORITY if hasattr(x, 'SETTINGS_PRIORITY') else 999999999) ) + return self + + def __sub__(self, other): + self._modules.remove(other) + return self + + def __getattribute__(self, name): + for module in object.__getattribute__(self, '_modules'): + if hasattr(module, name): return getattr(module, name) + + return object.__getattribute__(self, name) + + +conf = SettingsManager() \ No newline at end of file diff --git a/pyforms/utils/timeit.py b/pyforms/utils/timeit.py index 6e780b1..2ea1eac 100755 --- a/pyforms/utils/timeit.py +++ b/pyforms/utils/timeit.py @@ -12,14 +12,14 @@ import time -from datetime import datetime, timedelta +from datetime import datetime as datetime_dt, timedelta def timeit(method): def timed(*args, **kw): ts = time.time() result = method(*args, **kw) te = time.time() - time_elapsed = datetime(1,1,1) + timedelta(seconds=(te-ts) ) + time_elapsed = datetime_dt(1,1,1) + timedelta(seconds=(te-ts) ) print("%s: %d:%d:%d:%d;%d" % (method.__name__, time_elapsed.day-1, time_elapsed.hour, time_elapsed.minute, time_elapsed.second, time_elapsed.microsecond)) return result return timed \ No newline at end of file diff --git a/setup.py b/setup.py index b4bb60b..d2ddd72 100644 --- a/setup.py +++ b/setup.py @@ -6,35 +6,32 @@ version = '' with open('pyforms/__init__.py', 'r') as fd: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - fd.read(), re.MULTILINE).group(1) + fd.read(), re.MULTILINE).group(1) if not version: raise RuntimeError('Cannot find version information') setup( name='PyForms', version=version, - description="""Pyforms is a Python 2.7 and 3.4 framework to develop GUI application, - which promotes modular software design and code reusability with minimal effort.""", + description="""Pyforms is a Python 2.7 and 3.4 framework to develop GUI applications based on pyqt""", author='Ricardo Ribeiro', author_email='ricardojvr@gmail.com', license='MIT', url='https://github.com/UmSenhorQualquer/pyforms', - - packages=[ - 'pyforms', - 'pyforms.utils', - 'pyforms.terminal', - 'pyforms.terminal.Controls', - 'pyforms.gui', - 'pyforms.gui.dialogs', - 'pyforms.gui.Controls', - 'pyforms.gui.Controls.ControlEventTimeline', - 'pyforms.gui.Controls.ControlEventsGraph', - 'pyforms.gui.Controls.ControlPlayer'], - + install_requires=[ + 'anyqt', + 'pyqt5', + 'pyopengl', + 'QScintilla', + 'visvis', + 'matplotlib', + 'python-dateutil', + 'numpy' + ], + packages=find_packages(), package_data={'pyforms': [ - 'gui/Controls/uipics/*.png', - 'gui/mainWindow.ui', 'gui/Controls/*.ui', 'gui/Controls/ControlPlayer/*.ui', - 'gui/Controls/ControlEventTimeline/*.ui'] + 'gui/controls/uipics/*.png', + 'gui/mainWindow.ui', 'gui/controls/*.ui', 'gui/controls/control_player/*.ui', + 'gui/controls/control_event_timeline/*.ui'] }, ) diff --git a/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/Example.py b/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/Example.py index 2e5ae24..ba5265e 100644 --- a/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/Example.py +++ b/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/Example.py @@ -20,8 +20,8 @@ def __init__(self): #Definition of the forms fields self._videofile = ControlFile('Video') self._outputfile = ControlText('Results output file') - self._threshold = ControlSlider('Threshold', 114, 0,255) - self._blobsize = ControlSlider('Minimum blob size', 100, 100,2000) + self._threshold = ControlSlider('Threshold', default=114, 0,255) + self._blobsize = ControlSlider('Minimum blob size', default=100, 100,2000) self._player = ControlPlayer('Player') self._runbutton = ControlButton('Run') @@ -30,7 +30,7 @@ def __init__(self): #Define the event that will be called when the run button is processed self._runbutton.value = self.__runEvent #Define the event called before showing the image in the player - self._player.processFrame = self.__processFrame + self._player.process_frame_event = self.__process_frame #Define the organization of the Form Controls self.formset = [ @@ -47,7 +47,7 @@ def __videoFileSelectionEvent(self): """ self._player.value = self._videofile.value - def __processFrame(self, frame): + def __process_frame(self, frame): """ Do some processing to the frame and return the result frame """ diff --git a/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/__init__.py b/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/__init__.py index 0ca0ca9..31685a5 100644 --- a/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/__init__.py +++ b/tutorials/1.SimpleExamples/ComputerVisionAlgorithmExample/__init__.py @@ -11,10 +11,10 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlSlider -from pyforms.Controls import ControlFile -from pyforms.Controls import ControlPlayer +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlSlider +from pyforms.controls import ControlFile +from pyforms.controls import ControlPlayer import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample1/__init__.py b/tutorials/1.SimpleExamples/SimpleExample1/__init__.py index a543081..540aab2 100644 --- a/tutorials/1.SimpleExamples/SimpleExample1/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample1/__init__.py @@ -11,7 +11,7 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlMatplotlib +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlMatplotlib import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample2/__init__.py b/tutorials/1.SimpleExamples/SimpleExample2/__init__.py index b36dd67..6725787 100644 --- a/tutorials/1.SimpleExamples/SimpleExample2/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample2/__init__.py @@ -10,7 +10,7 @@ __status__ = "Development" from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample3/__init__.py b/tutorials/1.SimpleExamples/SimpleExample3/__init__.py index 60246a6..274f2e3 100644 --- a/tutorials/1.SimpleExamples/SimpleExample3/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample3/__init__.py @@ -1,6 +1,6 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample4/__init__.py b/tutorials/1.SimpleExamples/SimpleExample4/__init__.py index 60246a6..274f2e3 100644 --- a/tutorials/1.SimpleExamples/SimpleExample4/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample4/__init__.py @@ -1,6 +1,6 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample5/__init__.py b/tutorials/1.SimpleExamples/SimpleExample5/__init__.py index 60246a6..274f2e3 100644 --- a/tutorials/1.SimpleExamples/SimpleExample5/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample5/__init__.py @@ -1,6 +1,6 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton import pyforms \ No newline at end of file diff --git a/tutorials/1.SimpleExamples/SimpleExample6/__init__.py b/tutorials/1.SimpleExamples/SimpleExample6/__init__.py index c093436..486a386 100644 --- a/tutorials/1.SimpleExamples/SimpleExample6/__init__.py +++ b/tutorials/1.SimpleExamples/SimpleExample6/__init__.py @@ -1,6 +1,6 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton import pyforms \ No newline at end of file diff --git a/tutorials/2.ControlsExamples/__init__.py b/tutorials/2.ControlsExamples/__init__.py index 32d5a79..7717b87 100644 --- a/tutorials/2.ControlsExamples/__init__.py +++ b/tutorials/2.ControlsExamples/__init__.py @@ -10,25 +10,25 @@ __status__ = "Development" from pyforms import BaseWidget -from pyforms.Controls import ControlBoundingSlider -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlCheckBox -from pyforms.Controls import ControlCheckBoxList -from pyforms.Controls import ControlCombo -from pyforms.Controls import ControlDir -from pyforms.Controls import ControlDockWidget -from pyforms.Controls import ControlEmptyWidget -from pyforms.Controls import ControlFile -from pyforms.Controls import ControlFilesTree -from pyforms.Controls import ControlImage -from pyforms.Controls import ControlList -from pyforms.Controls import ControlPlayer -from pyforms.Controls import ControlProgress -from pyforms.Controls import ControlSlider -from pyforms.Controls import ControlVisVis -from pyforms.Controls import ControlVisVisVolume -from pyforms.Controls import ControlEventTimeline -from pyforms.Controls import ControlCodeEditor +from pyforms.controls import ControlBoundingSlider +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlCheckBox +from pyforms.controls import ControlCheckBoxList +from pyforms.controls import ControlCombo +from pyforms.controls import ControlDir +from pyforms.controls import ControlDockWidget +from pyforms.controls import ControlEmptyWidget +from pyforms.controls import ControlFile +from pyforms.controls import ControlFilesTree +from pyforms.controls import ControlImage +from pyforms.controls import ControlList +from pyforms.controls import ControlPlayer +from pyforms.controls import ControlProgress +from pyforms.controls import ControlSlider +from pyforms.controls import ControlVisVis +from pyforms.controls import ControlVisVisVolume +from pyforms.controls import ControlEventTimeline +from pyforms.controls import ControlCodeEditor import pyforms \ No newline at end of file diff --git a/tutorials/3.CodeOrganization/AddMenuFuntionality.py b/tutorials/3.CodeOrganization/AddMenuFuntionality.py index d83b09e..9a6c595 100644 --- a/tutorials/3.CodeOrganization/AddMenuFuntionality.py +++ b/tutorials/3.CodeOrganization/AddMenuFuntionality.py @@ -23,7 +23,7 @@ def __init__(self): def __savePeople(self): - filename = QtGui.QFileDialog.getSaveFileName(parent=self, + filename, _ = QtGui.QFileDialog.getSaveFileName(parent=self, caption="Save file", directory=".", filter="*.dat") @@ -31,7 +31,7 @@ def __savePeople(self): if filename!=None and filename!='': self.save(filename) def __loadPeople(self): - filename = QtGui.QFileDialog.getOpenFileName(parent=self, + filename, _ = QtGui.QFileDialog.getOpenFileName(parent=self, caption="Import file", directory=".", filter="*.dat") diff --git a/tutorials/3.CodeOrganization/PeopleWindow.py b/tutorials/3.CodeOrganization/PeopleWindow.py index 1854124..913d10f 100644 --- a/tutorials/3.CodeOrganization/PeopleWindow.py +++ b/tutorials/3.CodeOrganization/PeopleWindow.py @@ -3,7 +3,7 @@ from PersonWindow import PersonWindow from AddMenuFuntionality import AddMenuFuntionality -from pyforms.Controls import ControlDockWidget +from pyforms.controls import ControlDockWidget class PeopleWindow(AddMenuFuntionality, People, BaseWidget): diff --git a/tutorials/3.CodeOrganization/PersonWindow.py b/tutorials/3.CodeOrganization/PersonWindow.py index 4b98edc..8301246 100644 --- a/tutorials/3.CodeOrganization/PersonWindow.py +++ b/tutorials/3.CodeOrganization/PersonWindow.py @@ -1,11 +1,11 @@ -from pysettings import conf; +from pyforms.utils.settings_manager import conf; import settings conf+=settings import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton from Person import Person diff --git a/tutorials/3.CodeOrganization/__init__.py b/tutorials/3.CodeOrganization/__init__.py index 5ff2a7b..e712839 100644 --- a/tutorials/3.CodeOrganization/__init__.py +++ b/tutorials/3.CodeOrganization/__init__.py @@ -11,9 +11,9 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlList +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlList import pyforms diff --git a/tutorials/4.MdiApplication/Core/BaseWindow.py b/tutorials/4.MdiApplication/Core/BaseWindow.py index 78425ed..eb2cdac 100644 --- a/tutorials/4.MdiApplication/Core/BaseWindow.py +++ b/tutorials/4.MdiApplication/Core/BaseWindow.py @@ -1,10 +1,10 @@ import pyforms from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlDockWidget -from pyforms.Controls import ControlMdiArea +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlDockWidget +from pyforms.controls import ControlMdiArea from Core.SimpleExample1 import SimpleExample1 from Core.Controllers.ProjectTree import ProjectTree diff --git a/tutorials/4.MdiApplication/Core/Controllers/ProjectTree.py b/tutorials/4.MdiApplication/Core/Controllers/ProjectTree.py index c0a7a56..1b1aa28 100644 --- a/tutorials/4.MdiApplication/Core/Controllers/ProjectTree.py +++ b/tutorials/4.MdiApplication/Core/Controllers/ProjectTree.py @@ -3,9 +3,9 @@ from PyQt4 import QtGui, QtCore -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton -from pyforms.Controls import ControlTree +from pyforms.controls import ControlText +from pyforms.controls import ControlButton +from pyforms.controls import ControlTree class ProjectTree(BaseWidget): diff --git a/tutorials/4.MdiApplication/Core/SimpleExample1.py b/tutorials/4.MdiApplication/Core/SimpleExample1.py index f162d2e..4bc0078 100644 --- a/tutorials/4.MdiApplication/Core/SimpleExample1.py +++ b/tutorials/4.MdiApplication/Core/SimpleExample1.py @@ -12,8 +12,8 @@ from pyforms import BaseWidget -from pyforms.Controls import ControlText -from pyforms.Controls import ControlButton +from pyforms.controls import ControlText +from pyforms.controls import ControlButton diff --git a/tutorials/Controls4Docs/__init__.py b/tutorials/Controls4Docs/__init__.py index a0df076..fa42181 100644 --- a/tutorials/Controls4Docs/__init__.py +++ b/tutorials/Controls4Docs/__init__.py @@ -11,5 +11,5 @@ from pyforms import BaseWidget -from pyforms.Controls import * +from pyforms.controls import * import pyforms \ No newline at end of file