Переглянути джерело

lots of cleanup in dirnotes-gui; eliminate cursor; only open the database once; use the QFileDialog as a dir-picker; implement fileCopy; add a dir-picker to dirnotes-tui; add a desktop icon and .destop file

Pat Beirne 2 роки тому
батько
коміт
b63ec7ebc5
6 змінених файлів з 163 додано та 133 видалено
  1. 4 1
      README.md
  2. 48 102
      dirnotes
  3. 1 0
      dirnotes-cli
  4. 59 30
      dirnotes-tui
  5. 9 0
      dirnotes.desktop
  6. 42 0
      dirnotes.xpm

+ 4 - 1
README.md

@@ -54,7 +54,7 @@ By default, the file **~/.dirnotes.conf** will be used to load the user's config
 This is a JSON file, with three attributes that are important:
 This is a JSON file, with three attributes that are important:
 
 
 > * xattr_tag (default: <code>usr.xdg.comment</code>)
 > * xattr_tag (default: <code>usr.xdg.comment</code>)
-> * database (default: <code>~/.dirnotes.db</code>) 
+> * database (default: <code>~/.dirnotes.db</code>, sensible alt: <code>/var/lib/dirnotes.db</code>) 
 > * start_mode (_xattr_ or _db_ priority)
 > * start_mode (_xattr_ or _db_ priority)
 
 
 The _config_file_ should be auto-generated the first time one of the **dirnotes** apps is run.
 The _config_file_ should be auto-generated the first time one of the **dirnotes** apps is run.
@@ -87,6 +87,8 @@ If you want to copy files to a remote machine and include the _xattr_ comments,
 
 
 Some editing apps (like _vim_) will create a new file when saving the data, which orphans the _xattr_ comments. For these apps, use the _database_ system.  
 Some editing apps (like _vim_) will create a new file when saving the data, which orphans the _xattr_ comments. For these apps, use the _database_ system.  
 
 
+Removable disk devices (usb sticks) which are formatted with a Linux-based filesystem (ext2/3/4, btrfs, xfs, zfs) will carry the _xattr_ comments embedded in the filesystem metadata, and are portable to anther computer.
+
 ## database
 ## database
 
 
 Comments stored in the database work for all filesystem types (including vfat/exfat/sshfs)
 Comments stored in the database work for all filesystem types (including vfat/exfat/sshfs)
@@ -100,6 +102,7 @@ mounted in a consistent way, so that the complete path name is reproducable.
 Comments stored in the database do not travel with the files when
 Comments stored in the database do not travel with the files when
 they are moved or copied, unless using the **dirnotes** family of tools. 
 they are moved or copied, unless using the **dirnotes** family of tools. 
 
 
+The _database_ comments that are stored in <code>~/.dirnotes.db</code> are inherently associated with a single user. If the _database_ is located in <code>/var/lib/dirnotes.db</code>, it is shared by all the users in the system. The comment with the 'most recent timestamp' wins.
 
 
 ## PROGRAMMER NOTES
 ## PROGRAMMER NOTES
 
 

+ 48 - 102
dirnotes

@@ -1,4 +1,9 @@
 #!/usr/bin/python3
 #!/usr/bin/python3
+# TODO: get rid of sqlite cursors; execute on connection
+# TODO: add index to table creation
+# TODO: why are there TWO sqlite.connect()??
+#   try auto-commit (isolation_level = "IMMEDIATE")
+#   try dict-parameters in sql statements
 # TODO: pick up comment for cwd and display at the top somewhere, or maybe status line
 # TODO: pick up comment for cwd and display at the top somewhere, or maybe status line
 """ a simple gui or command line app
 """ a simple gui or command line app
 to view and create/edit file comments
 to view and create/edit file comments
@@ -18,7 +23,7 @@ nav tools are enabled, so you can double-click to go into a dir
 
 
 """
 """
 
 
-VERSION = "0.4"
+VERSION = "0.5"
 
 
 helpMsg = f"""<table width=100%><tr><td><h1>Dirnotes</h1><td>
 helpMsg = f"""<table width=100%><tr><td><h1>Dirnotes</h1><td>
 <td align=right>Version: {VERSION}</td></tr></table>
 <td align=right>Version: {VERSION}</td></tr></table>
@@ -52,8 +57,7 @@ media (as long as the disk format allows it).
 the comment box gets a light yellow background.
 the comment box gets a light yellow background.
 """
 """
 
 
-import sys,os,argparse,stat,getpass
-#~ from dirWidget import DirWidget
+import sys,os,argparse,stat,getpass,shutil
 from PyQt5.QtGui import *
 from PyQt5.QtGui import *
 from PyQt5.QtWidgets import *
 from PyQt5.QtWidgets import *
 from PyQt5.QtCore import Qt, pyqtSignal
 from PyQt5.QtCore import Qt, pyqtSignal
@@ -108,30 +112,16 @@ class dnDataBase:
     except sqlite3.OperationalError:
     except sqlite3.OperationalError:
       print(f"Database {dbFile} not found")
       print(f"Database {dbFile} not found")
       raise
       raise
-    self.db_cursor = self.db.cursor()
     try:
     try:
-      self.db_cursor.execute("select * from dirnotes")
+      self.db.execute("select * from dirnotes")
     except sqlite3.OperationalError:
     except sqlite3.OperationalError:
       print_v("Table %s created" % ("dirnotes"))
       print_v("Table %s created" % ("dirnotes"))
-      self.db_cursor.execute("create table dirnotes (name TEXT, date DATETIME, size INTEGER, comment TEXT, comment_date DATETIME, author TEXT)")
-    
+      self.db.execute("create table dirnotes (name TEXT, date DATETIME, size INTEGER, comment TEXT, comment_date DATETIME, author TEXT)")
+      self.db_cursor.execute("create index dirnotes_i on dirnotes(name)") 
+  # getData is only used by the restore-from-database.......consider deleting it
   def getData(self, fileName):
   def getData(self, fileName):
-    self.db_cursor.execute("select * from dirnotes where name=? and comment<>'' order by comment_date desc",(os.path.abspath(fileName),))
-    return self.db_cursor.fetchone()
-  def setData(self, fileName, comment):
-    s = os.lstat(fileName)
-    print_v ("params: %s %s %d %s %s" % ((os.path.abspath(fileName), 
-        dnDataBase.epochToDb(s.st_mtime), s.st_size, comment, 
-        dnDataBase.epochToDb(time.time()))))
-    try:
-      self.db_cursor.execute("insert into dirnotes values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'),?)",
-        (os.path.abspath(fileName), s.st_mtime, s.st_size, str(comment), time.time(), getpass.getuser()))
-      self.db.commit()
-    except sqlite3.OperationalError:
-      print("database is locked or unwriteable")
-      errorBox("database is locked or unwriteable")
-      #TODO: put up a message box for locked database
-
+    c = self.db.execute("select * from dirnotes where name=? and comment<>'' order by comment_date desc",(os.path.abspath(fileName),))
+    return c.fetchone()
 
 
   @staticmethod
   @staticmethod
   def epochToDb(epoch):
   def epochToDb(epoch):
@@ -157,13 +147,14 @@ class FileObj():
   FILE_IS_LINK   = -2
   FILE_IS_LINK   = -2
   FILE_IS_SOCKET = -3
   FILE_IS_SOCKET = -3
   def __init__(self, fileName):
   def __init__(self, fileName):
-    self.fileName = os.path.abspath(fileName)
-    self.displayName = os.path.split(fileName)[1]
+    self.fileName = os.path.abspath(fileName) # full path; dirs end WITHOUT a terminal /
+    self.displayName = os.path.split(fileName)[1]   # base name; dirs end with a /
     s = os.lstat(self.fileName)
     s = os.lstat(self.fileName)
     self.date = s.st_mtime
     self.date = s.st_mtime
     if stat.S_ISDIR(s.st_mode):
     if stat.S_ISDIR(s.st_mode):
       self.size = FileObj.FILE_IS_DIR
       self.size = FileObj.FILE_IS_DIR
-      self.displayName += '/'
+      if not self.displayName.endswith('/'):
+        self.displayName += '/'
     elif stat.S_ISLNK(s.st_mode):
     elif stat.S_ISLNK(s.st_mode):
       self.size = FileObj.FILE_IS_LINK
       self.size = FileObj.FILE_IS_LINK
     elif stat.S_ISSOCK(s.st_mode):
     elif stat.S_ISSOCK(s.st_mode):
@@ -187,13 +178,13 @@ class FileObj():
 
 
   def getName(self):
   def getName(self):
     return self.fileName
     return self.fileName
-  def getFileName(self):
+  def getDisplayName(self):
     return self.displayName
     return self.displayName
 
 
   # with an already open database cursor
   # with an already open database cursor
-  def loadDbComment(self,cursor):
-    cursor.execute("select comment,author,comment_date from dirnotes where name=? and comment<>'' order by comment_date desc",(self.fileName,))
-    a = cursor.fetchone()
+  def loadDbComment(self,db):
+    c = db.execute("select comment,author,comment_date from dirnotes where name=? and comment<>'' order by comment_date desc",(self.fileName,))
+    a = c.fetchone()
     if a:
     if a:
       self.dbComment, self.dbAuthor, self.dbDate = a
       self.dbComment, self.dbAuthor, self.dbDate = a
       self.commentsDiffer = True if self.xattrComment == self.dbComment else False
       self.commentsDiffer = True if self.xattrComment == self.dbComment else False
@@ -204,19 +195,19 @@ class FileObj():
     return self.dbAuthor
     return self.dbAuthor
   def getDbDate(self):
   def getDbDate(self):
     return self.dbDate
     return self.dbDate
-  def setDbComment(self,newComment):
-    try:
-      self.db = sqlite3.connect(dbName)
-    except sqlite3.OperationalError:
-      print_v(f"database {dbName} not found")
-      raise
-    c = self.db.cursor()
+  def setDbComment(self,db,newComment):
     s = os.lstat(self.fileName)
     s = os.lstat(self.fileName)
     try:
     try:
-      c.execute("insert into dirnotes (name,date,size,comment,comment_date,author) values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'),?)",
-          (os.path.abspath(self.fileName), s.st_mtime, s.st_size,
+      # TODO: copy from /g/test_file to /home/patb/project/dirnotes/r    fails on database.commit()
+      print_v(f"setDbComment db {db}, file: {self.fileName}")
+      print_v("insert into dirnotes (name,date,size,comment,comment_date,author) values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'),?)",
+          (self.fileName, s.st_mtime, s.st_size,
           str(newComment), time.time(), getpass.getuser()))
           str(newComment), time.time(), getpass.getuser()))
-      self.db.commit()
+      db.execute("insert into dirnotes (name,date,size,comment,comment_date,author) values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'),?)",
+          (self.fileName, s.st_mtime, s.st_size,
+          str(newComment), time.time(), getpass.getuser()))
+      print_v(f"setDbComment, execute done, about to commit()")
+      db.commit()
       print_v(f"database write for {self.fileName}")
       print_v(f"database write for {self.fileName}")
       self.dbComment = newComment
       self.dbComment = newComment
     except sqlite3.OperationalError:
     except sqlite3.OperationalError:
@@ -341,47 +332,6 @@ icon = ["32 32 6 1",  # the QPixmap constructor allows for str[]
 "  ...........................   ",
 "  ...........................   ",
 "                                "]
 "                                "]
 
 
-''' a widget that shows only a dir listing
- '''
-
-class DirWidget(QListWidget):
-  ''' a simple widget that shows a list of directories, staring
-  at the directory passed into the constructor
-
-  a mouse click or 'enter' key will send a 'selected' signal to
-  anyone who connects to this.
-
-  the .. parent directory is shown for all dirs except /
-
-  the most interesting parts of the interface are:
-  constructor - send in the directory to view
-  method - currentPath() returns text of current path
-  signal - selected calls a slot with a single arg: new path
-  '''
-
-  selected = pyqtSignal(str)
-  def __init__(self, directory='.', parent=None):
-    super(DirWidget,self).__init__(parent)
-    self.directory = directory
-    self.refill()
-    self.itemActivated.connect(self.selectionByLWI)
-    # it would be nice to pick up single-mouse-click for selection as well
-    # but that seems to be a system-preferences global
-  def selectionByLWI(self, li):
-    self.directory = os.path.abspath(self.directory + '/' + str(li.text()))
-    self.refill()
-    self.selected.emit(self.directory)
-  def refill(self):
-    current,dirs,files = next(os.walk(self.directory,followlinks=True))
-    dirs.sort()
-    if '/' not in dirs:
-      dirs = ['..'] + dirs
-    self.clear()
-    for d in dirs:
-      li = QListWidgetItem(d,self)
-  def currentPath(self):
-    return self.directory
-    
 
 
 # sortable TableWidgetItem, based on idea by Aledsandar
 # sortable TableWidgetItem, based on idea by Aledsandar
 # http://stackoverflow.com/questions/12673598/python-numerical-sorting-in-qtablewidget
 # http://stackoverflow.com/questions/12673598/python-numerical-sorting-in-qtablewidget
@@ -457,7 +407,7 @@ class DirNotes(QMainWindow):
     
     
     if len(filename)>0:
     if len(filename)>0:
       for i in range(lb.rowCount()):
       for i in range(lb.rowCount()):
-        if filename == lb.item(i,0).data(32).getFileName():
+        if filename == lb.item(i,0).data(32).getDisplayName():
           lb.setCurrentCell(i,3)
           lb.setCurrentCell(i,3)
           break
           break
     
     
@@ -509,19 +459,16 @@ class DirNotes(QMainWindow):
     if not fo.isDir() and not fo.isLink():  # TODO: add check for socket 
     if not fo.isDir() and not fo.isLink():  # TODO: add check for socket 
       print_v(f"copy file {fo.getName()}")
       print_v(f"copy file {fo.getName()}")
       # open the dir.picker
       # open the dir.picker
-      # TODO: move all this out to a function
-      qd = QDialog(self.parent)
-      qd.setWindowTitle("Select destination for FileCopy")
-      qd.setWindowModality(Qt.ApplicationModal)
-      dw = DirWidget('.',qd)
-      d_ok = QPushButton('select')
-      d_ok.setDefault(True)
-      d_ok.clicked.connect(QDialog.accept)
-      d_nope = QPushButton('cancel')
-      d_nope.clicked.connect(QDialog.reject)
-      # if returns from <enter>, copy the file and comments
-      r = qd.exec() 
+      r = QFileDialog.getExistingDirectory(self.parent, "Select destination for FileCopy")
       print_v(f"copy to {r}")
       print_v(f"copy to {r}")
+      if r:
+        dest = os.path.join(r,fo.getDisplayName())
+        try:
+          shutil.copy2(fo.getName(), dest) # copy2 preserves the xattr
+          f = FileObj(dest)       # can't make the FileObj until it exists
+          f.setDbComment(self.db,fo.getDbComment())
+        except:
+          errorBox(f"file copy to <{dest}> failed; check permissions")
     pass
     pass
       
       
   def refill(self):
   def refill(self):
@@ -550,13 +497,13 @@ class DirNotes(QMainWindow):
     # this is a list of all the file
     # this is a list of all the file
     #~ print("insert {} items into cleared table {}".format(len(d),current))
     #~ print("insert {} items into cleared table {}".format(len(d),current))
     for i,name in enumerate(d):
     for i,name in enumerate(d):
-      this_file = FileObj(current+'/'+name)
-      this_file.loadDbComment(self.db.db_cursor)
+      this_file = FileObj(os.path.join(current,name))
+      this_file.loadDbComment(self.db)
       print_v("FileObj created as {} and the db-comment is <{}>".format(this_file.displayName, this_file.dbComment))
       print_v("FileObj created as {} and the db-comment is <{}>".format(this_file.displayName, this_file.dbComment))
       #~ print("insert order check: {} {} {} {}".format(d[i],i,this_file.getName(),this_file.getDate()))
       #~ print("insert order check: {} {} {} {}".format(d[i],i,this_file.getName(),this_file.getDate()))
       #~ self.files.update({this_file.getName(),this_file})
       #~ self.files.update({this_file.getName(),this_file})
       self.files = self.files + [this_file]
       self.files = self.files + [this_file]
-      display_name = this_file.getFileName()
+      display_name = this_file.getDisplayName()
       if this_file.getSize() == FileObj.FILE_IS_DIR:
       if this_file.getSize() == FileObj.FILE_IS_DIR:
         item = SortableTableWidgetItem(display_name,' '+display_name, this_file)  # directories sort first
         item = SortableTableWidgetItem(display_name,' '+display_name, this_file)  # directories sort first
       else:
       else:
@@ -569,7 +516,7 @@ class DirNotes(QMainWindow):
       # get the comment from database & xattrs, either can fail
       # get the comment from database & xattrs, either can fail
       comment = this_file.getComment()
       comment = this_file.getComment()
       other_comment = this_file.getOtherComment()
       other_comment = this_file.getOtherComment()
-      ci = QTableWidgetItem(comment)
+      ci = SortableTableWidgetItem(comment,'',this_file)
       ci.setToolTip(f"comment: {comment}\ncomment date: {this_file.getDbDate()}\nauthor: {this_file.getDbAuthor()}")
       ci.setToolTip(f"comment: {comment}\ncomment date: {this_file.getDbDate()}\nauthor: {this_file.getDbAuthor()}")
       if other_comment != comment:
       if other_comment != comment:
         ci.setBackground(QBrush(QColor(255,255,160)))
         ci.setBackground(QBrush(QColor(255,255,160)))
@@ -607,11 +554,10 @@ class DirNotes(QMainWindow):
     print_v("      selected file: "+self.lb.item(x.row(),0).file_object.getName())
     print_v("      selected file: "+self.lb.item(x.row(),0).file_object.getName())
     the_file = self.lb.item(x.row(),0).file_object
     the_file = self.lb.item(x.row(),0).file_object
     print_v("      and the row file is "+the_file.getName())
     print_v("      and the row file is "+the_file.getName())
-    the_file.setDbComment(str(x.text()))
+    the_file.setDbComment(self.db,str(x.text()))
     r = the_file.setXattrComment(str(x.text())) 
     r = the_file.setXattrComment(str(x.text())) 
-    # TODO: change this to FileObj.setDbComment()
     if r:
     if r:
-      self.db.setData(the_file.getName(),x.text())
+      the_file.setDbComment(self.db,x.text())
 
 
   def switchMode(self):
   def switchMode(self):
     global mode
     global mode
@@ -686,7 +632,7 @@ if __name__=="__main__":
   
   
   print_v(f"here is the .json {repr(config)}")
   print_v(f"here is the .json {repr(config)}")
   dbName = os.path.expanduser(config["database"])
   dbName = os.path.expanduser(config["database"])
-  db = dnDataBase(dbName)
+  db = dnDataBase(dbName).db
   xattr_comment = config["xattr_tag"]
   xattr_comment = config["xattr_tag"]
   xattr_author  = xattr_comment + ".author"
   xattr_author  = xattr_comment + ".author"
   xattr_date    = xattr_comment + ".date"
   xattr_date    = xattr_comment + ".date"

+ 1 - 0
dirnotes-cli

@@ -1,4 +1,5 @@
 #!/usr/bin/python3
 #!/usr/bin/python3
+# TODO starting with a dir shoild list all-files
 # TODO: get rid of the -n option; multiple files are prefixed by 'filename:', single files aren't
 # TODO: get rid of the -n option; multiple files are prefixed by 'filename:', single files aren't
 
 
 VERSION = "0.3"
 VERSION = "0.3"

+ 59 - 30
dirnotes-tui

@@ -186,7 +186,7 @@ class Pane:
       self.setStatus("The xattr and database comments differ where shown in green")
       self.setStatus("The xattr and database comments differ where shown in green")
     #  time.sleep(2)
     #  time.sleep(2)
 
 
-    self.file_pad.refresh(self.first_visible,0,2,1,self.h-3,self.w-2)
+    self.file_pad.refresh(self.first_visible,0, 2,1, self.h-3,self.w-2)
  
  
   def refill(self):
   def refill(self):
     self.win.bkgdset(' ',curses.color_pair(CP_BODY))
     self.win.bkgdset(' ',curses.color_pair(CP_BODY))
@@ -498,61 +498,90 @@ class FileObj():
 
 
 ##########  dest folder picker ###############
 ##########  dest folder picker ###############
 # returns None if the user hits <esc>
 # returns None if the user hits <esc>
+#     the dir_pad contents are indexed from 0,0, matching self.fs
 class showFolderPicker:
 class showFolderPicker:
   def __init__(self,starting_dir,title):
   def __init__(self,starting_dir,title):
+    self.selected = None
+    self.title = title
+    self.starting_dir = self.cwd = os.path.abspath(starting_dir)
+
+    # draw the perimeter...it doesn't change  
     self.W = curses.newwin(20,60,5,5)
     self.W = curses.newwin(20,60,5,5)
     self.W.bkgd(' ',COLOR_HELP)
     self.W.bkgd(' ',COLOR_HELP)
+    self.h, self.w = self.W.getmaxyx()
     self.W.keypad(True)
     self.W.keypad(True)
-    self.title = title
-    self.starting_dir = starting_dir
-    self.cwd = starting_dir
+    #self.W.clear()
+    self.W.border()
+    self.W.addnstr(0,1,self.title,self.w-2)
+    self.W.addstr(self.h-1,1,"<Enter> to select or change dir, <esc> to exit")
+    self.W.refresh()
+
     self.fill()
     self.fill()
-    self.selected = None
 
 
-    indialog = True
+    inDialog = True
     selected = ''
     selected = ''
-    while indialog:
+    while inDialog:
       c = self.W.getch()
       c = self.W.getch()
-      y,x = self.W.getyx()
+      y,x = self.dir_pad.getyx()
       if c == curses.KEY_UP:
       if c == curses.KEY_UP:
-        if y>1: self.W.move(y-1,1)
+        if y==0:
+          continue 
+        y -= 1
+        self.dir_pad.move(y,0)
+        if y < self.first_visible:
+          self.first_visible = y 
+        self.refresh()
       elif c == curses.KEY_DOWN:
       elif c == curses.KEY_DOWN:
-        if y<len(self.fs)+1: self.W.move(y+1,1)
+        if y == len(self.fs)-1:
+          continue
+        y += 1
+        self.dir_pad.move(y,0)
+        if y-self.first_visible > self.h-3:
+          self.first_visible += 1
+        self.refresh()
       elif c == CMD_CD:
       elif c == CMD_CD:
         # cd to new dir and refill
         # cd to new dir and refill
-        if y==1 and self.fs[0].startswith('<'):    # current dir
+        if y==0 and self.fs[0].startswith('<use'):    # current dir
           self.selected = self.cwd
           self.selected = self.cwd
-          indialog = False
+          inDialog = False
         else:
         else:
-          self.cwd = self.cwd + '/' + self.fs[y-1]
-          self.cwd = os.path.realpath(self.cwd)
+          self.cwd = os.path.abspath(self.cwd + '/' + self.fs[y])
           #logging.info(f"change dir to {self.cwd}")
           #logging.info(f"change dir to {self.cwd}")
-          self.fill()
+          self.fill()   # throw away the old self.dir_pad
       elif c == CMD_ESC:
       elif c == CMD_ESC:
-        indialog = False
+        inDialog = False
     del self.W
     del self.W
 
 
   def value(self):
   def value(self):
-    #logging.info(f"dir picker returns {self.selected}")
+    logging.info(f"dir picker returns {self.selected}")
     return self.selected
     return self.selected
 
 
+  def refresh(self):
+    y,x = self.W.getbegyx()
+    self.dir_pad.refresh(self.first_visible,0, x+1,y+1, x+self.h-2,y+self.w-2)
+
   def fill(self):
   def fill(self):
-    h, w = self.W.getmaxyx()
-    self.W.clear()
-    self.W.border()
-    self.W.addnstr(0,1,self.title,w-2)
-    self.W.addstr(h-1,1,"<Enter> to select or change dir, <esc> to exit")
-    self.fs = os.listdir(self.cwd)
-    self.fs = [a for a in self.fs if os.path.isdir(a)]
+    # change to os.path.walk() and just use the directories
+    # self.fs is the list of candidates, prefixed by "use this" and ".."
+    d, self.fs, _ = next(os.walk(self.cwd))
     self.fs.sort()
     self.fs.sort()
     if self.cwd != '/':
     if self.cwd != '/':
       self.fs.insert(0,"..")
       self.fs.insert(0,"..")
     if self.cwd != self.starting_dir:
     if self.cwd != self.starting_dir:
       self.fs.insert(0,f"<use this dir> {os.path.basename(self.cwd)}")
       self.fs.insert(0,f"<use this dir> {os.path.basename(self.cwd)}")
-    for i,f in enumerate(self.fs):
-      self.W.addnstr(i+1,1,f,w-2)
-    self.W.move(1,1)
+    
+    # create a pad big enough to hold all the entries
+    self.pad_height = max(self.h-2,len(self.fs))
+    self.dir_pad = curses.newpad(self.pad_height, self.w - 2)
+    self.dir_pad.bkgdset(' ',curses.color_pair(CP_BODY))
+    self.dir_pad.clear()
+    self.first_visible = 0
 
 
+    # and fill it with strings
+    for i,f in enumerate(self.fs):
+      self.dir_pad.addnstr(i,0,f,self.w-2)
+    self.dir_pad.move(0,0)
+    self.refresh()
 
 
 ########### comment management code #################
 ########### comment management code #################
 
 
@@ -867,7 +896,7 @@ def main(w, cwd):
         # and copy the database record
         # and copy the database record
         f = FileObj(dest)
         f = FileObj(dest)
         f.setDbComment(files[mywin.cursor].getDbComment())
         f.setDbComment(files[mywin.cursor].getDbComment())
-      mywin.refresh()
+      mywin.refresh() 
 
 
     elif c == CMD_MOVE:
     elif c == CMD_MOVE:
       if files[mywin.cursor].displayName == "..":
       if files[mywin.cursor].displayName == "..":
@@ -885,9 +914,9 @@ def main(w, cwd):
         shutil.move(src, dest_dir)
         shutil.move(src, dest_dir)
         # and copy the database record
         # and copy the database record
         f = FileObj(dest)
         f = FileObj(dest)
-        f.setDbComment(files[mywin.cursor].getDbComment())
+        f.setDbComment(files[mywin.cursor].getDbComment())  
         files = Files(cwd)
         files = Files(cwd)
-        mywin = Pane(w,cwd,files)
+        mywin = Pane(w,cwd,files) # is this the way to refresh the main pane? TODO
 
 
     elif c == curses.KEY_RESIZE:
     elif c == curses.KEY_RESIZE:
       mywin.resize()
       mywin.resize()

+ 9 - 0
dirnotes.desktop

@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Name=Dirnotes
+Comment=add comments to file
+Icon=/home/patb/.local/share/icons/dirnotes.xpm
+Exec=/home/patb/.local/bin/dirnotes
+Terminal=false
+Categories=Tags;Describing;Application

+ 42 - 0
dirnotes.xpm

@@ -0,0 +1,42 @@
+/* XPM */
+static const char *const dirnotes[] = {
+"32 32 6 1",  
+"   c None",
+".  c #666666",
+"+  c #FFFFFF",
+"@  c #848484",
+"#  c #000000",
+"$  c #FCE883",
+"                                ",
+"  ........                      ",
+" .++++++++.                     ",
+" .+++++++++..................   ",
+" .+++++++++++++++++++++++++++.  ",
+" .+++++++++++++++++++++++++++.  ",
+" .++..+......++@@@@@@@@@@@@@@@@@",
+" .++..++++++++#################@",
+" .+++++++++++#$$$$$$$$$$$$$$$$$#",
+" .++..+.....+#$$$$$$$$$$$$$$$$$#",
+" .++..+++++++#$$$$$$$$$$$$$$$$$#",
+" .+++++++++++#$$#############$$#",
+" .++..+.....+#$$$$$$$$$$$$$$$$$#",
+" .++..+++++++#$$########$$$$$$$#",
+" .+++++++++++#$$$$$$$$$$$$$$$$$#",
+" .++..+.....+#$$$$$$$$$$$$$$$$$#",
+" .++..++++++++#######$$$####### ",
+" .++++++++++++++++++#$$#++++++  ",
+" .++..+............+#$#++++++.  ",
+" .++..++++++++++++++##+++++++.  ",
+" .++++++++++++++++++#++++++++.  ",
+" .++..+............++++++++++.  ",
+" .++..+++++++++++++++++++++++.  ",
+" .+++++++++++++++++++++++++++.  ",
+" .++..+................++++++.  ",
+" .++..+++++++++++++++++++++++.  ",
+" .+++++++++++++++++++++++++++.  ",
+" .++..+................++++++.  ",
+" .++..+++++++++++++++++++++++.  ",
+" .+++++++++++++++++++++++++++.  ",
+"  ...........................   ",
+"                                "
+};