1+ from qtpy .QtCore import Signal , QSortFilterProxyModel , Qt
2+ from qtpy .QtSql import QSqlTableModel , QSqlDatabase , QSqlQuery
3+ from qtpy .QtWidgets import QApplication , QWidget , QVBoxLayout , QMessageBox , QStyledItemDelegate , QTableView , QAbstractItemView , \
4+ QHBoxLayout , \
5+ QLabel , QSpacerItem , QSizePolicy , QFileDialog , QComboBox
6+
7+ # for search feature
8+ from pyqt_openai .pyqt_openai_data import DB
9+ from pyqt_openai .widgets .button import Button
10+ from pyqt_openai .widgets .searchBar import SearchBar
11+
12+
13+ class FilterProxyModel (QSortFilterProxyModel ):
14+ def __init__ (self ):
15+ super ().__init__ ()
16+ self .__searchedText = ''
17+
18+ @property
19+ def searchedText (self ):
20+ return self .__searchedText
21+
22+ @searchedText .setter
23+ def searchedText (self , value ):
24+ self .__searchedText = value
25+ self .invalidateFilter ()
26+
27+
28+ # for align text in every cell to center
29+ class AlignDelegate (QStyledItemDelegate ):
30+ def initStyleOption (self , option , index ):
31+ super ().initStyleOption (option , index )
32+ option .displayAlignment = Qt .AlignmentFlag .AlignCenter
33+
34+
35+ class SqlTableModel (QSqlTableModel ):
36+ added = Signal (int , str )
37+ updated = Signal (int , str )
38+ deleted = Signal (list )
39+ addedCol = Signal ()
40+ deletedCol = Signal ()
41+
42+ def flags (self , index ):
43+ if index .column () == self .column_index_by_name ('name' ):
44+ return Qt .ItemFlag .ItemIsEnabled | Qt .ItemFlag .ItemIsSelectable | Qt .ItemFlag .ItemIsEditable
45+ return Qt .ItemFlag .ItemIsEnabled | Qt .ItemFlag .ItemIsSelectable
46+
47+ def column_index_by_name (self , name ):
48+ return self .fieldIndex (name )
49+
50+
51+ class ChatNavWidget (QWidget ):
52+ added = Signal ()
53+ clicked = Signal (int , str )
54+ cleared = Signal ()
55+ onImport = Signal (str )
56+ onExport = Signal (list )
57+
58+ def __init__ (self , columns , table_nm ):
59+ super ().__init__ ()
60+ self .__initVal (columns , table_nm )
61+ self .__initUi ()
62+
63+ def __initVal (self , columns , table_nm ):
64+ self .__columns = columns
65+ self .__table_nm = table_nm
66+
67+ def __initUi (self ):
68+ # Set up the database and table model (you'll need to configure this part based on your database)
69+ self .__imageDb = QSqlDatabase .addDatabase ('QSQLITE' ) # Replace with your database type
70+ self .__imageDb .setDatabaseName ('conv.db' ) # Replace with your database name
71+ self .__imageDb .open ()
72+
73+ imageGenerationHistoryLbl = QLabel ()
74+ imageGenerationHistoryLbl .setText ('History' )
75+
76+ self .__addBtn = Button ()
77+ self .__delBtn = Button ()
78+ self .__importBtn = Button ()
79+ self .__saveBtn = Button ()
80+ self .__clearBtn = Button ()
81+
82+ self .__addBtn .setStyleAndIcon ('ico/add.svg' )
83+ self .__delBtn .setStyleAndIcon ('ico/delete.svg' )
84+ self .__importBtn .setStyleAndIcon ('ico/import.svg' )
85+ self .__saveBtn .setStyleAndIcon ('ico/save.svg' )
86+ self .__clearBtn .setStyleAndIcon ('ico/close.svg' )
87+
88+ self .__addBtn .setToolTip ('Add' )
89+ self .__delBtn .setToolTip ('Delete' )
90+ self .__importBtn .setToolTip ('SQLite DB Import (Working)' )
91+ self .__saveBtn .setToolTip ('Save' )
92+ self .__delBtn .setToolTip ('Remove All' )
93+
94+ self .__addBtn .clicked .connect (self .add )
95+ self .__delBtn .clicked .connect (self .__delete )
96+ self .__importBtn .clicked .connect (self .__import )
97+ self .__saveBtn .clicked .connect (self .__export )
98+ self .__clearBtn .clicked .connect (self .__clear )
99+
100+ lay = QHBoxLayout ()
101+ lay .addWidget (imageGenerationHistoryLbl )
102+ lay .addSpacerItem (QSpacerItem (10 , 10 , QSizePolicy .Policy .MinimumExpanding ))
103+ lay .addWidget (self .__addBtn )
104+ lay .addWidget (self .__delBtn )
105+ lay .addWidget (self .__clearBtn )
106+ lay .addWidget (self .__importBtn )
107+ lay .addWidget (self .__saveBtn )
108+ lay .setContentsMargins (0 , 0 , 0 , 0 )
109+
110+ menuSubWidget1 = QWidget ()
111+ menuSubWidget1 .setLayout (lay )
112+
113+ self .__searchBar = SearchBar ()
114+ self .__searchBar .setPlaceHolder ('Search...' )
115+ self .__searchBar .searched .connect (self .__search )
116+
117+ self .__searchOptionCmbBox = QComboBox ()
118+ self .__searchOptionCmbBox .addItems (['Title' , 'Content' ])
119+ self .__searchOptionCmbBox .setMinimumHeight (self .__searchBar .sizeHint ().height ())
120+ self .__searchOptionCmbBox .currentIndexChanged .connect (
121+ lambda _ : self .__search (self .__searchBar .getSearchBar ().text ()))
122+
123+ lay = QHBoxLayout ()
124+ lay .addWidget (self .__searchBar )
125+ lay .addWidget (self .__searchOptionCmbBox )
126+ lay .setContentsMargins (0 , 0 , 0 , 0 )
127+
128+ menuSubWidget2 = QWidget ()
129+ menuSubWidget2 .setLayout (lay )
130+
131+ lay = QVBoxLayout ()
132+ lay .addWidget (menuSubWidget1 )
133+ lay .addWidget (menuSubWidget2 )
134+ lay .setContentsMargins (0 , 0 , 0 , 0 )
135+
136+ menuWidget = QWidget ()
137+ menuWidget .setLayout (lay )
138+
139+ self .__model = SqlTableModel (self )
140+ self .__model .setTable (self .__table_nm )
141+ self .__model .beforeUpdate .connect (self .__updated )
142+
143+ for i in range (len (self .__columns )):
144+ self .__model .setHeaderData (i , Qt .Orientation .Horizontal , self .__columns [i ])
145+ self .__model .select ()
146+ # descending order by insert date
147+ idx = self .__columns .index ('insert_dt' )
148+ self .__model .sort (idx , Qt .SortOrder .DescendingOrder )
149+
150+ # init the proxy model
151+ self .__proxyModel = FilterProxyModel ()
152+
153+ # set the table model as source model to make it enable to feature sort and filter function
154+ self .__proxyModel .setSourceModel (self .__model )
155+
156+ # set up the view
157+ self .__tableView = QTableView ()
158+ self .__tableView .setModel (self .__proxyModel )
159+ self .__tableView .setEditTriggers (QTableView .EditTrigger .DoubleClicked | QTableView .EditTrigger .SelectedClicked )
160+ self .__tableView .setSortingEnabled (True )
161+
162+ # align to center
163+ delegate = AlignDelegate ()
164+ for i in range (self .__model .columnCount ()):
165+ self .__tableView .setItemDelegateForColumn (i , delegate )
166+
167+ # set selection/resize policy
168+ self .__tableView .setSelectionBehavior (QAbstractItemView .SelectionBehavior .SelectRows )
169+ self .__tableView .setSelectionMode (QAbstractItemView .SelectionMode .ExtendedSelection )
170+
171+ self .__tableView .clicked .connect (self .__clicked )
172+ self .__tableView .activated .connect (self .__clicked )
173+
174+ lay = QVBoxLayout ()
175+ lay .addWidget (menuWidget )
176+ lay .addWidget (self .__tableView )
177+ self .setLayout (lay )
178+
179+ self .refreshData ()
180+
181+ def add (self , called_from_parent = False ):
182+ if called_from_parent :
183+ pass
184+ else :
185+ self .added .emit ()
186+ self .__model .select ()
187+
188+ def __import (self ):
189+ filename = QFileDialog .getOpenFileName (self , 'Import' , '' , 'SQLite DB files (*.db)' )
190+ if filename :
191+ filename = filename [0 ]
192+ self .onImport .emit (filename )
193+
194+ def __export (self ):
195+ self .onExport .emit (self .__getSelectedIds ())
196+
197+ def __updated (self , i , r ):
198+ # send updated signal
199+ self .__model .updated .emit (r .value ('id' ), r .value ('name' ))
200+
201+ def refreshData (self , title = None ):
202+ self .__model .select ()
203+ # index -1 will be read from all columns
204+ # otherwise it will be read the current column number indicated by combobox
205+ self .__proxyModel .setFilterKeyColumn (- 1 )
206+ # regular expression can be used
207+ self .__proxyModel .setFilterRegularExpression (title )
208+
209+ def __clicked (self , idx ):
210+ # get id of record
211+ id = self .__model .data (self .__proxyModel .mapToSource (idx .siblingAtColumn (0 )), role = Qt .ItemDataRole .DisplayRole )
212+ title = self .__model .data (self .__proxyModel .mapToSource (idx .siblingAtColumn (1 )), role = Qt .ItemDataRole .DisplayRole )
213+
214+ self .clicked .emit (id , title )
215+
216+ def __getSelectedIds (self ):
217+ idx_s = [idx .siblingAtColumn (0 ) for idx in self .__tableView .selectedIndexes ()]
218+ idx_s = list (set (idx_s ))
219+ ids = [self .__model .data (idx , role = Qt .ItemDataRole .DisplayRole ) for idx in idx_s ]
220+ return ids
221+
222+ def __delete (self ):
223+ ids = self .__getSelectedIds ()
224+ for _id in ids :
225+ DB .deleteConv (_id )
226+ self .__model .select ()
227+ self .cleared .emit ()
228+
229+ def __clear (self ):
230+ '''
231+ Clear all data in the table
232+ '''
233+ # Before clearing, confirm the action
234+ reply = QMessageBox .question (self , 'Confirm' , 'Are you sure to clear all data?' , QMessageBox .StandardButton .Yes | QMessageBox .StandardButton .No )
235+ if reply == QMessageBox .StandardButton .Yes :
236+ DB .deleteConv ()
237+ self .__model .select ()
238+ self .cleared .emit ()
239+
240+ def __search (self , search_text ):
241+ # title
242+ if self .__searchOptionCmbBox .currentText () == 'Title' :
243+ self .refreshData (search_text )
244+ # content
245+ elif self .__searchOptionCmbBox .currentText () == 'Content' :
246+ if search_text :
247+ convs = DB .selectAllContentOfConv (content_to_select = search_text )
248+ ids = [_ [0 ] for _ in convs ]
249+ self .__model .setQuery (QSqlQuery (f"SELECT { ',' .join (self .__columns )} FROM { self .__table_nm } "
250+ f"WHERE id IN ({ ',' .join (map (str , ids ))} )" ))
251+ else :
252+ self .refreshData ()
253+
254+ def isCurrentConvExists (self ):
255+ return self .__model .rowCount () > 0 and self .__tableView .currentIndex ()
256+
257+
258+ if __name__ == "__main__" :
259+ import sys
260+
261+ app = QApplication (sys .argv )
262+ w = ChatNavWidget (['id' , 'name' , 'update_dt' , 'insert_dt' ], 'conv_tb' )
263+ w .show ()
264+ sys .exit (app .exec ())
0 commit comments