CSV-Editor.lsp

(以下のスクリプトには、guisever 1.42 以上が必要です)

; CSV-Editor with gs:table
; 2010/10/ 8 first release.
; 2010/10/ 9 using flag of *tabList*.
; 2010/10/10 adding macro get-XXX for the current table information.
; 2010/10/12 adding adjustButton for adjusting the current table column and remove null-end-cell.
; 2010/10/13 adding File Menu.
; 2010/10/14 adding Edit Menu and functions of Edit.
; 2010/10/15 adding View Menu and functions for HTML.
; 2010/10/16 adding View Menu and functions for List.

; utility
(define *newlispDir* (env "NEWLISPDIR"))
(load (append *newlispDir* "/guiserver.lsp"))
(include "macro.lsp")
(include "newlisp-utility.lsp")

; define global variables
(define *tabList* '()) ; ((table filename (row column) flag)(...))
(define *currentCSV*)
(macro (get-tableID) (lookup *currentCSV* *tabList* 0))
(macro (get-filename) (lookup *currentCSV* *tabList* 1))
(macro (get-row/col) (lookup *currentCSV* *tabList* -2))
(macro (get-flag) (lookup *currentCSV* *tabList* -1))
(macro (get-index) ((ref *currentCSV* *tabList*) 0))
(define *currentRow* -1)
(define *currentCol* -1)
(define *rowBuffer*)
(define *colBuffer*)
(define *cellBuffer*)
(define *tabName*)
(define *Numerics* true)
(define *FontName* "Monospaced")
(define *FontSize* 8 )
(define *fileDir* (real-path))
(define *filemask* "csv CSV")
(define *description* "csv file")
(define *newTitle* "Untitled")
(define *newCount* -1)

; define the function for CSV (Comma-Separated Values)
(define CRLF "\x0d\x0a")
(define regexCRLF "\r*\n")
(defun stringEx (x)
  (if (string? x) (string "\"" x "\"") (string x)))
(defun string-convert (str)
  (if (catch (eval-string str) 'res) (if res res str) str))
(defun list2csv (lst)
  (let (csv "")
    (dolist (row lst)
      (extend csv (join (map stringEx row) ",") CRLF))))
(defun list2csv|number (lst)
  (let (csv "")
    (dolist (row lst)
      (extend csv (join (map (fn (x) (stringEx (string-convert x))) row) ",") CRLF))))
(defun csv2list (csv)
  (let (lst '())
    (dolist (row (parse (if (ends-with csv regexCRLF 0) (chop csv) csv) regexCRLF 0))
      (push (map string-convert (parse row ",")) lst -1))
    (setf (lst -1 -1) (if (string? $it) (trim $it "\r") $it))
    lst))
(defun csvfile2list (file)
  (cond ((directory? file) nil)
         ((file? file)
          (csv2list (read-file file)))
         (true nil)))
(defun delete-end-null-row (lsts)  
  (while (and lsts (for-all empty? (lsts -1)))
    (pop lsts -1))
  lsts)
(defun delete-end-null-all (lsts)
  (aif (delete-end-null-row (transpose lsts))
       (delete-end-null-row (transpose it))
    '(())))
(defun insert-row (lst pos (item '()))
  (let (len (length (lst 0)))
    (push (apply append (dup (list item) len)) lst pos)))
(defun delete-row (lst (pos -1))
  (if lst (pop lst pos))
  lst)

; define the function for gs:table
(defun clear-table (table)
  (gs:table-get-size table)
  (dotimes (i (gs:table-size 0))
    (dotimes (j (gs:table-size 1))
      (gs:table-set-cell table i j ""))))
(defun make-table (file-name)
  (let (csv (gensym))
    (push (list csv file-name '(-1 -1) true) *tabList* -1)
    (gs:table csv 'table-action (map string (sequence 1 5)))
    (time (gs:table-add-row csv) 5)
		;(gs:mouse-event edit-tab 'editarea-mouse-handler)
	csv))
(defun set-table (lst table)
  (let (len (length (lst 0)))
    (gs:table-get-size table)
    (when (< (gs:table-size 1) len)
      (for (i (i+ (gs:table-size 1)) len) (gs:table-add-column table "")))
      (dolist (row lst)
        (let (r $idx)
          (if (< r (gs:table-size 0))
              (dolist (c row) (gs:table-set-cell table r $idx (string c)))
            (gs:table-add-row table (map string row))))))
  (unless (for-all (curry = "") (flat lst)) (adjust-column table)))
(defun adjust-column (table)
  (gs:table-get-size table)
  (gs:table-get table)
  (dotimes (i (gs:table-size 1))
    (let (lst (transpose gs:table-full))
      (gs:table-set-column table i (* 100 (apply max (map length (lst i))))))))

; define the function for HTML
(define *header* [text]<!-- generated page -->
<html>
<table border="1">
[/text])
(define tableROW '("<tr>" "</tr>"))
(define tableDATA '("<td align=\"right\">" "</td>"))
(define tableHEADER '("<th>" "</th>"))
(define *footer* [text]</table>
</html>
[/text])
(defun addHTMLtag (data tag)
  (string (tag 0) data (tag 1)))
(defun makeHTMLtable (lst)
  (let (html *header*)
    (dolist (row lst)
      (if *Numerics* (setq row (map string-convert row)))
      (extend html (tableROW 0))
      (dolist (col row)
        (extend html (addHTMLtag col (if (string? col) tableHEADER tableDATA))))
      (extend html (tableROW 1) "\n"))
    (extend html *footer*)))

; define handler
(define (HTML-save id op file)
  (when file
    (let (fname (base64-dec file))
      (write-file fname (gs:get-text 'OutputArea)))))
(define (HTMLfile-action)
  (let (tmp (if-not (directory? *tabName*) (parse *tabName* {\\|/} 0)))
    (gs:save-file-dialog 'Frame 'HTML-save (join (chop tmp) "/")
                                           (string ((parse (tmp -1) ".") 0) ".html")
                                           "html HTML" "HTML file")))
(define (clip-action)
  (gs:select-text 'OutputArea 0)
  (gs:copy-text 'OutputArea))
(define (dialog-mouse-handler id type x y button cnt mods)
 (if (or (= button 3) (= mods 18))
  (gs:show-popup 'viewMenuPopup 'OutputArea x y)
 ))
(define (dialog-action id)
  (gs:dialog 'Dialog 'Frame "HTML or List" FWidth FHeight nil true)
  (gs:set-border-layout 'Dialog)
  (gs:table-get *currentCSV*)
  (cond ((ends-with id "HTML")
         (gs:disable 'toolCopy)
         (gs:enable 'toolHTMLFile)
         (gs:text-pane 'OutputArea 'gs:no-action "text/html")
         (gs:set-text 'OutputArea (makeHTMLtable gs:table-full)))
        ((ends-with id "List")
         (gs:disable 'toolHTMLFile)
         (gs:enable 'toolCopy)
         (gs:text-pane 'OutputArea 'gs:no-action "text/plain")
         (dolist (row gs:table-full)
           (if (zero? $idx) (gs:set-text 'OutputArea "(")
             (gs:append-text 'OutputArea " "))
           (gs:append-text 'OutputArea (string (if *Numerics* (map string-convert row) row) "\n")))
         (gs:append-text 'OutputArea ")"))
        (true ))
  (gs:add-to 'Dialog 'OutputArea "center")
  (gs:mouse-event 'OutputArea 'dialog-mouse-handler)
  (gs:set-visible 'Dialog true))
(define (openfile-action id op file)
  (when file
    (let (fname (if (= op "new") file (base64-dec file)))
      (gs:set-text 'Status fname)
      (let (lst (if (= op "new") '(()) (csvfile2list fname)))
        (if lst
            (begin  
            (let (table (aif (ref fname *tabList*) (*tabList* (it 0) 0) (make-table fname)))
              (gs:insert-tab 'CSVtab table (lookup table *tabList* 1) (length *tabList*))
              (gs:request-focus 'CSVtab (length *tabList*))
              (set-table lst table)))
          (gs:set-text 'Status (string fname " is not csv file.")))))))
(define (open-file-dialog)
  (if *currentCSV* (setf (get-row/col) (list *currentRow* *currentCol*)))
  (gs:open-file-dialog 'Frame 'openfile-action *fileDir* *filemask* *description*))
(define (savefile-action id op file)
  (when (or (and (= op "save") file) (not op))
    (gs:table-get *currentCSV*)
    (let (fname (if file (base64-dec file) (get-filename))
          lsts (delete-end-null-all gs:table-full))
      (gs:set-text 'Status fname)
      (write-file fname (if *Numerics*
                            (list2csv|number lsts)
                          (list2csv lsts)))
      (when (= op "save")
        (setf (get-filename) fname)
        (gs:set-text 'CSVtab fname (get-index)))
      (setf (get-flag) true))))
(define (save-file-dialog)
  (let (tmp (if-not (directory? *tabName*) (parse *tabName* {\\|/} 0)))
    (if tmp 
        (gs:save-file-dialog 'Frame 'savefile-action (join (chop tmp) "/") (tmp -1) *filemask* *description*)
      (gs:save-file-dialog 'Frame 'savefile-action *fileDir* "" *filemask* *description*))))
(define (save-action)
  (unless (get-flag)
    (if (starts-with (get-filename) "Untitled") (save-file-dialog) (savefile-action))))
(define (clear-action)
  (setf (get-flag) nil)
  (clear-table *currentCSV*))
(define (table-action id row col data)
  ;(println "id=" id " row=" row " col=" col " data=" data)
  (setf (get-flag) nil)
  (setq *currentRow* row *currentCol* col)
  (gs:set-text 'tableInfo (string "row:"row " column:" col " value=" data))
)
(define (tab-changed id tabID title tabPos)
  ;(println "id=" id " tabID=" tabID " title=" (base64-dec title) " tabPos=" tabPos)
  (let (fname (base64-dec title))
    (if *currentCSV* (setf (get-row/col) (list *currentRow* *currentCol*)))
    (aif (ref fname *tabList*) (setq *currentCSV* (*tabList* (it 0) 0)))
    (setq *currentRow* ((get-row/col) 0)
          *currentCol* ((get-row/col) 1))
    (gs:set-text 'Status fname)
    (setq gs:table-cell "")
    (if (and (> *currentRow* -1) (> *currentCol* -1)) (gs:table-get-cell *currentCSV* *currentRow* *currentCol*))
    (gs:set-text 'tableInfo (string "row:"*currentRow* " column:" *currentCol* " value=" gs:table-cell))
    (setq *tabName* fname)))
(define (check-action id selected)
  (setf (get-flag) nil)
  ;(println "id:" id " checked:" selected)
  (setq *Numerics* selected)
)
(define (new-action)
  (++ *newCount*)
  (let (fname (if (zero? *newCount*)
                  (string *newTitle*  ".csv")
                (string *newTitle* " (" *newCount*  ").csv")))
    (openfile-action "id" "new" fname)))
(define (key-action id type code modifiers)
  ;(println "id:" id " type:" type " key code:" code " modifiers:" modifiers)
 )
(define (insert-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (if (ends-with id "Row")
      (set-table (insert-row gs:table-full *currentRow* "") *currentCSV*)
      (set-table (transpose (insert-row (transpose gs:table-full) *currentCol* "")) *currentCSV*)))
(define (delete-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (clear-table *currentCSV*)
  (unless (apply empty? gs:table-full)
    (if (ends-with id "Row")
        (set-table (delete-row gs:table-full *currentRow*) *currentCSV*)
      (set-table (transpose (delete-row (transpose gs:table-full) *currentCol*)) *currentCSV*))))
(macro (cut-row L P) (pop L P))
(define (cut-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (clear-table *currentCSV*)
  (unless (apply empty? gs:table-full) 
    (cond ((and (ends-with id "Row") (> *currentRow* -1))
           (setq *rowBuffer* (cut-row gs:table-full *currentRow*))
           (gs:enable 'editPasteRow)
           (set-table gs:table-full *currentCSV*))
          ((and (ends-with id "Col") (> *currentCol* -1))
           (let (tmp (transpose gs:table-full))
             (setq *colBuffer* (cut-row tmp *currentCol*))
             (gs:enable 'editPasteCol)
             (set-table (transpose tmp) *currentCSV*)))
          (true ))))
(define (copy-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (unless (apply empty? gs:table-full) 
    (cond ((and (ends-with id "Row") (> *currentRow* -1))
           (gs:enable 'editPasteRow)
           (setq *rowBuffer* (gs:table-full *currentRow*)))
          ((and (ends-with id "Col") (> *currentCol* -1))
           (gs:enable 'editPasteCol)
           (setq *colBuffer* ((transpose gs:table-full) *currentCol*)))
          (true ))))
(define (paste-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (clear-table *currentCSV*)
  (cond ((and (ends-with id "Row") (> *currentRow* -1))
         (setf (gs:table-full *currentRow*) *rowBuffer*)
         (set-table gs:table-full *currentCSV*))
        ((and (ends-with id "Col") (> *currentCol* -1))
         (gs:table-get-size *currentCSV*)
         (letn (len (- (length *colBuffer*) (gs:table-size 0))
                tmp (if (< len 1) gs:table-full
                      (append gs:table-full (dup (apply append (dup '("") (gs:table-size 1))) len))))
           (setq tmp (transpose tmp))
           (setf (tmp *currentCol*) (if (< len 1) 
                                        (append *colBuffer* (apply append (dup '("") (- len))))
                                      *colBuffer*))
           (set-table (transpose tmp) *currentCSV*)))
        (true )))
(define (edit-action id)
  (setf (get-flag) nil)
  (gs:table-get *currentCSV*)
  (cond ((ends-with id "Cut")
         (gs:enable 'editPaste)
         (setq *cellBuffer* "")
         (swap (gs:table-full *currentRow* *currentCol*) *cellBuffer*)
         (set-table gs:table-full *currentCSV*))
        ((ends-with id "Copy")
         (gs:enable 'editPaste)
         (setq *cellBuffer* (gs:table-full *currentRow* *currentCol*)))
        ((ends-with id "Paste")
         (setf (gs:table-full *currentRow* *currentCol*) *cellBuffer*)
         (set-table gs:table-full *currentCSV*))
        (true ))
  (set-table gs:table-full *currentCSV*))
(define (remove-csv id res)
  (case res
    ;(0 ) ; yes
    (1 ) ; no
    (2 ) ; cancel
    (true (let (tab (get-index)
                table *currentCSV*)
            (setq *currentCSV* nil)
            (gs:remove-tab 'CSVtab tab)
            (pop-assoc table *tabList*)))
  ))
(define (close-action id)
  (if (< 1 (length *tabList*)) 
      (if (get-flag)
          (remove-csv)
        (gs:confirm-dialog 'Frame
                           'remove-csv
                           "close CSV file"
                           (string "Abandon unsaved " (get-filename))
                           "yes-no"))
    (closed-window)))
(define (quit-action id res)
	(if (= res 0) (exit)))
(define (closed-window)
  (if (dolist (lst *tabList* (nil? (lst -1))) nil)
      (gs:confirm-dialog 'Frame
                         'quit-action
                         "Quit CSV Editor"
                         "Quit and lose unsaved content ?" 
                         "yes-no")
    (exit)))
(define (adjust-action)
  (unless (get-flag)
    (gs:table-get *currentCSV*)
    (letn (idx (get-index)
           fname (get-filename)
           table (make-table fname))
      (pop *tabList* -1)
      (setf (get-tableID) table)
      (gs:insert-tab 'CSVtab table fname idx)
      (set-table (delete-end-null-all gs:table-full) table)
      (gs:remove-tab 'CSVtab (i+ idx))
      (gs:request-focus 'CSVtab idx)
    )))
(define (resize-action id width height)
  (setq FWidth width FHeight height))
; initialization
(gs:init)
(gs:get-version)
(setq FPosX 50 FPosY 50 FWidth 640 FHeight 480)
(gs:frame 'Frame FPosX FPosY FWidth FHeight "CSV Editor with gs:table")
(gs:tool-bar 'ButtonPanel)
;(gs:set-flow-layout 'ButtonPanel "left")
(gs:panel 'StatusPanel)
(gs:label 'Status (real-path))
(gs:label 'tableInfo "row:-1 column:-1 value=")
(gs:check-box 'Number 'check-action "Numerics prior" *Numerics*)
(gs:set-border-layout 'StatusPanel)
(gs:add-to 'StatusPanel 'Status "west" 'tableInfo "center" 'Number "east")
(gs:image-button 'newButton 'new-action "/local/new32.png" "/local/new-down32.png")
(gs:image-button 'fileButton 'open-file-dialog "/local/folder-opened32.png" "/local/folder-opened-down32.png")
(gs:image-button 'saveButton 'save-action "/local/save32.png" "/local/save-down32.png")
(gs:image-button 'adjustButton 'adjust-action "/local/restart32.png" "/local/restart-down32.png")
(gs:image-button 'clearButton 'clear-action "/local/clear32.png" "/local/clear-down32.png")
(gs:image-button 'closeButton 'close-action "/local/dotred32.png")
(gs:label 'LabelRow " Row ")
(gs:label 'LabelCol " Column ")
(gs:button 'insButtonRow 'insert-action "insert")
(gs:button 'delButtonRow 'delete-action "delete")
(gs:button 'insButtonCol 'insert-action "insert")
(gs:button 'delButtonCol 'delete-action "delete")
;(gs:image-button 'reLoadButton 'clear-action "/local/restart32.png" "/local/restart32-down32.png")
(gs:set-tool-tip 'newButton "new table")
(gs:set-tool-tip 'fileButton "open tsble")
(gs:set-tool-tip 'saveButton "save csv")
(gs:set-tool-tip 'adjustButton "adjust current table")
(gs:set-tool-tip 'clearButton "clear current table")
(gs:set-tool-tip 'closeButton "close current table")
(gs:add-to 'ButtonPanel 'newButton 'fileButton 'saveButton 'clearButton)
(gs:add-separator 'ButtonPanel)
(gs:add-to 'ButtonPanel 'clearButton)
(gs:add-to 'ButtonPanel 'closeButton)
(gs:add-separator 'ButtonPanel)
(gs:add-to 'ButtonPanel 'LabelRow 'insButtonRow 'delButtonRow)
(gs:add-separator 'ButtonPanel)
(gs:add-to 'ButtonPanel 'LabelCol 'insButtonCol 'delButtonCol)
(gs:add-to 'ButtonPanel 'adjustButton)
(gs:add-separator 'ButtonPanel)
(gs:menu 'fileMenu "File")
(gs:menu-item 'fileNew 'new-action "New table")
(gs:menu-item 'fileClose 'close-action "Close table")
(gs:menu-item 'fileOpen 'open-file-dialog "Open ...")
(gs:menu-item 'fileSave 'save-action "Save")
(gs:menu-item 'fileSaveAs 'save-file-dialog "Save As ...")
(gs:menu-item 'fileExit 'closed-window "Exit")
(gs:add-to 'fileMenu 'fileNew 'fileClose )
(gs:add-separator 'fileMenu)
(gs:add-to 'fileMenu 'fileOpen 'fileSave 'fileSaveAs)
(gs:add-separator 'fileMenu)
(gs:add-to 'fileMenu 'fileExit)
(gs:set-accelerator 'fileNew "shift ctrl N")
(gs:set-accelerator 'fileClose "ctrl W")
(gs:set-accelerator 'fileOpen "ctrl O")
(gs:set-accelerator 'fileSave "ctrl S")
(gs:set-accelerator 'fileSaveAs "shift ctrl S")
(gs:menu 'editMenu "Edit")
(gs:menu-item 'editCut 'edit-action "Cell Cut")
(gs:menu-item 'editCopy 'edit-action "Cell Copy")
(gs:menu-item 'editPaste 'edit-action "Cell Paste")
(gs:menu-item 'editCutRow 'cut-action "Row Cut")
(gs:menu-item 'editCopyRow 'copy-action "Row Copy")
(gs:menu-item 'editPasteRow 'paste-action "Row Paste")
(gs:menu-item 'editCutCol 'cut-action "Column Cut")
(gs:menu-item 'editCopyCol 'copy-action "Column Copy")
(gs:menu-item 'editPasteCol 'paste-action "Column Paste")
(gs:disable 'editPaste 'editPasteRow 'editPasteCol)
;(gs:set-accelerator 'editCut "ctrl X")
;(gs:set-accelerator 'editCopy "ctrl C")
;(gs:set-accelerator 'editPaste "ctrl V")
(gs:add-to 'editMenu 'editCut 'editCopy 'editPaste)
(gs:add-separator 'editMenu)
(gs:add-to 'editMenu 'editCutRow 'editCopyRow 'editPasteRow)
(gs:add-separator 'editMenu)
(gs:add-to 'editMenu 'editCutCol 'editCopyCol 'editPasteCol)
(gs:menu 'viewMenu "View")
(gs:menu-item 'viewAdjust 'adjust-action "adjust current table")
(gs:menu-item 'viewHTML 'dialog-action "to html-table")
(gs:menu-item 'viewList 'dialog-action "to lisp-list text")
(gs:add-to 'viewMenu 'viewAdjust 'viewHTML 'viewList)
(gs:menu-bar 'Frame 'fileMenu 'editMenu 'viewMenu)
(gs:menu-popup 'viewMenuPopup "View")
(gs:menu-item 'toolCopy 'clip-action "copy")
(gs:menu-item 'toolHTMLFile 'HTMLfile-action "to .html file")
(gs:add-to 'viewMenuPopup 'toolCopy 'toolHTMLFile)
(gs:tabbed-pane 'CSVtab 'tab-changed "top")
(new-action)
; mount all on frame
(gs:set-border-layout 'Frame)
(gs:add-to 'Frame 'ButtonPanel "north" 'CSVtab "center" 'StatusPanel "south")
(gs:window-resized 'Frame 'resize-action)
(gs:window-closed 'Frame 'closed-window)
(gs:set-visible 'Frame true)
; main routine
(gs:listen)
(exit)

No comments yet

コメントを残す