diff --git a/_freeze.py b/_freeze.py
index 1b2d77c..5607fe0 100644
--- a/_freeze.py
+++ b/_freeze.py
@@ -2,18 +2,31 @@
# FIXME: Get py2exe working for Windows builds (since it produces a single EXE).
import sys
+import os
+import glob
from cx_Freeze import setup , Executable
from constants import *
+base_dir = os.path.split( os.path.abspath(__file__) )[ 0 ]
+os.chdir( base_dir )
+
+import asl_cards
+
# ---------------------------------------------------------------------
+def get_extra_files( fspec ) :
+ """Locate extra files to include in the release."""
+ fnames = glob.glob( fspec )
+ return zip( fnames , fnames )
+
# initialize
-extra_files = [
- "resources/app.ico"
-]
+extra_files = []
+extra_files.extend( get_extra_files( "resources/*.ico" ) )
+extra_files.extend( get_extra_files( "resources/*.png" ) )
+extra_files.extend( get_extra_files( "ui/*ui" ) )
build_options = {
- "packages": [ "os" ] ,
+ "packages": [ "os" , "sqlalchemy" ] ,
"excludes": [ "tkinter" ] ,
"include_files": extra_files ,
}
@@ -30,7 +43,7 @@ setup(
version = APP_VERSION ,
description = APP_DESCRIPTION ,
options = {
- APP_NAME: build_options
+ "build_exe": build_options
} ,
executables = [ target ]
)
diff --git a/asl_cards/__init__.py b/asl_cards/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/asl_cards/__main__.py b/asl_cards/__main__.py
index b405ea3..2c11346 100755
--- a/asl_cards/__main__.py
+++ b/asl_cards/__main__.py
@@ -5,8 +5,9 @@ import sys
import os
import getopt
-from parse import PdfParser
-import db
+sys.path.append( ".." ) # fudge! need this to allow a script to run within a package :-/
+from asl_cards.parse import PdfParser
+from asl_cards import db
# ---------------------------------------------------------------------
@@ -45,9 +46,6 @@ def main( args ) :
raise RuntimeError( "Unknown argument: {}".format( opt ) )
if not db_fname : raise RuntimeError( "No database was specified." )
- # initialize
- db.open_database( db_fname )
-
# do the requested processing
pdf_parser = PdfParser( progress_callback if log_progress else None )
if parse_targets :
@@ -56,8 +54,10 @@ def main( args ) :
cards.extend (
pdf_parser.parse( pt , max_pages=max_pages , images=extract_images )
)
+ db.open_database( db_fname , True )
db.add_cards( cards )
elif dump :
+ db.open_database( db_fname , False )
db.dump_database()
else :
raise RuntimeError( "No action." )
diff --git a/asl_cards/db.py b/asl_cards/db.py
index c702b21..8dd2a8a 100644
--- a/asl_cards/db.py
+++ b/asl_cards/db.py
@@ -60,11 +60,17 @@ class AslCardImage( DbBase , DbBaseMixin ) :
# ---------------------------------------------------------------------
-def open_database( fname ) :
+def open_database( fname , create ) :
"""Open the database."""
# open the database
is_new = not os.path.isfile( fname )
+ if create :
+ if not is_new :
+ raise Exception( "File exists: {}".format( fname ) )
+ else :
+ if is_new :
+ raise Exception( "Can't find file: {}".format( fname ) )
conn_string = "sqlite:///{}".format( fname )
global db_engine
db_engine = create_engine( conn_string , convert_unicode=True )
@@ -76,7 +82,7 @@ def open_database( fname ) :
db_session.execute( "PRAGMA foreign_keys = on" ) # nb: foreign keys are disabled by default in SQLite
# check if we are creating a new database
- if is_new :
+ if create :
# yup - make it so
DbBase.metadata.create_all( db_engine )
diff --git a/asl_cards/parse.py b/asl_cards/parse.py
index f8ed6fe..43de795 100644
--- a/asl_cards/parse.py
+++ b/asl_cards/parse.py
@@ -14,15 +14,17 @@ from pdfminer.pdfpage import PDFPage
import ghostscript
from PIL import Image , ImageChops
-from db import AslCard , AslCardImage
+from asl_cards.db import AslCard , AslCardImage
# ---------------------------------------------------------------------
class PdfParser:
- def __init__( self , progress=None ) :
+ def __init__( self , progress=None , progress2=None ) :
# initialize
- self.progress = progress
+ self.progress = progress # nb: for tracking file progress
+ self.progress2 = progress2 # nb: for tracking page progress within a file
+ self.cancelling = False
def parse( self , target , max_pages=-1 , images=True ) :
"""Extract the cards from a PDF file."""
@@ -38,6 +40,7 @@ class PdfParser:
# parse each file
cards = []
for fname in fnames :
+ if self.cancelling : raise RuntimeError("Cancelled.")
cards.extend( self._do_parse_file( fname , max_pages , images ) )
return cards
@@ -49,10 +52,11 @@ class PdfParser:
interp = PDFPageInterpreter( rmgr , dev )
cards = []
with open(fname,"rb") as fp :
- self._progress( 0 , "Loading file: {}".format( fname ) )
+ self._progress( 0 , "Analyzing {}...".format( os.path.split(fname)[1] ) )
pages = list( PDFPage.get_pages( fp ) )
for page_no,page in enumerate(pages) :
- self._progress( float(page_no)/len(pages) , "Extracting card info from page {}...".format( 1+page_no ) )
+ if self.cancelling : raise RuntimeError("Cancelled.")
+ self._progress2( float(page_no) / len(pages) )
page_cards = self._parse_page( cards , interp , page_no , page )
cards.extend( page_cards )
if max_pages > 0 and 1+page_no >= max_pages :
@@ -64,6 +68,7 @@ class PdfParser:
if len(cards) != len(card_images) :
raise RuntimeError( "Found {} cards, {} card images.".format( len(cards) , len(card_images) ) )
for i in range(0,len(cards)) :
+ if self.cancelling : raise RuntimeError("Cancelled.")
cards[i].card_image = AslCardImage( image_data=card_images[i] )
return cards
@@ -75,12 +80,14 @@ class PdfParser:
# locate the info box for each card (in the top-left corner)
info_boxes = []
for item in lt_page :
+ if self.cancelling : raise RuntimeError("Cancelled.")
if type(item) is not LTTextBoxHorizontal : continue
item_text = item.get_text().strip()
if item_text.startswith( ("Vehicle","Ordnance") ) :
info_boxes.append( [item] )
# get the details from each info box
for item in lt_page :
+ if self.cancelling : raise RuntimeError("Cancelled.")
if type(item) is not LTTextBoxHorizontal : continue
# check if the next item could be part of an info box - it must be within the left/right boundary
# of the first item (within a certain tolerance), and below it (but not too far)
@@ -93,7 +100,6 @@ class PdfParser:
# generate an AslCard from each info box
for info_box in info_boxes :
card = self._make_asl_card( lt_page , info_box )
- self._progress( None , "Found card: {}".format( card ) )
cards.append( card )
return cards
@@ -132,7 +138,7 @@ class PdfParser:
# FIXME! clean up left-over temp files before we start
args = [ s.encode(locale.getpreferredencoding()) for s in args ]
# FIXME! stop GhostScript from issuing warnings (stdout).
- self._progress( 0 , "Extracting images..." )
+ self._progress( 0 , "Extracting images from {}...".format( os.path.split(fname)[1] ) )
ghostscript.Ghostscript( *args )
# figure out how many files were created (so we can show progress)
npages = 0
@@ -144,8 +150,9 @@ class PdfParser:
# extract the cards from each page
card_images = []
for page_no in range(0,npages) :
+ if self.cancelling : raise RuntimeError("Cancelled.")
# open the next page image
- self._progress( float(page_no)/npages , "Extracting card images from page {}...".format( 1+page_no ) )
+ self._progress2( float(page_no) / npages )
fname = fname_template % (1+page_no)
img = Image.open( fname )
img_width , img_height = img.size
@@ -196,10 +203,14 @@ class PdfParser:
os.unlink( fname )
return buf , rgn.size
- def _progress( self , progress , msg ) :
+ def _progress( self , pval , msg ) :
"""Call the progress callback."""
if self.progress :
- self.progress( progress , msg )
+ self.progress( pval , msg )
+ def _progress2( self , pval ) :
+ """Call the progress callback."""
+ if self.progress2 :
+ self.progress2( pval )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/asl_cards/tests/test_real_data.py b/asl_cards/tests/test_real_data.py
index 9a8cb58..b981670 100755
--- a/asl_cards/tests/test_real_data.py
+++ b/asl_cards/tests/test_real_data.py
@@ -5,8 +5,9 @@ import os
import unittest
base_dir = os.path.split( __file__ )[ 0 ]
-sys.path.append( os.path.join( base_dir , ".." ) )
-from parse import PdfParser , AslCard
+
+sys.path.append( ".." ) # fudge! need this to allow a script to run within a package :-/
+from asl_cards.parse import PdfParser , AslCard
# ---------------------------------------------------------------------
diff --git a/constants.py b/constants.py
old mode 100755
new mode 100644
diff --git a/main.py b/main.py
index fac4884..ae85f0d 100755
--- a/main.py
+++ b/main.py
@@ -9,7 +9,6 @@ from PyQt5.QtWidgets import QApplication
from constants import *
import globals
-import asl_cards.db as db
# ---------------------------------------------------------------------
@@ -41,41 +40,36 @@ def do_main( args ) :
raise RuntimeError( "Unknown argument: {}".format( opt ) )
if not settings_fname :
# try to locate the settings file
- settings_fname = os.path.join( globals.base_dir , globals.app_name+".ini" )
+ fname = globals.app_name+".ini" if sys.platform == "win32" else "."+globals.app_name
+ settings_fname = os.path.join( globals.base_dir , fname )
if not os.path.isfile( settings_fname ) :
- settings_fname = os.path.split(settings_fname)[ 1 ]
- if sys.platform != "win32" :
- settings_fname = "." + settings_fname
- settings_fname = os.path.join( QDir.homePath() , settings_fname )
+ settings_fname = os.path.join( QDir.homePath() , fname )
if not db_fname :
- # try to locate the database
+ # use the default location
db_fname = os.path.join( globals.base_dir , globals.app_name+".db" )
- if not os.path.isfile( db_fname ) :
- raise RuntimeError( "Can't find database: {}".format( db_fname ) )
# load our settings
globals.app_settings = QSettings( settings_fname , QSettings.IniFormat )
fname = os.path.join( os.path.split(settings_fname)[0] , "debug.ini" )
globals.debug_settings = QSettings( fname , QSettings.IniFormat )
- # open the database
- db.open_database( db_fname )
- globals.cards = db.load_cards()
-
# do main processing
app = QApplication( sys.argv )
- import main_window
- main_window = main_window.MainWindow()
+ from main_window import MainWindow
+ main_window = MainWindow( db_fname )
main_window.show()
+ if os.path.isfile( db_fname ) :
+ main_window.start_main_app( db_fname )
return app.exec_()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def print_help() :
- print( "{} {{options}}".format( os.path.split(sys.argv[0])[1] ) ) # FIXME! frozen?
+ print( "{} {{options}}".format( globals.app_name ) )
print( " {}".format( APP_DESCRIPTION ) )
print()
print( " -c --config Config file." )
+ print( " -d --db Database file." )
sys.exit()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/main_window.py b/main_window.py
old mode 100755
new mode 100644
index b0a7dd5..8a4b670
--- a/main_window.py
+++ b/main_window.py
@@ -2,14 +2,15 @@ import sys
import os
from PyQt5.QtCore import Qt , QPoint , QSize
-from PyQt5.QtWidgets import QMainWindow , QVBoxLayout , QHBoxLayout , QWidget , QTabWidget , QLabel
+from PyQt5.QtWidgets import QApplication , QMainWindow , QVBoxLayout , QHBoxLayout , QWidget , QTabWidget , QLabel
from PyQt5.QtWidgets import QDialog , QMessageBox , QAction
from PyQt5.QtGui import QPainter , QPixmap , QIcon , QBrush
import asl_cards.db as db
from constants import *
import globals
-import add_card_dialog
+from add_card_dialog import AddCardDialog
+from startup_widget import StartupWidget
# ---------------------------------------------------------------------
@@ -42,19 +43,25 @@ class AslCardWidget( QWidget ) :
class MainWindow( QMainWindow ) :
- def __init__( self ) :
+ _instance = None
+
+ def __init__( self , db_fname ) :
+ # initialize
super().__init__()
+ assert MainWindow._instance is None
+ MainWindow._instance = self
# initialize the window
self.setWindowTitle( APP_NAME )
self.setWindowIcon( QIcon("resources/app.ico") )
# initialize the menu
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu( "&File" )
- action = QAction( "&Add" , self )
- action.setShortcut( "Ctrl+A" )
- action.setStatusTip( "Add an ASL Card." )
- action.triggered.connect( self.on_add_card )
- file_menu.addAction( action )
+ self.add_card_action = QAction( "&Add" , self )
+ self.add_card_action.setEnabled( False )
+ self.add_card_action.setShortcut( "Ctrl+A" )
+ self.add_card_action.setStatusTip( "Add an ASL Card." )
+ self.add_card_action.triggered.connect( self.on_add_card )
+ file_menu.addAction( self.add_card_action )
action = QAction( "E&xit" , self )
action.setStatusTip( "Close the program." )
action.triggered.connect( self.close )
@@ -62,22 +69,57 @@ class MainWindow( QMainWindow ) :
# load the window settings
self.resize( globals.app_settings.value( MAINWINDOW_SIZE , QSize(500,300) ) )
self.move( globals.app_settings.value( MAINWINDOW_POSITION , QPoint(200,200) ) )
- # initialize the window controls
+ # show the startup form
+ self.setCentralWidget(
+ StartupWidget( db_fname , parent=self )
+ )
+
+ def start_main_app( self , db_fname ) :
+ """Start the main app."""
+ # we can now close the startup widget and replace it with the main tab widget
self.tab_widget = QTabWidget( self )
self.tab_widget.setTabsClosable( True )
self.setCentralWidget( self.tab_widget )
+ # open the database
+ db.open_database( db_fname , False )
+ globals.cards = db.load_cards()
+ # ask the user to add the first card
+ self.add_card_action.setEnabled( True )
+ self.on_add_card()
+
+ @staticmethod
+ def show_info_msg( msg ) :
+ """Show an informational message."""
+ QMessageBox.information( MainWindow._instance , APP_NAME , msg )
+ @staticmethod
+ def show_error_msg( msg ) :
+ """Show an error message."""
+ QMessageBox.warning( MainWindow._instance , APP_NAME , msg )
+ @staticmethod
+ def ask( msg , buttons , default ) :
+ """Show an error message."""
+ return QMessageBox.question( MainWindow._instance , APP_NAME , msg , buttons , default )
def closeEvent( self , evt ) :
"""Handle window close."""
# confirm the close
- if globals.app_settings.value( CONFIRM_EXIT , True , type=bool ) :
- rc = QMessageBox.question( self , "Confirm close" ,
- "Do you want to the close the program?" ,
- QMessageBox.Ok | QMessageBox.Cancel ,
- QMessageBox.Cancel
- )
- if rc != QMessageBox.Ok :
+ widget = self.centralWidget()
+ if type(widget) is StartupWidget :
+ # don't allow this if we are analyzing files
+ if widget.analyze_thread :
+ QApplication.beep()
evt.ignore()
+ return
+ else :
+ # check if we should confirm the exit
+ if globals.app_settings.value( CONFIRM_EXIT , True , type=bool ) :
+ rc = QMessageBox.question( self , "Confirm close" ,
+ "Do you want to the close the program?" ,
+ QMessageBox.Ok | QMessageBox.Cancel ,
+ QMessageBox.Cancel
+ )
+ if rc != QMessageBox.Ok :
+ evt.ignore()
# save the window settings
# FIXME! handle fullscreen
globals.app_settings.setValue( MAINWINDOW_POSITION , self.pos() )
@@ -89,7 +131,7 @@ class MainWindow( QMainWindow ) :
self.close()
def on_add_card( self ) :
- dlg = add_card_dialog.AddCardDialog( self )
+ dlg = AddCardDialog( self )
rc = dlg.exec()
if rc == QDialog.Accepted :
# add a new tab for the selected card
diff --git a/resources/analyze.png b/resources/analyze.png
new file mode 100755
index 0000000..5a22e4b
Binary files /dev/null and b/resources/analyze.png differ
diff --git a/resources/dir_dialog.png b/resources/dir_dialog.png
new file mode 100755
index 0000000..6d0cd50
Binary files /dev/null and b/resources/dir_dialog.png differ
diff --git a/resources/file_dialog.png b/resources/file_dialog.png
new file mode 100755
index 0000000..009cb0d
Binary files /dev/null and b/resources/file_dialog.png differ
diff --git a/resources/load_db.png b/resources/load_db.png
new file mode 100755
index 0000000..4d860df
Binary files /dev/null and b/resources/load_db.png differ
diff --git a/resources/open_file.png b/resources/open_file.png
new file mode 100755
index 0000000..e70def6
Binary files /dev/null and b/resources/open_file.png differ
diff --git a/resources/stop.png b/resources/stop.png
new file mode 100755
index 0000000..76e97f2
Binary files /dev/null and b/resources/stop.png differ
diff --git a/startup_widget.py b/startup_widget.py
new file mode 100644
index 0000000..61d609e
--- /dev/null
+++ b/startup_widget.py
@@ -0,0 +1,232 @@
+import os
+
+from PyQt5 import uic
+from PyQt5.QtCore import QThread , pyqtSignal
+from PyQt5.QtWidgets import QWidget , QFrame , QFileDialog , QMessageBox
+from PyQt5.QtGui import QPixmap , QIcon
+
+from asl_cards.parse import PdfParser
+import asl_cards.db as db
+
+from constants import *
+import globals
+
+# ---------------------------------------------------------------------
+
+class AnalyzeThread( QThread ) :
+
+ # define our signals
+ progress_signal = pyqtSignal( float , str , name="progress" )
+ progress2_signal = pyqtSignal( float , name="progress2" )
+ completed_signal = pyqtSignal( str , name="completed" )
+
+ def __init__( self , cards_dir , db_fname ) :
+ # initialize
+ super(AnalyzeThread,self).__init__()
+ self.cards_dir = cards_dir
+ self.db_fname = db_fname
+
+ def run( self ) :
+ """Run the worker thread."""
+ try :
+ # initialize
+ if os.path.isfile( self.db_fname ) :
+ os.unlink( self.db_fname )
+ # parse the files
+ self.parser = PdfParser(
+ progress = lambda pval,msg: self.progress_signal.emit( -1 if pval is None else pval , msg ) ,
+ progress2 = lambda pval: self.progress2_signal.emit( pval )
+ )
+ cards = self.parser.parse( self.cards_dir )
+ db.open_database( self.db_fname , True )
+ db.add_cards( cards )
+ except Exception as ex :
+ # notify slots that something went wrong
+ if globals.debug_settings.value("Debug/LogAnalyzeExceptions",type=bool) :
+ import traceback
+ traceback.print_exc()
+ self.completed_signal.emit( str(ex) )
+ else :
+ # notify slots that we've finished
+ self.completed_signal.emit( "" )
+
+# ---------------------------------------------------------------------
+
+class StartupWidget( QWidget ) :
+ """This form lets the user initialize a new database, or load an existing one."""
+
+ def __init__( self , db_fname , parent=None ) :
+ # initialize
+ super(StartupWidget,self).__init__( parent=parent )
+ self.analyze_thread = None
+ # FUDGE! Workaround recursive import's :-/
+ global MainWindow
+ from main_window import MainWindow
+ # initialize the widget
+ uic.loadUi( os.path.join(globals.base_dir,"ui/startup_widget.ui") , self )
+ self.setMinimumSize( self.size() )
+ self.frm_analyze_progress.hide()
+ # initialize the widget
+ self.lbl_analyze_icon.setPixmap(
+ QPixmap( os.path.join( globals.base_dir , "resources/analyze.png" ) )
+ )
+ self.lbl_analyze_icon.setFrameStyle( QFrame.NoFrame )
+ self.btn_cards_dir.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/dir_dialog.png" ) )
+ )
+ self.btn_save_db_fname.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/file_dialog.png" ) )
+ )
+ self.btn_analyze.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/analyze.png" ) )
+ )
+ self.btn_analyze.setText( " " + self.btn_analyze.text() )
+ self.btn_cancel_analyze.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/stop.png" ) )
+ )
+ self.btn_cancel_analyze.setText( " " + self.btn_cancel_analyze.text() )
+ # initialize the widget
+ self.lbl_load_db_icon.setPixmap(
+ QPixmap( os.path.join( globals.base_dir , "resources/load_db.png" ) )
+ )
+ self.lbl_load_db_icon.setFrameStyle( QFrame.NoFrame )
+ self.btn_load_db_fname.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/file_dialog.png" ) )
+ )
+ self.btn_load_db.setIcon(
+ QIcon( os.path.join( globals.base_dir , "resources/load_db.png" ) )
+ )
+ self.btn_load_db.setText( " " + self.btn_load_db.text() )
+ # load the widget
+ if os.path.isfile( db_fname ) :
+ self.le_load_db_fname.setText( db_fname )
+ else :
+ self.le_save_db_fname.setText( db_fname )
+ # connect our handlers
+ self.btn_cards_dir.clicked.connect( self.on_btn_cards_dir )
+ self.btn_save_db_fname.clicked.connect( self.on_btn_save_db_fname )
+ self.btn_analyze.clicked.connect( self.on_analyze )
+ self.btn_load_db_fname.clicked.connect( self.on_btn_load_db_fname )
+ self.btn_load_db.clicked.connect( self.on_btn_load_db )
+
+ def on_btn_cards_dir( self ) :
+ """Let the user browse to where the "ASL Cards" files are."""
+ dname = self.le_cards_dir.text().strip()
+ dname = QFileDialog.getExistingDirectory( self , "ASL Cards" , dname , QFileDialog.ShowDirsOnly )
+ if not dname :
+ return
+ self.le_cards_dir.setText( dname )
+ self.le_save_db_fname.setFocus()
+
+ def on_btn_save_db_fname( self ) :
+ """Let the user browse to where to save the database."""
+ fname = self.le_save_db_fname.text().strip()
+ fname = QFileDialog.getSaveFileName(
+ self , "Save results" ,
+ fname , "Database files (*.db)"
+ )[ 0 ]
+ if not fname :
+ return
+ self.le_save_db_fname.setText( fname )
+ self.btn_analyze.setFocus()
+
+ def on_analyze( self ) :
+ """Analyze the ASL Card files."""
+ # validate the "ASL Cards" directory
+ cards_dir = self.le_cards_dir.text().strip()
+ if not cards_dir :
+ MainWindow.show_error_msg( "Please specify where the \"ASL Cards\" PDF files are." )
+ self.le_cards_dir.setFocus()
+ return
+ if not os.path.isdir( cards_dir ) :
+ MainWindow.show_error_msg( "Can't find the \"ASL Cards\" directory." )
+ self.le_cards_dir.setFocus()
+ return
+ # validate the database filename
+ fname = self.le_save_db_fname.text().strip()
+ if not fname :
+ MainWindow.show_error_msg( "Please choose where you want to save the results." )
+ self.le_save_db_fname.setFocus()
+ return
+ # run the analysis (in a worker thread)
+ self.frm_open_db.hide()
+ self.frm_analyze_progress.show()
+ self._update_analyze_ui( False )
+ self.btn_cancel_analyze.setEnabled( True )
+ self.btn_cancel_analyze.clicked.connect( self.on_cancel_analyze )
+ self.analyze_thread = AnalyzeThread( cards_dir , fname )
+ self.analyze_thread.progress_signal.connect( self.on_analyze_progress )
+ self.analyze_thread.progress2_signal.connect( self.on_analyze_progress2 )
+ self.analyze_thread.completed_signal.connect( self.on_analyze_completed )
+ self.analyze_thread.start()
+
+ def on_analyze_progress( self , pval , msg ) :
+ """Update the analysis progress in the UI."""
+ if pval >= 0 :
+ self.pb_files.setValue( int( 100*pval + 0.5 ) )
+ self.pb_files.setFormat( msg )
+ self.pb_pages.setValue( 0 )
+ def on_analyze_progress2( self , pval ) :
+ """Update the analysis progress in the UI."""
+ self.pb_pages.setValue( int( 100*pval + 0.5 ) )
+
+ def on_cancel_analyze( self ) :
+ """Cancel the analyze worker thread."""
+ if not self.analyze_thread or self.analyze_thread.parser.cancelling :
+ return
+ rc = MainWindow.ask( "Cancel the analysis?" , QMessageBox.Ok|QMessageBox.Cancel , QMessageBox.Cancel )
+ if rc != QMessageBox.Ok :
+ return
+ self.analyze_thread.parser.cancelling = True
+ self.pb_files.setFormat( "Cancelling, please wait..." )
+ self.btn_cancel_analyze.setEnabled( False )
+
+ def on_analyze_completed( self , ex ) :
+ # clean up
+ self.analyze_thread = None
+ # check if the analysis failed
+ if ex :
+ MainWindow.show_error_msg( "Analyze failed:\n\n{}".format( ex ) )
+ self._update_analyze_ui( True )
+ self.frm_analyze_progress.hide()
+ self.le_cards_dir.setFocus()
+ return
+ # the analysis completed successully - start the main app
+ self.pb_files.setValue( 100 )
+ self.pb_pages.setValue( 100 )
+ MainWindow.show_info_msg( "The \"ASL Cards\" files were analyzed successully." )
+ self.parent().start_main_app( self.le_save_db_fname.text().strip() )
+
+ def _update_analyze_ui( self , enable ) :
+ # update the UI
+ widgets = [ self.lbl_cards_dir , self.le_cards_dir, self.btn_cards_dir ]
+ widgets.extend( [ self.lbl_save_db_fname , self.le_save_db_fname , self.btn_save_db_fname ] )
+ widgets.append( self.btn_analyze )
+ for w in widgets :
+ w.setEnabled( enable )
+
+ def on_btn_load_db_fname( self ) :
+ """Let the user browse to a database to load."""
+ fname = self.le_load_db_fname.text().strip()
+ fname = QFileDialog.getOpenFileName(
+ self , "Load database" ,
+ fname , "Database files (*.db)"
+ )[ 0 ]
+ if not fname :
+ return
+ self.le_load_db_fname.setText( fname )
+ self.btn_load_db.setFocus()
+
+ def on_btn_load_db( self ) :
+ """Load the database and start the main application."""
+ fname = self.le_load_db_fname.text().strip()
+ if not fname :
+ MainWindow.show_error_msg( "Please choose a database to load." )
+ self.le_load_db_fname.setFocus()
+ return
+ if not os.path.isfile( fname ) :
+ MainWindow.show_error_msg( "Can't find this database file." )
+ self.le_load_db_fname.setFocus()
+ return
+ # notify the main window it can start the main app
+ self.parent().start_main_app( fname )
diff --git a/ui/add_card_dialog.ui b/ui/add_card_dialog.ui
index 7003d56..a54ffcd 100644
--- a/ui/add_card_dialog.ui
+++ b/ui/add_card_dialog.ui
@@ -114,19 +114,19 @@
-
-
+
- &OK
-
-
- true
+ Cancel
-
-
+
- Cancel
+ &OK
+
+
+ true
diff --git a/ui/startup_widget.ui b/ui/startup_widget.ui
new file mode 100644
index 0000000..86f3e72
--- /dev/null
+++ b/ui/startup_widget.ui
@@ -0,0 +1,513 @@
+
+
+ StartupWidget
+
+
+
+ 0
+ 0
+ 566
+ 399
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ false
+
+
+ QFrame::Box
+
+
+
+
+
+
+ -
+
+
+
+ 2
+
+
+ 8
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ If this is the first time you have run this program, you need to analyze the PDF files first, and save the results in a database.
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 8
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Where the "ASL Cards" &files are:
+
+
+ le_cards_dir
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 30
+ 30
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ &Save the results here:
+
+
+ le_save_db_fname
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 30
+ 30
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ &Analyze
+
+
+
+ -
+
+
+ true
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 24
+
+
+
+ -
+
+
+
+ 0
+ 15
+
+
+
+
+ 16777215
+ 15
+
+
+
+ 24
+
+
+ false
+
+
+
+ -
+
+
+ &Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ true
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 50
+ 50
+
+
+
+
+ 50
+ 50
+
+
+
+ QFrame::Box
+
+
+
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ If you have already analyzed the PDF files, open the &database:
+
+
+ le_load_db_fname
+
+
+
+ -
+
+
+
+ 2
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 30
+ 30
+
+
+
+
+ 30
+ 30
+
+
+
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 8
+ true
+
+
+
+ Put the database in the same directory as this program, and it will be loaded automatically, or add a "--db ..." parameter to the command-line arguments.
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ &Open
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ le_cards_dir
+ btn_cards_dir
+ le_save_db_fname
+ btn_save_db_fname
+ btn_analyze
+ btn_cancel_analyze
+ le_load_db_fname
+ btn_load_db_fname
+
+
+
+