class CustomSplitter(QWidget): def __init__(self): QWidget.__init__(self) self.splitter = QSplitter(self) self.splitter.addWidget(QTextEdit(self)) self.splitter.addWidget(QTextEdit(self)) layout = QVBoxLayout(self) layout.addWidget(self.splitter) handle = self.splitter.handle(1) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) button = QToolButton(handle) button.setArrowType(Qt.LeftArrow) button.clicked.connect(lambda: self.handleSplitterButton(True)) layout.addWidget(button) button = QToolButton(handle) button.setArrowType(Qt.RightArrow) button.clicked.connect(lambda: self.handleSplitterButton(False)) layout.addWidget(button) handle.setLayout(layout) def handleSplitterButton(self, left=True): if not all(self.splitter.sizes()): self.splitter.setSizes([1, 1]) elif left: self.splitter.setSizes([0, 1]) else: self.splitter.setSizes([1, 0])
class MainWidget(QWidget): def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) logger.add(self.log) self.mainlayout = QVBoxLayout() self.setLayout(self.mainlayout) self.splitter = QSplitter(Qt.Vertical) self.stack = QStackedWidget() self.splitter.addWidget(self.stack) # mod list widget self.modlistwidget = QWidget() self.modlistlayout = QVBoxLayout() self.modlistlayout.setContentsMargins(0, 0, 0, 0) self.modlistwidget.setLayout(self.modlistlayout) self.stack.addWidget(self.modlistwidget) # search bar self.searchbar = QLineEdit() self.searchbar.setPlaceholderText('Search...') self.modlistlayout.addWidget(self.searchbar) # mod list self.modlist = ModList(self, model) self.modlistlayout.addWidget(self.modlist) self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e)) # welcome message welcomelayout = QVBoxLayout() welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomewidget = QWidget() welcomewidget.setLayout(welcomelayout) welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent # type: ignore welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent # type: ignore welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent # type: ignore welcomewidget.dropEvent = self.modlist.dropEvent # type: ignore welcomewidget.setAcceptDrops(True) icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico'))) iconpixmap = icon.pixmap(32, 32) icon = QLabel() icon.setPixmap(iconpixmap) icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) icon.setContentsMargins(4, 4, 4, 4) welcomelayout.addWidget(icon) welcome = QLabel('''<p><font> No mod installed yet. Drag a mod into this area to get started! </font></p>''') welcome.setAttribute(Qt.WA_TransparentForMouseEvents) welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomelayout.addWidget(welcome) self.stack.addWidget(welcomewidget) # output log self.output = QTextEdit(self) self.output.setTextInteractionFlags(Qt.NoTextInteraction) self.output.setReadOnly(True) self.output.setContextMenuPolicy(Qt.NoContextMenu) self.output.setPlaceholderText('Program output...') self.splitter.addWidget(self.output) # TODO: enhancement: add a launch game icon # TODO: enhancement: show indicator if scripts have to be merged self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 0) self.mainlayout.addWidget(self.splitter) if len(model): self.stack.setCurrentIndex(0) self.splitter.setSizes([self.splitter.size().height(), 50]) else: self.stack.setCurrentIndex(1) self.splitter.setSizes([self.splitter.size().height(), 0]) model.updateCallbacks.append(self.modelUpdateEvent) def keyPressEvent(self, event: QKeyEvent) -> None: if event.key() == Qt.Key_Escape: self.modlist.setFocus() self.searchbar.setText('') elif event.matches(QKeySequence.Find): self.searchbar.setFocus() elif event.matches(QKeySequence.Paste): self.pasteEvent() super().keyPressEvent(event) def pasteEvent(self) -> None: clipboard = QApplication.clipboard().text().splitlines() if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]): self.parentWidget().showDownloadModDialog() else: urls = [ url for url in QApplication.clipboard().text().splitlines() if len(str(url.strip())) ] if all( isValidModDownloadUrl(url) or isValidFileUrl(url) for url in urls): asyncio.create_task(self.modlist.checkInstallFromURLs(urls)) def modelUpdateEvent(self, model: Model) -> None: if len(model) > 0: if self.stack.currentIndex() != 0: self.stack.setCurrentIndex(0) self.repaint() else: if self.stack.currentIndex() != 1: self.stack.setCurrentIndex(1) self.repaint() def unhideOutput(self) -> None: if self.splitter.sizes()[1] < 10: self.splitter.setSizes([self.splitter.size().height(), 50]) def unhideModList(self) -> None: if self.splitter.sizes()[0] < 10: self.splitter.setSizes([50, self.splitter.size().height()]) def log(self, message: Any) -> None: # format log messages to user readable output settings = QSettings() record = message.record message = record['message'] extra = record['extra'] level = record['level'].name.lower() name = str(extra['name'] ) if 'name' in extra and extra['name'] is not None else '' path = str(extra['path'] ) if 'path' in extra and extra['path'] is not None else '' dots = bool( extra['dots'] ) if 'dots' in extra and extra['dots'] is not None else False newline = bool( extra['newline'] ) if 'newline' in extra and extra['newline'] is not None else False output = bool( extra['output'] ) if 'output' in extra and extra['output'] is not None else bool( message) modlist = bool( extra['modlist'] ) if 'modlist' in extra and extra['modlist'] is not None else False if level in ['debug' ] and settings.value('debugOutput', 'False') != 'True': if newline: self.output.append(f'') return n = '<br>' if newline else '' d = '...' if dots else '' if len(name) and len(path): path = f' ({path})' if output: message = html.escape(message, quote=True) if level in ['success', 'error', 'warning']: message = f'<strong>{message}</strong>' if level in ['success']: message = f'<font color="#04c45e">{message}</font>' if level in ['error', 'critical']: message = f'<font color="#ee3b3b">{message}</font>' if level in ['warning']: message = f'<font color="#ff6500">{message}</font>' if level in ['debug', 'trace']: message = f'<font color="#aaa">{message}</font>' path = f'<font color="#aaa">{path}</font>' if path else '' d = f'<font color="#aaa">{d}</font>' if d else '' time = record['time'].astimezone( tz=None).strftime('%Y-%m-%d %H:%M:%S') message = f'<font color="#aaa">{time}</font> {message}' self.output.append( f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}' ) else: self.output.append(f'') self.output.verticalScrollBar().setValue( self.output.verticalScrollBar().maximum()) self.output.repaint() if modlist: self.unhideModList() if settings.value('unhideOutput', 'True') == 'True' and output: self.unhideOutput()
# --------------------------- # Splitterの左右の比率を変える # --------------------------- import sys from PySide2.QtWidgets import QApplication, QSplitter, QTextEdit app = QApplication(sys.argv) qw_text_edit_left = QTextEdit() qw_text_edit_left.append('left') qw_text_edit_right = QTextEdit() qw_text_edit_right.append('right') qw_splitter = QSplitter() # Orientationの初期値は水平 qw_splitter.addWidget(qw_text_edit_left) qw_splitter.addWidget(qw_text_edit_right) qw_splitter_size = qw_splitter.size() # Splitterのサイズを取得する qw_splitter_size_width = qw_splitter_size.width() # Splitterの横サイズを取得する qw_splitter.setSizes( [qw_splitter_size_width * 0.1, qw_splitter_size_width * 0.9]) # Splitterの横の比率を1:9に変更する print(qw_splitter.size()) # Splitter全体のサイズ print(qw_splitter.sizes()) # Splitterの子widgetごとのサイズ qw_splitter.show() sys.exit(app.exec_())
class MainWindow(QtWidgets.QMainWindow): def __init__(self, appctxt=None): super().__init__() self.appcontext = appctxt self.BASE_TITLE = 'PyMarkup' self.REFRESH_DELAY_MS = 200 self.modified = False self.refresh_timer = QtCore.QTimer() self.refresh_timer.setSingleShot(True) self.refresh_timer.setInterval(self.REFRESH_DELAY_MS) self.refresh_timer.timeout.connect(self.Refresh) self.settings = self.LoadSettings() self.init_widgets() self.init_layout() self.init_mainmenu() self.python_path = None self.save_html = True # Always true unless disabled in debug mode try: self.css = load_css(self.GetPath('template_css')) except KeyError: self.css = '' self.LoadResources() # print('Singles:',len(self.singles.keys())) # self.singles = {} # print('Macros:',len(self.macros.keys())) self.macros = {} # print('Images:',len(self.images.keys())) self.images = {} self.subitems = {} try: self.LoadFileFromPath(self.settings['paths']['lastopened']) except KeyError: pass self.Refresh() def GetPath(self,key): default_names = { 'template_css': 'template.css', 'template_html': 'template.html', 'singles': 'singles.csv', 'table_products': 'singles.csv', 'table_macros': 'macros.csv', 'img_folder': 'img', } try: path = self.settings['paths'][key] if os.path.exists(path): # print('Returning path for {}: {}'.format(key,self.settings['paths'][key])) return path except KeyError: pass try: return os.path.join(self.settings['paths']['sysfolder'],default_names[key]) except KeyError: pass return None # Load two tables and images def LoadResources(self): self.singles = read_csv(self.GetPath('table_products')) return self.subitems = get_subitems(self.singles) self.macros = read_csv_adv(self.GetPath('table_macros')) self.images = get_images(self.GetPath('img_folder')) self.ConsoleLog('Database aggiornato.') def ScheduleRefresh(self): # print('Refresh timer started for 100 ms') self.modified = True self.UpdateTitle() self.refresh_timer.start() def init_widgets(self): if ADD_WEBENGINE: self.browser = Browser(self) # self.browser.setZoomFactor(1.5) # Valid values: 0.25 to 5.0 self.browser.setZoomFactor(self.settings['browserzoomfactor']) # self.texteditor = QtWidgets.QPlainTextEdit() self.texteditor = QCodeEditor() self.texteditor.setPlainText('cliente = "Asd"') # self.texteditor.setPlainText(self.settings['texteditor']) self.texteditor.zoomIn(self.settings['zoom_texteditor']) self.texteditor.textChanged.connect(self.ScheduleRefresh) self.console = QtWidgets.QPlainTextEdit() self.console.setReadOnly(True) self.console.zoomIn(self.settings['zoom_console']) def init_mainmenu(self): menubar = self.menuBar() # filemenu = menubar.addMenu('File') # viewmenu = menubar.addMenu('Visualizza') menus = { 'File': [ ['Nuovo file', 'Ctrl+N', self.NewFile], ['Apri...', 'Ctrl+O', self.ChooseFile], ['Salva', 'Ctrl+S', self.Save], ['Salva con nome...', 'Ctrl+Shift+S', self.SaveAs], [], ['Esporta PDF (preventivo)', 'Ctrl+E', self.RenderPDF_estimate], ['Esporta PDF (proforma)', 'Ctrl+T', self.RenderPDF_proforma], [], ['Chiudi', 'Ctrl+Shift+Q', self.close], ], 'Visualizza': [ ['Ingrandisci editor',None,self.ZoomInEditor], ['Riduci editor',None,self.ZoomOutEditor], [], ['Ingrandisci anteprima',None,self.ZoomInBrowser], ['Riduci anteprima',None,self.ZoomOutBrowser], ], 'Strumenti': [ ['Aggiorna anteprima', 'Ctrl+R', self.Refresh], ['Aggiorna database', 'Ctrl+L', self.LoadResources], ['Impostazioni...', 'Ctrl+I', self.OpenSettingsDialog], ] } if DEBUG: menus['Debug'] = [ # ['Print pwd', 'Ctrl+P', self.PrintPwd], ['Which Python', None, self.RunSubprocess], ['Print this folder', None, self.PrintThisFolder], ['Save html on', None, self.EnableSaveHTML], ['Save html off', None, self.DisableSaveHTML], ['Print settings', None, self.PrintSettings], ] for menu_name,entries in menus.items(): menu = menubar.addMenu(menu_name) for data in entries: if len(data)>0: label,shortcut,function = data # label,shortcut,function = item new_action = QAction(label,self) if shortcut: new_action.setShortcut(shortcut) new_action.triggered.connect(function) menu.addAction(new_action) else: menu.addSeparator() def EnableSaveHTML(self): self.save_html = True; def DisableSaveHTML(self): self.save_html = False; def PrintSettings(self): settings = json.dumps(self.settings,indent=4) self.ConsoleLog(settings) paths = [ 'template_css', 'template_html', 'singles', 'table_products', 'table_macros', 'img_folder', ] print('Paths:') for path in paths: print(self.GetPath(path)) print('\nSettings:') print(settings) def PrintPwd(self): path = os.path.abspath('./') self.ConsoleLog(path) def PrintThisFolder(self): self.ConsoleLog(THIS_FOLDER); def ZoomInEditor(self): self.texteditor.zoomIn() def ZoomOutEditor(self): self.texteditor.zoomOut() def ZoomInBrowser(self): self.browser.setZoomFactor(self.browser.zoomFactor() + 0.1) def ZoomOutBrowser(self): self.browser.setZoomFactor(self.browser.zoomFactor() - 0.1) def Dummy(self): return def GetPythonPath(self): cmd = 'which python3' response = run(cmd, shell=True, stdout=PIPE, stderr=PIPE) return response.stdout.decode('utf-8') def RunSubprocess(self): self.ConsoleLog(self.GetPythonPath()) ''' cmd1 = '/Users/pc/Dev/pymarkup_installer_redux/local/sys_folder/xhtml2pdf.command' cmd2 = 'pwd > /Users/pc/Dev/pymarkup_installer_redux/local/sys_folder/pwd.txt' cmd = 'which python3' response = run(cmd, shell=True, stdout=PIPE, stderr=PIPE) #self.ConsoleLog(str(response)) log = [cmd, response.returncode, response.stdout.decode('utf-8'), response.stderr] #log = [cmd, response] self.ConsoleLog('\n'.join([str(x) for x in log])) ''' ''' process = os.popen("which python") result = process.read() print(result) self.ConsoleLog(result) ''' def ConfirmClose(self): # print('ConfirmClose') if self.modified: dialog = QMessageBox() # dialog.setIcon(QMessageBox.Question) dialog.setWindowTitle('Modifiche non salvate') dialog.setInformativeText(u"Abbandonare le modifiche in corso?") dialog.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) choice = dialog.exec_() return choice == QMessageBox.Ok return True def ChooseFile(self): if self.ConfirmClose(): chosen_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Apri file', '', '*.toml',) if chosen_path[0] != '': self.settings['paths']['lastopened'] = chosen_path[0] self.LoadFileFromPath(chosen_path[0]) self.modified = False self.UpdateTitle() def LoadFileFromPath(self,path): if os.path.exists(path): with open(path, 'r+') as file: self.texteditor.setPlainText(file.read()) self.modified = False self.UpdateTitle() def NewFile(self): if self.ConfirmClose(): self.modified = False self.settings['paths']['lastopened'] = '' self.setWindowTitle(self.BASE_TITLE+' - senza titolo') self.texteditor.setPlainText('') self.Refresh() self.DisplayHTML('') self.ConsoleLog('Nuovo file creato.') def Save(self): path = self.settings['paths']['lastopened'] if os.path.exists(path): try: with open(path,'w+') as file: file.write(self.texteditor.toPlainText()) self.ConsoleLog('File salvato:\n'+path) self.modified = False self.UpdateTitle() except Exception as e: self.ConsoleLog(e) def SaveAs(self): path,_ = QtWidgets.QFileDialog().getSaveFileName(self, "Salva file", '', "Toml (*.toml)") if path != '': try: with open(path,'w+') as file: file.write(self.texteditor.toPlainText()) self.settings['paths']['lastopened'] = path self.modified = False self.ConsoleLog('File salvato:\n'+path) self.UpdateTitle() except Exception as e: print(str(e)) self.ConsoleLog(str(e)) def UpdateTitle(self): path = self.settings['paths']['lastopened'] new_title = '{} - {}'.format(self.BASE_TITLE,path) if self.modified: new_title += ' (modificato)' self.setWindowTitle(new_title) def init_layout(self): self.setWindowTitle(self.BASE_TITLE) ''' left_layout = QtWidgets.QVBoxLayout() left_layout.addWidget(self.texteditor) left_layout.addWidget(self.console) left_layout_widget = QtWidgets.QWidget() left_layout_widget.setLayout(left_layout) ''' self.layout_left_splitter = QSplitter() self.layout_left_splitter.setOrientation(QtCore.Qt.Vertical) self.layout_left_splitter.addWidget(self.texteditor) self.layout_left_splitter.addWidget(self.console) self.layout_panes = QSplitter() self.layout_panes.addWidget(self.layout_left_splitter) if ADD_WEBENGINE: self.layout_panes.addWidget(self.browser) layout = QtWidgets.QVBoxLayout() layout.addWidget(self.layout_panes) # Set size for left splitter for settings_key,splitter in [ ['left_splitter_sizes', self.layout_left_splitter], ['splitter_sizes', self.layout_panes], ]: splitter_size = self.settings.get(settings_key) if splitter_size is not None: splitter.setSizes(splitter_size) # Set size for main splitter # splitter_sizes = self.settings.get('splitter_sizes') # if splitter_sizes is not None: # self.layout_panes.setSizes(splitter_sizes) centralWidget = QtWidgets.QWidget() centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.setGeometry(*self.settings['geometry']) def RenderHTML(self, **kwargs): toml_text = self.texteditor.toPlainText() toml_model = read_toml(toml_text, css=self.css) order_model = merge(toml_model, self.singles, self.subitems, self.macros, self.images) # print('TOML merge OK') template_path = 'template.html' # print('Template path:',template_path) path_html = self.GetPath('template_html') path_css = self.GetPath('template_css') return render_html(order_model, path_html=path_html, path_css=path_css, **kwargs) def RenderPDF(self, **kwargs): folder,filename = os.path.split(self.settings['paths']['lastopened']) name,_ = os.path.splitext(filename) defaultpath = os.path.join(folder,name+'.pdf') # print('Last opened folder/filename:',folder) savepath,_ = QtWidgets.QFileDialog().getSaveFileName(self, "Salva PDF", defaultpath, "PDF (*.pdf)") if savepath == '': return path_sys = self.settings['paths']['sysfolder'] html = self.RenderHTML(**kwargs) htmlpath = os.path.join(path_sys,'tmp.html') # Save 'tmp.html' # MAY BE DISABLED FOR DEBUGGING if self.save_html: with open(htmlpath, 'w+', encoding='UTF-8') as file: file.write(html) sys_folder = self.settings['paths']['sysfolder'] #path_script = os.path.join(self.settings['paths']['renderpdf'], 'Contents/MacOS/renderpdf') path_script = os.path.join(sys_folder, 'renderpdf.py') #self.ConsoleLog(html) print('-----') print('RenderPDF paths:') print(path_script, os.path.exists(path_script)) print(htmlpath, os.path.exists(htmlpath)) print(savepath) print('-----') #render_pdf(path_script,htmlpath,savepath) #python_path = '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3' python_path = self.settings['paths']['python'] command = f'{python_path} "{path_script}" --args "{savepath}"' #command = f'xhtml2pdf {htmlpath} {savepath}' ''' cmd_lines = [ 'from xhtml2pdf import pisa', f'resultFile = open("{savepath}", "w+b")', f'htmlFile = open("{htmlpath}","r+")', f'pisa.CreatePDF(htmlFile.read(),dest=resultFile)', 'resultFile.close()', 'htmlFile.close()', ] command = "python3 -c '" + ';'.join(cmd_lines) + "'" ''' #command = f'python3 "{path_script}" "{savepath}"' # Path to renderpdf.command (as a proxy to execute renderpdf.py) #command = os.path.join(sys_folder, 'xhtml2pdf.command') print('Calling command:') print(command) print('-----') try: #result = call(command,shell=True) # shell=True is needed because subprocess.call is just a wrapper for Popen result = call(command, shell=True) self.ConsoleLog(command) #self.ConsoleLog('Xhtml2 call result: '+str(result)) #self.ConsoleLog('PDF salvato: '+savepath) except FileNotFoundError as e: print('Subprocess.call raised an exception:') print(e) self.ConsoleLog(str(e)) except Exception as e: self.ConsoleLog(str(e)) with open(os.path.join(sys_folder,'applog.txt'),'r+') as file: file.write(str(e)) # os.startfile('rendered.pdf') def RenderPDF_estimate(self): self.RenderPDF(preview=False,render_estimate=True) def RenderPDF_proforma(self): self.RenderPDF(preview=False,render_proforma=True) def DisplayHTML(self, html): # print('Display HTML') self.browser.SetHTML(html) def Refresh(self): # print('Refresh') try: html = self.RenderHTML(preview=True, render_estimate=True) self.DisplayHTML(html) self.ConsoleLog('OK') # self.ConsoleLog('Render HTML OK. Html length: {}\n{}'.format(len(html),html)) except Exception as e: print(e) self.ConsoleLog(str(e)) def ConsoleLog(self,msg): self.console.setPlainText(msg) def LoadSettings(self): default_settings = { 'geometry': [500,300,1400,1000], # 'texteditor': '', 'splitter_sizes': None, 'left_splitter_sizes': None, 'browserzoomfactor': 1, 'paths': { 'lastopened': '', 'python': '/Library/Frameworks/Python.framework/Versions/3.6/bin/python3', }, 'zoom_texteditor': 2, 'zoom_console': 2, } qsettings = QtCore.QSettings('Company','Appname') jsontext = qsettings.value('settings',None) # print('Json settings from QSettings:',jsontext) settings = json.loads(jsontext) if jsontext else {} for key in default_settings.keys(): if key in settings: default_settings[key] = settings[key] # Check all paths and delete them if they are broken for key,path in default_settings['paths'].items(): if not os.path.exists(path): default_settings['paths'][key] = '' return default_settings def SaveSettings(self): # print('Saving settings') g = self.geometry() self.settings['geometry'] = [g.x(), g.y() ,g.width(), g.height()] # self.settings['texteditor'] = self.texteditor.toPlainText() self.settings['splitter_sizes'] = self.layout_panes.sizes() self.settings['left_splitter_sizes'] = self.layout_left_splitter.sizes() self.settings['browserzoomfactor'] = self.browser.zoomFactor() qsettings = QtCore.QSettings('Company','Appname') qsettings.setValue('settings', json.dumps(self.settings)) def OpenSettingsDialog(self): dialog = SettingsDialog(self) dialog.exec_() def closeEvent(self,event): # print('closeEvent') if self.ConfirmClose(): event.ignore() self.SaveSettings() # print('Closing program (settings saved)') event.accept() else: event.ignore() '''
class TableWidget(QWidget): def __init__(self, db, name, select, update_ui): super().__init__() self.db = db self.setWindowTitle(name) self.dirty = False self.make_widgets(select) self.make_layout() self.make_connections(update_ui) def make_widgets(self, select): self.sqlEdit = SQLEdit.SQLEdit(select) self.sqlEdit.setTabChangesFocus(True) self.tableModel = TableModel.TableModel(self.db, Sql.uncommented(select)) self.tableView = QTableView() self.tableView.setModel(self.tableModel) self.statusLabel = QLabel() self.statusLabel.setTextFormat(Qt.RichText) self.update_status(select) def make_layout(self): self.splitter = QSplitter(Qt.Vertical) self.splitter.addWidget(self.sqlEdit) self.splitter.addWidget(self.tableView) self.splitter.setStretchFactor(1, 11) vbox = QVBoxLayout() vbox.addWidget(self.splitter) vbox.addWidget(self.statusLabel) self.setLayout(vbox) def make_connections(self, update_ui): self.sqlEdit.textChanged.connect(update_ui) self.sqlEdit.copyAvailable.connect(update_ui) self.tableModel.sql_error.connect(self.on_sql_error) @property def sizes(self): return self.splitter.sizes() @property def is_select(self): return Sql.is_select(self.sql) @property def sql(self): return self.sqlEdit.toPlainText() def refresh(self): if not self.is_select: self.statusLabel.setText('<font color=red>Only SELECT ' 'statements are supported here</font>') else: select = Sql.uncommented(self.sqlEdit.toPlainText()) if re.match(r'\s*SELECT(:?\s+(:?ALL|DISTINCT))?\s+\*', select, re.IGNORECASE | re.DOTALL): try: names = ', '.join([ Sql.quoted(name) for name in self.db.field_names_for_select(select) ]) select = select.replace('*', names, 1) except apsw.SQLError as err: self.on_sql_error(str(err)) return self.tableModel.refresh(select) self.update_status(select) def on_sql_error(self, err): self.statusLabel.setText(f'<font color=red>{err}</font>') def update_status(self, select): try: count = self.db.select_row_count(select) s = 's' if count != 1 else '' self.statusLabel.setText(f'{count:,} row{s}') except (apsw.SQLError, Sql.Error) as err: self.on_sql_error(str(err)) def closeEvent(self, event): self.save(closing=True) event.accept() def save(self, *, closing=False): print(f'TableWidget.save dirty={self.dirty} closing={closing}') saved = False errors = False if self.dirty and bool(self.db): # TODO save change to list view or form view errors = [] # self.db.save_... if errors: if not closing: error = '\n'.join(errors) QMessageBox.warning(self, f'Save error — {APPNAME}', f'Failed to save:\n{error}') else: saved = True self.dirty = False return saved