diff --git a/app/libs/events_handler.py b/app/libs/events_handler.py index 2021684..167c240 100644 --- a/app/libs/events_handler.py +++ b/app/libs/events_handler.py @@ -22,7 +22,7 @@ class EventHandler(QtCore.QObject): sig_indicator_settings_canceled = QtCore.Signal(object) sig_indicator_settings_reseted = QtCore.Signal(object) - sig_action_triggered = QtCore.Signal(str) + sig_action_triggered = QtCore.Signal(str, dict) sig_favorite_created = QtCore.Signal(str) sig_favorite_loaded = QtCore.Signal(list) @@ -32,3 +32,8 @@ class EventHandler(QtCore.QObject): sig_favorite_clicked = QtCore.Signal(str) sig_articles = QtCore.Signal(dict) + + sig_graph_clicked = QtCore.Signal(list, object) + sig_graph_mouse_moved = QtCore.Signal(object) + sig_graph_mouse_pressed = QtCore.Signal(object) + sig_graph_mouse_released = QtCore.Signal(object) diff --git a/app/libs/graph/graphwidget.py b/app/libs/graph/graphwidget.py index 7e58082..857be52 100644 --- a/app/libs/graph/graphwidget.py +++ b/app/libs/graph/graphwidget.py @@ -2,6 +2,7 @@ from PySide2 import QtCore, QtGui, QtWidgets from libs.graph.candlestick import CandlestickItem +from libs.events_handler import EventHandler from utils import utils # TODO import from palette or Qss @@ -19,11 +20,16 @@ def __init__(self, parent=None): self.v_line = None self.h_line = None + self.signals = EventHandler() + self.g_quotation = self.addPlot(row=0, col=0, name="Quotation") self.g_quotation.showGrid(x=True, y=True, alpha=0.3) self.g_vb = self.g_quotation.vb self.set_cross_hair() + self.g_quotation.scene().sigMouseClicked.connect( + self._on_mouse_clicked + ) def plot_quotation(self, data, clear=True): """Plot the quotation @@ -79,6 +85,7 @@ def set_cross_hair(self): def set_time_x_axis(self, widget): widget.setAxisItems({"bottom": pg.DateAxisItem(orientation="bottom")}) + @QtCore.Slot(object) def _on_mouse_moved(self, event): """Signal on mouse moved @@ -91,6 +98,29 @@ def _on_mouse_moved(self, event): self.v_line.setPos(mousePoint.x()) self.h_line.setPos(mousePoint.y()) + @QtCore.Slot(object) + def _on_mouse_clicked(self, event): + """Called on mouse clicked + + :param event: Mouse clicked + :type event: event + """ + items = self.g_quotation.scene().items(event.scenePos()) + plot_items = [x for x in items if isinstance(x, pg.PlotItem)] + self.signals.sig_graph_clicked.emit(plot_items, event) + + def mousePressEvent(self, event): + self.signals.sig_graph_mouse_pressed.emit(event) + super(GraphView, self).mousePressEvent(event) + + def mouseReleaseEvent(self, event): + self.signals.sig_graph_mouse_released.emit(event) + super(GraphView, self).mouseReleaseEvent(event) + + def mouseMoveEvent(self, event): + self.signals.sig_graph_mouse_moved.emit(event) + super(GraphView, self).mouseMoveEvent(event) + class GraphWidget(QtWidgets.QWidget): """Widget wrapper for the graph""" @@ -103,6 +133,22 @@ def __init__(self, parent=None): layout.addWidget(self._graph) self.setLayout(layout) + self.signals = EventHandler() + + # Relay Signals + self._graph.signals.sig_graph_clicked.connect( + self.signals.sig_graph_clicked.emit + ) + self._graph.signals.sig_graph_mouse_pressed.connect( + self.signals.sig_graph_mouse_pressed.emit + ) + self._graph.signals.sig_graph_mouse_released.connect( + self.signals.sig_graph_mouse_released.emit + ) + self._graph.signals.sig_graph_mouse_moved.connect( + self.signals.sig_graph_mouse_moved.emit + ) + @property def graph(self): """Return the graph diff --git a/app/libs/roi_manager.py b/app/libs/roi_manager.py new file mode 100644 index 0000000..5d28070 --- /dev/null +++ b/app/libs/roi_manager.py @@ -0,0 +1,101 @@ +import time +import pyqtgraph as pg +from PySide2 import QtCore, QtWidgets + + +class ROIManager(QtCore.QObject): + def __init__(self, parent=None): + super(ROIManager, self).__init__(parent) + + # Constants + self.current_tool = None + self.current_handle = None + self.current_graph = None + self._graph = self.parent().wgt_graph.graph + + # Signals + self.parent().wgt_graph.signals.sig_graph_clicked.connect( + self._on_roi_add_requested + ) + self.parent().wgt_graph.signals.sig_graph_mouse_moved.connect(self._on_mouse_moved) + # self.parent().wgt_graph.signals.sig_graph_mouse_pressed.connect(self._on_mouse_pressed) + # self.parent().wgt_graph.signals.sig_graph_mouse_released.connect(self._on_mouse_released) + + def drawer(self, graph, event): + """Drawer over the graph + + :param graph: The graph on whch to draw + :type graph: pg.PlotItem + :param event: The event + :type event: object + """ + self.current_graph = graph + vb = graph.vb + mouse_point = vb.mapSceneToView(event.pos()) + if self.current_tool: + self.current_tool(initial_pos=mouse_point) # Exec the current tool + + def bounded_line_drawer(self, initial_pos, **kwargs): + """Draw a bounded line + """ + roi = pg.LineSegmentROI((initial_pos, initial_pos), removable=True) + self.current_handle = roi.getHandles()[-1] + self.current_graph.addItem(roi) + roi.sigRemoveRequested.connect(self._on_roi_remove_requested) + + def set_tool(self, **kwargs): + """Set the current tool. kwargs may have a tool which corresponds to + the method to call inside this module. + + :return: The current tool + :rtype: method if found, None instead + """ + self.current_tool = getattr(self, kwargs.get("tool", None), None) + return self.current_tool + + def unset_tool(self): + """Unset the current tool""" + self.current_tool = None + self.current_handle = None + self.current_graph = None + + def remove_roi(self, roi): + """Remove the given roi + + :param roi: The roi to remove + :type roi: pg.ROI + """ + print(roi) + + @QtCore.Slot(object) + def _on_mouse_moved(self, event): + if self.current_handle: + vb = self.current_graph.vb + mousePoint = vb.mapSceneToView(event.pos()) + self.current_handle.setPos(mousePoint) + + @QtCore.Slot(object) + def _on_mouse_released(self, event): + print("Released", event) + + @QtCore.Slot(object) + def _on_mouse_pressed(self, event): + print("Pressed", event) + + @QtCore.Slot(list, object) + def _on_roi_add_requested(self, objects, event): + """Called on a draw requested""" + if not self.current_handle: + if objects: + self.drawer(graph=objects[0], event=event) + else: + self.unset_tool() + + @QtCore.Slot(object) + def _on_roi_remove_requested(self, roi): + """Called on a request for ROI deletion + + :param roi: The roi to remove + :type roi: pg.ROI + """ + self.remove_roi(roi=roi) diff --git a/app/libs/widgets/toolbar.py b/app/libs/widgets/toolbar.py index bdb1b34..97b4638 100644 --- a/app/libs/widgets/toolbar.py +++ b/app/libs/widgets/toolbar.py @@ -28,7 +28,7 @@ def __init__(self, parent=None): def _trigger_action(self): """Called on action triggered""" sender = self.sender() - self.signals.sig_action_triggered.emit(sender.action) + self.signals.sig_action_triggered.emit(sender.action, sender.args) def init_toolbar(self): """This method inits the toolbar of the app""" @@ -136,6 +136,7 @@ def __init__(self, parent=None, text=None, action=None, **kwargs): # Properties self.action = action + self.args = kwargs.get("args", {}) icon = kwargs.get("icon", None) if icon: diff --git a/app/resources/toolbar/toolbar.json b/app/resources/toolbar/toolbar.json index f3f4d7b..dd5c0bd 100644 --- a/app/resources/toolbar/toolbar.json +++ b/app/resources/toolbar/toolbar.json @@ -16,7 +16,10 @@ "type": "resources", "value": ":/svg/trend-line.svg" }, - "action": "method_to_call", + "action": "roi_manager.set_tool", + "args": { + "tool": "bounded_line_drawer" + }, "tooltip": "Trend Line", "text": "Trend Line", "shortcut": "Alt+T", diff --git a/app/view.py b/app/view.py index ee274fe..50902e5 100644 --- a/app/view.py +++ b/app/view.py @@ -13,6 +13,7 @@ from libs.thread_pool import ThreadPool from libs.graph.candlestick import CandlestickItem from libs.io.favorite_settings import FavoritesManager +from libs.roi_manager import ROIManager from ui import main_window @@ -27,7 +28,7 @@ def __init__(self, parent=None, data=None): super(MainWindow, self).__init__(parent=parent) self.setupUi(self) - self.setWindowState(QtCore.Qt.WindowMaximized) + # self.setWindowState(QtCore.Qt.WindowMaximized) # Constants self.tickers_dialog = None @@ -40,6 +41,7 @@ def __init__(self, parent=None, data=None): self.thread_pool = ThreadPool() self.signals = EventHandler() self.favorites_manager = FavoritesManager(parent=self) + self.roi_manager = ROIManager(parent=self) # Signals self.lie_ticker.mousePressEvent = self.tickers_dialog.show @@ -67,7 +69,6 @@ def __init__(self, parent=None, data=None): self.signals.sig_ticker_infos_fetched.connect( self.wgt_company._on_ticker_infos ) - ## self.signals.sig_ticker_articles_fetched.connect( self.wgt_articles.get_articles ) @@ -157,16 +158,18 @@ def _on_indicator_switched(self, indicator: object, state: bool): else: indicator.remove_indicator(graph_view=self.wgt_graph.graph) - @QtCore.Slot(str) - def _on_action_triggered(self, action: str): + @QtCore.Slot(str, dict) + def _on_action_triggered(self, action: str, args: dict): """Callback on action triggered from the toolbar :param action: The action to find and call :type action: str + :param args: Args for the action + :type args: dict """ action_obj = utils.find_method(module=action, obj=self) if action_obj: - action_obj() + action_obj(**args) def resizeEvent(self, event): if self.tickers_dialog: