]> Softwares of Agnibho - ddstorm.git/commitdiff
Major code rearrangement
authorAgnibho Mondal <mail@agnibho.com>
Wed, 23 Dec 2015 09:41:04 +0000 (15:11 +0530)
committerAgnibho Mondal <mail@agnibho.com>
Wed, 23 Dec 2015 09:41:04 +0000 (15:11 +0530)
12 files changed:
__main__.py
_differentials.py [deleted file]
_extras.py [deleted file]
_symptoms.py [deleted file]
alias.py
compile.py
conf.py
ddstorm.py
extras.py [new file with mode: 0644]
gui.py [new file with mode: 0644]
index.py
panes.py [new file with mode: 0644]

index 1b430c221ab74992c142930a43b151b77ccebbe1..2d7ca4691ba380055c2bf56aed10a910735370fd 100644 (file)
 #! /usr/bin/python3
 
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+Start the application
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
 
-This file is part of DDStorm.
+This file is part of DDStorm.
 
-DDStorm is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-DDStorm is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
 
-import sys, time, subprocess
-from PyQt4 import QtGui, QtCore
-from _symptoms import Symptoms
-from _differentials import Differentials
-from ddstorm import DDStorm
-from conf import Conf
-from _extras import *
-from const import *
-
-conf=False
-
-class Content(QtGui.QWidget):
-    change=QtCore.pyqtSignal()
-    def __init__(self):
-        super(Content, self).__init__()
-        self.dd=DDStorm(True,conf)
-        if(not self.dd.compiler.clean):
-            ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log")
-            if(ret==1):
-                x_logfile()
-        self.initUI()
-    def initUI(self):
-        global conf
-
-        grid=QtGui.QGridLayout()
-        self.setLayout(grid)
-
-        self.symp=Symptoms(self.dd.symptoms())
-        self.symp.setFrameShape(QtGui.QFrame.StyledPanel)
-        self.symp.changed.connect(self.update)
-
-        self.diff=Differentials()
-        self.diff.setFrameShape(QtGui.QFrame.StyledPanel)
-
-        grid.addWidget(self.symp, 0, 0)
-        grid.addWidget(self.diff, 0, 1)
-        grid.setColumnStretch(0, 1)
-        grid.setColumnStretch(1, 1)
-        QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
-    def update(self, data):
-        self.diff.update(self.dd.dd(data))
-        self.change.emit()
-
-class Window(QtGui.QMainWindow):
-    def __init__(self):
-        super(Window, self).__init__()
-        self.initUI()
-    def initUI(self):
-        global conf
-        self.con=Content()
-        self.sett=SettingsDialog(conf)
-        if(conf.get("status_message")=="on"):
-            self.con.change.connect(self.showStatus)
-
-        menu=self.menuBar()
-        menuFile=menu.addMenu("&File")
-        menuFile.addAction("&Save").triggered.connect(self.savefile)
-        menuFile.addAction("E&xit").triggered.connect(self.close)
-        menuEdit=menu.addMenu("&Edit")
-        menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem)
-        menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms)
-        rmAction=QtGui.QAction("&Remove", self)
-        rmAction.setShortcut("Delete")
-        rmAction.triggered.connect(self.con.symp.remove)
-        menuEdit.addAction(rmAction)
-        menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll)
-        menuTool=menu.addMenu("&Tools")
-        menuTool.addAction("&Library").triggered.connect(x_lib)
-        menuTool.addAction("&Settings").triggered.connect(self.settings)
-        menuTool.addAction("&View Log").triggered.connect(x_logfile)
-        menuHelp=menu.addMenu("&Help")
-        menuHelp.addAction("&Help").triggered.connect(x_help)
-        menuHelp.addAction("&About").triggered.connect(self.about)
-
-        self.setCentralWidget(self.con)
-        self.status=self.statusBar()
-        self.setGeometry(200, 200, 600, 400)
-        self.setWindowTitle("D/D Storm")
-        self.setWindowIcon(QtGui.QIcon("icons/icon.png"))
-        self.showMaximized()
-        self.con.symp.new.setFocus()
-    def showStatus(self):
-        if(self.con.symp.getList() and self.con.diff.getList()):
-            self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).")
-        else:
-            self.status.showMessage("")
-    def savefile(self):
-        x_save(self, self.con.symp.getList(), self.con.diff.getList())
-    def settings(self):
-        self.sett.exec_()
-    def about(self):
-        QtGui.QMessageBox.about(self, "About", "<h1>DDStorm</h1>\nBrainstorm Medicine")
-
-def main():
-    app=QtGui.QApplication(sys.argv)
-    
-    global conf
-    conf=Conf()
-    if(conf.get("clean_log")=="yes"):
-        open(LOG_FILE, "w").close()
-    if(conf.get("splash_screen")=="yes"):
-        ss=True
-    else:
-        ss=False
-    if(ss):
-        splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png"))
-        splash.show()
-        time.sleep(0.1)
-        app.processEvents()
-        splash.showMessage("Loading...")
-
-    w=Window()
-    if(ss):
-        splash.finish(w)
-    
-    sys.exit(app.exec_())
+import gui
 
 if(__name__=="__main__"):
-    main()
+    gui.main()
diff --git a/_differentials.py b/_differentials.py
deleted file mode 100644 (file)
index 16f86d3..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
-
-from PyQt4 import QtGui
-
-class Differentials(QtGui.QFrame):
-    data=[]
-    def __init__(self):
-        super(Differentials, self).__init__()
-        self.initUI()
-    def initUI(self):
-        self.label=QtGui.QLabel("Differential Diagnosis")
-        self.label.setStyleSheet("font-size:18px")
-        self.listWidget=QtGui.QListWidget(self)
-        self.listWidget.setStyleSheet("font-size:14px")
-        self.listWidget.setSelectionMode(0)
-        box=QtGui.QVBoxLayout()
-        box.addWidget(self.label)
-        box.addWidget(self.listWidget)
-        self.setLayout(box)
-
-    def update(self, data):
-        self.data=data
-        self.listWidget.clear()
-        if(self.data):
-            for d in self.data:
-                QtGui.QListWidgetItem(d, self.listWidget)
-
-    def getList(self):
-        return self.data
diff --git a/_extras.py b/_extras.py
deleted file mode 100644 (file)
index d15e627..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
-
-import subprocess, os
-from PyQt4 import QtGui, QtCore
-from const import *
-
-def x_settings():
-    subprocess.Popen(["xdg-open", CONF_FILE])
-
-def x_lib():
-    if(os.path.isfile(CONF_FILE)):
-            with open(CONF_FILE) as conf:
-                for line in conf:
-                    if(line.startswith("library_path=")):
-                       library_path=line[13:-1]
-                       if(os.path.isdir(library_path)):
-                           subprocess.Popen(["xdg-open", library_path])
-
-def x_save(w, symp, diff):
-    fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')")
-    if(not fname.endswith(".html")):
-        fname=fname+".html"
-    with open(fname, "w") as f:
-        print("<!DOCTYPE html><html><head><title>Differential Diagnosis</title></head>", file=f)
-        print("<body><h1>Differential Diagnosis</h1>", file=f)
-        print("<table style='width:100%'>", file=f)
-        print("<tr><th>Symptoms</th><th>Diffrential Diagnosis</th></tr>", file=f)
-        print("<tr><td style='vertical-align:text-top'><ol>", file=f)
-        for s in symp:
-            print("<li>"+s+"</li>", file=f)
-        print("</ol></td><td style='vertical-align:text-top'><ol>", file=f)
-        for d in diff:
-            print("<li>"+d+"</li>", file=f)
-        print("</ol></td></tr></table></body></html>", file=f)
-
-def x_help():
-    if(os.path.isfile(HELP_FILE)):
-        subprocess.Popen(["xdg-open", HELP_FILE])
-    else:
-        subprocess.Popen(["xdg-open", "http://www.agnibho.com"])
-
-def x_logfile():
-    subprocess.Popen(["xdg-open", LOG_FILE])
-
-class SettingsDialog(QtGui.QDialog):
-    def __init__(self, conf):
-        super(SettingsDialog, self).__init__()
-        self.setWindowTitle("Settings")
-        self.conf=conf
-        self.initUI()
-    def initUI(self):
-        self.lpLabel=QtGui.QLabel("Libary Path:")
-        self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path"))
-        self.lpBrowse=QtGui.QPushButton("Browse")
-        self.lpBrowse.clicked.connect(self.lpUpdate)
-        self.mpLabel=QtGui.QLabel("Module Path:")
-        self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path"))
-        self.mpBrowse=QtGui.QPushButton("Browse")
-        self.mpBrowse.clicked.connect(self.mpUpdate)
-        self.splash=QtGui.QCheckBox("Show Splash Screen")
-        if(self.conf.get("splash_screen")=="yes"):
-            self.splash.setChecked(True)
-        self.clean=QtGui.QCheckBox("Clean Log on Exit")
-        if(self.conf.get("clean_log")=="yes"):
-            self.clean.setChecked(True)
-        self.status=QtGui.QCheckBox("Show Status Message")
-        if(self.conf.get("status_message")=="on"):
-            self.status.setChecked(True)
-        self.ok=QtGui.QPushButton("Ok")
-        self.ok.clicked.connect(self.save)
-        self.cancel=QtGui.QPushButton("Cancel")
-        self.cancel.clicked.connect(self.close)
-        self.default=QtGui.QPushButton("Default")
-        self.default.clicked.connect(self.reset)
-
-        ctrl=QtGui.QHBoxLayout()
-        ctrl.addWidget(self.ok)
-        ctrl.addWidget(self.cancel)
-        ctrl.addWidget(self.default)
-        layout=QtGui.QGridLayout(self)
-        layout.addWidget(self.lpLabel, 0, 0)
-        layout.addWidget(self.lpEdit, 0, 1)
-        layout.addWidget(self.lpBrowse, 0, 2)
-        layout.addWidget(self.mpLabel, 1, 0)
-        layout.addWidget(self.mpEdit, 1, 1)
-        layout.addWidget(self.mpBrowse, 1, 2)
-        layout.addWidget(self.splash, 2, 0)
-        layout.addWidget(self.clean, 3, 0)
-        layout.addWidget(self.status, 4, 0)
-        layout.addLayout(ctrl, 5, 1)
-
-        self.cancel.setFocus()
-
-    def lpUpdate(self):
-        self.lpEdit.setText(self.getFolder())
-    def cpUpdate(self):
-        self.cpEdit.setText(self.getFolder())
-    def mpUpdate(self):
-        self.mpEdit.setText(self.getFolder())
-
-    def getFolder(self):
-        dn=QtGui.QFileDialog.getExistingDirectory()
-        if(dn.startswith(QtCore.QDir.currentPath())):
-           dn="."+dn[len(QtCore.QDir.currentPath()):]+"/"
-        else:
-            dn=dn+"/"
-        return dn
-
-    def save(self):
-        self.conf.set("library_path", self.lpEdit.text())
-        self.conf.set("class_path", self.cpEdit.text())
-        self.conf.set("module_path", self.mpEdit.text())
-        if(self.splash.isChecked()):
-            self.conf.set("splash_screen", "yes")
-        else:
-            self.conf.set("splash_screen", "no")
-        if(self.clean.isChecked()):
-            self.conf.set("clean_log", "yes")
-        else:
-            self.conf.set("clean_log", "no")
-        if(self.status.isChecked()):
-            self.conf.set("status_message", "on")
-        else:
-            self.conf.set("status_message", "off")
-        QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm")
-        self.close()
-        self.conf.write()
-
-    def reset(self):
-        self.conf.default()
-        self.lpEdit.setText(self.conf.get("library_path"))
-        self.cpEdit.setText(self.conf.get("class_path"))
-        self.mpEdit.setText(self.conf.get("module_path"))
-        if(self.conf.get("splash_screen")=="yes"):
-            self.splash.setChecked(True)
-        else:
-            self.splash.setChecked(False)
-        if(self.conf.get("clean_log")=="yes"):
-            self.clean.setChecked(True)
-        else:
-            self.clean.setChecked(False)
-        if(self.conf.get("status_message")=="on"):
-            self.status.setChecked(True)
-        else:
-            self.status.setChecked(False)
diff --git a/_symptoms.py b/_symptoms.py
deleted file mode 100644 (file)
index 2d7d8d6..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
-
-# This file is part of DDStorm.
-
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
-
-from PyQt4 import QtGui, QtCore
-
-class Symptoms(QtGui.QFrame):
-    sympList=[]
-    changed=QtCore.pyqtSignal(list)
-    def __init__(self, auto):
-        super(Symptoms, self).__init__()
-        self.auto=auto
-        self.initUI()
-
-    def initUI(self):
-        self.label=QtGui.QLabel("Symptoms")
-        self.label.setStyleSheet("font-size:18px")
-
-        self.listWidget=QtGui.QListWidget(self)
-        self.listWidget.setStyleSheet("font-size:14px")
-        self.listWidget.setSelectionMode(4)
-
-        self.rm=QtGui.QPushButton("Remove")
-        self.rm.clicked.connect(self.remove)
-
-        self.cl=QtGui.QPushButton("Clear All")
-        self.cl.clicked.connect(self.removeAll)
-
-        self.browse=QtGui.QPushButton("Browse Symptoms")
-        self.browse.clicked.connect(self.browseSymptoms)
-
-        self.add=QtGui.QPushButton("Add")
-        self.add.clicked.connect(self.addItem)
-
-        self.new=QtGui.QLineEdit(self)
-        self.new.returnPressed.connect(self.addItem)
-        self.completer=QtGui.QCompleter(self.auto)
-        self.completer.setCaseSensitivity(0)
-        self.completer.setCompletionMode(2)
-        self.new.setCompleter(self.completer)
-
-        self.browser=SymptomBrowser(self.auto)
-        self.browser.added.connect(self.addItem)
-
-        hboxt=QtGui.QHBoxLayout()
-        hboxt.addWidget(self.new)
-        hboxt.addWidget(self.add)
-        hboxb=QtGui.QHBoxLayout()
-        hboxb.addWidget(self.rm)
-        hboxb.addWidget(self.cl)
-        hboxb.addWidget(self.browse)
-        vbox=QtGui.QVBoxLayout()
-        vbox.addWidget(self.label)
-        vbox.addLayout(hboxt)
-        vbox.addWidget(self.listWidget)
-        vbox.addLayout(hboxb)
-        self.setLayout(vbox)
-
-    def addItem(self, text=""):
-        if(not text):
-            text=self.new.text()
-        if(len(text)>0):
-            if(text in self.sympList):
-                QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.")
-            elif(text in self.auto):
-                QtGui.QListWidgetItem(text, self.listWidget)
-                self.sympList.append(text)
-                self.new.clear()
-                self.changed.emit(list(self.sympList))
-            else:
-                QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.")
-
-    def remove(self, all=False):
-        if(len(self.listWidget.selectedItems())>0):
-            for item in self.listWidget.selectedItems():
-                self.sympList.remove(item.text())
-                self.listWidget.takeItem(self.listWidget.row(item))
-            self.changed.emit(list(self.sympList))
-
-    def removeAll(self):
-        self.listWidget.clear()
-        del self.sympList[:]
-        self.changed.emit(list(self.sympList))
-
-    def browseSymptoms(self):
-        self.browser.exec_()
-
-    def getList(self):
-        return self.sympList
-
-class SymptomBrowser(QtGui.QDialog):
-    added=QtCore.pyqtSignal(str)
-    def __init__(self, items):
-        super(SymptomBrowser, self).__init__()
-        self.setWindowTitle("Choose Symptom")
-        self.items=items
-        self.initUI()
-
-    def initUI(self):
-        self.search=QtGui.QLineEdit()
-        self.search.textChanged.connect(self.refresh)
-        self.listItems=QtGui.QListWidget()
-        self.listItems.activated.connect(self.sendUp)
-
-        layout=QtGui.QVBoxLayout(self)
-        layout.addWidget(self.search)
-        layout.addWidget(self.listItems)
-
-        self.listItems.addItems(self.items)
-        self.search.setFocus()
-
-    def refresh(self):
-        term=self.search.text()
-        buff=[]
-        for i in self.items:
-            if(term.lower() in i.lower()):
-                buff.append(i)
-        self.listItems.clear()
-        self.listItems.addItems(buff)
-    
-    def sendUp(self):
-        self.added.emit(self.listItems.currentItem().text())
-        self.close()
index d4e013ee069bb8dd66c5250998629c84ede38db1..a1b9d104e709ba92b3cff0b1eeb8c0a821ee7475 100644 (file)
--- a/alias.py
+++ b/alias.py
@@ -1,32 +1,41 @@
 #! /usr/bin/python3
 
-# DDStorm
-# -------
-Copyright (c) 2015 Agnibho Mondal
-All rights reserved
+''' This module handles the aliases of the symptoms. '''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
 
-This file is part of DDStorm.
+This file is part of DDStorm.
 
-DDStorm is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-DDStorm is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
 
-import sys, os
+import sys
+import os
 from fnmatch import fnmatch
+
 from conf import Conf
 from const import *
 
 class Alias:
+    ''' Provides the class to handle symptom aliases '''
+    
     def __init__(self, conf=False):
+        '''
+        Initiates the alias object
+        Accepts a Conf object as parameter
+        '''
         self.data={}
         if(conf):
             self.conf=conf
@@ -35,19 +44,25 @@ class Alias:
         self.compile()
 
     def compile(self):
+        ''' Compile the plaintext index files to a program usable format '''
+        # Loop over the alias files
         for path, subdirs, files in os.walk(self.conf.get("alias_path")):
             for name in files:
                 if(fnmatch(name, "*.txt")):
+                    # Open the *.txt files
                     with open(self.conf.get("alias_path")+name, "r") as f:
                         for line in f:
+                            # Ignore lines starting with #
                             line=line.rstrip().split("#")[0]
                             if(len(line)==0):
                                 pass
                             else:
                                 terms=[]
+                                # Split words separated by ; and add to the terms
                                 for i in line.split(";"):
                                     if(i.strip()):
                                         terms.append(i.strip())
+                                # If alias present, add terms to the data
                                 if(len(terms)==2):
                                     self.data[terms[-1]]=terms[0]
                                 elif(len(terms)>2):
@@ -56,12 +71,22 @@ class Alias:
                                         self.data[i]=t
 
     def get(self, term):
+        '''
+        Return the alias of the queried symptom
+
+        Parameter:
+        term - Queried string
+
+        Return value:
+        String containing the alias of the term
+        '''
         if(term in self.data):
             return self.data[term]
         else:
             return term
 
 def main():
+    ''' Print the alias of the command line argument '''
     a=Alias()
     if(len(sys.argv)>1):
         print(a.get(sys.argv[1]))
index acb5ec7b39a61bf978b81888350c9f6a43cbdf1b..80bb4d1456f07621b9e71890592f309c14b69768 100644 (file)
@@ -57,41 +57,57 @@ class Compile:
             self._conf=conf
         else:
             self._conf=Conf()
+        self.clean=True
 
     def compile(self):
         ''' Compile the text files to DDStorm modules. '''
         self.source=set()
         self.custom=set()
         self.alias=Alias(self._conf)
-        self.clean=True
+        
+        # Loop over library files and add *.txt files to source
         for path, subdirs, files in os.walk(self._conf.get("library_path")):
             for name in files:
                 if(fnmatch(name, "*.txt")):
                     self.source.add(os.path.join(path, name))
+
+        # Loop over custom files and add *.txt files to custom
         for path, subdirs, files in os.walk(self._conf.get("custom_path")):
             for name in files:
                 if(fnmatch(name, "*.txt")):
                     self.custom.add(os.path.join(path, name))
+
+        # Create module directory if not already present and delete all module files
         if(not os.path.isdir(self._conf.get("module_path"))):
             os.makedirs(self._conf.get("module_path"))
         for f in os.listdir(self._conf.get("module_path")):
             if(fnmatch(f, "*.module")):
                 os.unlink(self._conf.get("module_path")+f)
+
+        # Create a regex for calculating priority from filename
         self.priorityRegex=re.compile("(?<=\.)\d+$")
+
+        # First sort files by priority then compile them to module
         for src in self._sortPriority(self.source):
             self._makeModule(src)
         for src in self._sortPriority(self.custom):
             self._makeModule(src)
 
     def _sortPriority(self, files):
+        ''' Sort data files based on their priority settings. '''
         ls=[]
+        # Loop over the files
         for addr in files:
+            # Format the file name
             name=os.path.splitext(os.path.basename(addr))[0].lower().replace("_"," ").replace("-", " ")
+            # Search for priority tag on file name
             m=re.search(self.priorityRegex, name)
+            # Add to ls as (symptom name, priority number, file name) with default priority of 100
             if(m):
                 ls.append((name.replace("."+m.group(), ""), int(m.group()), addr))
             else:
                 ls.append((name, 100, addr))
+        # Sort the file list, first by the symptom name, then by the priority number
         ls.sort(reverse=True)
         if(ls):
             return(list(zip(*ls))[2])
@@ -99,13 +115,19 @@ class Compile:
             return ls
         
     def _makeModule(self, src):
+        ''' Create application usable modules from data files. '''
+        # Format the file name
         module=os.path.splitext(os.path.basename(src))[0].lower().replace("_"," ").replace("-", " ")
+        # Remove the priority tag from file name
         m=re.search(self.priorityRegex, module)
         if(m):
             module=module.replace("."+m.group(), "")
+        # Create the module file name
         modFile=self._conf.get("module_path")+module+".module"
         modFlag=False
+        # Loop over both files, the source data file and the target module file
         with open(src, "r") as sf, open(modFile, "a") as tf:
+            # Ignore lines starting with ! or #, + and - has special meaning, write other lines to module. Log the errors.
             for line in sf:
                 line=line.strip().split("#")[0]
                 if(len(line)==0):
@@ -123,17 +145,20 @@ class Compile:
                 else:
                     self.clean=False
                     logging.warning("Syntax error in file '"+src+"': "+line)
+        # Deal with special lines
         if(modFlag):
             modFlag=False
             with open(src, "r") as f:
                 for line in f:
                     line=line.strip().split("#")[0]
                     if(line[1:].replace(" ","").replace("-","").replace("_","").replace("'","").isalnum()):
+                        # If line starts with + add it to the module file
                         if(line.startswith("+")):
                             with open(modFile, "r") as fn:
                                 text=fn.read()
                             with open(modFile, "w") as fn:
                                 print(self.alias.get(line[1:]).capitalize()+"\n"+text, file=fn)
+                        # If line starts with - remove corresponding item from the module file
                         elif(line.startswith("-")):
                             with open(modFile, "r") as fn:
                                 text=fn.read()
@@ -141,7 +166,12 @@ class Compile:
                             with open(modFile, "w") as fn:
                                 print(text, file=fn)
 
+    def is_clean(self):
+            '''Report if compilation ended successfully'''
+            return self.clean
+
 def main():
+    ''' Compile the data files into formatted module files '''
     c=Compile().compile()
 
 if(__name__=="__main__"):
diff --git a/conf.py b/conf.py
index 5b848515d7c2ee91155da76c51799aa22f567455..03ee5e8d4441a54ef2fb2f5f062312886d8245de 100644 (file)
--- a/conf.py
+++ b/conf.py
@@ -25,7 +25,7 @@ along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
 import os
 import logging
 
-from const import *                     # Import constants
+from const import *
 
 logging.basicConfig(filename=LOG_FILE)
 
@@ -40,7 +40,7 @@ class Conf:
       function calls.
     '''
     
-    _conf={} #Initiates configuration dictionary
+    _conf={}
     
     def __init__(self, filename=CONF_FILE):
         '''
@@ -65,47 +65,59 @@ class Conf:
 
     def read(self):
         ''' Read the configuration file and collect the values '''
-        if(os.path.isfile(self.filename)):         # If file is actually present
+        # If file is actually present
+        if(os.path.isfile(self.filename)):
             try:
-                with open(self.filename) as f:     # Open file
+                with open(self.filename) as f:
                     for line in f:
-                        line="".join(line.split()) # Removes any stray whitespaces
-                        if(line.startswith("#")):  # Ignores comments
+                        # Removes any stray whitespaces
+                        line="".join(line.split())
+                        # Ignores comments starting with #
+                        if(line.startswith("#")):
                             pass
-                        elif(line.startswith("library_path=")):                 # Library files path
+                        # Library path
+                        elif(line.startswith("library_path=")):
                             self._conf["library_path"]=line[13:]
                             if(os.path.isdir(self._conf["library_path"])):
                                if(not self._conf["library_path"].endswith("/")):
                                    self._conf["library_path"]+="/"
-                        elif(line.startswith("custom_path=")):                  # Custom files path
+                        # Custom path
+                        elif(line.startswith("custom_path=")):
                             self._conf["custom_path"]=line[12:]
                             if(os.path.isdir(self._conf["custom_path"])):
                                if(not self._conf["custom_path"].endswith("/")):
                                    self._conf["custom_path"]+="/"
-                        elif(line.startswith("index_path=")):                   # Index files path
+                        # Index path
+                        elif(line.startswith("index_path=")):
                             self._conf["index_path"]=line[11:]
                             if(os.path.isdir(self._conf["index_path"])):
                                if(not self._conf["index_path"].endswith("/")):
                                    self._conf["index_path"]+="/"
-                        elif(line.startswith("alias_path=")):                   # Alias files path
+                        # Alias path
+                        elif(line.startswith("alias_path=")):
                             self._conf["alias_path"]=line[11:]
                             if(os.path.isdir(self._conf["alias_path"])):
                                if(not self._conf["alias_path"].endswith("/")):
                                    self._conf["alias_path"]+="/"
-                        elif(line.startswith("module_path=")):                  # Path to save compiled modules
+                        # Module path
+                        elif(line.startswith("module_path=")):
                             self._conf["module_path"]=line[12:]
                             if(os.path.isdir(self._conf["module_path"])):
                                if(not self._conf["module_path"].endswith("/")):
                                    self._conf["module_path"]+="/"
-                        elif(line.startswith("splash_screen=")):                # Whether to show a splash screen
+                        # Splash screen
+                        elif(line.startswith("splash_screen=")):
                             self._conf["splash_screen"]=line[14:]
-                        elif(line.startswith("clean_log=")):                    # Whether to clean logs before exit
+                        # Clean log
+                        elif(line.startswith("clean_log=")):
                             self._conf["clean_log"]=line[10:]
-                        elif(line.startswith("status_message=")):               # Whether to show status messages
+                        # Status message
+                        elif(line.startswith("status_message=")):
                             self._conf["status_message"]=line[15:]
+                        # Unknown option
                         else:
-                            logging.warning("Unrecognized configuration: "+line) # Log a warning if unrecognized option found
-            except:     # Go with default if file could not be read and log an error
+                            logging.warning("Unrecognized configuration: "+line)
+            except:
                 logging.error("Configuration file "+self.filename+" could not be read. Using default configurations.")
 
     def get(self, key=False):
index 7fb300a32eb3b6c0eb13cecf64cecb0f20da1774..2b6664cba4a77ad2ebfe03cebe3dcd142256077a 100644 (file)
@@ -1,53 +1,94 @@
 #! /usr/bin/python3
 
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+DDStorm is a python application for finding differential diagnosis for
+a given list of symptoms.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
 
-This file is part of DDStorm.
+This file is part of DDStorm.
 
-DDStorm is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-DDStorm is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import os
 
-import sys, os
 from compile import Compile
 from conf import Conf
 from index import Index
 
 class DDStorm:
+    ''' Provides the class for finding differential diagnosis. '''
     conf=False
+    
     def __init__(self, comp=False, conf=False):
+        '''
+        Initiate the diagnosis finder.
+
+        Parameters:
+        comp - Recompiles the data files if set to True
+        conf - Supply a Conf object
+        '''
         if(conf):
             self.conf=conf
         else:
             self.conf=Conf()
+        self.compiler=Compile(conf)
         if(comp):
-            self.compiler=Compile(conf).compile()
+            self.compiler.compile()
         self.index=Index(conf)
 
     def dd(self, symptoms):
+        '''
+        Find the differential diagnosis list.
+
+        Parameter:
+        symptom - list of strings containing symptoms
+
+        Return value:
+        List of strings containing the differential diagnosis
+        '''
+
+        # Return empty list if symptom list is empty
         if(not symptoms):
             return
+        
+        # Find DD of first symptom and discard it
         diff1=self._getDiff(symptoms.pop(0))
+
+        # Loop through the rest of the list
         for s in symptoms:
+            
+            # Find DD of the current item in the list
             diff2=self._getDiff(s)
+
+            # List for temporary holding the DDs
             temp=[]
+
+            # Make both lists the same length by appending empty strings to the end
             if(len(diff1)>len(diff2)):
                 diff2+=[""]*(len(diff1)-len(diff2))
             elif(len(diff2)>len(diff1)):
                 diff1+=[""]*(len(diff2)-len(diff1))
+
+            # Loop over both lists
             for (s1, s2) in zip(diff1, diff2):
+
+                # Add s1 to temp if s1 or any of its upstream ancestor is common to both list
                 if((s1 not in temp) and (len(s1)>0)):
                     if(s1 in diff2):
                         temp.append(s1)
@@ -56,6 +97,8 @@ class DDStorm:
                         for i in us:
                             if(i in diff2):
                                 temp.append(i)
+
+                # Add s2 to temp if s2 or any of its upstream ancestor is common to both list
                 if((s2 not in temp) and (len(s2)>0)):
                     if(s2 in diff1):
                         temp.append(s2)
@@ -64,10 +107,14 @@ class DDStorm:
                         for i in us:
                             if(i in diff1):
                                 temp.append(i)
+
+            # Copy temp to first list
             diff1=list(temp)
+
         return diff1
 
     def _getDiff(self, symptom):
+        ''' Return differential diagnosis for a single symptom '''
         diff=[]
         symptom=symptom.lower().replace("_"," ").replace("-", " ")
         if(os.path.isfile(self.conf.get("module_path")+symptom+".module")):
@@ -77,12 +124,26 @@ class DDStorm:
         return diff
         
     def symptoms(self):
+        '''
+        Return a full list of available symptoms
+
+        Return value:
+        List of string containing symptoms
+        '''
         symp=[]
         for n in os.listdir(self.conf.get("module_path")):
             symp.append(os.path.splitext(os.path.basename(n))[0].capitalize())
         return symp
 
 def main():
+    '''
+    Find differential diagnosis in command line mode.
+    Accepts symptoms as command line arguments. Prints a list of
+    avialable symptoms if called without any argument.
+
+    Command line arguments:
+    A list of symptoms separated by space
+    '''
     s=DDStorm()
     if(len(sys.argv)>1):
         for d in s.dd(sys.argv[1:]):
diff --git a/extras.py b/extras.py
new file mode 100644 (file)
index 0000000..8021012
--- /dev/null
+++ b/extras.py
@@ -0,0 +1,196 @@
+'''
+This module provides some extra functionalities to the main window.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import subprocess
+import os
+from PyQt4 import QtGui, QtCore
+from const import *
+
+def x_settings():
+    ''' Open the configuration file '''
+    subprocess.Popen(["xdg-open", CONF_FILE])
+
+def x_lib():
+    ''' Open the library path '''
+    if(os.path.isfile(CONF_FILE)):
+            with open(CONF_FILE) as conf:
+                for line in conf:
+                    if(line.startswith("library_path=")):
+                       library_path=line[13:-1]
+                       if(os.path.isdir(library_path)):
+                           subprocess.Popen(["xdg-open", library_path])
+
+def x_save(w, symp, diff):
+    ''' Save data to a file '''
+    fname=QtGui.QFileDialog.getSaveFileName(w, "Save File", "~", "HTML files('*.html')")
+    if(not fname.endswith(".html")):
+        fname=fname+".html"
+    with open(fname, "w") as f:
+        print("<!DOCTYPE html><html><head><title>Differential Diagnosis</title></head>", file=f)
+        print("<body><h1>Differential Diagnosis</h1>", file=f)
+        print("<table style='width:100%'>", file=f)
+        print("<tr><th>Symptoms</th><th>Diffrential Diagnosis</th></tr>", file=f)
+        print("<tr><td style='vertical-align:text-top'><ol>", file=f)
+        for s in symp:
+            print("<li>"+s+"</li>", file=f)
+        print("</ol></td><td style='vertical-align:text-top'><ol>", file=f)
+        for d in diff:
+            print("<li>"+d+"</li>", file=f)
+        print("</ol></td></tr></table></body></html>", file=f)
+
+def x_help():
+    ''' Show help '''
+    if(os.path.isfile(HELP_FILE)):
+        subprocess.Popen(["xdg-open", HELP_FILE])
+    else:
+        subprocess.Popen(["xdg-open", "http://www.agnibho.com"])
+
+def x_logfile():
+    ''' Open log file '''
+    subprocess.Popen(["xdg-open", LOG_FILE])
+
+
+class SettingsDialog(QtGui.QDialog):
+    '''
+    Provides a dialog box to configure application settings with a
+    graphical user interface.
+    '''
+    
+    def __init__(self, conf):
+        ''' Initiate the dialog '''
+        super(SettingsDialog, self).__init__()
+        self.setWindowTitle("Settings")
+        self.conf=conf
+        self.initUI()
+
+    def initUI(self):
+        ''' Initiate the user interface '''
+        self.lpLabel=QtGui.QLabel("Libary Path:")
+        self.lpEdit=QtGui.QLineEdit(self.conf.get("library_path"))
+        self.lpBrowse=QtGui.QPushButton("Browse")
+        self.lpBrowse.clicked.connect(self.lpUpdate)
+        self.cpLabel=QtGui.QLabel("Custom Path:")
+        self.cpEdit=QtGui.QLineEdit(self.conf.get("custom_path"))
+        self.cpBrowse=QtGui.QPushButton("Browse")
+        self.cpBrowse.clicked.connect(self.cpUpdate)
+        self.mpLabel=QtGui.QLabel("Module Path:")
+        self.mpEdit=QtGui.QLineEdit(self.conf.get("module_path"))
+        self.mpBrowse=QtGui.QPushButton("Browse")
+        self.mpBrowse.clicked.connect(self.mpUpdate)
+        self.splash=QtGui.QCheckBox("Show Splash Screen")
+        if(self.conf.get("splash_screen")=="yes"):
+            self.splash.setChecked(True)
+        self.clean=QtGui.QCheckBox("Clean Log on Exit")
+        if(self.conf.get("clean_log")=="yes"):
+            self.clean.setChecked(True)
+        self.status=QtGui.QCheckBox("Show Status Message")
+        if(self.conf.get("status_message")=="on"):
+            self.status.setChecked(True)
+        self.ok=QtGui.QPushButton("Ok")
+        self.ok.clicked.connect(self.save)
+        self.cancel=QtGui.QPushButton("Cancel")
+        self.cancel.clicked.connect(self.close)
+        self.default=QtGui.QPushButton("Default")
+        self.default.clicked.connect(self.reset)
+
+        ctrl=QtGui.QHBoxLayout()
+        ctrl.addWidget(self.ok)
+        ctrl.addWidget(self.cancel)
+        ctrl.addWidget(self.default)
+        layout=QtGui.QGridLayout(self)
+        layout.addWidget(self.lpLabel, 0, 0)
+        layout.addWidget(self.lpEdit, 0, 1)
+        layout.addWidget(self.lpBrowse, 0, 2)
+        layout.addWidget(self.cpLabel, 1, 0)
+        layout.addWidget(self.cpEdit, 1, 1)
+        layout.addWidget(self.cpBrowse, 1, 2)
+        layout.addWidget(self.mpLabel, 2, 0)
+        layout.addWidget(self.mpEdit, 2, 1)
+        layout.addWidget(self.mpBrowse, 2, 2)
+        layout.addWidget(self.splash, 3, 0)
+        layout.addWidget(self.clean, 4, 0)
+        layout.addWidget(self.status, 5, 0)
+        layout.addLayout(ctrl, 6, 1)
+
+        self.cancel.setFocus()
+
+    def lpUpdate(self):
+        ''' Updates the library path '''
+        self.lpEdit.setText(self.getFolder())
+        
+    def cpUpdate(self):
+        ''' Updates the custom path '''
+        self.cpEdit.setText(self.getFolder())
+        
+    def mpUpdate(self):
+        ''' Updates the module path '''
+        self.mpEdit.setText(self.getFolder())
+
+    def getFolder(self):
+        ''' Returns the selected directory '''
+        dn=QtGui.QFileDialog.getExistingDirectory()
+        if(dn.startswith(QtCore.QDir.currentPath())):
+           dn="."+dn[len(QtCore.QDir.currentPath()):]+"/"
+        else:
+            dn=dn+"/"
+        return dn
+
+    def save(self):
+        ''' Saves the configuration to disk '''
+        self.conf.set("library_path", self.lpEdit.text())
+        self.conf.set("custom_path", self.cpEdit.text())
+        self.conf.set("module_path", self.mpEdit.text())
+        if(self.splash.isChecked()):
+            self.conf.set("splash_screen", "yes")
+        else:
+            self.conf.set("splash_screen", "no")
+        if(self.clean.isChecked()):
+            self.conf.set("clean_log", "yes")
+        else:
+            self.conf.set("clean_log", "no")
+        if(self.status.isChecked()):
+            self.conf.set("status_message", "on")
+        else:
+            self.conf.set("status_message", "off")
+        QtGui.QMessageBox.information(self, "Restart required", "Some settings takes effect only after restarting DDStorm")
+        self.close()
+        self.conf.write()
+
+    def reset(self):
+        ''' Resests the settings to the default factory value '''
+        self.conf.default()
+        self.lpEdit.setText(self.conf.get("library_path"))
+        self.cpEdit.setText(self.conf.get("class_path"))
+        self.mpEdit.setText(self.conf.get("module_path"))
+        if(self.conf.get("splash_screen")=="yes"):
+            self.splash.setChecked(True)
+        else:
+            self.splash.setChecked(False)
+        if(self.conf.get("clean_log")=="yes"):
+            self.clean.setChecked(True)
+        else:
+            self.clean.setChecked(False)
+        if(self.conf.get("status_message")=="on"):
+            self.status.setChecked(True)
+        else:
+            self.status.setChecked(False)
diff --git a/gui.py b/gui.py
new file mode 100644 (file)
index 0000000..55320cb
--- /dev/null
+++ b/gui.py
@@ -0,0 +1,192 @@
+#! /usr/bin/python3
+
+'''
+This module provides the main graphical user interface for DDStorm
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import time
+import subprocess
+
+from PyQt4 import QtGui, QtCore
+
+from panes import Symptoms, Differentials
+from ddstorm import DDStorm
+from conf import Conf
+from extras import *
+from const import *
+
+conf=False
+
+class Content(QtGui.QWidget):
+    '''
+    Provides the main content widget. Contains the sysmptoms and
+    the diagnosis panes. Also creates the DDStorm object and performs
+    the main operation.
+    '''
+
+    # Signal to detect when 
+    change=QtCore.pyqtSignal()
+    
+    def __init__(self):
+        ''' Initiate the content widget '''
+        super(Content, self).__init__()
+        
+        # Create DDStorm object with the global configuration
+        self.dd=DDStorm(True, conf)
+
+        # Show warning if any error happened during data compilation 
+        if(not self.dd.compiler.is_clean()):
+            ret=QtGui.QMessageBox.warning(self, "Compilation Error", "Error was encountered while compiling the Knowledgebase.", "Ignore", "View Log")
+            if(ret==1):
+                x_logfile()
+        
+        self.initUI()
+        
+    def initUI(self):
+        ''' Create the user interface of the widget '''
+        
+        global conf
+
+        grid=QtGui.QGridLayout()
+        self.setLayout(grid)
+
+        self.symp=Symptoms(self.dd.symptoms())
+        self.symp.setFrameShape(QtGui.QFrame.StyledPanel)
+        self.symp.changed.connect(self.update)
+
+        self.diff=Differentials()
+        self.diff.setFrameShape(QtGui.QFrame.StyledPanel)
+
+        grid.addWidget(self.symp, 0, 0)
+        grid.addWidget(self.diff, 0, 1)
+        grid.setColumnStretch(0, 1)
+        grid.setColumnStretch(1, 1)
+        QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
+        
+    def update(self, data):
+        ''' Update the inteface with refreshed information '''
+        self.diff.update(self.dd.dd(data))
+        self.change.emit()
+
+
+class Window(QtGui.QMainWindow):
+    '''
+    Provides main application window. Acts as a container for the
+    content widget. Also contains the menubar and the status bar.
+    '''
+    
+    def __init__(self):
+        ''' Initiate the main window '''
+        super(Window, self).__init__()
+        self.initUI()
+
+    def initUI(self):
+        ''' Create the user interface '''
+        global conf
+        self.con=Content()
+        self.sett=SettingsDialog(conf)
+        if(conf.get("status_message")=="on"):
+            self.con.change.connect(self.showStatus)
+
+        menu=self.menuBar()
+        menuFile=menu.addMenu("&File")
+        menuFile.addAction("&Save").triggered.connect(self.savefile)
+        menuFile.addAction("E&xit").triggered.connect(self.close)
+        menuEdit=menu.addMenu("&Edit")
+        menuEdit.addAction("&Add").triggered.connect(self.con.symp.addItem)
+        menuEdit.addAction("&Browse Symptoms").triggered.connect(self.con.symp.browseSymptoms)
+        rmAction=QtGui.QAction("&Remove", self)
+        rmAction.setShortcut("Delete")
+        rmAction.triggered.connect(self.con.symp.remove)
+        menuEdit.addAction(rmAction)
+        menuEdit.addAction("&Clear All").triggered.connect(self.con.symp.removeAll)
+        menuTool=menu.addMenu("&Tools")
+        menuTool.addAction("&Library").triggered.connect(x_lib)
+        menuTool.addAction("&Settings").triggered.connect(self.settings)
+        menuTool.addAction("&View Log").triggered.connect(x_logfile)
+        menuHelp=menu.addMenu("&Help")
+        menuHelp.addAction("&Help").triggered.connect(x_help)
+        menuHelp.addAction("&About").triggered.connect(self.about)
+
+        self.setCentralWidget(self.con)
+        self.status=self.statusBar()
+        self.setGeometry(200, 200, 600, 400)
+        self.setWindowTitle("D/D Storm")
+        self.setWindowIcon(QtGui.QIcon("icons/icon.png"))
+        self.showMaximized()
+        self.con.symp.new.setFocus()
+
+    def showStatus(self):
+        ''' Show status message '''
+        if(self.con.symp.getList() and self.con.diff.getList()):
+            self.status.showMessage(str(len(self.con.diff.getList()))+" differential diagnosis for "+str(len(self.con.symp.getList()))+" symptom(s).")
+        else:
+            self.status.showMessage("")
+
+    def savefile(self):
+        ''' Save data to a file '''
+        x_save(self, self.con.symp.getList(), self.con.diff.getList())
+
+    def settings(self):
+        ''' Open the settings dialog '''
+        self.sett.exec_()
+
+    def about(self):
+        ''' Show information about this application '''
+        QtGui.QMessageBox.about(self, "About", "<h1>DDStorm</h1>\nBrainstorm Medicine")
+
+
+def main():
+    ''' Start the main application interface '''
+    app=QtGui.QApplication(sys.argv)
+
+    # Initiate the global configuration
+    global conf
+    conf=Conf()
+    
+    # Clean the log file
+    if(conf.get("clean_log")=="yes"):
+        open(LOG_FILE, "w").close()
+
+    # Show splash-screen
+    if(conf.get("splash_screen")=="yes"):
+        ss=True
+    else:
+        ss=False
+    if(ss):
+        splash=QtGui.QSplashScreen(QtGui.QPixmap("icons/splash.png"))
+        splash.show()
+        time.sleep(0.1)
+        app.processEvents()
+        splash.showMessage("Loading...")
+
+    # Create main window
+    w=Window()
+    if(ss):
+        splash.finish(w)
+
+    # Start application
+    sys.exit(app.exec_())
+
+if(__name__=="__main__"):
+    main()
index f1da7492f8c23d3cbf858bb280651737dfae9888..d7e7b67150fa9d43685d080bfd8296312a7ccefd 100644 (file)
--- a/index.py
+++ b/index.py
@@ -1,32 +1,51 @@
 #! /usr/bin/python3
 
-# DDStorm
-# -------
-# Copyright (c) 2015 Agnibho Mondal
-# All rights reserved
+'''
+This module handles the upstream ancestors of symptoms.
 
-# This file is part of DDStorm.
+The symptoms can be classified in different levels, with the more
+generalized symtoms at upstream and the more specialized symptoms at
+the downstream.
 
-# DDStorm is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+For example:
+'Acute peritonitis' is a special type of 'peritonitis'
+Hence 'peritionitis' will be at upstream and 'acute peritonitis' will be
+at downstream.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
 
-# DDStorm is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+This file is part of DDStorm.
 
-# You should have received a copy of the GNU General Public License
-# along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-import sys, os
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import os
 from fnmatch import fnmatch
+
 from conf import Conf
 from const import *
 
 class Index:
+    ''' Provides an index of the upstream ancestors of symptoms '''
     def __init__(self, conf=False):
+        '''
+        Initiate the index object. Accepts a Conf object as an
+        optional parameter.
+        '''
         if(conf):
             self.conf=conf
         else:
@@ -35,55 +54,101 @@ class Index:
         self.compile()
 
     def compile(self):
+        '''
+        Compile the text index files to a application usable format.
+        '''
+        # Loop over all files under index_path
         for path, subdirs, files in os.walk(self.conf.get("index_path")):
             for name in files:
+                # Only files with *.txt suffix
                 if(fnmatch(name, "*.txt")):
                     with open(self.conf.get("index_path")+name, "r") as f:
+                        # Two dimensional list buffer to hold the index
                         buff=[]
                         buff.append([])
                         buff.append([])
+                        # Loop over all lines as separate entries
                         for line in f:
+                            # Ignore commnents starting with #
                             line=line.rstrip().split("#")[0]
                             if(len(line)==0):
                                 pass
                             else:
+                                # Find number of leading whitespaces
                                 ws=len(line)-len(line.lstrip())
+                                # Format the entry
                                 line=line.lstrip().capitalize()
+                                
+                                # No leading whitespace means a top level entry i.e. no upstream ancestor
                                 if(ws==0):
+                                    # Empty the buffer and add the entry
                                     del buff[0][:]
                                     buff[0].append(line.lstrip())
+                                    # Reset the whitespace index
                                     del buff[1][:]
                                     buff[1].append(0)
+                                # If leading whitespace > indexed whitespace, the entry is a subcategory of previous entry
                                 elif(ws>buff[1][-1]):
+                                    # Append entry to buffer as new item
                                     buff[0].append(line.lstrip())
+                                    # Record leading whitespace to buffer
                                     buff[1].append(ws)
+                                    # Copy buffer to data list
                                     self.data[buff[0][-1]]=list(reversed(buff[0]))
+                                # If leading whitespace == indexed whitespace, the entry is at the same level with the previous entry
                                 elif(ws==buff[1][-1]):
+                                    # Append entry to last item of buffer
                                     buff[0][-1]=line.lstrip()
                                     buff[1][-1]=ws
+                                    # Copy buffer to data list
                                     self.data[buff[0][-1]]=list(reversed(buff[0]))
+                                # If leading whitespace < indexed whitespace, the entry is at an upper category than the previous entry
                                 elif(ws<buff[1][-1]):
+                                    # Loop over the buffer in reverse
                                     for i in reversed(buff[1]):
+                                        # Discard all lower category items
                                         if(i>=ws):
                                             buff[0].pop()
                                             buff[1].pop()
+                                        #Append the entry to buffer
                                         else:
                                             buff[0].append(line.lstrip())
                                             buff[1].append(ws)
                                             break
+                                    # Copy buffer to data list
                                     self.data[buff[0][-1]]=list(reversed(buff[0]))
 
     def upstream(self, name):
+        '''
+        Return the upstream list of a symptom
+
+        Parameter:
+        name - the name of the symptom as string
+
+        Return value:
+        List of strings containing the upstream items
+        '''
         if(len(name)>0):
             if name in self.data:
                 return self.data[name]
             else:
                 return []
 
+    def names(self):
+        ''' Return all indexed symptoms name '''
+        return list(self.data.keys())
+
 def main():
+    '''
+    Prints upstream items of a symptom provided as command line
+    argument.
+    If no argument provided, returns a list of all indexed symptoms.
+    '''
     i=Index()
     if(len(sys.argv)>1):
         print(i.upstream(sys.argv[1]))
+    else:
+        print(i.names())
 
 if(__name__=="__main__"):
     main()
diff --git a/panes.py b/panes.py
new file mode 100644 (file)
index 0000000..79636a4
--- /dev/null
+++ b/panes.py
@@ -0,0 +1,214 @@
+'''
+This module provides the application panes containing essential
+functionalities.
+'''
+'''
+Copyright (c) 2015 Agnibho Mondal
+All rights reserved
+
+This file is part of DDStorm.
+
+DDStorm is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+DDStorm is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with DDStorm.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from PyQt4 import QtGui, QtCore
+
+class Symptoms(QtGui.QFrame):
+    ''' Provides the widget for symptoms input '''
+
+    # List to hold the symptoms
+    sympList=[]
+
+    # Signal to notify any change in input
+    changed=QtCore.pyqtSignal(list)
+    
+    def __init__(self, auto):
+        '''
+        Initiate the symptom input pane. Takes a list of string as
+        argument. The list is used as the auto-complete list during
+        user input.
+
+        Parameter:
+        auto - A list of string containing all available symptoms for
+               autocomplete.
+        '''
+        super(Symptoms, self).__init__()
+        self.auto=auto
+        self.initUI()
+
+    def initUI(self):
+        ''' Initiate the user interface '''
+        self.label=QtGui.QLabel("Symptoms")
+        self.label.setStyleSheet("font-size:18px")
+
+        self.listWidget=QtGui.QListWidget(self)
+        self.listWidget.setStyleSheet("font-size:14px")
+        self.listWidget.setSelectionMode(4)
+
+        self.rm=QtGui.QPushButton("Remove")
+        self.rm.clicked.connect(self.remove)
+
+        self.cl=QtGui.QPushButton("Clear All")
+        self.cl.clicked.connect(self.removeAll)
+
+        self.browse=QtGui.QPushButton("Browse Symptoms")
+        self.browse.clicked.connect(self.browseSymptoms)
+
+        self.add=QtGui.QPushButton("Add")
+        self.add.clicked.connect(self.addItem)
+
+        self.new=QtGui.QLineEdit(self)
+        self.new.returnPressed.connect(self.addItem)
+        self.completer=QtGui.QCompleter(self.auto)
+        self.completer.setCaseSensitivity(0)
+        self.completer.setCompletionMode(2)
+        self.new.setCompleter(self.completer)
+
+        self.browser=SymptomBrowser(self.auto)
+        self.browser.added.connect(self.addItem)
+
+        hboxt=QtGui.QHBoxLayout()
+        hboxt.addWidget(self.new)
+        hboxt.addWidget(self.add)
+        hboxb=QtGui.QHBoxLayout()
+        hboxb.addWidget(self.rm)
+        hboxb.addWidget(self.cl)
+        hboxb.addWidget(self.browse)
+        vbox=QtGui.QVBoxLayout()
+        vbox.addWidget(self.label)
+        vbox.addLayout(hboxt)
+        vbox.addWidget(self.listWidget)
+        vbox.addLayout(hboxb)
+        self.setLayout(vbox)
+
+    def addItem(self, text=""):
+        ''' Add a new symptom '''
+        if(not text):
+            text=self.new.text()
+        if(len(text)>0):
+            if(text in self.sympList):
+                QtGui.QMessageBox.information(self, "Duplicate Symptom", "'"+text+"' has already been added to the symptom list.")
+            elif(text in self.auto):
+                QtGui.QListWidgetItem(text, self.listWidget)
+                self.sympList.append(text)
+                self.new.clear()
+                self.changed.emit(list(self.sympList))
+            else:
+                QtGui.QMessageBox.warning(self, "Symptom Unvailable", "'"+text+"' is not available in the current Library.")
+
+    def remove(self, all=False):
+        ''' Remove selected symptoms '''
+        if(len(self.listWidget.selectedItems())>0):
+            for item in self.listWidget.selectedItems():
+                self.sympList.remove(item.text())
+                self.listWidget.takeItem(self.listWidget.row(item))
+            self.changed.emit(list(self.sympList))
+
+    def removeAll(self):
+        ''' Clear all symptoms '''
+        self.listWidget.clear()
+        del self.sympList[:]
+        self.changed.emit(list(self.sympList))
+
+    def browseSymptoms(self):
+        ''' Open the symptom browser '''
+        self.browser.exec_()
+
+    def getList(self):
+        ''' Return a list of all symptoms '''
+        return self.sympList
+
+
+class SymptomBrowser(QtGui.QDialog):
+    '''
+    Provides a dialog with a list of symptoms for alternative user
+    input.
+    '''
+    added=QtCore.pyqtSignal(str)
+    
+    def __init__(self, items):
+        ''' Initiate the input dialog '''
+        super(SymptomBrowser, self).__init__()
+        self.setWindowTitle("Choose Symptom")
+        self.items=items
+        self.initUI()
+
+    def initUI(self):
+        ''' Initiate the dialog interface '''
+        self.search=QtGui.QLineEdit()
+        self.search.textChanged.connect(self.refresh)
+        self.listItems=QtGui.QListWidget()
+        self.listItems.activated.connect(self.sendUp)
+
+        layout=QtGui.QVBoxLayout(self)
+        layout.addWidget(self.search)
+        layout.addWidget(self.listItems)
+
+        self.listItems.addItems(self.items)
+        self.search.setFocus()
+
+    def refresh(self):
+        ''' Refresh the symptom list based on search term '''
+        term=self.search.text()
+        buff=[]
+        for i in self.items:
+            if(term.lower() in i.lower()):
+                buff.append(i)
+        self.listItems.clear()
+        self.listItems.addItems(buff)
+    
+    def sendUp(self):
+        ''' Emit signal and close when a symptom is selected '''
+        self.added.emit(self.listItems.currentItem().text())
+        self.close()
+
+
+class Differentials(QtGui.QFrame):
+    ''' Provides the widget for differential diagnosis output '''
+    data=[]
+    
+    def __init__(self):
+        ''' Initiate the diagnosis output pane '''
+        super(Differentials, self).__init__()
+        self.initUI()
+        
+    def initUI(self):
+        ''' Initiate the user interface '''
+        self.label=QtGui.QLabel("Differential Diagnosis")
+        self.label.setStyleSheet("font-size:18px")
+        self.listWidget=QtGui.QListWidget(self)
+        self.listWidget.setStyleSheet("font-size:14px")
+        self.listWidget.setSelectionMode(0)
+        box=QtGui.QVBoxLayout()
+        box.addWidget(self.label)
+        box.addWidget(self.listWidget)
+        self.setLayout(box)
+
+    def update(self, data):
+        '''
+        Update the outut pane with updated diagnosis list.
+        
+        Parameter:
+        data - List of strings containing new list of differential
+               diagnosis
+        '''
+        self.data=data
+        self.listWidget.clear()
+        if(self.data):
+            for d in self.data:
+                QtGui.QListWidgetItem(d, self.listWidget)
+
+    def getList(self):
+        ''' Return a list of current differential diagnosis '''
+        return self.data