Fixed 'wrong type for this column' error - by alex2ndr
[findit] / src / findit.py
1 #!/usr/bin/env python
2 # -*-coding: utf-8 -*-
3 # vim: sw=4 ts=4 expandtab ai
4 # pylint: disable-msg=C0301
5
6 """findIT: Gui prorgram to find various information.
7    At the moment it only finds largest files
8 """
9
10 import gtk
11 import gobject
12 import pango
13 from os import walk
14 from os.path import join, abspath, normcase, basename, \
15                     isdir, getsize, getatime, getmtime, expanduser
16 from heapq import nlargest
17 import gettext
18 import time
19 from sys import platform
20
21 try: 
22     import hildon
23     HILDON = True
24 except ImportError:
25     HILDON = False
26
27 try:
28     # Подразумевается, что ru/LC_MESSAGES/program.mo находится в текущем каталоге (sys.path[0])
29     # Для стандартного /usr/share/locale писать gettext.translation('findit')
30     #langRU = gettext.translation('findit', sys.path[0], languages=['ru'])
31     LANGRU = gettext.translation('findit')
32     LANGRU.install()
33 except IOError:
34     # Закомментировать перед использованием pygettext
35     def _(text): 
36         return text
37
38
39 ### Common functions ###########################################################
40
41 # Функция которая возвращает строку из числа и единиц для столбца "Размер"("Size")
42 def size_convert(size):
43     """Return string with file size in b or Kb or Mb or Gb or Tb."""
44     for i, unit in enumerate(['%d b', '%.1f Kb', '%.2f Mb', '%.3f Gb', '%.4f Tb']):
45         if size < 1024**(i+1):
46             return unit % (size/1024.**i)
47     return '>1024 Tb'
48
49 # Функция поставляющая размер файла и путь к нему
50 def filegetter(startdir, obj):
51     """Generator of file sizes and paths based on os.walk."""
52     # Список игнорируемых каталогов:
53     ignore_dirs = ['/dev', '/proc', '/sys', '/mnt']
54     # Проходим по всем папкам вглубь от заданного пути
55     for dirpath, dirnames, fnames in walk(startdir):
56     # Исключаем каталоги из поиска в соответствии со списком исключений
57         for ign_dir in ignore_dirs[:]:
58             for dirname in dirnames[:]:
59                 if ign_dir == normcase(join(abspath(dirpath), dirname)):
60                     dirnames.remove(dirname)
61                     ignore_dirs.remove(ign_dir)
62
63         for fname in fnames:
64             flpath = abspath(join(dirpath, fname))
65             # Выводим текущий опрашиваемый файл в строку статуса
66             obj.statusbar.push(obj.context_id, flpath)
67             # обновляем окно
68             gtk.main_iteration()
69             # Останавливаем цикл по нажатию кнопки стоп
70             if obj.stopit:
71                 obj.stopit = False
72                 raise StopIteration
73             # Проверяем можем ли мы определить размер файла - иначе пропускаем его
74             try:
75                 # Возвращаем размер и полный путь файла
76                 yield getsize(flpath), flpath
77             except OSError:
78                 continue
79
80 # Fullscreen
81 def toggle_fullscreen(obj):
82     """Switch fullscreen on/off."""
83     if obj.fullscreen:
84         obj.window.unfullscreen()
85     else: 
86         obj.window.fullscreen()
87     obj.fullscreen = not obj.fullscreen
88
89 # Нажатие на кнопку клавиатуры
90 def on_key_press(obj, event):
91     """Key press callback."""
92     # Toggle fullscreen on Maemo when hw key is pressed
93     if HILDON and event.keyval == gtk.keysyms.F6:
94         toggle_fullscreen(obj)
95
96
97 ### Properties dialog ##########################################################
98
99 class PropertiesDialog(gtk.Dialog):
100     """File property dialog window."""
101
102     def __init__(self, app, path, size, bytesize):
103         """Create&show file properties dialog."""
104         gtk.Dialog.__init__(self, _('File properties'), app,
105             buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
106         self.set_wmclass('PropertiesDialog', 'FindIT')
107         self.set_resizable(False)
108
109         # Достаем свойства выбранного файла
110         name = basename(path)
111         access = time.strftime('%x %X', time.localtime(getatime(path)))
112         modified = time.strftime('%x %X', time.localtime(getmtime(path)))
113
114         # Таблица надписей
115         table = gtk.Table()
116         table.set_border_width(10)
117         table.set_col_spacings(10)
118         table.set_row_spacings(10)
119
120         # Надписи (подпись: значение)
121         lbls = [(gtk.Label(_(name)), gtk.Label(value)) for name, value in 
122             [('Name', name), ('Size', "%s (%d b)" % (size, bytesize)),
123              ('Opened', access), ('Modified', modified)]]
124
125         # Упаковка надписей в таблицу и выравнивание
126         for i, lbl in enumerate(lbls):
127             name, value = lbl
128             table.attach(name, 0, 1, i, i+1)
129             table.attach(value, 1, 2, i, i+1)
130             name.set_alignment(1, 0.5)
131             value.set_alignment(0, 0.5)
132
133         # Упаковка таблицы в vbox диалога
134         self.vbox.add(table)
135         self.show_all()
136         self.run()
137         self.destroy()
138
139
140 ### About dialog ###############################################################
141
142 class AboutDialog(gtk.AboutDialog):
143     """About dialog window."""
144
145     def __init__(self, *args):
146         """Create&show about dialog."""
147         gtk.AboutDialog.__init__(self)
148         self.set_wmclass('AboutDialog', 'FindIT')
149
150         self.set_authors([ 'Alex Taker\n   * Email: alteker@gmail.com\n',
151                            'Eugene Gagarin\n   * Email: mosfet07@ya.ru\n',
152                            'Alexandr Popov\n   * Email: popov2al@gmail.com' ])
153         
154         self.set_comments('Tool for find some information on computer.')
155         self.set_version('0.1.0')
156         self.set_license("This program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 3\nof the License, or (at your option) any later version.")
157         self.set_copyright('')
158         self.set_website('')
159
160         self.show_all()
161         self.run()
162         self.destroy()
163
164 ### Main window ################################################################
165
166 class MainWindow(gtk.Window):
167     """Main window class."""
168
169     # Окно сообщения заданного типа с заданным текстом
170     def mess_window(self, mestype, content):
171         """Show popup message window."""
172         dialog = gtk.MessageDialog(parent=self, flags=gtk.DIALOG_MODAL,
173                                    type=mestype, buttons=gtk.BUTTONS_OK,
174                                    message_format=content)
175         dialog.set_wmclass('ErrorDialog', 'FindIT')
176         dialog.set_title( _('Error!') )
177         dialog.run()
178         dialog.destroy()
179
180     # Функция выполняющаяся при нажатии на кнопку "Показать"
181     def start_print(self, widget):
182         """Start file search. Button "Go" activate callback."""         
183         self.start_path = self.srch_p_entr.get_text()
184         # Проверяем правильное ли значение введено
185         if isdir(self.start_path):
186             self.butt_start.set_sensitive(False)
187             self.butt_stop.set_sensitive(True)
188             self.propertiesbtn.set_sensitive(False)
189             # Получаем значение количества файлов из SpinButton
190             self.fl_cnt = int( self.file_cnt.get_value() )
191             # Очищаем список
192             self.treestore.clear()
193             self.treeview.columns_autosize()
194             # Получаем нужное количество самых больших файлов
195             for fsize, fpath in nlargest(self.fl_cnt, filegetter(self.start_path, self)):
196                 # Возвращаем значения в treeview в таком порядке - путь,
197                 # размер в Мб строкой и размер в байтах
198                 # self.treestore.append(None, [fpath.replace(self.start_path,'', 1),
199                 #        size_convert(fsize), fsize])
200
201                 # Выдает какую-то перманентную ошибку при присвоении значений treestore -
202                 # кто увидит скажите - нужна статистика
203                 try: 
204                     self.treestore.append(None, [fpath, size_convert(fsize), fsize])
205                 except SystemError:
206 #                    print 'error', fpath, size_convert(fsize), fsize
207                     self.mess_window('error','Error in %s' % fpath)
208             self.butt_start.set_sensitive(True)
209             self.butt_stop.set_sensitive(False)
210             self.propertiesbtn.set_sensitive(True)
211             self.srch_p_entr.grab_focus()
212         else:
213             # Иначе выводим окошко с ошибкой
214             self.mess_window('error', _('Invalid directory') )
215
216     # Функция выполняющаяся при нажатии на кнопку "Стоп"
217     def stop_print(self, widget):
218         """Stop search. "Stop" button clicked callback."""
219         self.stopit = True
220
221     # Функция выполняющаяся при нажатии на кнопку "Свойства файла"
222     def show_properties_dialog(self, *args):
223         """Show property dialog window."""
224         selection = self.treeview.get_selection()
225         (model, item) = selection.get_selected()
226         try:
227             path = model.get_value(item, 0)
228             size = model.get_value(item, 1)
229             bytesize = model.get_value(item, 2)
230         except (TypeError, ValueError):
231             self.mess_window('error', _('Please select file') )
232             return
233         PropertiesDialog(self, path, size, bytesize)
234
235     # Создание меню
236     def create_menu(self):
237         """ Create main menu """
238         menubar = gtk.MenuBar()
239
240         # File menu
241         fileitem = gtk.MenuItem( _('_File') )     # Файл
242         filemenu = gtk.Menu()
243         fileitem.set_submenu(filemenu)
244
245         open_menuitem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
246         delete_menuitem = gtk.ImageMenuItem(gtk.STOCK_DELETE)
247         properties_menuitem = gtk.ImageMenuItem(gtk.STOCK_PROPERTIES)
248         quit_menuitem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
249         filemenu.add(open_menuitem)
250         filemenu.add(delete_menuitem)
251         filemenu.add(properties_menuitem)
252         filemenu.add(quit_menuitem)
253         properties_menuitem.connect('activate', self.show_properties_dialog)
254         quit_menuitem.connect('activate', gtk.main_quit)
255
256         # View menu
257         viewitem = gtk.MenuItem( _('_View') )    # Вид
258         viewmenu = gtk.Menu()
259
260         # Help menu
261         helpitem = gtk.MenuItem( _('_Help') )    # Помощь
262         helpmenu = gtk.Menu()
263         helpitem.set_submenu(helpmenu)
264
265         about_menuitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
266         helpmenu.add(about_menuitem)
267         about_menuitem.connect('activate', AboutDialog)
268
269         # Packing
270         menubar.add(fileitem)
271         menubar.add(viewitem)
272         menubar.add(helpitem)
273
274         return menubar
275
276 #     def create_hildon_menu(self):
277 #         """ Create Hildon main menu """
278
279     ### Window initialization ##################################################
280
281     def __init__(self, win_width, win_height, st_path):
282         """Create MainWindow."""
283         # Создаем новое окно
284         gtk.Window.__init__(self)
285         self.set_default_size(win_width, win_height)
286         self.set_border_width(4)
287         self.fullscreen = False
288         self.connect('delete_event', gtk.main_quit)
289         self.connect("key-press-event", on_key_press)
290         self.set_wmclass('MainWindow', 'FindIT')
291
292         #########  Добавляем элементы ################
293         # 1. Строка ввода каталога с которого начинать поиск
294         #    переменная в которой храниться стартовый каталог = self.start_path
295         self.srch_p_entr = gtk.Entry()
296         self.start_path = st_path
297         self.srch_p_entr.set_text(self.start_path)
298         # Отключаем автокапитализацию(ввод первой буквы заглавной) на таблетке
299         if HILDON:
300             self.srch_p_entr.set_property("hildon-input-mode", 'full')
301         # Нажатие Enter в поле ввода
302         self.srch_p_entr.connect("activate", self.start_print)
303
304         # 2. Кнопка "Обзор"
305
306         # 3. Надпись1 "Количество отображаемых файлов:"
307         label1 = gtk.Label( _('Files quantity') )
308
309         # 4. Окошко ввода количества файлов, мин значение=1 макс=65536 по умолчанию 10
310         #    данные храняться в переменной self.fl_cnt
311         self.fl_cnt = 10
312         if HILDON:
313             self.file_cnt = hildon.NumberEditor(1, 99)
314             self.file_cnt.set_value(self.fl_cnt)
315         else:
316             adj = gtk.Adjustment(self.fl_cnt, 1, 65536, 1, 5, 0)
317             self.file_cnt = gtk.SpinButton(adj, 0, 0)
318
319         # 5.1 Кнопка "Показать"
320         self.butt_start = gtk.Button( _('Go') )
321         self.butt_start.connect('released', self.start_print)
322
323         # 5.2 Кнопка "Остановить"
324         self.butt_stop = gtk.Button( _('Stop') )
325         self.butt_stop.set_sensitive(False)
326         self.butt_stop.connect('clicked', self.stop_print)
327         self.stopit = False
328
329         # 5.3 Кнопка "Свойства файла"
330         self.propertiesbtn = gtk.Button( _('File properties') )
331         self.propertiesbtn.connect('clicked', self.show_properties_dialog)
332         self.propertiesbtn.set_sensitive(False)
333
334         # 6. Закладки
335
336         # 6.1 Список файлов
337         scrollwind = gtk.ScrolledWindow()
338         scrollwind.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
339
340         # Определяем переменную в которой будет храниться выводимый список
341         self.treestore = gtk.TreeStore(str, str, gobject.TYPE_INT64)
342         self.treeview = gtk.TreeView(self.treestore)
343         # На таблетке не отображаються заголовки столбцов по умолчанию -
344         # след строка заставляет их отображаться принудительно
345         self.treeview.set_headers_visible(1)
346         self.treeview.connect('row-activated', self.show_properties_dialog)
347
348         self.treestore.append(None, ['', '', 0])
349
350         # Создаем и настраиваем колонку с размером файла
351         size_col = gtk.TreeViewColumn( _('Size') )
352         cell = gtk.CellRendererText()
353         cell.set_property('width', 90)
354         size_col.pack_start(cell, True)
355         size_col.add_attribute(cell, 'text', 1)
356         self.treeview.append_column(size_col)
357         # Создаем и настраиваем колонку с именем файла
358         path_col = gtk.TreeViewColumn( _('Path') )
359         cell2 = gtk.CellRendererText()
360         path_col.pack_start(cell2, True)
361         path_col.add_attribute(cell2, 'text', 0)
362         self.treeview.append_column(path_col)
363
364         # Добавляем сортировку для колонок
365         self.treeview.set_search_column(1)
366         path_col.set_sort_column_id(0)
367         size_col.set_sort_column_id(2)
368
369         # 6.2 Надпись "Найти"
370
371         # 6.3 Строка выводящая текущий осматриваемый файл
372         self.statusbar = gtk.Statusbar()
373         self.context_id = self.statusbar.get_context_id("Current file path")
374
375         # 7 Меню
376         if HILDON:
377             main_menu = self.create_hildon_menu()
378         else:
379             main_menu = self.create_menu()
380
381         #########  Упаковываем элементы ################
382         # Создаем основной вертикальный контейнер
383         main_vbox = gtk.VBox(False, 4)
384
385         # Создаем вспомогательный горизонтальный контейнер для Надписи1,
386         # окошка ввода количества файлов и кнопки "Показать"
387         hbox1 = gtk.HBox(False, 5)
388         # Добавляем вышеперечисленные элементы во вспомогат. контейнер
389         hbox1.pack_start(label1, False, False, 5)
390         hbox1.pack_start(self.file_cnt, False, False, 0)
391         hbox1.pack_start(self.butt_start, True, True, 0)
392         hbox1.pack_start(self.butt_stop, True, True, 0)
393         hbox1.pack_start(self.propertiesbtn, True, True, 0)
394
395         # Добавляем элементы в основной контейнер
396         main_vbox.pack_start(main_menu, False, False, 0)
397         main_vbox.pack_start(self.srch_p_entr, False, False, 0)
398         main_vbox.pack_start(hbox1, False, False, 0)
399         scrollwind.add(self.treeview)
400         main_vbox.pack_start(scrollwind, True, True, 0)
401         main_vbox.pack_start(self.statusbar, False, False, 0)
402
403         self.add(main_vbox)
404
405     def run(self):
406         """Show all widgets and run gtk.main()."""
407         self.show_all()
408         gtk.main()
409
410
411 ### Main call ##################################################################
412 def main():
413     """Main function."""
414     gobject.set_application_name( _('FindIT') )
415
416     if platform == 'win32':
417         startpath = 'c:\\'
418     else:
419         startpath = expanduser('~')
420
421     MainWindow(575, 345, startpath).run()
422
423 if __name__ == '__main__':
424     main()