summaryrefslogtreecommitdiff
path: root/src/main/python/utils/toolbar.py
blob: dbfff22bfa621219fafc54832c6448757d26b092 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtCore import QSize, Qt, pyqtSignal, QMimeData
from PyQt5.QtGui import QIcon, QDrag
from PyQt5.QtWidgets import (QBoxLayout, QDockWidget, QGridLayout, QLineEdit,
                             QScrollArea, QToolButton, QWidget, QStyle, QLabel)
from re import search, IGNORECASE

from .data import toolbarItems
from .app import fileImporter, app
from .layout import flowLayout

class toolbar(QDockWidget):
    """
    Defines the right side toolbar, using QDockWidget. 
    """
    toolbuttonClicked = pyqtSignal(dict) #signal for any object button pressed 
    
    def __init__(self, parent = None):
        super(toolbar, self).__init__(parent)
        self.toolbarButtonDict = dict() #initializes empty dict to store toolbar buttons
        self.toolbarLabelDict = dict()
        self.toolbarItems(toolbarItems.keys()) #creates all necessary buttons
        
        self.setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable)
        #mainly used to disable closeability of QDockWidget
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.NoDockWidgetArea)
        #declare main widget and layout
        self.widget = QWidget(self)
        self.widget.setObjectName("ToolbarWidget")
        self.layout = QBoxLayout(QBoxLayout.TopToBottom, self.widget)
        self.setWindowFlags(Qt.FramelessWindowHint)
        
        self.searchBox = QLineEdit(self.widget) #search box to search through componenets
        
        #connect signal to filter slot, add searchbar to toolbar
        self.searchBox.textChanged.connect(self.searchQuery)
        self.layout.addWidget(self.searchBox, alignment=Qt.AlignHCenter)
        
        #create a scrollable area to house all buttons
        self.diagArea = QScrollArea(self)
        self.diagArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.diagArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.diagArea.setWidgetResizable(True)
        self.layout.addWidget(self.diagArea, stretch=1)
        self.diagAreaWidget = QWidget(self.diagArea) #inner widget for scroll area
        self.diagAreaWidget.setObjectName("ToolbarScrollWidget")
        #custom layout for inner widget
        self.diagAreaLayout = flowLayout(self.diagAreaWidget)
        self.diagAreaLayout.setSizeConstraint(flowLayout.SetMinimumSize)
        self.setWidget(self.widget) #set main widget to dockwidget
    
    def clearLayout(self):
        # used to clear all items from toolbar, by parenting it to the toolbar instead
        # this works because changing parents moves widgets to be the child of the new
        # parent, setting it to none, would have qt delete them to free memory
        for i in reversed(range(self.diagAreaLayout.count())): 
            # since changing parent would effect indexing, its important to go in reverse
            self.diagAreaLayout.itemAt(i).widget().setParent(self)
            
    def populateToolbar(self, filterFunc=None):
        #called everytime the button box needs to be updated(incase of a filter)
        self.clearLayout() #clears layout
        for itemClass in self.toolbarButtonDict.keys():
            self.diagAreaLayout.addWidget(self.toolbarLabelDict[itemClass])
            for item in filter(filterFunc, self.toolbarButtonDict[itemClass].keys()):
                self.diagAreaLayout.addWidget(self.toolbarButtonDict[itemClass][item])
        self.resize()
            
    def searchQuery(self):
        # shorten toolbaritems list with search items
        # self.populateToolbar() # populate with toolbar items
        text = self.searchBox.text() #get text
        if text == '':
            self.populateToolbar() # restore everything on empty string
        else:
            # use regex to search filter through button list and add the remainder to toolbar
            self.populateToolbar(lambda x: search(text, x, IGNORECASE))                             

    def resize(self):
        # called when main window resizes, overloading resizeEvent caused issues.
        parent = self.parentWidget() #used to get parent dimensions
        self.layout.setDirection(QBoxLayout.TopToBottom) # here so that a horizontal toolbar can be implemented later
        # self.setFixedHeight(self.height()) #span available height
        self.searchBox.setMinimumWidth(.18*parent.width())
        width = self.width()
        scrollBar = self.diagArea.verticalScrollBar()
        height = self.diagAreaLayout.heightForWidth(width)
        if scrollBar.isVisible():
            width -= app.app.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        
        # the following line, sets the required height for the current width, so that blank space doesnt occur
        self.diagAreaWidget.setMinimumHeight(height)
        self.setMinimumWidth(.2*parent.width()) #12% of parent width
        # self.setMinimumWidth(self.diagAreaLayout.minimumSize().width()) #12% of parent width
        self.diagAreaWidget.setLayout(self.diagAreaLayout)
        self.diagArea.setWidget(self.diagAreaWidget)
        
        for _, label in self.toolbarLabelDict.items():
            label.setFixedWidth(width)
    
    def resizeEvent(self, event):
        self.resize()  

    def toolbarItems(self, itemClasses):
        #helper functions to create required buttons
        for itemClass in itemClasses:
            self.toolbarButtonDict[itemClass] = {}
            label = SectionLabel(itemClass)
            self.toolbarLabelDict[itemClass] = label
            for item in toolbarItems[itemClass].keys():
                obj = toolbarItems[itemClass][item]
                button = toolbarButton(self, obj)
                self.toolbarButtonDict[itemClass][item] = button
            
    @property
    def toolbarItemList(self):
        #generator to iterate over all buttons
        for i in self.toolbarButtonDict.keys():
            yield  i
            
class toolbarButton(QToolButton):
    """
    Custom buttons for components that implements drag and drop functionality
    item -> dict from toolbarItems dict, had 4 properties, name, object, icon and default arguments.
    """
    def __init__(self, parent = None, item = None):
        super(toolbarButton, self).__init__(parent)
        #uses fbs resource manager to get icons
        self.setIcon(QIcon(fileImporter('toolbar', item['icon'])))
        self.setIconSize(QSize(64, 64)) #unecessary but left for future references
        self.dragStartPosition = None #intialize value for drag event
        self.itemObject = item['object'] #refer current item object, to handle drag mime
        for i in item['args']:
            self.itemObject += f"/{i}"
        self.setText(item["name"]) #button text
        self.setToolTip(item["name"]) #button tooltip

    def mousePressEvent(self, event):
        #check if button was pressed or there was a drag intent
        super(toolbarButton, self).mousePressEvent(event)
        if event.button() == Qt.LeftButton:
            self.dragStartPosition = event.pos() #set dragstart position
    
    def mouseMoveEvent(self, event):
        #handles drag
        if not (event.buttons() and Qt.LeftButton):
            return #ignore if left click is not held
        if (event.pos() - self.dragStartPosition).manhattanLength() < app.app.startDragDistance():
            return #check if mouse was dragged enough, manhattan length is a rough and quick method in qt
        
        drag = QDrag(self) #create drag object
        mimeData = QMimeData() #create drag mime
        mimeData.setText(self.itemObject) # set mime value for view to accept
        drag.setMimeData(mimeData) # attach mime to drag
        drag.exec(Qt.CopyAction) #execute drag
        
    def sizeHint(self):
        #defines button size
        return self.minimumSizeHint()
    
    def minimumSizeHint(self):
        #defines button size
        return QSize(55, 55)

class SectionLabel(QLabel):
    
    def __init__(self, *args):
        super(SectionLabel, self).__init__(*args)