Added a startup form that lets the user initialize/load a database.

master
Pacman Ghost 7 years ago
parent 2a8af8ed8b
commit bd26c9a38a
  1. 23
      _freeze.py
  2. 0
      asl_cards/__init__.py
  3. 10
      asl_cards/__main__.py
  4. 10
      asl_cards/db.py
  5. 31
      asl_cards/parse.py
  6. 5
      asl_cards/tests/test_real_data.py
  7. 0
      constants.py
  8. 26
      main.py
  9. 76
      main_window.py
  10. BIN
      resources/analyze.png
  11. BIN
      resources/dir_dialog.png
  12. BIN
      resources/file_dialog.png
  13. BIN
      resources/load_db.png
  14. BIN
      resources/open_file.png
  15. BIN
      resources/stop.png
  16. 232
      startup_widget.py
  17. 14
      ui/add_card_dialog.ui
  18. 513
      ui/startup_widget.ui

@ -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 ]
)

@ -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." )

@ -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 )

@ -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 )
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

@ -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
# ---------------------------------------------------------------------

@ -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()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -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 )

@ -114,19 +114,19 @@
</spacer>
</item>
<item>
<widget class="QPushButton" name="ok_button">
<widget class="QPushButton" name="cancel_button">
<property name="text">
<string>&amp;OK</string>
</property>
<property name="default">
<bool>true</bool>
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancel_button">
<widget class="QPushButton" name="ok_button">
<property name="text">
<string>Cancel</string>
<string>&amp;OK</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>

@ -0,0 +1,513 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StartupWidget</class>
<widget class="QWidget" name="StartupWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>566</width>
<height>399</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QFrame" name="frm_analyze">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="lbl_analyze_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>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.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_5" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="lbl_cards_dir">
<property name="text">
<string>Where the &quot;ASL Cards&quot; &amp;files are:</string>
</property>
<property name="buddy">
<cstring>le_cards_dir</cstring>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_6" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="le_cards_dir"/>
</item>
<item>
<widget class="QPushButton" name="btn_cards_dir">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="lbl_save_db_fname">
<property name="text">
<string>&amp;Save the results here:</string>
</property>
<property name="buddy">
<cstring>le_save_db_fname</cstring>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="le_save_db_fname"/>
</item>
<item>
<widget class="QPushButton" name="btn_save_db_fname">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="btn_analyze">
<property name="text">
<string>&amp;Analyze</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frm_analyze_progress">
<property name="enabled">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QProgressBar" name="pb_files">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="pb_pages">
<property name="minimumSize">
<size>
<width>0</width>
<height>15</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>15</height>
</size>
</property>
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="btn_cancel_analyze">
<property name="text">
<string>&amp;Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frm_open_db">
<property name="enabled">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="lbl_load_db_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>50</horstretch>
<verstretch>50</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_7" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>If you have already analyzed the PDF files, open the &amp;database:</string>
</property>
<property name="buddy">
<cstring>le_load_db_fname</cstring>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_9" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="le_load_db_fname"/>
</item>
<item>
<widget class="QPushButton" name="btn_load_db_fname">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>8</pointsize>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Put the database in the same directory as this program, and it will be loaded automatically, or add a &quot;--db ...&quot; parameter to the command-line arguments.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_load_db">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Open</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<tabstops>
<tabstop>le_cards_dir</tabstop>
<tabstop>btn_cards_dir</tabstop>
<tabstop>le_save_db_fname</tabstop>
<tabstop>btn_save_db_fname</tabstop>
<tabstop>btn_analyze</tabstop>
<tabstop>btn_cancel_analyze</tabstop>
<tabstop>le_load_db_fname</tabstop>
<tabstop>btn_load_db_fname</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
Loading…
Cancel
Save