libervia-backend/frontends/primitivus/files_management.py
Goffi 663851e2c4 Primitivus: new FileDialog
Advanced file choosing dialog with:
- path understanding when removing part with C-w
- completion
- listing of GTK & KDE's bookmarks
- selection of file by typing the first letters of the name
2010-08-16 21:11:00 +08:00

234 lines
9.3 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import urwid
import custom_widgets
from tools.jid import JID
import os, os.path
from xml.dom import minidom
from logging import debug, info, error
from time import time
class PathEdit(custom_widgets.AdvancedEdit):
"""AdvancedEdit with manage file paths"""
def keypress(self, size, key):
if key == '~' and self.edit_pos==0:
expanded = os.path.expanduser('~')
self.set_edit_text(os.path.normpath(expanded+'/'+self.edit_text))
self.set_edit_pos(len(expanded)+1)
elif key == 'ctrl w':
if self.edit_pos<2:
return
before = self.edit_text[:self.edit_pos]
pos = (before[:-1] if before.endswith('/') else before).rfind("/")+1
self.set_edit_text(before[:pos] + self.edit_text[self.edit_pos:])
self.set_edit_pos(pos)
return
else:
return super(PathEdit, self).keypress(size, key)
class FilesViewer(urwid.WidgetWrap):
"""List specialised for files"""
def __init__(self, onPreviousDir, onDirClick, onFileClick):
self.path=''
self.key_cache = ''
self.key_time = time()
self.onPreviousDir = onPreviousDir
self.onDirClick = onDirClick
self.onFileClick = onFileClick
self.files_list = urwid.SimpleListWalker([])
self.show_hidden = True
listbox = urwid.ListBox(self.files_list)
urwid.WidgetWrap.__init__(self, listbox)
def keypress(self, size, key):
if key=='meta h':
#(un)hide hidden files
self.show_hidden = not self.show_hidden
self.showDirectory(self.path)
if key=='meta d':
#jump to directories
if self.files_list:
self._w.set_focus(0)
elif key=='meta f':
for idx in range(len(self.files_list)):
if isinstance(self.files_list[idx].base_widget,urwid.Divider):
if idx<len(self.files_list)-1:
self._w.set_focus(idx+1)
break
elif len(key) == 1:
if time() - self.key_time > 2:
self.key_cache=key
else:
self.key_cache+=key
self.key_time = time()
for idx in range(len(self.files_list)):
if isinstance(self.files_list[idx],custom_widgets.ClickableText) and self.files_list[idx].get_text().lower().startswith(self.key_cache.lower()):
self._w.set_focus(idx)
break
else:
return self._w.keypress(size, key)
def showDirectory(self, path):
self.path = path
del self.files_list[:]
directories = []
files = []
try:
for filename in os.listdir(path):
fullpath = os.path.join(path,filename)
if os.path.isdir(fullpath):
directories.append(filename)
else:
files.append(filename)
except OSError:
self.files_list.append(urwid.Text(("warning",_("Impossible to list directory")),'center'))
directories.sort()
files.sort()
if os.path.abspath(path)!='/' and os.path.abspath(path) != '//':
previous_wid = custom_widgets.ClickableText('..',default_attr='directory')
urwid.connect_signal(previous_wid,'click',self.onPreviousDir)
self.files_list.append(previous_wid)
for directory in directories:
if directory.startswith('.') and not self.show_hidden:
continue
dir_wid = custom_widgets.ClickableText(directory,default_attr='directory')
urwid.connect_signal(dir_wid,'click',self.onDirClick)
self.files_list.append(dir_wid)
self.files_list.append(urwid.AttrMap(urwid.Divider('-'),'separator'))
for filename in files:
if filename.startswith('.') and not self.show_hidden:
continue
file_wid = custom_widgets.ClickableText(filename)
urwid.connect_signal(file_wid,'click',self.onFileClick)
self.files_list.append(file_wid)
class FileDialog(urwid.WidgetWrap):
def __init__(self, ok_cb, cancel_cb, title=_("Please select a file"), style=[]):
"""Create file dialog
@param title: title of the window/popup
@param style: NOT USED YET #FIXME
"""
self.ok_cb = ok_cb
self.__home_path = os.path.expanduser('~')
self.path_wid = PathEdit(_('Path: '))
self.path_wid.setCompletionMethod(self._directory_completion)
urwid.connect_signal(self.path_wid, 'change', self.onPathChange)
header = urwid.Pile([self.path_wid, urwid.Divider(u'─')])
bookm_list = urwid.SimpleListWalker([])
self.bookmarks = list(self.getBookmarks())
self.bookmarks.sort()
for bookmark in self.bookmarks:
if bookmark.startswith(self.__home_path):
bookmark="~"+bookmark[len(self.__home_path):]
book_wid = custom_widgets.ClickableText(bookmark)
urwid.connect_signal(book_wid, 'click', self.onBookmarkSelected)
bookm_list.append(book_wid)
bookm_wid = urwid.Frame(urwid.ListBox(bookm_list), urwid.AttrMap(urwid.Text(_('Bookmarks'),'center'),'title'))
self.files_wid = FilesViewer(self.onPreviousDir, self.onDirClick, self.onFileClick)
center_row = urwid.Columns([('weight',2,bookm_wid),
('weight',8,custom_widgets.VerticalSeparator(self.files_wid))])
buttons = []
buttons.append(custom_widgets.CustomButton(_('Cancel'),cancel_cb))
max_len = max([button.getSize() for button in buttons])
buttons_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
main_frame = custom_widgets.FocusFrame(center_row, header, buttons_wid)
decorated = custom_widgets.LabelLine(main_frame, custom_widgets.SurroundedText(title))
urwid.WidgetWrap.__init__(self, decorated)
self.path_wid.set_edit_text(os.getcwdu())
def _directory_completion(self, path, completion_data):
path=os.path.abspath(path)
if not os.path.isdir(path):
head,dir_start = os.path.split(path)
else:
head=path
dir_start=''
try:
filenames = os.listdir(head)
filenames.sort()
try:
start_idx=filenames.index(completion_data['last_dir'])+1
if start_idx == len(filenames):
start_idx = 0
except (KeyError,ValueError):
start_idx = 0
for idx in range(start_idx,len(filenames)) + range(0,start_idx):
full_path = os.path.join(head,filenames[idx])
if filenames[idx].lower().startswith(dir_start.lower()) and os.path.isdir(full_path):
completion_data['last_dir'] = filenames[idx]
return full_path
except OSError:
pass
return path
def getBookmarks(self):
gtk_bookm = os.path.expanduser("~/.gtk-bookmarks")
kde_bookm = os.path.expanduser("~/.kde/share/apps/kfileplaces/bookmarks.xml")
bookmarks = set()
try:
with open(gtk_bookm) as gtk_fd:
for bm in gtk_fd.readlines():
if bm.startswith("file:///"):
bookmarks.add(bm[7:].replace('\n',''))
except IOError:
info(_('No GTK bookmarks file found'))
pass
try:
dom = minidom.parse(kde_bookm)
for elem in dom.getElementsByTagName('bookmark'):
bm = elem.getAttribute("href")
if bm.startswith("file:///"):
bookmarks.add(bm[7:])
except IOError:
info(_('No KDE bookmarks file found'))
pass
return bookmarks
def onBookmarkSelected(self, button):
self.path_wid.set_edit_text(os.path.expanduser(button.get_text()))
def onPathChange(self, edit, path):
if os.path.isdir(path):
self.files_wid.showDirectory(path)
def onPreviousDir(self, wid):
path = os.path.abspath(self.path_wid.get_edit_text())
if not os.path.isdir(path):
path = dirname(path)
self.path_wid.set_edit_text(os.path.split(path)[0])
def onDirClick(self, wid):
path = os.path.abspath(self.path_wid.get_edit_text())
if not os.path.isdir(path):
path = dirname(path)
self.path_wid.set_edit_text(os.path.join(path,wid.get_text()))
def onFileClick(self, wid):
self.ok_cb(os.path.abspath(os.path.join(self.files_wid.path,wid.get_text())))