← Back to team overview

zim-wiki team mailing list archive

new plugin: fileview

 

Am Dienstag, den 25.05.2010, 12:21 +0200 schrieb Jaap Karssenberg:
> I'm really interested in the plugin to preview / browse attachments.
> This is something I wanted to create myself, but didn't find time for
> yet. Might even want this as core functionality instead of as a
> plugin.

Sorry for the even later reply. 

Here is a very basic version of the plugin. 
Still a lot to do, but works for me. I use it to browse collections of
pdf files (technical documentation, data sheets, etc).


For more flexibility I suggest changing the zim layout to something like
this:
+-----------------------------+
|menu                         |
+-----+----------------+------+
|     |  top pane      |      |
|     |                |      |
| s   +----------------+  s   |
| i   | wiki editor    |  i   |
| d   |                |  d   |
| e   |                |  e   |
| b   +----------------+  b   |
| a   |tabs|           |  a   |
| r   | bottom pane    |  r   |
|     |                |      |
+----------------------+------+



# -*- coding: utf-8 -*-
#
# Copyright 2010 Thorsten Hackbarth <thorsten.hackbarth@xxxxxx>
# License:  same as zim (gpl)
#
# 2010-06-29
#
# TODO:
# * integer plugin_preferences do not to work as expected (zim bug?)
# * toggle:  hide/show => disconect/update
# * toggle: toolbar button state wrong  
# * toggle-btn icon
# * where to store thumbnails?
#     in current dir: .thumb_<filename>.jpg  (hide on windows?)
#     ~/.thumbnails/  (gnome/nautilus)
#     .zim/cache/
#     Thumbs.db
#     let the user decide
# * thumbnail all images?
# * dont start all thumbnailing processes at a time, and make them nice 10
# * small and lagre thumbs 
# * textrendering: syntay-hl 
# * use mimtype not extension
# * rethumb: thmub-size==0 or broken (e.g. shutdown while thumbnailing)
# * option to remove all thumbnails
# * update view when thumb-cration finished
# * code cleanup
# * new gui concept for zim : sidepane r/l,bottom- and top pane both with tabs (see gedit)
# * show file infos in tooltip (which?)
# * update icon when thumbnail is ready
# * mimi-type specific icons
# * evaluate imagemagick python libs 
#
#
# tooltip example
#  http://nullege.com/codes/show/src@pygtk-2.14.1@examples@pygtk-demo@demos@xxxxxxxxxx/160/gtk.gdk.Color
# file info example
#  http://ubuntuforums.org/showthread.php?t=880967
#

'''Zim plugin to display files in atachments folder.'''

import gobject
import gtk

import pango

import os
import stat
import time

import re
import logging
from datetime import date as dateclass

from zim.plugins import PluginClass
from zim.gui import Dialog
from zim.gui.widgets import Button
from zim.notebook import Path
from zim.stores import encode_filename
from zim.fs import *
from zim.errors import Error
logger = logging.getLogger('zim.plugins.fileview')
from zim.applications import Application
from zim.gui.applications import OpenWithMenu


ui_toggle_actions = (
	# name, stock id, label, accelerator, tooltip, readonly
	('toggle_fileview', gtk.STOCK_MISSING_IMAGE, _('Fileview'),  '', 'Show Fileview',False, True), # T: menu item
)


#Menubar and toolbar
ui_xml = '''
<ui>
	<menubar name='menubar'>
		<menu action='view_menu'>
			<placeholder name="plugin_items">
				<menuitem action="toggle_fileview" />
			</placeholder>
		</menu>
	</menubar>
	<toolbar name='toolbar'>
		<placeholder name='tools'>
			<toolitem action='toggle_fileview'/>
		</placeholder>
	</toolbar>
</ui>
'''



ICON_SIZE_MIN=16
ICON_SIZE_MAX=256
THUMB_SIZE_MIN=16
THUMB_SIZE_MAX=1024


# TODO: chaneg size
pdftopng_cmd = ('convert','-size', '480x480', '-trim','+repage','-resize','480x480>','-quality','50')
#pdftopng_cmd = ('convert','-trim')
#txttopng_cmd = ('dvipng', '-q', '-bg', 'Transparent', '-T', 'tight', '-o')
#txttopng_cmd = ('convert', '-size', '480x480'  'caption:')
pdftojpg_cmd = ('convert','-size', '480x480', '-trim','+repage','-resize','480x480>','-quality','50')


class FileviewPlugin(PluginClass):

	plugin_info = {
		'name': _('Fileview'), # T: plugin name
		'description': _('''\
This shows the storage directory of the current page as icon view at bottom pane.
ImageMagick dependencies: 
	html-preview: html2ps
'''), # T: plugin description
		'author': 'Thorsten Hackbarth <thorsten.hackbarth@xxxxxx>',
		'help': 'Plugins:Fileview',
	}

	plugin_preferences = (
		# key, type, label, default
		('icon_size', 'int', _('Icon size [px]'), [ICON_SIZE_MIN,128,ICON_SIZE_MAX]), # T: preferences option
		('preview_size', 'int', _('Tooltip preview size [px]'), (THUMB_SIZE_MIN,480,THUMB_SIZE_MAX)), # T: input label
		('thumb_quality', 'int', _('Preview jpeg Quality [0..100]'), (0,50,100)), # T: input label
	)
	
	@classmethod
	def check_dependencies(klass):
		return [("ImageMagick",Application(pdftojpg_cmd).tryexec())]

	def __init__(self, ui):
		PluginClass.__init__(self, ui)
		self.bottompane_widget = None
		self.scrollpane = None		
		if self.ui.ui_type == 'gtk':
			self.ui.add_toggle_actions(ui_toggle_actions, self)
			#self.ui.add_actions(ui_actions, self)
			self.ui.add_ui(ui_xml, self)
		self.ui.connect_after('open-notebook', self.do_open_notebook)
	
	
	def do_open_notebook(self, ui, notebook):
		self.do_preferences_changed()
		#notebook.register_hook('suggest_link', self.suggest_link)


	def disconnect(self):
		PluginClass.disconnect(self)


	def add_to_mainwindow(self):
		bottompane = self.ui.mainwindow.pageview.get_parent()
		if self.bottompane_widget is None:
			self.bottompane_widget = FileviewPluginWidget(self)
			self.scrollpane=gtk.ScrolledWindow()
			self.scrollpane.set_size_request(-1,160)
			self.scrollpane.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
			self.scrollpane.add_with_viewport(self.bottompane_widget)
			bottompane.pack_end(self.scrollpane, False)
		#bottompane.pack_end(self.bottompane_widget, False)
		
		#bottompane.reorder_child(self.bottompane_widget, 0)
		self.handlerID_do_open_notebook=self.ui.connect_after('open-notebook', self.do_open_notebook)
		#self.bottompane_widget.show_all()
		self.scrollpane.show_all()
	
	def remove_from_mainwindow(self):
		if self.bottompane_widget is not None:
			#doesnt work:? self.ui.disconnect(self.handlerID_do_open_notebook)
			self.scrollpane.hide_all()
		
	


	def do_preferences_changed(self):
		print self.preferences['icon_size']
		
		# bug?
		# 
		# self.preferences['icon_size'] is integer after  changeing it
		# but must be (min,val,max) for the dialog, which is strange 
		
		self.add_to_mainwindow()
		
	def toggle_fileview(self, enable=None):
		action = self.actiongroup.get_action('toggle_fileview')
		if enable is None or enable != action.get_active():
			action.activate()
		else:
			self.do_toggle_fileview(enable=enable)

	def do_toggle_fileview(self, enable=None):
		#~ print 'do_toggle_fileview', enable
		if enable is None:
			action = self.actiongroup.get_action('toggle_fileview')
			enable = action.get_active()
		if enable:
			# print "enabled"
			self.add_to_mainwindow()
		else:
			# print "disabled"
			self.remove_from_mainwindow()
		self.uistate['active'] = enable
		return False # we can be called from idle event

class Fileview(gtk.IconView):
	'''Custom fileview widget class. Adds an 'activate' signal for what i dont know yet'''

	# define signals we want to use - (closure type, return type and arg types)
	__gsignals__ = {
		'activate': (gobject.SIGNAL_RUN_LAST, None, ()),
	}




# Need to register classes defining gobject signals
gobject.type_register(Fileview)


class FileviewPluginWidget(gtk.HBox):
	__gsignals__ = {
		'modified-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
	}
	def __init__(self, plugin):
		gtk.HBox.__init__(self)
		self.plugin = plugin
	
		self.fileview = Fileview()
		self.store = None
		self.store=gtk.ListStore(str, gtk.gdk.Pixbuf,str) #gtk.TextBuffer()
		#self.fileview.set_buffer(self.textbuffer)
		self.fileview.set_model(self.store)
		self.fileview.set_text_column(0)
		self.fileview.set_pixbuf_column(1)

		self.pack_start(self.fileview, True)

		self.plugin.ui.connect('open-page', self.on_open_page)

		self.fileview.connect_object('button-press-event',FileviewPluginWidget.on_button_press_event, self)
		#self.fileview.set_tooltip_column(2) # filename as tooltip
		self.fileview.props.has_tooltip = True
		# custom tooltip	
		self.fileview.connect("query-tooltip", self.query_tooltip_icon_view_cb)  	
	
	def on_button_press_event(self,event):
		# print 'on_button_press_event'
		if event.button == 3:
			popup_menu=gtk.Menu()
			x = int(event.x)
			y = int(event.y)
			time = event.time
			#iteminfo = self.fileview.get_item_at_pos(x, y)
			pathinfo = self.fileview.get_path_at_pos(x, y)
			if pathinfo is not None:
				self.fileview.grab_focus()
				print self.store.get_value(self.store.get_iter(pathinfo),2)
				popup_menu.popup(None, None, None, event.button, time)
				self.do_populate_popup(popup_menu,pathinfo)
				return True
		return False

	def thumbnailfile(self,path,filename):
		return path+'/.thumb_'+filename+'.jpg'	

	def do_populate_popup(self, menu,pathinfo):
		# print "do_populate_popup"
		file= File(self.store.get_value(self.store.get_iter(pathinfo),2)+os.sep+self.store.get_value(self.store.get_iter(pathinfo),0))
		
		# open with & open folder
		item = gtk.MenuItem(_('Open Folder'))
			# T: menu item to open containing folder of files
		menu.prepend(item)
		
		dir = file.dir
		if dir.exists():
			item.connect('activate', lambda o: self.plugin.ui.open_file(dir))
		else:
			item.set_sensitive(False)
		
		
		
		
		item = gtk.MenuItem(_('Open With...'))
		menu.prepend(item)
		
		submenu = OpenWithMenu(file)
		item.set_submenu(submenu)
				
		menu.show_all()


	def query_tooltip_icon_view_cb(self, widget, x, y, keyboard_tip, tooltip):
		if not widget.get_tooltip_context(x, y, keyboard_tip):
			return False
		else:
			model, path, iter = widget.get_tooltip_context(x, y, keyboard_tip)
			value = model.get(iter, 0)
			tooltip.set_markup("<b>Filename: </b> %s \n <b>Size: </b> %s \n <b>Date: </b> %s" %(value[0],'(UNKNOWN)','(UNKNOWN)'))
			filename=model.get(iter, 0)[0]
			filepath=model.get(iter, 2)[0]
			filenameabs=filepath+os.sep+filename
			
			#thumbfilenameabs=model.get(iter, 3)[0]
			if isinstance(self.plugin.preferences['preview_size'], int):
				w=self.plugin.preferences['preview_size']
				h=self.plugin.preferences['preview_size']
			else:
				w=480
				h=480
			# TODO: textfiles: use text not thumb for preview
			try:
				pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filenameabs,w,h)
				tooltip.set_icon(pixbuf)
			except:
				try:
					thumbfilenameabs=self.thumbnailfile(filepath,filename)
					pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(thumbfilenameabs,w,h)
					tooltip.set_icon(pixbuf)
				except:
					logger.debug('No such thumbnail:%s ',filenameabs)
					tooltip.set_icon_from_stock(gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_DIALOG)
			widget.set_tooltip_item(tooltip, path)
			return True

	
	def file_to_image(self,filenameabs,thumbfilenameabs):
		logger.debug('file_to_image()');
		extension=filenameabs.split(".")[-1].upper()
		print extension
		magickextensions=('SVG','PDF','PS','EPS','DVI','DJVU','RAW','DOT','HTML','HTM','TTF','XCF')
		textextensions=('SH','BAT','TXT','C','C++','CPP','H','H++','HPP','PY','PL')
		#'AVI','MPG','M2V','M4V','MPEG'
		if extension in magickextensions:
			# pdf to thumbnail
			try:
				#touch the thumbnail first
				open(thumbfilenameabs, "a")
				#filenameabs=path+os.sep+filename
				logger.debug('  trying Imagemagick')
				filenameabs_p1=filenameabs +'[0]'
				pdftopng = Application(pdftopng_cmd)
				pdftopng.spawn((filenameabs_p1, thumbfilenameabs))
			except:
				logger.debug('  Error converting PDF')
		elif extension in textextensions:
			#convert -size 400x  caption:@-  caption_manual.gif
			try:
				#touch the thumbnail first
				open(thumbfilenameabs, "a")
				textcont='caption:'
				file = open(filenameabs)
				linecount=0;
				# lines: 18 at 128px
				# linewidth 35 at 128px
				while linecount<18:
				    line = file.readline()
				    if not line:
				        break
				    linecount+=1
				    textcont+=line[0:35]
				logger.debug('  trying TXT')
				
				txttopng_cmd = ('convert','-family','System','-size', '200x320', '-frame', '4' ,'-quality','40')
				txttopng = Application(txttopng_cmd)
				txttopng.spawn((textcont,thumbfilenameabs))
			except:
				logger.warn('  Error converting TXT')
	

	def set_current_folder(self,path):
		self.store.clear()
		if isinstance(self.plugin.preferences['icon_size'],int):
			w = self.plugin.preferences['icon_size'] 
			h = self.plugin.preferences['icon_size']
		else:
			w=128
			h=128

		filelist = os.listdir(path)
		for filename in filelist:
			pixbuf=None
			#	self.textbuffer.insert_at_cursor(filename+'\n')		
			filenameabs=path+os.sep+filename
			thumbfilenameabs=filenameabs
			if os.path.isfile(filenameabs) and filename[0]!='.': #.encode('utf-8')):
				try:
					pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filenameabs,w,h)
					#pixbuf = rotate_pixbuf(pixbuf)
				except:
					logger.debug('No such image: %s', filenameabs)
					thumbfilenameabs=self.thumbnailfile(path,filename)
					# ToDo: check for existance
					# ToDo: check age
					try:
						print "thumb check:" +thumbfilenameabs
						#print os.stat(thumbfilenameabs)[st_mtime]
						if (not os.path.isfile(thumbfilenameabs)) or (os.stat(thumbfilenameabs).st_mtime < os.stat(filenameabs).st_mtime): 				
							#print 'thumbnail does not  exist or too old'
							self.file_to_image(filenameabs,thumbfilenameabs)
						#else: 
							#print 'thumbnail up to date'	
						pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(thumbfilenameabs,w,h)
					except:
						logger.debug('No such thumbnail:%s %s',thumbfilenameabs, filenameabs)
						widget = gtk.HBox() # Need *some* widget here...
						pixbuf = widget.render_icon(gtk.STOCK_MISSING_IMAGE,gtk.ICON_SIZE_DIALOG)
							
				if pixbuf:
					self.store.append( [filename,pixbuf,path] )


	def on_open_page(self, ui, page, path):
		try:
			#print path
			#print encode_filename(page.name)
			self.set_current_folder(str(self.plugin.ui.notebook.get_attachments_dir(page)+os.sep))
		except AssertionError:
			pass






Follow ups

References