rexx address book
This commit is contained in:
commit
1571221293
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
docs/
|
||||
*.iml
|
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Rexx Address Book
|
||||
|
||||
```oorexx
|
||||
/***************************************************************************
|
||||
* Rexx Address Book *
|
||||
* *
|
||||
* A simple application for storing, maintaining, and browsing contact *
|
||||
* information. *
|
||||
* *
|
||||
* Backend: Object Rexx with unix system, and sqlite extensions. *
|
||||
* Frontend: Object Rexx with ncurses extensions. *
|
||||
**************************************************************************/
|
||||
```
|
||||
|
363
addbook.rex
Executable file
363
addbook.rex
Executable file
@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env rexx
|
||||
/***************************************************************************
|
||||
* Rexx Address Book *
|
||||
* *
|
||||
* A simple application for storing, maintaining, and browsing contact *
|
||||
* information. *
|
||||
* *
|
||||
* Backend: Object Rexx with unix system, and sqlite extensions. *
|
||||
* Frontend: Object Rexx with ncurses extensions. *
|
||||
**************************************************************************/
|
||||
signal on HALT name ProgramHalt
|
||||
.environment['STOPNOW'] = 0
|
||||
call setEnv
|
||||
|
||||
app = .AddressBookApp~new()
|
||||
app~run()
|
||||
|
||||
Do forever
|
||||
if .environment['STOPNOW'] = 1 then do
|
||||
app~cleanup() /* Clean up before exiting */
|
||||
address system 'clear'
|
||||
Say "Exiting Address Book."
|
||||
exit 0
|
||||
end
|
||||
app~reloop()
|
||||
end
|
||||
|
||||
Exit
|
||||
|
||||
::routine SysWait
|
||||
use arg seconds
|
||||
address system 'sleep' seconds
|
||||
return
|
||||
|
||||
|
||||
::ROUTINE setEnv
|
||||
.local~home = SysGetpwnam("gmgauthier", "d")
|
||||
.local~projectRoot = .home||"/Projects/rexx-address-book"
|
||||
.local~dbPath = .projectRoot||"/db/contacts.sqlite"
|
||||
|
||||
|
||||
::CLASS AddressBookApp PUBLIC
|
||||
|
||||
::METHOD Init
|
||||
expose ui db
|
||||
db = .AddressBookDB~new()
|
||||
ui = .AddressBookUI~new()
|
||||
return
|
||||
|
||||
::method run
|
||||
expose ui
|
||||
ui~initialize
|
||||
ui~mainLoop
|
||||
ui~cleanup
|
||||
return
|
||||
|
||||
::method reloop
|
||||
expose ui
|
||||
ui~mainLoop
|
||||
RETURN
|
||||
|
||||
::method cleanup
|
||||
expose db
|
||||
db~closeDb()
|
||||
return
|
||||
|
||||
|
||||
::CLASS AddressBookDB PUBLIC
|
||||
|
||||
::method init
|
||||
expose db
|
||||
|
||||
/* Create database directory if it doesn't exist */
|
||||
if SysFileExists(.dbPath) == .false then DO
|
||||
SAY "Initializing new address book"
|
||||
db = .ooSQLiteConnection~new(.dbPath)
|
||||
self~createTables
|
||||
END
|
||||
Else Do
|
||||
db = .ooSQLiteConnection~new(.dbPath,.ooSQLite~OPEN_READWRITE)
|
||||
End
|
||||
return
|
||||
|
||||
::METHOD getFileName
|
||||
expose db
|
||||
return db~fileName()
|
||||
|
||||
::METHOD closeDb
|
||||
expose db
|
||||
db~Close()
|
||||
RETURN
|
||||
|
||||
::method createTables
|
||||
expose db
|
||||
|
||||
/* Contacts table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS contacts ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"first_name TEXT,"||,
|
||||
"last_name TEXT,"||,
|
||||
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"||,
|
||||
"updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"||,
|
||||
")")
|
||||
|
||||
/* Phone numbers table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS phone_numbers ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, mobile, etc.
|
||||
"number TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Email addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS email_addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"email TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
|
||||
/* Physical addresses table */
|
||||
db~exec("CREATE TABLE IF NOT EXISTS addresses ("||,
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,"||,
|
||||
"contact_id INTEGER,"||,
|
||||
"type TEXT,"||, -- home, work, etc.
|
||||
"street TEXT,"||,
|
||||
"city TEXT,"||,
|
||||
"state TEXT,"||,
|
||||
"postal_code TEXT,"||,
|
||||
"country TEXT,"||,
|
||||
"FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||,
|
||||
")")
|
||||
return
|
||||
|
||||
/* UI handling class */
|
||||
::class AddressBookUI public
|
||||
|
||||
::method init
|
||||
expose win mainMenu
|
||||
return
|
||||
|
||||
::method initialize
|
||||
expose win mainMenu
|
||||
|
||||
win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
|
||||
self~setupMainMenu(win)
|
||||
return
|
||||
|
||||
::METHOD DrawMainPanel
|
||||
use arg clrset, with_box
|
||||
win = .window~new() /* The first invocation must be with no arguments */
|
||||
win~curs_set(0) /* Hide the cursor by default */
|
||||
win~keypad(1) /* Enable function keys and arrow keys */
|
||||
win~raw
|
||||
win~noecho
|
||||
win~clear
|
||||
win~cbreak
|
||||
|
||||
self~SetPanelColor(win, clrset)
|
||||
|
||||
if with_box = .true then DO
|
||||
win~box(0, 0) /* Draw box with default ACS characters */
|
||||
win~refresh
|
||||
END
|
||||
|
||||
return win
|
||||
|
||||
::METHOD DrawSubPanel
|
||||
use arg height, width, starty, startx, clrset, title, with_box
|
||||
new_win = .window~new(height, width, starty, startx)
|
||||
new_win~curs_set(0)
|
||||
|
||||
/* Set the panel color */
|
||||
self~SetPanelColor(new_win, clrset)
|
||||
|
||||
/* Display title */
|
||||
new_win~attron(new_win~A_BOLD)
|
||||
new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
|
||||
new_win~attroff(new_win~A_BOLD)
|
||||
new_win~refresh
|
||||
if with_box = .true then DO
|
||||
new_win~box(0,0)
|
||||
new_win~refresh
|
||||
END
|
||||
return new_win
|
||||
|
||||
::METHOD SetPanelColor
|
||||
use arg panel, clrset
|
||||
|
||||
panel~start_color()
|
||||
SELECT
|
||||
/* white text on blue background */
|
||||
when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
|
||||
/* white text on grey background */
|
||||
when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
|
||||
/* yellow text on red background */
|
||||
when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
|
||||
/* black text on white background */
|
||||
when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
|
||||
/* black text on green background */
|
||||
when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
|
||||
/* black text on cyan background */
|
||||
when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
|
||||
/* Default to white text on black background */
|
||||
OTHERWISE DO
|
||||
clrset=0
|
||||
panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
|
||||
END
|
||||
END
|
||||
panel~bkgd(panel~color_pair(clrset+1))
|
||||
panel~refresh
|
||||
|
||||
::method setupMainMenu
|
||||
expose mainMenu win menuwin menuItems menu_keys
|
||||
use arg win
|
||||
|
||||
max_y = win~lines
|
||||
max_x = win~cols
|
||||
|
||||
menu_height = 21
|
||||
menu_width = 40
|
||||
start_y = (max_y - menu_height) % 2
|
||||
start_x = (max_x - menu_width) % 2
|
||||
clrset=1
|
||||
title = "Rexx Address Book"
|
||||
with_box = .true
|
||||
|
||||
menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
|
||||
menuItems = .array~of("[A]dd Contact", "[R]emove Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "E[X]it")
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
/* Display menu items */
|
||||
selected = 1
|
||||
self~DrawMenu(menuwin, menuItems, selected, win)
|
||||
/* menuwin~mvaddstr(menu_height - 2, 3, "Type 'X' to exit") */
|
||||
menuwin~refresh
|
||||
RETURN
|
||||
|
||||
::method DrawMenu
|
||||
expose win menuItems selected mainwin
|
||||
use arg win, items, selected, mainwin
|
||||
|
||||
do i = 1 to items~items
|
||||
if i = selected then do
|
||||
win~attron(mainwin~COLOR_PAIR(2))
|
||||
win~attron(mainwin~A_BOLD)
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
win~attroff(mainwin~COLOR_PAIR(2))
|
||||
win~attroff(mainwin~A_BOLD)
|
||||
end
|
||||
else do
|
||||
win~mvaddstr(i+3, 2, items[i])
|
||||
end
|
||||
end
|
||||
win~refresh
|
||||
return
|
||||
|
||||
/** MAIN LOOP **/
|
||||
::method mainLoop
|
||||
expose win mainMenu menuwin
|
||||
|
||||
running = .true
|
||||
|
||||
do while running
|
||||
menuwin~refresh
|
||||
key = win~getch
|
||||
|
||||
menu_keys = .array~of("a", "r", "e", "s", "l", "x")
|
||||
|
||||
select
|
||||
when key = win~KEY_UP then do
|
||||
if selected > 1 then selected = selected - 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = win~KEY_DOWN then do
|
||||
if selected < menuItems~items then selected = selected + 1
|
||||
call DrawMenu menuwin, menuItems, selected, win
|
||||
end
|
||||
when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
|
||||
menuwin~refresh
|
||||
call ProcessSelection menu_keys[selected], home
|
||||
return
|
||||
end
|
||||
when key = D2C(88) | key = D2C(120) | key = "x" | key = C2D("x") then do
|
||||
menuwin~endwin
|
||||
.environment['STOPNOW'] = 1
|
||||
RETURN
|
||||
end
|
||||
otherwise do
|
||||
if datatype(key) = 'CHAR' then do
|
||||
key = lower(key)
|
||||
pos = self~findInArray(menu_keys, key)
|
||||
if pos > 0 then do
|
||||
menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
|
||||
menuwin~refresh
|
||||
call SysWait 0.5
|
||||
self~ProcessSelection(menuwin, key)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end /* do while running */
|
||||
return
|
||||
|
||||
/* Process selection */
|
||||
::METHOD ProcessSelection
|
||||
use arg menuwin, key_char
|
||||
select
|
||||
when key_char = 'a' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the ADD panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'r' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the REMOVE panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'e' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the EDIT panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 's' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the SEARCH panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
when key_char = 'l' then do
|
||||
menuwin~mvaddstr(19 - 3, 5, "I would launch the LIST panel ");menuwin~refresh;
|
||||
menuwin~refresh
|
||||
call SysWait 1
|
||||
END
|
||||
otherwise nop
|
||||
end
|
||||
return
|
||||
|
||||
::METHOD findInArray
|
||||
use arg array, item
|
||||
do i = 1 to array~items
|
||||
if array[i] = item then return i
|
||||
end
|
||||
return 0 /* Not found */
|
||||
|
||||
::method cleanup
|
||||
expose win
|
||||
/* Clean up ncurses */
|
||||
win~endwin
|
||||
return
|
||||
|
||||
/* Handle program termination */
|
||||
::ROUTINE ProgramHalt
|
||||
signal off halt
|
||||
Say "PROGRAM TERMINATED AT THE KEYBOARD"
|
||||
exit 0
|
||||
|
||||
/** External Libraries **/
|
||||
::requires 'ooSQLite.cls'
|
||||
::requires "rxunixsys" LIBRARY
|
||||
::requires 'ncurses.cls'
|
BIN
db/contacts.sqlite
Normal file
BIN
db/contacts.sqlite
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user