def __init__(self): ''' Set up user interface from ui_main.py file ''' sys.excepthook = exception_handler QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.hide_menu_options() self.init_ui() self.show()
def __init__(self, app, force_quit=False): """ Set up user interface from ui_main.py file. """ self.app = app self.force_quit = force_quit sys.excepthook = exception_handler QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.hide_menu_options() font = 'font: ' + str(self.app.settings['fontsize']) + 'pt ' font += '"' + self.app.settings['font'] + '";' self.setStyleSheet(font) self.init_ui() self.show()
class MainWindow(QtWidgets.QMainWindow): """ Main GUI window. Project data is stored in a directory with .qda suffix core data is stored in data.qda sqlite file. Journal and coding dialogs can be shown non-modally - multiple dialogs open. There is a risk of a clash if two coding windows are open with the same file text or two journals open with the same journal entry. Note: App.settings does not contain projectName, conn or path (to database) app.project_name and app.project_path contain these. """ project = {"databaseversion": "", "date": "", "memo": "", "about": ""} dialogList = [] # keeps active and track of non-modal windows def __init__(self, app, force_quit=False): """ Set up user interface from ui_main.py file. """ self.app = app self.force_quit = force_quit sys.excepthook = exception_handler QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.hide_menu_options() font = 'font: ' + str(self.app.settings['fontsize']) + 'pt ' font += '"' + self.app.settings['font'] + '";' self.setStyleSheet(font) self.init_ui() self.show() def init_ui(self): """ Set up menu triggers """ # project menu self.ui.actionCreate_New_Project.triggered.connect(self.new_project) self.ui.actionOpen_Project.triggered.connect(self.open_project) self.ui.actionProject_Memo.triggered.connect(self.project_memo) self.ui.actionClose_Project.triggered.connect(self.close_project) self.ui.actionSettings.triggered.connect(self.change_settings) self.ui.actionProject_Exchange_Export.triggered.connect(self.REFI_project_export) self.ui.actionREFI_Codebook_export.triggered.connect(self.REFI_codebook_export) self.ui.actionREFI_Codebook_import.triggered.connect(self.REFI_codebook_import) self.ui.actionREFI_QDA_Project_import.triggered.connect(self.REFI_project_import) self.ui.actionRQDA_Project_import.triggered.connect(self.rqda_project_import) self.ui.actionExit.triggered.connect(self.closeEvent) # file cases and journals menu self.ui.actionManage_files.triggered.connect(self.manage_files) self.ui.actionManage_journals.triggered.connect(self.journals) self.ui.actionManage_cases.triggered.connect(self.manage_cases) self.ui.actionManage_attributes.triggered.connect(self.manage_attributes) self.ui.actionImport_survey.triggered.connect(self.import_survey) # codes menu self.ui.actionCodes.triggered.connect(self.text_coding) self.ui.actionCode_image.triggered.connect(self.image_coding) self.ui.actionCode_audio_video.triggered.connect(self.av_coding) self.ui.actionExport_codebook.triggered.connect(self.codebook) # reports menu self.ui.actionCoding_reports.triggered.connect(self.report_coding) self.ui.actionCoding_comparison.triggered.connect(self.report_coding_comparison) self.ui.actionCode_frequencies.triggered.connect(self.report_code_frequencies) self.ui.actionView_Graph.triggered.connect(self.view_graph_original) #TODO self.ui.actionText_mining.triggered.connect(self.text_mining) self.ui.actionSQL_statements.triggered.connect(self.report_sql) # help menu self.ui.actionContents.triggered.connect(self.help) self.ui.actionAbout.triggered.connect(self.about) font = 'font: ' + str(self.app.settings['fontsize']) + 'pt ' font += '"' + self.app.settings['font'] + '";' self.setStyleSheet(font) self.settings_report() def hide_menu_options(self): """ No project opened, hide most menu options. Enable project import options.""" # project menu self.ui.actionClose_Project.setEnabled(False) self.ui.actionProject_Memo.setEnabled(False) self.ui.actionProject_Exchange_Export.setEnabled(False) self.ui.actionREFI_Codebook_export.setEnabled(False) self.ui.actionREFI_Codebook_import.setEnabled(False) self.ui.actionREFI_QDA_Project_import.setEnabled(True) self.ui.actionRQDA_Project_import.setEnabled(True) # files cases journals menu self.ui.actionManage_files.setEnabled(False) self.ui.actionManage_journals.setEnabled(False) self.ui.actionManage_cases.setEnabled(False) self.ui.actionManage_attributes.setEnabled(False) self.ui.actionImport_survey.setEnabled(False) # codes menu self.ui.actionCodes.setEnabled(False) self.ui.actionCode_image.setEnabled(False) self.ui.actionCode_audio_video.setEnabled(False) self.ui.actionCategories.setEnabled(False) self.ui.actionView_Graph.setEnabled(False) self.ui.actionExport_codebook.setEnabled(False) # reports menu self.ui.actionCoding_reports.setEnabled(False) self.ui.actionCoding_comparison.setEnabled(False) self.ui.actionCode_frequencies.setEnabled(False) self.ui.actionText_mining.setEnabled(False) self.ui.actionSQL_statements.setEnabled(False) def show_menu_options(self): """ Project opened, show most menu options. Disable project import options. """ # project menu self.ui.actionClose_Project.setEnabled(True) self.ui.actionProject_Memo.setEnabled(True) self.ui.actionProject_Exchange_Export.setEnabled(True) self.ui.actionREFI_Codebook_export.setEnabled(True) self.ui.actionREFI_Codebook_import.setEnabled(True) self.ui.actionREFI_QDA_Project_import.setEnabled(False) self.ui.actionRQDA_Project_import.setEnabled(False) # files cases journals menu self.ui.actionManage_files.setEnabled(True) self.ui.actionManage_journals.setEnabled(True) self.ui.actionManage_cases.setEnabled(True) self.ui.actionManage_attributes.setEnabled(True) self.ui.actionImport_survey.setEnabled(True) # codes menu self.ui.actionCodes.setEnabled(True) self.ui.actionCode_image.setEnabled(True) self.ui.actionCode_audio_video.setEnabled(True) self.ui.actionCategories.setEnabled(True) self.ui.actionView_Graph.setEnabled(True) self.ui.actionExport_codebook.setEnabled(True) # reports menu self.ui.actionCoding_reports.setEnabled(True) self.ui.actionCoding_comparison.setEnabled(True) self.ui.actionCode_frequencies.setEnabled(True) self.ui.actionSQL_statements.setEnabled(True) #TODO FOR FUTURE EXPANSION text mining self.ui.actionText_mining.setEnabled(False) self.ui.actionText_mining.setVisible(False) def settings_report(self): msg = _("Settings") msg += "\n========\n" msg += _("Coder") + ": " + self.app.settings['codername'] + "\n" msg += _("Font") + ": " + self.app.settings['font'] + " " + str(self.app.settings['fontsize']) + "\n" msg += _("Tree font size") + ": " + str(self.app.settings['treefontsize']) + "\n" msg += _("Directory") + ": " + self.app.settings['directory'] + "\n" msg += _("Project_path:") + self.app.project_path + "\n" msg += _("Show IDs") + ": " + str(self.app.settings['showids']) + "\n" msg += _("Language") + ": " + self.app.settings['language'] + "\n" msg += _("Backup on open") + ": " + str(self.app.settings['backup_on_open']) + "\n" msg += _("Backup AV files") + ": " + str(self.app.settings['backup_av_files']) msg += "\n========" self.ui.textEdit.append(msg) def report_sql(self): """ Run SQL statements on database. """ ui = DialogSQL(self.app, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() """def text_mining(self): ''' text analysis of files / cases / codings. NOT CURRENTLY IMPLEMENTED, FOR FUTURE EXPANSION. ''' ui = DialogTextMining(self.app, self.ui.textEdit) ui.show()""" def report_coding_comparison(self): """ Compare two or more coders using Cohens Kappa. """ for d in self.dialogList: if type(d).__name__ == "DialogCoderComparison": return ui = DialogReportCoderComparisons(self.app, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_code_frequencies(self): """ Show code frequencies overall and by coder. """ for d in self.dialogList: if type(d).__name__ == "DialogCodeFrequencies": return ui = DialogReportCodeFrequencies(self.app, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_coding(self): """ Report on coding and categories. """ ui = DialogReportCodes(self.app, self.ui.textEdit, self.dialogList) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def view_graph_original(self): """ Show acyclic graph of codes and categories. """ ui = ViewGraphOriginal(self.app) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def help(self): """ Display help information in browser. """ webbrowser.open(path + "/GUI/QualCoder_Manual.pdf") self.clean_dialog_refs() def about(self): """ About dialog. """ for d in self.dialogList: if type(d).__name__ == "DialogInformation" and d.windowTitle() == "About": return ui = DialogInformation("About", "") self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def manage_attributes(self): """ Create, edit, delete, rename attributes. """ ui = DialogManageAttributes(self.app, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def import_survey(self): """ Import survey flat sheet: csv file. Create cases and assign attributes to cases. Identify qualitative questions and assign these data to the source table for coding and review. Modal dialog. """ ui = DialogImportSurvey(self.app, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def manage_cases(self): """ Create, edit, delete, rename cases, add cases to files or parts of files, add memos to cases. """ for d in self.dialogList: if type(d).__name__ == "DialogCases": return ui = DialogCases(self.app, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def manage_files(self): """ Create text files or import files from odt, docx, html and plain text. Rename, delete and add memos to files. """ for d in self.dialogList: if type(d).__name__ == "DialogManageFiles": return ui = DialogManageFiles(self.app, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def journals(self): """ Create and edit journals. """ for d in self.dialogList: if type(d).__name__ == "DialogJournals": return ui = DialogJournals(self.app, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def text_coding(self): """ Create edit and delete codes. Apply and remove codes and annotations to the text in imported text files. """ ui = DialogCodeText(self.app, self.ui.textEdit, self.dialogList) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def image_coding(self): """ Create edit and delete codes. Apply and remove codes to the image (or regions) """ ui = DialogCodeImage(self.app, self.ui.textEdit, self.dialogList) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def av_coding(self): """ Create edit and delete codes. Apply and remove codes to segements of the audio or video file. Added try block in case VLC bindings do not work. """ try: ui = DialogCodeAV(self.app, self.ui.textEdit, self.dialogList) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() except Exception as e: logger.debug(str(e)) print(e) QtWidgets.QMessageBox.warning(None, "A/V Coding", str(e), QtWidgets.QMessageBox.Ok) self.clean_dialog_refs() def codebook(self): """ Export a text file code book of categories and codes. """ Codebook(self.app, self.ui.textEdit) def REFI_project_export(self): """ Export the project as a qpdx zipped folder. Follows the REFI Project Exchange standards. CURRENTLY IN TESTING AND NOT COMPLETE NOR VALIDATED. VARIABLES ARE NOT SUCCESSFULLY EXPORTED YET. CURRENTLY GIFS ARE EXPORTED UNCHANGED (NEED TO BE PNG OR JPG)""" Refi_export(self.app, self.ui.textEdit, "project") msg = "NOT FULLY TESTED - EXPERIMENTAL\n" QtWidgets.QMessageBox.warning(None, "REFI QDA Project export", msg) def REFI_codebook_export(self): """ Export the codebook as .qdc Follows the REFI standard version 1.0. https://www.qdasoftware.org/ """ # Refi_export(self.app, self.ui.textEdit, "codebook") def REFI_codebook_import(self): """ Import a codebook .qdc into an opened project. Follows the REFI-QDA standard version 1.0. https://www.qdasoftware.org/ """ Refi_import(self.app, self.ui.textEdit, "qdc") def REFI_project_import(self): """ Import a qpdx QDA project into a new project space. Follows the REFI standard. """ self.close_project() self.ui.textEdit.append("IMPORTING REFI-QDA PROJECT") self.new_project() # check project created successfully if self.app.project_name == "": QtWidgets.QMessageBox.warning(None, "Project creation", "Project not successfully created") return Refi_import(self.app, self.ui.textEdit, "qdpx") msg = "EXPERIMENTAL - NOT FULLY TESTED\n" msg += "PDFs not imported\n" msg += "Audio, video, transcripts, transcript codings and synchpoints not tested.\n" msg += "Sets and Graphs not imported as QualCoders does not have this functionality.\n" msg += "External sources over 2GB not imported." QtWidgets.QMessageBox.warning(None, "REFI QDA Project import", msg) def rqda_project_import(self): """ Import an RQDA format project into a new poject space. """ self.close_project() self.ui.textEdit.append("IMPORTING RQDA PROJECT") self.new_project() # check project created successfully if self.app.project_name == "": QtWidgets.QMessageBox.warning(None, "Project creation", "Project not successfully created") return Rqda_import(self.app, self.ui.textEdit) def closeEvent(self, event): """ Override the QWindow close event. Close all dialogs and database connection. If selected via menu option exit: event == False If selected via window x close: event == QtGui.QCloseEvent """ if not self.force_quit: quit_msg = _("Are you sure you want to quit?") reply = QtWidgets.QMessageBox.question(self, 'Message', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.dialogList = None if self.app.conn is not None: try: self.app.conn.commit() self.app.conn.close() except: pass QtWidgets.qApp.quit() return if event is False: return else: event.ignore() def new_project(self): """ Create a new project folder with data.qda (sqlite) and folders for documents, images, audio and video. Note the database does not keep a table specifically for users (coders), instead usernames can be freely entered through the settings dialog and are collated from coded text, images and a/v. v2 had added column in code_text table to link to avid in code_av table. """ self.app = App() if self.app.settings['directory'] == "": self.app.settings['directory'] = os.path.expanduser('~') self.app.project_path = QtWidgets.QFileDialog.getSaveFileName(self, _("Enter project name"), self.app.settings['directory'], ".qda")[0] if self.app.project_path == "": QtWidgets.QMessageBox.warning(None, _("Project"), _("No project created.")) return if self.app.project_path.find(".qda") == -1: self.app.project_path = self.app.project_path + ".qda" try: os.mkdir(self.app.project_path) os.mkdir(self.app.project_path + "/images") os.mkdir(self.app.project_path + "/audio") os.mkdir(self.app.project_path + "/video") os.mkdir(self.app.project_path + "/documents") except Exception as e: logger.critical(_("Project creation error ") + str(e)) QtWidgets.QMessageBox.warning(None, _("Project"), _("No project created. Exiting. ") + str(e)) exit(0) self.app.project_name = self.app.project_path.rpartition('/')[2] self.app.settings['directory'] = self.app.project_path.rpartition('/')[0] self.app.create_connection(self.app.project_path) cur = self.app.conn.cursor() cur.execute("CREATE TABLE project (databaseversion text, date text, memo text,about text);") cur.execute("CREATE TABLE source (id integer primary key, name text, fulltext text, mediapath text, memo text, owner text, date text, unique(name));") cur.execute("CREATE TABLE code_image (imid integer primary key,id integer,x1 integer, y1 integer, width integer, height integer, cid integer, memo text, date text, owner text);") cur.execute("CREATE TABLE code_av (avid integer primary key,id integer,pos0 integer, pos1 integer, cid integer, memo text, date text, owner text);") cur.execute("CREATE TABLE annotation (anid integer primary key, fid integer,pos0 integer, pos1 integer, memo text, owner text, date text);") cur.execute("CREATE TABLE attribute_type (name text primary key, date text, owner text, memo text, caseOrFile text, valuetype text);") cur.execute("CREATE TABLE attribute (attrid integer primary key, name text, attr_type text, value text, id integer, date text, owner text);") cur.execute("CREATE TABLE case_text (id integer primary key, caseid integer, fid integer, pos0 integer, pos1 integer, owner text, date text, memo text);") cur.execute("CREATE TABLE cases (caseid integer primary key, name text, memo text, owner text,date text, constraint ucm unique(name));") cur.execute("CREATE TABLE code_cat (catid integer primary key, name text, owner text, date text, memo text, supercatid integer, unique(name));") cur.execute("CREATE TABLE code_text (cid integer, fid integer,seltext text, pos0 integer, pos1 integer, owner text, date text, memo text, avid integer, unique(cid,fid,pos0,pos1, owner));") cur.execute("CREATE TABLE code_name (cid integer primary key, name text, memo text, catid integer, owner text,date text, color text, unique(name));") cur.execute("CREATE TABLE journal (jid integer primary key, name text, jentry text, date text, owner text);") cur.execute("INSERT INTO project VALUES(?,?,?,?)", ('v2',datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),'','QualCoder')) self.app.conn.commit() try: # get and display some project details self.ui.textEdit.append("\n" + _("New project: ") + self.app.project_path + _(" created.")) #self.settings['projectName'] = self.path.rpartition('/')[2] self.ui.textEdit.append(_("Opening: ") + self.app.project_path) self.setWindowTitle("QualCoder " + self.app.project_name) cur.execute('select sqlite_version()') self.ui.textEdit.append("SQLite version: " + str(cur.fetchone())) cur.execute("select databaseversion, date, memo, about from project") result = cur.fetchone() self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] self.ui.textEdit.append(_("New Project Created") + "\n========\n" + _("DB Version:") + str(self.project['databaseversion']) + "\n" + _("Date: ") + str(self.project['date']) + "\n" + _("About: ") + str(self.project['about']) + "\n" + _("Coder:") + str(self.app.settings['codername']) + "\n" + "========") except Exception as e: msg = _("Problem creating database ") logger.warning(msg + self.app.project_path + " Exception:" + str(e)) self.ui.textEdit.append("\n" + msg + "\n" + self.app.project_path) self.ui.textEdit.append(str(e)) self.close_project() return self.open_project(self.app.project_path) def change_settings(self): """ Change default settings - the coder name, font, font size. Non-modal. Backup options """ ui = DialogSettings(self.app) ui.exec_() self.settings_report() font = 'font: ' + str(self.app.settings['fontsize']) + 'pt ' font += '"' + self.app.settings['font'] + '";' self.setStyleSheet(font) def project_memo(self): """ Give the entire project a memo. Modal dialog. """ cur = self.app.conn.cursor() cur.execute("select memo from project") memo = cur.fetchone()[0] ui = DialogMemo(self.app, _("Memo for project ") + self.app.project_name, memo) self.dialogList.append(ui) ui.exec_() if memo != ui.memo: cur.execute('update project set memo=?', (ui.memo,)) self.app.conn.commit() self.ui.textEdit.append(_("Project memo entered.")) def open_project(self, path=""): """ Open an existing project. Also save a backup datetime stamped copy at the same time. """ if self.app.project_name != "": self.close_project() self.setWindowTitle("QualCoder" + _("Open Project")) if path == "" or path is False: #print("appsettings dir ", self.app.settings['directory']) # tmp path = QtWidgets.QFileDialog.getExistingDirectory(self, _('Open project directory'), self.app.settings['directory']) if path == "" or path is False: return msg = "" if len(path) > 3 and path[-4:] == ".qda": try: self.app.create_connection(path) except Exception as e: self.app.conn = None msg += " " + str(e) logger.debug(msg) if self.app.conn is None: msg += "\n" + path QtWidgets.QMessageBox.warning(None, _("Cannot open file"), msg) self.app.project_path = "" self.app.project_name = "" return # get and display some project details self.app.append_recent_project(self.app.project_path) self.setWindowTitle("QualCoder " + self.app.project_name) cur = self.app.conn.cursor() cur.execute("select databaseversion, date, memo, about from project") result = cur.fetchall()[-1] self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] # check avid column in code_text table # database version < 2 try: cur.execute("select avid from code_text") except: cur.execute("ALTER TABLE code_text ADD avid integer;") self.app.conn.commit() # Save a datetime stamped backup if self.app.settings['backup_on_open'] == 'True': nowdate = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") backup = self.app.project_path[0:-4] + "_BACKUP_" + nowdate + ".qda" if self.app.settings['backup_av_files'] == 'True': shutil.copytree(self.app.project_path, backup) else: shutil.copytree(self.app.project_path, backup, ignore=shutil.ignore_patterns('*.mp3','*.wav','*.mp4', '*.mov','*.ogg','*.wmv','*.MP3','*.WAV','*.MP4', '*.MOV','*.OGG','*.WMV')) self.ui.textEdit.append(_("WARNING: audio and video files NOT backed up. See settings.")) self.ui.textEdit.append(_("Project backup created: ") + backup) self.ui.textEdit.append(_("Project Opened: ") + self.app.project_name + "\n========\n" + _("Path: ") + self.app.project_path + "\n" + _("Directory: ") + self.app.settings['directory'] + "\n" + _("Database version: ") + self.project['databaseversion'] + ". " + _("Date: ") + str(self.project['date']) + "\n" + _("About: ") + self.project['about'] + "\n========\n") self.show_menu_options() def close_project(self): """ Close an open project. """ self.ui.textEdit.append("Closing project: " + self.app.project_name + "\n========\n") try: self.app.conn.commit() self.app.conn.close() except: pass self.app.conn = None self.app.project_path = "" self.app.project_name = "" self.app.settings['directory'] = "" self.project = {"databaseversion": "", "date": "", "memo": "", "about": ""} self.hide_menu_options() self.clean_dialog_refs() def clean_dialog_refs(self): """ Test the list of dialog refs to see if they have been cleared and create a new list of current dialogs. Also need to keep these dialog references to keep non-modal dialogs open. Non-modal example - having a journal open and a coding dialog. """ tempList = [] for d in self.dialogList: try: #logger.debug(str(d) + ", isVisible:" + str(d.isVisible()) + " Title:" + d.windowTitle()) #d.windowTitle() if d.isVisible(): tempList.append(d) # RuntimeError: wrapped C/C++ object of type DialogSQL has been deleted except RuntimeError as e: logger.error(str(e)) self.dialogList = tempList self.update_dialog_lists_in_modeless_dialogs() def update_dialog_lists_in_modeless_dialogs(self): ''' This is to assist: Update code and category tree in DialogCodeImage, DialogCodeAV, DialogCodeText, DialogReportCodes ''' for d in self.dialogList: if isinstance(d, DialogCodeText): d.dialog_list = self.dialogList if isinstance(d, DialogCodeAV): d.dialog_list = self.dialogList if isinstance(d, DialogCodeImage): d.dialog_list = self.dialogList if isinstance(d, DialogReportCodes): d.dialog_list = self.dialogList
class MainWindow(QtWidgets.QMainWindow): """ Main GUI window. Project data is stored in a directory with .qda suffix core data is stored in data.qda sqlite file. Journal and coding dialogs can be shown non-modally - multiple dialogs open. There is a risk of a clash if two coding windows are open with the same file text or two journals open with the same journal entry. """ settings = { "conn": None, "directory": home, "projectName": "", "showIDs": False, 'path': home, "codername": "default", "font": "Noto Sans", "fontsize": 10, 'treefontsize': 10, "language": "en" } project = {"databaseversion": "", "date": "", "memo": "", "about": ""} dialogList = [] # keeps active and track of non-modal windows def __init__(self): """ Set up user interface from ui_main.py file. """ sys.excepthook = exception_handler QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.hide_menu_options() self.init_ui() self.conn = None self.show() def init_ui(self): """ Set up menu triggers """ # project menu self.ui.actionCreate_New_Project.triggered.connect(self.new_project) self.ui.actionOpen_Project.triggered.connect(self.open_project) self.ui.actionProject_Memo.triggered.connect(self.project_memo) self.ui.actionClose_Project.triggered.connect(self.close_project) self.ui.actionSettings.triggered.connect(self.change_settings) self.ui.actionProject_Exchange_Export.triggered.connect( self.project_exchange_export) self.ui.actionExit.triggered.connect(self.closeEvent) # file cases and journals menu self.ui.actionManage_files.triggered.connect(self.manage_files) self.ui.actionManage_journals.triggered.connect(self.journals) self.ui.actionManage_cases.triggered.connect(self.manage_cases) self.ui.actionManage_attributes.triggered.connect( self.manage_attributes) self.ui.actionImport_survey.triggered.connect(self.import_survey) # codes menu self.ui.actionCodes.triggered.connect(self.text_coding) self.ui.actionCode_image.triggered.connect(self.image_coding) self.ui.actionCode_audio_video.triggered.connect(self.av_coding) self.ui.actionExport_codebook.triggered.connect(self.codebook) self.ui.actionView_Graph.triggered.connect(self.view_graph) # reports menu self.ui.actionCoding_reports.triggered.connect(self.report_coding) self.ui.actionCoding_comparison.triggered.connect( self.report_coding_comparison) self.ui.actionCode_frequencies.triggered.connect( self.report_code_frequencies) #TODO self.ui.actionText_mining.triggered.connect(self.text_mining) self.ui.actionSQL_statements.triggered.connect(self.report_sql) # help menu self.ui.actionContents.triggered.connect(self.help) self.ui.actionAbout.triggered.connect(self.about) # load_settings from file stored in home/.qualcoder/ try: with open(home + '/.qualcoder/QualCoder_settings.txt') as f: txt = f.read() txt = txt.split("\n") self.settings['codername'] = txt[0] self.settings['font'] = txt[1] self.settings['fontsize'] = int(txt[2]) self.settings['treefontsize'] = int(txt[3]) self.settings['directory'] = txt[4] self.settings['showIDs'] = False if txt[5] == "True": self.settings['showIDs'] = True # With workarounds for an empty or non-existant language line try: self.settings['language'] = txt[6] if txt[6] == "": self.settings['language'] = "en" except: self.settings['language'] = "en" except: f = open(home + '/.qualcoder/QualCoder_settings.txt', 'w') f.write("default\nNoto Sans\n10\n10\n" + home + "\nFalse\nen") f.close() new_font = QtGui.QFont(self.settings['font'], self.settings['fontsize'], QtGui.QFont.Normal) self.setFont(new_font) self.settings_report() def hide_menu_options(self): """ No project opened, hide these menu options """ self.ui.actionClose_Project.setEnabled(False) self.ui.actionProject_Memo.setEnabled(False) self.ui.actionProject_Exchange_Export.setEnabled(False) # files cases journals menu self.ui.actionManage_files.setEnabled(False) self.ui.actionManage_journals.setEnabled(False) self.ui.actionManage_cases.setEnabled(False) self.ui.actionManage_attributes.setEnabled(False) self.ui.actionImport_survey.setEnabled(False) # codes menu self.ui.actionCodes.setEnabled(False) self.ui.actionCode_image.setEnabled(False) self.ui.actionCode_audio_video.setEnabled(False) self.ui.actionCategories.setEnabled(False) self.ui.actionView_Graph.setEnabled(False) self.ui.actionExport_codebook.setEnabled(False) # reports menu self.ui.actionCoding_reports.setEnabled(False) self.ui.actionCoding_comparison.setEnabled(False) self.ui.actionCode_frequencies.setEnabled(False) self.ui.actionText_mining.setEnabled(False) self.ui.actionSQL_statements.setEnabled(False) def show_menu_options(self): """ Project opened, show these menu options """ self.ui.actionClose_Project.setEnabled(True) self.ui.actionProject_Memo.setEnabled(True) self.ui.actionProject_Exchange_Export.setEnabled(True) # files cases journals menu self.ui.actionManage_files.setEnabled(True) self.ui.actionManage_journals.setEnabled(True) self.ui.actionManage_cases.setEnabled(True) self.ui.actionManage_attributes.setEnabled(True) self.ui.actionImport_survey.setEnabled(True) # codes menu self.ui.actionCodes.setEnabled(True) self.ui.actionCode_image.setEnabled(True) self.ui.actionCode_audio_video.setEnabled(True) self.ui.actionCategories.setEnabled(True) self.ui.actionView_Graph.setEnabled(True) self.ui.actionExport_codebook.setEnabled(True) # reports menu self.ui.actionCoding_reports.setEnabled(True) self.ui.actionCoding_comparison.setEnabled(True) self.ui.actionCode_frequencies.setEnabled(True) self.ui.actionSQL_statements.setEnabled(True) #TODO FOR FUTURE EXPANSION text mining self.ui.actionText_mining.setEnabled(False) def settings_report(self): msg = _("Settings") msg += "\n========\n" msg += _("Coder") + ": " + self.settings['codername'] + "\n" msg += _("Font") + ": " + self.settings['font'] + " " + str( self.settings['fontsize']) msg += ". " msg += _("Code font size") + ": " + self.settings['font'] + " " + str( self.settings['treefontsize']) + "\n" msg += _("Directory") + ": " + self.settings['directory'] + "\n" msg += _("Show IDs") + ": " + str(self.settings['showIDs']) + "\n" msg += _("Language") + ": " + self.settings['language'] msg += "\n========" self.ui.textEdit.append(msg) def report_sql(self): """ Run SQL statements on database. """ ui = DialogSQL(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() """def text_mining(self): ''' text analysis of files / cases / codings. NOT CURRENTLY IMPLEMENTED, FOR FUTURE EXPANSION. ''' ui = DialogTextMining(self.settings, self.ui.textEdit) ui.show()""" def report_coding_comparison(self): """ Compare two or more coders using Cohens Kappa. """ for d in self.dialogList: if type(d).__name__ == "DialogCoderComparison": return ui = DialogReportCoderComparisons(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_code_frequencies(self): """ Show code frequencies overall and by coder. """ for d in self.dialogList: if type(d).__name__ == "DialogCodeFrequencies": return ui = DialogReportCodeFrequencies(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_coding(self): """ Report on coding and categories. """ ui = DialogReportCodes(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def view_graph(self): """ Show acyclic graph of codes and categories. """ ui = ViewGraph(self.settings) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def help(self): """ Help dialog. """ ui = DialogInformation("Help contents", "GUI/en_Help.html") self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def about(self): """ About dialog. """ for d in self.dialogList: if type(d).__name__ == "DialogInformation" and d.windowTitle( ) == "About": return ui = DialogInformation("About", "GUI/About.html") self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def manage_attributes(self): """ Create, edit, delete, rename attributes. """ ui = DialogManageAttributes(self.settings, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def import_survey(self): """ Import survey flat sheet: csv file. Create cases and assign attributes to cases. Identify qualitative questions and assign these data to the source table for coding and review. Modal dialog. """ ui = DialogImportSurvey(self.settings, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def manage_cases(self): """ Create, edit, delete, rename cases, add cases to files or parts of files, add memos to cases. """ for d in self.dialogList: if type(d).__name__ == "DialogCases": return ui = DialogCases(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def manage_files(self): """ Create text files or import files from odt, docx, html and plain text. Rename, delete and add memos to files. """ for d in self.dialogList: if type(d).__name__ == "DialogManageFiles": return ui = DialogManageFiles(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def journals(self): """ Create and edit journals. """ for d in self.dialogList: if type(d).__name__ == "DialogJournals": return ui = DialogJournals(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def text_coding(self): """ Create edit and delete codes. Apply and remove codes and annotations to the text in imported text files. """ ui = DialogCodeText(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def image_coding(self): """ Create edit and delete codes. Apply and remove codes to the image (or regions) """ ui = DialogCodeImage(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def av_coding(self): """ Create edit and delete codes. Apply and remove codes to segements of the audio or video file. Added try block in case VLC bindings do not work. """ try: ui = DialogCodeAV(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() except Exception as e: logger.debug(str(e)) print(e) QtWidgets.QMessageBox.warning(None, "A/V Coding", str(e), QtWidgets.QMessageBox.Ok) self.clean_dialog_refs() def codebook(self): """ Export a text file code book of categories and codes. """ Codebook(self.settings, self.ui.textEdit) def project_exchange_export(self): """ Export the project as a qpdx zipped folder. Follows the REFI Project Exchange standards. CURRENTLY IN TESTING AND NOT COMPLETE NOR VALIDATED. VARIABLES ARE NOT SUCCESSFULLY EXPORTED YET. CURRENTLY GIFS ARE EXPORTED UNCHANGED (NEED TO BE PNG OR JPG)""" Refi(self.settings, self.ui.textEdit) def closeEvent(self, event): """ Override the QWindow close event. Close all dialogs and database connection. If selected via menu option exit: event == False If selected via window x close: event == QtGui.QCloseEvent """ quit_msg = _("Are you sure you want to quit?") reply = QtWidgets.QMessageBox.question(self, 'Message', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.dialogList = None if self.settings['conn'] is not None: try: self.settings['conn'].commit() self.settings['conn'].close() except: pass QtWidgets.qApp.quit() return if event is False: return else: event.ignore() def new_project(self): """ Create a new project folder with data.qda (sqlite) and folders for documents, images, audio and video. """ #logger.debug("settings[directory]:" + self.settings['directory']) self.settings['path'] = QtWidgets.QFileDialog.getSaveFileName( self, _("Enter project name"), self.settings['directory'], ".qda")[0] if self.settings['path'] == "": QtWidgets.QMessageBox.warning(None, _("Project"), _("No project created.")) return if self.settings['path'].find(".qda") == -1: self.settings['path'] = self.settings['path'] + ".qda" try: os.mkdir(self.settings['path']) os.mkdir(self.settings['path'] + "/images") os.mkdir(self.settings['path'] + "/audio") os.mkdir(self.settings['path'] + "/video") os.mkdir(self.settings['path'] + "/documents") except Exception as e: logger.critical(_("Project creation error ") + str(e)) QtWidgets.QMessageBox.warning( None, _("Project"), _("No project created. Exiting. ") + str(e)) exit(0) self.settings['projectName'] = self.settings['path'].rpartition('/')[2] self.settings['directory'] = self.settings['path'].rpartition('/')[0] #try: self.settings['conn'] = sqlite3.connect(self.settings['path'] + "/data.qda") cur = self.settings['conn'].cursor() cur.execute( "CREATE TABLE project (databaseversion text, date text, memo text,about text);" ) cur.execute( "CREATE TABLE source (id integer primary key, name text, fulltext text, mediapath text, memo text, owner text, date text, unique(name));" ) cur.execute( "CREATE TABLE code_image (imid integer primary key,id integer,x1 integer, y1 integer, width integer, height integer, cid integer, memo text, date text, owner text);" ) cur.execute( "CREATE TABLE code_av (avid integer primary key,id integer,pos0 integer, pos1 integer, cid integer, memo text, date text, owner text);" ) cur.execute( "CREATE TABLE annotation (anid integer primary key, fid integer,pos0 integer, pos1 integer, memo text, owner text, date text);" ) cur.execute( "CREATE TABLE attribute_type (name text primary key, date text, owner text, memo text, caseOrFile text, valuetype text);" ) cur.execute( "CREATE TABLE attribute (attrid integer primary key, name text, attr_type text, value text, id integer, date text, owner text);" ) cur.execute( "CREATE TABLE case_text (id integer primary key, caseid integer, fid integer, pos0 integer, pos1 integer, owner text, date text, memo text);" ) cur.execute( "CREATE TABLE cases (caseid integer primary key, name text, memo text, owner text,date text, constraint ucm unique(name));" ) cur.execute( "CREATE TABLE code_cat (catid integer primary key, name text, owner text, date text, memo text, supercatid integer, unique(name));" ) cur.execute( "CREATE TABLE code_text (cid integer, fid integer,seltext text, pos0 integer, pos1 integer, owner text, date text, memo text, unique(cid,fid,pos0,pos1, owner));" ) cur.execute( "CREATE TABLE code_name (cid integer primary key, name text, memo text, catid integer, owner text,date text, color text, unique(name));" ) cur.execute( "CREATE TABLE journal (jid integer primary key, name text, jentry text, date text, owner text);" ) cur.execute( "INSERT INTO project VALUES(?,?,?,?)", ('v1', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), '', 'QualCoder')) self.settings['conn'].commit() try: # get and display some project details self.ui.textEdit.append("\n" + _("New project: ") + self.settings['path'] + _(" created.")) #self.settings['projectName'] = self.path.rpartition('/')[2] self.ui.textEdit.append(_("Opening: ") + self.settings['path']) self.setWindowTitle("QualCoder " + self.settings['projectName']) cur = self.settings['conn'].cursor() cur.execute('select sqlite_version()') self.ui.textEdit.append("SQLite version: " + str(cur.fetchone())) cur.execute( "select databaseversion, date, memo, about from project") result = cur.fetchone() self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] self.ui.textEdit.append( _("New Project Created") + "\n========\n" + _("DB Version:") + str(self.project['databaseversion']) + "\n" + _("Date: ") + str(self.project['date']) + "\n" + _("About: ") + str(self.project['about']) + "\n" + _("Coder:") + str(self.settings['codername']) + "\n" + "========") except Exception as e: msg = _("Problem creating database ") logger.warning(msg + self.settings['path'] + " Exception:" + str(e)) self.ui.textEdit.append("\n" + msg + "\n" + self.settings['path']) self.ui.textEdit.append(str(e)) self.close_project() return self.open_project(self.settings['path']) def change_settings(self): """ Change default settings - the coder name, font, font size. Non-modal. """ ui = DialogSettings(self.settings) ui.exec_() self.settings_report() newfont = QtGui.QFont(self.settings['font'], self.settings['fontsize'], QtGui.QFont.Normal) self.setFont(newfont) def project_memo(self): """ Give the entire project a memo. Modal dialog. """ cur = self.settings['conn'].cursor() cur.execute("select memo from project") memo = cur.fetchone()[0] ui = DialogMemo(self.settings, _("Memo for project ") + self.settings['projectName'], memo) self.dialogList.append(ui) ui.exec_() if memo != ui.memo: cur.execute('update project set memo=?', (ui.memo, )) self.settings['conn'].commit() self.ui.textEdit.append(_("Project memo entered.")) def open_project(self, path=""): """ Open an existing project. Also save a backup datetime stamped copy at the same time. """ if self.settings['projectName'] != "": self.close_project() self.setWindowTitle("QualCoder" + _("Open Project")) if path == "" or path is False: path = QtWidgets.QFileDialog.getExistingDirectory( self, _('Open project directory'), self.settings['directory']) if path == "" or path is False: return if len(path) > 3 and path[-4:] == ".qda": self.settings['path'] = path msg = "" try: self.settings['conn'] = sqlite3.connect(self.settings['path'] + "/data.qda") except Exception as e: self.settings['conn'] = None msg += str(e) logger.debug(str(e)) if self.settings['conn'] is None: QtWidgets.QMessageBox.warning( None, _("Cannot open file"), self.settings['path'] + _(" is not a .qda file ")) self.settings['path'] = "" return # get and display some project details self.settings['path'] = path self.settings['projectName'] = self.settings['path'].rpartition('/')[2] self.settings['directory'] = self.settings['path'].rpartition('/')[0] self.setWindowTitle("QualCoder " + self.settings['projectName']) cur = self.settings['conn'].cursor() cur.execute("select databaseversion, date, memo, about from project") result = cur.fetchone() self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] # Save a datetime stamped backup nowdate = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") backup = self.settings['path'][0:-4] + "_BACKUP_" + nowdate + ".qda" shutil.copytree(self.settings['path'], backup) self.ui.textEdit.append( _("Project Opened: ") + self.settings['projectName'] + "\n========\n" + _("Path: ") + self.settings['path'] + "\n" + _("Directory: ") + self.settings['directory'] + "\n" + _("Database version: ") + self.project['databaseversion'] + ". " + _("Date: ") + str(self.project['date']) + "\n" + _("About: ") + self.project['about'] + "\n" + _("Language: ") + self.settings['language'] + "\n" + _("Project backup created: ") + backup + "\n========\n") self.show_menu_options() def close_project(self): """ Close an open project. """ self.ui.textEdit.append("Closing project: " + self.settings['projectName'] + "\n========\n") try: self.settings['conn'].commit() self.settings['conn'].close() except: pass self.conn = None self.settings['conn'] = None self.settings['path'] = "" self.settings['projectName'] = "" self.settings['directory'] = "" self.project = { "databaseversion": "", "date": "", "memo": "", "about": "" } self.hide_menu_options() self.clean_dialog_refs() def clean_dialog_refs(self): """ Test the list of dialog refs to see if they have been cleared and create a new list of current dialogs. Also need to keep these dialog references to keep non-modal dialogs open. Non-modal example - having a journal open and a coding dialog. """ tempList = [] for d in self.dialogList: try: #logger.debug(str(d) + ", isVisible:" + str(d.isVisible()) + " Title:" + d.windowTitle()) #d.windowTitle() if d.isVisible(): tempList.append(d) # RuntimeError: wrapped C/C++ object of type DialogSQL has been deleted except RuntimeError as e: logger.error(str(e)) self.dialogList = tempList
class MainWindow(QtWidgets.QMainWindow): ''' Main GUI window. Project data is stored in a directory with .qda suffix core data is stored in data.qda sqlite file. Journal and coding dialogs can beshown non=modally - multiple dialogs open. There is a risk of a clash if two coding windows are open with the same file text or two journals open with the same journal entry. ''' settings = { "conn": None, "directory": home, "projectName": "", "showIDs": False, 'path': home, "codername": "default", "font": "Noto Sans", "fontsize": 10, 'treefontsize': 10 } project = {"databaseversion": "", "date": "", "memo": "", "about": ""} dialogList = [] # keeps active and track of non-modal windows def __init__(self): ''' Set up user interface from ui_main.py file ''' sys.excepthook = exception_handler QtWidgets.QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.hide_menu_options() self.init_ui() self.show() def init_ui(self): ''' set up menu triggers ''' # project menu self.ui.actionCreate_New_Project.triggered.connect(self.new_project) self.ui.actionOpen_Project.triggered.connect(self.open_project) self.ui.actionProject_Memo.triggered.connect(self.project_memo) self.ui.actionClose_Project.triggered.connect(self.close_project) self.ui.actionSettings.triggered.connect(self.change_settings) self.ui.actionExit.triggered.connect(self.closeEvent) # file cases and journals menu self.ui.actionManage_files.triggered.connect(self.manage_files) self.ui.actionManage_journals.triggered.connect(self.journals) self.ui.actionManage_cases.triggered.connect(self.manage_cases) self.ui.actionManage_attributes.triggered.connect( self.manage_attributes) self.ui.actionImport_survey.triggered.connect(self.import_survey) # codes menu self.ui.actionCodes.triggered.connect(self.text_coding) self.ui.actionCode_image.triggered.connect(self.image_coding) self.ui.actionExport_codebook.triggered.connect(self.codebook) self.ui.actionView_Graph.triggered.connect(self.view_graph) # reports menu self.ui.actionCoding_reports.triggered.connect(self.report_coding) self.ui.actionCoding_comparison.triggered.connect( self.report_coding_comparison) self.ui.actionCode_frequencies.triggered.connect( self.report_code_frequencies) #TODO self.ui.actionText_mining.triggered.connect(self.text_mining) self.ui.actionSQL_statements.triggered.connect(self.report_sql) # help menu self.ui.actionContents.triggered.connect(self.help) self.ui.actionAbout.triggered.connect(self.about) # load_settings from file stored in home try: with open(home + '/QualCoder_settings.txt') as f: txt = f.read() txt = txt.split("\n") self.settings['codername'] = txt[0] self.settings['font'] = txt[1] self.settings['fontsize'] = int(txt[2]) self.settings['treefontsize'] = int(txt[3]) self.settings['directory'] = txt[4] self.settings['showIDs'] = False if txt[5] == "True": self.settings['showIDs'] = True except: f = open(home + '/QualCoder_settings.txt', 'w') f.write("default\nNoto Sans\n10\n10\n" + home + "\nFalse\n") f.close() new_font = QtGui.QFont(self.settings['font'], self.settings['fontsize'], QtGui.QFont.Normal) self.setFont(new_font) self.settings_report() def hide_menu_options(self): ''' No project opened, hide options ''' self.ui.actionClose_Project.setEnabled(False) self.ui.actionProject_Memo.setEnabled(False) # files cases journals menu self.ui.actionManage_files.setEnabled(False) self.ui.actionManage_journals.setEnabled(False) self.ui.actionManage_cases.setEnabled(False) self.ui.actionManage_attributes.setEnabled(False) self.ui.actionImport_survey.setEnabled(False) # codes menu self.ui.actionCodes.setEnabled(False) self.ui.actionCode_image.setEnabled(False) self.ui.actionCategories.setEnabled(False) self.ui.actionView_Graph.setEnabled(False) # reports menu self.ui.actionCoding_reports.setEnabled(False) self.ui.actionCoding_comparison.setEnabled(False) self.ui.actionCode_frequencies.setEnabled(False) self.ui.actionText_mining.setEnabled(False) self.ui.actionSQL_statements.setEnabled(False) def show_menu_options(self): ''' Project opened, hide options ''' self.ui.actionClose_Project.setEnabled(True) self.ui.actionProject_Memo.setEnabled(True) # files cases journals menu self.ui.actionManage_files.setEnabled(True) self.ui.actionManage_journals.setEnabled(True) self.ui.actionManage_cases.setEnabled(True) self.ui.actionManage_attributes.setEnabled(True) self.ui.actionImport_survey.setEnabled(True) # codes menu self.ui.actionCodes.setEnabled(True) self.ui.actionCode_image.setEnabled(True) self.ui.actionCategories.setEnabled(True) self.ui.actionView_Graph.setEnabled(True) # reports menu self.ui.actionCoding_reports.setEnabled(True) self.ui.actionCoding_comparison.setEnabled(True) self.ui.actionCode_frequencies.setEnabled(True) self.ui.actionSQL_statements.setEnabled(True) #TODO FOR FUTURE EXPANSION text mining self.ui.actionText_mining.setEnabled(False) def settings_report(self): msg = "Settings\n========\nCoder: " + self.settings['codername'] + "\n" msg += "Font: " + self.settings['font'] + " " + str( self.settings['fontsize']) msg += ". Code font size: " + self.settings['font'] + " " + str( self.settings['treefontsize']) msg += "\nDirectory: " + self.settings['directory'] msg += "\nShowIDs: " + str(self.settings['showIDs']) msg += "\n========" self.ui.textEdit.append(msg) def report_sql(self): ''' Run SQL statements on database. non-modal. ''' ui = DialogSQL(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() """def text_mining(self): ''' text analysis of files / cases / codings. NOT CURRENTLY IMPLEMENTED, FOR FUTURE EXPANSION. ''' ui = DialogTextMining(self.settings, self.ui.textEdit) ui.show()""" def report_coding_comparison(self): ''' compare two or more coders using Cohens Kappa. non-modal. ''' ui = DialogReportCoderComparisons(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_code_frequencies(self): ''' show code frequencies overall and by coder. non-modal. ''' ui = DialogReportCodeFrequencies(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def report_coding(self): ''' report on coding and categories. non-modal. ''' ui = DialogReportCodes(self.settings, self.ui.textEdit) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def view_graph(self): ''' Show acyclic graph of codes and categories. non-modal. ''' ui = ViewGraph(self.settings) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def help(self): ''' Help dialog. non-modal. ''' ui = DialogInformation("Help contents", "Help.html") self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def about(self): ''' About dialog. non-modal. ''' ui = DialogInformation("About", "About.html") self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def manage_attributes(self): ''' Create, edit, delete, rename attributes ''' ui = DialogManageAttributes(self.settings, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def import_survey(self): ''' Import survey flat sheet: csv file. Create cases and assign attributes to cases. Identify qualitative questions and assign these data to the source table for coding and review ''' ui = DialogImportSurvey(self.settings, self.ui.textEdit) ui.exec_() def manage_cases(self): ''' Create, edit, delete, rename cases, add cases to files or parts of files, add memos to cases ''' ui = DialogCases(self.settings, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def manage_files(self): ''' Create text files or import files from odt, docx, html and plain text. Rename, delete and add memos to files. ''' ui = DialogManageFiles(self.settings, self.ui.textEdit) ui.exec_() self.clean_dialog_refs() def journals(self): ''' Create and edit journals. non-modal. ''' ui = DialogJournals(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def text_coding(self): ''' Create edit and delete codes. Apply and remove codes and annotations to the text in imported text files. Multiple coding windows can be displayed non-modal. ''' ui = DialogCodeText(self.settings, self.ui.textEdit) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def image_coding(self): ''' Create edit and delete codes. Apply and remove codes to the image (or regions) Multiple coding windows can be displayed non-modal. ''' ui = DialogCodeImage(self.settings) ui.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialogList.append(ui) ui.show() self.clean_dialog_refs() def codebook(self): ''' Export a code book of categories and codes. ''' Codebook(self.settings, self.ui.textEdit) def closeEvent(self, event): ''' Override the QWindow close event. Close all dialogs and database connection. ''' quit_msg = "Are you sure you want to quit?" reply = QtWidgets.QMessageBox.question(self, 'Message', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.dialogList = None if self.settings['conn'] is not None: try: self.settings['conn'].commit() self.settings['conn'].close() except: pass QtWidgets.qApp.quit() else: return False True def new_project(self): ''' Create a new project folder with data.qda (sqlite) and folders for documents, images and audio ''' #logger.debug("settings[directory]:" + self.settings['directory']) self.settings['path'] = QtWidgets.QFileDialog.getSaveFileName( self, "Enter project name", self.settings['directory'], ".qda")[0] if self.settings['path'] == "": QtWidgets.QMessageBox.warning(None, "Project", "No project created.") return if self.settings['path'].find(".qda") == -1: self.settings['path'] = self.settings['path'] + ".qda" try: os.mkdir(self.settings['path']) os.mkdir(self.settings['path'] + "/images") os.mkdir(self.settings['path'] + "/audio") os.mkdir(self.settings['path'] + "/documents") except Exception as e: logger.critical("Project creation error " + str(e)) QtWidgets.QMessageBox.warning( None, "Project", "No project created.Exiting. " + str(e)) exit(0) self.settings['projectName'] = self.settings['path'].rpartition('/')[2] self.settings['directory'] = self.settings['path'].rpartition('/')[0] #try: self.settings['conn'] = sqlite3.connect(self.settings['path'] + "/data.qda") cur = self.settings['conn'].cursor() cur.execute( "CREATE TABLE project (databaseversion text, date text, memo text,about text);" ) cur.execute( "CREATE TABLE source (id integer primary key, name text, fulltext text, imagepath text, memo text, owner text, date text, unique(name));" ) cur.execute( "CREATE TABLE code_image (imid integer primary key,id integer,x1 integer, y1 integer, width integer, height integer, cid integer, memo text, date text, owner text);" ) cur.execute( "CREATE TABLE annotation (anid integer primary key, fid integer,pos0 integer, pos1 integer, memo text, owner text, date text);" ) cur.execute( "CREATE TABLE attribute_type (name text primary key, date text, owner text, memo text, caseOrFile text, valuetype text);" ) cur.execute( "CREATE TABLE attribute (attrid integer primary key, name text, attr_type text, value text, id integer, date text, owner text);" ) cur.execute( "CREATE TABLE case_text (id integer primary key, caseid integer, fid integer, pos0 integer, pos1 integer, owner text, date text, memo text);" ) cur.execute( "CREATE TABLE cases (caseid integer primary key, name text, memo text, owner text,date text, constraint ucm unique(name));" ) cur.execute( "CREATE TABLE code_cat (catid integer primary key, name text, owner text, date text, memo text, supercatid integer, unique(name));" ) cur.execute( "CREATE TABLE code_text (cid integer, fid integer,seltext text, pos0 integer, pos1 integer, owner text, date text, memo text, unique(cid,fid,pos0,pos1, owner));" ) cur.execute( "CREATE TABLE code_name (cid integer primary key, name text, memo text, catid integer, owner text,date text, color text, unique(name));" ) cur.execute( "CREATE TABLE journal (jid integer primary key, name text, jentry text, date text, owner text);" ) cur.execute( "INSERT INTO project VALUES(?,?,?,?)", ('v1', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), '', 'QualCoder 1.0')) self.settings['conn'].commit() try: # get and display some project details self.ui.textEdit.append("\nNew project: " + self.settings['path'] + " created.") #self.settings['projectName'] = self.path.rpartition('/')[2] self.ui.textEdit.append("Opening: " + self.settings['path']) self.setWindowTitle("QualCoder " + self.settings['projectName']) cur = self.settings['conn'].cursor() cur.execute('select sqlite_version()') self.ui.textEdit.append("SQLite version: " + str(cur.fetchone())) cur.execute( "select databaseversion, date, memo, about from project") result = cur.fetchone() self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] self.ui.textEdit.append("New Project Created\n========\n" + "DBVersion:" + str(self.project['databaseversion']) + "\n" + "Date: " + str(self.project['date']) + "\n" + "About: " + str(self.project['about']) + "\n" + "Coder:" + str(self.settings['codername']) + "\n" + "========") except Exception as e: logger.warning("Problem creating Db " + self.settings['path'] + " Exception:" + str(e)) self.ui.textEdit.append("\nProblems creating Db\n" + self.settings['path']) self.ui.textEdit.append(str(e)) self.close_project() return self.open_project(self.settings['path']) def change_settings(self): ''' Change default settings - the coder name, font, font size ''' ui = DialogSettings(self.settings) ui.exec_() self.settings_report() newfont = QtGui.QFont(self.settings['font'], self.settings['fontsize'], QtGui.QFont.Normal) self.setFont(newfont) def project_memo(self): ''' Give the entire project a memo ''' cur = self.settings['conn'].cursor() cur.execute("select memo from project") memo = cur.fetchone()[0] ui = DialogMemo(self.settings, "Memo for project " + self.settings['projectName'], memo) ui.exec_() if memo != ui.memo: cur.execute('update project set memo=?', (ui.memo, )) self.settings['conn'].commit() self.ui.textEdit.append("Project memo entered.") def open_project(self, path=""): ''' Open an existing project. ''' if self.settings['projectName'] != "": self.close_project() self.setWindowTitle("QualCoder Open Project") if path == "" or path is False: path = QtWidgets.QFileDialog.getExistingDirectory( self, 'Open project directory', self.settings['directory']) if path == "" or path is False: return if len(path) > 3 and path[-4:] == ".qda": self.settings['path'] = path msg = "" try: self.settings['conn'] = sqlite3.connect(self.settings['path'] + "/data.qda") except Exception as e: self.settings['conn'] = None msg += str(e) logger.debug(str(e)) if self.settings['conn'] is None: QtWidgets.QMessageBox.warning( None, "Cannot open file", self.settings['path'] + " is not a .qda file ") self.settings['path'] = "" return # get and display some project details self.settings['path'] = path self.settings['projectName'] = self.settings['path'].rpartition('/')[2] self.settings['directory'] = self.settings['path'].rpartition('/')[0] self.setWindowTitle("QualCoder " + self.settings['projectName']) cur = self.settings['conn'].cursor() cur.execute("select databaseversion, date, memo, about from project") result = cur.fetchone() self.project['databaseversion'] = result[0] self.project['date'] = result[1] self.project['memo'] = result[2] self.project['about'] = result[3] #self.settings_report() self.ui.textEdit.append("Project Opened:" + self.settings['projectName'] + "\n========" + "\nPath: " + self.settings['path'] + "\nDirectory: " + self.settings['directory'] + "\nDBVersion:" + self.project['databaseversion'] + ". " + "Date: " + str(self.project['date']) + "\n" + "About: " + self.project['about'] + "\n========\n") self.show_menu_options() def close_project(self): ''' Close an open project ''' self.ui.textEdit.append("Closing project: " + self.settings['projectName'] + "\n========\n") try: self.settings['conn'].commit() self.settings['conn'].close() except: pass self.conn = None self.settings['conn'] = None self.settings['path'] = "" self.settings['projectName'] = "" self.settings['directory'] = "" self.project = { "databaseversion": "", "date": "", "memo": "", "about": "" } self.hide_menu_options() self.clean_dialog_refs() def clean_dialog_refs(self): ''' Test the list of dialog refs to see if they have been cleared and create a new list of current dialogs. Also need to keep these dialog references to keep non-modal dialogs open. Non-modal example - having a journal open and a coding dialog ''' tempList = [] for d in self.dialogList: try: #logger.debug(str(d) + ", isVisible:" + str(d.isVisible()) + " Title:" + d.windowTitle()) d.windowTitle() tempList.append(d) # RuntimeError: wrapped C/C++ object of type DialogSQL has been deleted except RuntimeError as e: logger.error(str(e)) self.dialogList = tempList