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 + + + +