PyXR

c:\projects\bitpim\src \ fileview.py



0001 #!/usr/bin/env python
0002 
0003 ### BITPIM
0004 ###
0005 ### Copyright (C) 2003-2006 Roger Binns <rogerb@rogerbinns.com>
0006 ###
0007 ### This program is free software; you can redistribute it and/or modify
0008 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0009 ###
0010 ### $Id: fileview.py 4412 2007-09-28 01:28:56Z djpham $
0011 
0012 
0013 ###
0014 ### File viewer
0015 ###
0016 from __future__ import with_statement
0017 import os
0018 import copy
0019 import cStringIO
0020 import time
0021 import base64
0022 import phone_media_codec
0023 import wx
0024 import guihelper
0025 import aggregatedisplay
0026 import pubsub
0027 import common
0028 import widgets
0029 import guiwidgets
0030 import shutil
0031 import database
0032 import helpids
0033 import tempfile
0034 
0035 
0036 basename=common.basename
0037 stripext=common.stripext
0038 getext=common.getext
0039 
0040 
0041 #-------------------------------------------------------------------------------
0042 class MediaDataObject(database.basedataobject):
0043     # modified_datatime is unix time
0044     _knownproperties=['name', 'origin', 'index', 'timestamp']
0045     _knownlistproperties=database.basedataobject._knownlistproperties.copy()
0046     _knownlistproperties.update( { 'mediadata': ['data'] })
0047     def __init__(self, data=None):
0048         if data is None or not isinstance(data, MediaEntry):
0049             return;
0050         self.update(data.get_db_dict())
0051 mediaobjectfactory=database.dataobjectfactory(MediaDataObject)
0052 
0053 #-------------------------------------------------------------------------------
0054 class MediaEntry(object):
0055     _id_index=0
0056     _max_id_index=999
0057     def __init__(self):
0058         self._data={ 'serials': [] }
0059         self._create_id()
0060 
0061     def get(self):
0062         res=copy.deepcopy(self._data, None, {})
0063         # account for the medidadata field
0064         if res.has_key('mediadata'):
0065             if res['mediadata'] is not None:
0066                 res['mediadata']=[{'data': buffer(res['mediadata']) }]
0067             else:
0068                 del res['mediadata']
0069         return res
0070     def set(self, d):
0071         self._data={}
0072         self._data.update(d)
0073         # check for mediadata
0074         if d.get('mediadata', None) is not None:
0075             self._data['mediadata']=str(d['mediadata'][0]['data'])
0076 
0077     def get_db_dict(self):
0078         return self.get()
0079     def set_db_dict(self, d):
0080         self.set(d)
0081 
0082     def _create_id(self):
0083         "Create a BitPim serial for this entry"
0084         self._data.setdefault("serials", []).append(\
0085             {"sourcetype": "bitpim",
0086              "id": '%.3f%03d'%(time.time(), MediaEntry._id_index) })
0087         if MediaEntry._id_index<MediaEntry._max_id_index:
0088             MediaEntry._id_index+=1
0089         else:
0090             MediaEntry._id_index=0
0091     def _get_id(self):
0092         s=self._data.get('serials', [])
0093         for n in s:
0094             if n.get('sourcetype', None)=='bitpim':
0095                 return n.get('id', None)
0096         return None
0097     def _set_id(self, id):
0098         s=self._data.get('serials', [])
0099         for n in s:
0100             if n.get('sourcetype', None)=='bitpim':
0101                 n['id']=id
0102                 return
0103         self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
0104     id=property(fget=_get_id, fset=_set_id)
0105 
0106     def _set_or_del(self, key, v, v_list=[]):
0107         if v is None or v in v_list:
0108             if self._data.has_key(key):
0109                 del self._data[key]
0110         else:
0111             self._data[key]=v
0112 
0113     def _get_origin(self):
0114         return self._data.get('origin', '')
0115     def _set_origin(self, v):
0116         if v is None:               
0117             if self._data.has_key('origin'):
0118                 del self._data['origin']
0119                 return
0120         if not isinstance(v, (str, unicode)):
0121             raise TypeError,'not a string or unicode type'
0122         self._data['origin']=v
0123     origin=property(fget=_get_origin, fset=_set_origin)
0124 
0125     def _get_mediadata(self):
0126         return self._data.get('mediadata', None)
0127     def _set_mediadata(self, v):
0128         if v is not None:
0129             self._set_or_del('mediadata', v, [])
0130     mediadata=property(fget=_get_mediadata, fset=_set_mediadata)
0131 
0132     def _get_name(self):
0133         return self._data.get('name', '')
0134     def _set_name(self, v):
0135         self._set_or_del('name', v, ('',))
0136     name=property(fget=_get_name, fset=_set_name)
0137 
0138     def _get_index(self):
0139         return self._data.get('index', -1)
0140     def _set_index(self, v):
0141         self._set_or_del('index', v, ('',))
0142     index=property(fget=_get_index, fset=_set_index)
0143 
0144     def _get_timestamp(self):
0145         return self._data.get('timestamp', None)
0146     def _set_timestamp(self, v):
0147         if v is not None:
0148             if not isinstance(v, (int, float)):
0149                 raise TypeError('timestamp property is an int arg')
0150             v=int(v)
0151         self._set_or_del('timestamp', v)
0152     timestamp=property(fget=_get_timestamp, fset=_set_timestamp)
0153 
0154 def DrawTextWithLimit(dc, x, y, text, widthavailable, guardspace, term="..."):
0155     """Draws text and if it will overflow the width available, truncates and  puts ... at the end
0156 
0157     @param x: start position for text
0158     @param y: start position for text
0159     @param text: the string to draw
0160     @param widthavailable: the total amount of space available
0161     @param guardspace: if the text is longer than widthavailable then this amount of space is
0162              reclaimed from the right handside and term put there instead.  Consequently
0163              this value should be at least the width of term
0164     @param term: the string that is placed in the guardspace if it gets truncated.  Make sure guardspace
0165              is at least the width of this string!
0166     @returns: The extent of the text that was drawn in the end as a tuple of (width, height)
0167     """
0168     w,h=dc.GetTextExtent(text)
0169     if w<widthavailable:
0170         dc.DrawText(text,x,y)
0171         return w,h
0172     extents=dc.GetPartialTextExtents(text)
0173     limit=widthavailable-guardspace
0174     # find out how many chars in we have to go before hitting limit
0175     for i,offset in enumerate(extents):
0176         if offset>limit:
0177             break
0178     # back off 1 in case the new text's a tad long
0179     if i:
0180         i-=1
0181     text=text[:i]+term
0182     w,h=dc.GetTextExtent(text)
0183     assert w<=widthavailable
0184     dc.DrawText(text, x, y)
0185     return w,h
0186 
0187 media_codec=phone_media_codec.codec_name
0188 class MyFileDropTarget(wx.FileDropTarget):
0189     def __init__(self, target, drag_over=False, enter_leave=False):
0190         wx.FileDropTarget.__init__(self)
0191         self.target=target
0192         self.drag_over=drag_over
0193         self.enter_leave=enter_leave
0194         
0195     def OnDropFiles(self, x, y, filenames):
0196         return self.target.OnDropFiles(x,y,filenames)
0197 
0198     def OnDragOver(self, x, y, d):
0199         if self.drag_over:
0200             return self.target.OnDragOver(x,y,d)
0201         return wx.FileDropTarget.OnDragOver(self, x, y, d)
0202 
0203     def OnEnter(self, x, y, d):
0204         if self.enter_leave:
0205             return self.target.OnEnter(x,y,d)
0206         return wx.FileDropTarget.OnEnter(self, x, y, d)
0207 
0208     def OnLeave(self):
0209         if self.enter_leave:
0210             return self.target.OnLeave()
0211         return wx.FileDropTarget.OnLeave(self)
0212 
0213 class FileView(wx.Panel, widgets.BitPimWidget):
0214 
0215     # Various DC objects used for drawing the items.  We have to calculate them in the constructor as
0216     # the app object hasn't been constructed when this file is imported.
0217     item_selection_brush=None
0218     item_selection_pen=None
0219     item_line_font=None
0220     item_term="..."
0221     item_guardspace=None
0222     # Files we should ignore
0223     skiplist= ( 'desktop.ini', 'thumbs.db', 'zbthumbnail.info' )
0224     database_key=""
0225 
0226     # how much data do we want in call to getdata
0227     NONE=0
0228     SELECTED=1
0229     ALL=2
0230 
0231     # maximum length of a filename
0232     maxlen=-1  # set via phone profile
0233     # acceptable characters in a filename
0234     filenamechars=None # set via phone profile
0235     # Default Help page, children can override
0236     helpid=helpids.ID_TAB_MEDIA
0237 
0238     def __init__(self, mainwindow, parent, media_root, watermark=None):
0239         wx.Panel.__init__(self,parent,style=wx.CLIP_CHILDREN)
0240         # adjust the DB to accommodate the new schema if necessary
0241         self._fixupdb(mainwindow.database)
0242         # item attributes
0243         if self.item_selection_brush is None:
0244             self.item_selection_brush=wx.TheBrushList.FindOrCreateBrush("MEDIUMPURPLE2", wx.SOLID)
0245             self.item_selection_pen=wx.ThePenList.FindOrCreatePen("MEDIUMPURPLE2", 1, wx.SOLID)
0246             f1=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.BOLD)
0247             f2=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
0248             self.item_line_font=[f1, f2, f2, f2]
0249             dc=wx.MemoryDC()
0250             dc.SelectObject(wx.EmptyBitmap(100,100))
0251             self.item_guardspace=dc.GetTextExtent(self.item_term)[0]
0252             del dc
0253 
0254         # no redraw ickiness
0255         # wx.EVT_ERASE_BACKGROUND(self, lambda evt: None)
0256         
0257         self.parent=parent
0258         self.mainwindow=mainwindow
0259         self.thedir=None
0260         self.wildcard="I forgot to set wildcard in derived class|*"
0261         self.__dragging=False
0262         self._in_context_menu=False
0263         self.media_root=media_root
0264         self.show_thumbnail=True
0265         self.active_section=""
0266         # origins that should not be used for phonebook
0267         self.excluded_origins=()
0268 
0269         # use the aggregatedisplay to do the actual item display
0270         self.aggdisp=aggregatedisplay.Display(self, self, watermark) # we are our own datasource
0271         self.vbs=wx.BoxSizer(wx.VERTICAL)
0272 
0273         ### toolbar
0274         self.tb=wx.ToolBar(self, -1, style=wx.TB_3DBUTTONS|wx.TB_HORIZONTAL)
0275         self.tb.SetToolBitmapSize(wx.Size(18,18))
0276         sz=self.tb.GetToolBitmapSize()
0277 
0278         # list and thumbnail tools
0279         self.tb.AddRadioLabelTool(guihelper.ID_FILEVIEW_THUMBNAIL, "Thumbnail",
0280                                     wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_THUMB_VIEW, wx.ART_TOOLBAR, sz),
0281                                     wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_THUMB_VIEW, wx.ART_TOOLBAR, sz),
0282                                     "Show Thumbnails", "Show items as thumbnails")
0283         self.tb.AddRadioLabelTool(guihelper.ID_FILEVIEW_LIST, "List", 
0284                                     wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_LIST_VIEW, wx.ART_TOOLBAR, sz),
0285                                     wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_LIST_VIEW, wx.ART_TOOLBAR, sz),
0286                                     "Show List", "Show items in a list")
0287         self.vbs.Add(self.tb, 0, wx.EXPAND|wx.ALL, 1)
0288         self.aggr_sizer=self.vbs.Add(self.aggdisp, 1, wx.EXPAND|wx.ALL, 2)
0289 
0290         # main list
0291         column_info=self.GetColumnNames()
0292         self.item_list=guiwidgets.BitPimListCtrl(self, column_info)
0293         self.nodes={}
0294         self.nodes_keys={}
0295         self.item_list.ResetView(self.nodes, self.nodes_keys)
0296         self.item_sizer=self.vbs.Add(self.item_list, 1, wx.EXPAND|wx.ALL, 2)
0297         self.item_sizer.Show(False)
0298         self.note=self.vbs.Add(wx.StaticText(self, -1, '  Note: Click column headings to sort data'), 0, wx.ALIGN_CENTRE|wx.BOTTOM, 10)
0299         self.note.Show(False)
0300         self.SetSizer(self.vbs)
0301         timerid=wx.NewId()
0302         self.thetimer=wx.Timer(self, timerid)
0303         wx.EVT_TIMER(self, timerid, self.OnTooltipTimer)
0304         self.motionpos=None
0305         wx.EVT_MOUSE_EVENTS(self.aggdisp, self.OnMouseEvent)
0306         self.tipwindow=None
0307         if True: # guihelper.IsMSWindows() or guihelper.IsGtk():
0308             # turn on drag-and-drag for all platforms
0309             wx.EVT_MOTION(self.aggdisp, self.OnStartDrag)
0310 
0311         # Menus
0312 
0313         self.itemmenu=wx.Menu()
0314         self.itemmenu.Append(guihelper.ID_FV_OPEN, "Open")
0315         self.itemmenu.Append(guihelper.ID_FV_SAVE, "Save ...")
0316         self.itemmenu.AppendSeparator()
0317         if guihelper.IsMSWindows():
0318             self.itemmenu.Append(guihelper.ID_FV_COPY, "Copy")
0319         self.itemmenu.Append(guihelper.ID_FV_DELETE, "Delete")
0320         self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename")
0321         self.movemenu=wx.Menu()
0322         self.itemmenu.AppendMenu(guihelper.ID_FV_MOVE, "Move to", self.movemenu)
0323         self.itemmenu.AppendSeparator()
0324         self.itemmenu.Append(guihelper.ID_FV_REPLACE, "Replace")
0325         # self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename")
0326         self.itemmenu.Append(guihelper.ID_FV_REFRESH, "Refresh")
0327 
0328         self.bgmenu=wx.Menu()
0329         self.bgmenu.Append(guihelper.ID_FV_ADD, "Add ...")
0330         self.bgmenu.Append(guihelper.ID_FV_PASTE, "Paste")
0331         self.bgmenu.Append(guihelper.ID_FV_REFRESH, "Refresh")
0332 
0333         wx.EVT_MENU(self.tb, guihelper.ID_FILEVIEW_THUMBNAIL, self.OnThumbnailView)
0334         wx.EVT_MENU(self.tb, guihelper.ID_FILEVIEW_LIST, self.OnListView)
0335 
0336 
0337         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_OPEN, self.OnLaunch)
0338         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_SAVE, self.OnSave)
0339         if guihelper.IsMSWindows():
0340             wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_COPY, self.OnCopy)
0341         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_DELETE, self.OnDelete)
0342         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_RENAME, self.OnRename)
0343         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_REPLACE, self.OnReplace)
0344         wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh())
0345         wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_ADD, self.OnAdd)
0346         wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_PASTE, self.OnPaste)
0347         wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh)
0348 
0349         wx.EVT_RIGHT_UP(self.aggdisp, self.OnRightClick)
0350         wx.EVT_LIST_ITEM_RIGHT_CLICK(self.item_list, self.item_list.GetId(), self.OnRightClick)
0351         aggregatedisplay.EVT_ACTIVATE(self.aggdisp, self.aggdisp.GetId(), self.OnLaunch)
0352         wx.EVT_LIST_ITEM_ACTIVATED(self.item_list, self.item_list.GetId(), self.OnLaunch)
0353 
0354         self.droptarget=MyFileDropTarget(self)
0355         self.SetDropTarget(self.droptarget)
0356         wx.EVT_SIZE(self, self.OnSize)
0357         wx.EVT_IDLE(self, self.OnIdle)
0358         wx.EVT_KEY_DOWN(self.aggdisp, self.OnKeyDown)
0359         wx.EVT_KEY_UP(self.aggdisp, self.OnKeyUp)
0360         self.tb.Realize()
0361         pubsub.subscribe(self.OnMediaInfo, pubsub.REQUEST_MEDIA_INFO)
0362         pubsub.subscribe(self.OnMediaOpen, pubsub.REQUEST_MEDIA_OPEN)
0363 
0364     def OnIdle(self, _):
0365         "Save out changed data"
0366         if self.modified:
0367             self.modified=False
0368             self._populatefs(self._data)
0369             self.OnListRequest() # broadcast changes
0370 
0371     def OnKeyDown(self, evt):
0372         if guihelper.IsGtk():
0373             if evt.GetKeyCode()==wx.WXK_SHIFT:
0374                 self._shift_down=True
0375         else:
0376             self._shift_down=evt.ShiftDown()
0377         evt.Skip()
0378 
0379     def OnKeyUp(self, evt):
0380         if guihelper.IsGtk():
0381             if evt.GetKeyCode()==wx.WXK_SHIFT:
0382                 self._shift_down=False
0383         else:
0384             self._shift_down=evt.ShiftDown()
0385         evt.Skip()
0386 
0387     def OnThumbnailView(self, _):
0388         self.thetimer.Stop()
0389         self.show_thumbnail=True
0390         self.item_sizer.Show(False)
0391         self.note.Show(False)
0392         self.aggr_sizer.Show(True)
0393         self.aggdisp.SetFocus()
0394         self.vbs.Layout()
0395 
0396     def OnListView(self, _):
0397         self.thetimer.Stop()
0398         self.show_thumbnail=False
0399         self.aggr_sizer.Show(False)
0400         # resize to hide the thumbnails otherwise it still gets the mouse scroll events.
0401         self.aggdisp.SetSize((1,1))
0402         self.item_sizer.Show(True)
0403         self.item_list.SetFocus()
0404         self.note.Show(True)
0405         self.vbs.Layout()
0406 
0407     def OnSelected(self, node):
0408         self.active_section=self.media_root.GetNodeName(self, node)
0409         self.aggdisp.SetActiveSection(self.active_section)
0410         self.MakeMoveMenu()
0411         self.OnRefreshList()
0412 
0413     def GetRightClickMenuItems(self, node):
0414         # we set these values so that the event hander knows only to save
0415         # this origin rather than all media, we clear these values after
0416         # the menu is dismissed (see OnRightClickMenuExit below)
0417         self.media_root.widget_to_save=self
0418         self.media_root.origin_to_save=self.active_section
0419         result=[]
0420         result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITADDENTRY, "Add to %s" % self.active_section, "Add a new media items"))
0421         result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITDELETEENTRY, "Delete Selected", "Delete Selected Items"))
0422         result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITSELECTALL, "Select All", "Select All Items"))
0423         if guihelper.IsMSWindows():
0424             result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITCOPY, "Copy", "Copy Selected Items"))
0425         result.append((widgets.BitPimWidget.MENU_SPACER, 0, "", ""))
0426         result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EXPORT_MEDIA_TO_DIR, "Export %s to Folder ..." % self.active_section, "Export the media to a folder on your hard drive"))
0427         result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EXPORT_MEDIA_TO_ZIP, "Export %s to Zip File ..." % self.active_section, "Export the media to a zip file"))
0428         return result
0429 
0430     def OnRightClickMenuExit(self):
0431         # clear these values now that the event has been processed
0432         self.media_root.widget_to_save=None
0433         self.media_root.origin_to_save=""
0434 
0435     def MakeMoveMenu(self):
0436         # redo the move menu
0437         menuItems = self.movemenu.GetMenuItems()
0438         for i, menuItem in enumerate(menuItems):
0439             self.Unbind(wx.EVT_MENU, id=menuItem.GetId())
0440             self.movemenu.DeleteItem(menuItem)
0441 
0442         # get the list of origins
0443         origins=self.media_root.GetNodeList(self)
0444         origins.remove(self.active_section)
0445         if len(origins):
0446             for origin in origins:
0447                 mid=wx.NewId()
0448                 self.movemenu.Append(mid, origin)
0449                 wx.EVT_MENU(self, mid, self.OnMoveItem)
0450 
0451     def GetColumnNames(self):
0452         columns=[]
0453         columns.append(("Name", 120, False))
0454         columns.append(("Size/bytes", 80, True))
0455         columns.append(("Date Modified", 120, False))
0456         columns.append(("File Details", 380, False))
0457         return columns
0458 
0459     def OnSize(self, evt):
0460         # stop the tool tip from poping up when we're resizing!
0461         if self.thetimer.IsRunning():
0462             self.thetimer.Stop()
0463         evt.Skip()
0464 
0465     def OnRightClick(self, evt):
0466         """Popup the right click context menu
0467 
0468         @param widget:  which widget to popup in
0469         @param position:  position in widget
0470         @param onitem: True if the context menu is for an item
0471         """
0472         if len(self.GetSelectedItems()):
0473             menu=self.itemmenu
0474             item=self.GetSelectedItems()[0]
0475             single=len(self.GetSelectedItems())==1
0476             menu.Enable(guihelper.ID_FV_RENAME, single)
0477             # we always launch on mac
0478             if not guihelper.IsMac():
0479                 menu.FindItemById(guihelper.ID_FV_OPEN).Enable(guihelper.GetOpenCommand(item.mimetypes, item.name) is not None)
0480         else:
0481             menu=self.bgmenu
0482             menu.Enable(guihelper.ID_FV_PASTE, self.CanPaste())
0483         if menu is None:
0484             return
0485         # we're putting up the context menu, quit the tool tip timer.
0486         self._in_context_menu=True
0487         self.aggdisp.PopupMenu(menu, evt.GetPosition())
0488         self._in_context_menu=False
0489 
0490     def OnMoveItem(self, evt):
0491         new_origin=None
0492         items=self.GetSelectedItems()
0493         new_origin=self.movemenu.FindItemById(evt.GetId()).GetLabel()
0494         for item in items:
0495             if new_origin!=None and new_origin in self.media_root.GetNodeList(self):
0496                 # make sure this name is not already used
0497                 for i in self._data[self.database_key]:
0498                     if self._data[self.database_key][i].origin==new_origin and \
0499                        self._data[self.database_key][i].name==item.name:
0500                         wx.MessageBox("A file with the same name already exists in %s!" % new_origin, "Move Error", wx.OK|wx.ICON_EXCLAMATION)
0501                         return
0502                 wx.BeginBusyCursor()
0503                 item.ChangeOriginInIndex(new_origin)
0504                 self.OnRefresh()
0505                 wx.EndBusyCursor()
0506 
0507     def _launch(self, item):
0508         # Open/Launch the specified item
0509         me=self._data[self.database_key][item.key]
0510         fname=self._gettempfile(me)
0511         if guihelper.IsMac():
0512             import findertools
0513             findertools.launch(fname)
0514             return
0515         cmd=guihelper.GetOpenCommand(item.mimetypes, fname)
0516         if cmd is None:
0517             wx.Bell()
0518         else:
0519             wx.Execute(cmd, wx.EXEC_ASYNC)
0520 
0521     @guihelper.BusyWrapper
0522     def OnLaunch(self, _):
0523         self._launch(self.GetSelectedItems()[0])
0524 
0525     if True: # guihelper.IsMSWindows() or guihelper.IsGtk():
0526         # drag-and-drop files should work on all platforms
0527         def OnStartDrag(self, evt):
0528             evt.Skip()
0529             if not evt.LeftIsDown():
0530                 return
0531             items=self.GetSelectedItems()
0532             if not len(items):
0533                 return
0534             drag_source=wx.DropSource(self)
0535             file_names=wx.FileDataObject()
0536             for item in items:
0537                 me=self._data[self.database_key][item.key]
0538                 fname=self._gettempfile(me)
0539                 if not os.path.isfile(fname):
0540                     continue
0541                 file_names.AddFile(fname)
0542             drag_source.SetData(file_names)
0543             self.__dragging=True
0544             res=drag_source.DoDragDrop(wx.Drag_AllowMove)
0545             self.__dragging=False
0546             # check of any of the files have been removed,
0547             # can't trust result returned by DoDragDrop
0548             for item in items:
0549                 me=self._data[self.database_key][item.key]
0550                 fname=self._gettempfile(me)
0551                 if not os.path.isfile(fname):
0552                     item.RemoveFromIndex()
0553 
0554     def OnMouseEvent(self, evt):
0555         self.motionpos=evt.GetPosition()
0556         # on windows if we quickly move the mouse out of bitpim window we never get an event and we will pop up
0557         # the tooltip when we should not, so we check the position after the timeout and see if it has moved.
0558         self.abs_mouse_pos=wx.GetMousePosition()
0559         evt.Skip()
0560         self.thetimer.Stop()
0561         if evt.AltDown() or evt.MetaDown() or evt.ControlDown() or \
0562            evt.ShiftDown() or evt.Dragging() or evt.IsButton() or \
0563            self._in_context_menu or not self.show_thumbnail:
0564             return
0565         self.thetimer.Start(1750, wx.TIMER_ONE_SHOT)
0566 
0567     def OnTooltipTimer(self, _):
0568         if self._in_context_menu or not self.show_thumbnail or \
0569            wx.GetApp().critical.isSet():
0570             # we're putting up a context menu or main app is busy, forget this
0571             return
0572         # see if we have moved
0573         if self.abs_mouse_pos!=wx.GetMousePosition():
0574             return
0575         x,y=self.aggdisp.CalcUnscrolledPosition(*self.motionpos)
0576         res=self.aggdisp.HitTest(x,y)
0577         if res.item is not None:
0578             try:    self.tipwindow.Destroy()
0579             except: pass
0580             self.tipwindow=res.item.DisplayTooltip(self.aggdisp, res.itemrectscrolled)
0581 
0582     def OnRefresh(self):
0583         # update aggregate view
0584         self.aggdisp.UpdateItems()
0585         self.OnRefreshList()
0586         self.media_root.DoMediaSummary()
0587 
0588     def OnRefreshList(self):
0589         # update list view
0590         self.nodes={}
0591         self.nodes_keys={}
0592         index=0
0593         for k,e in self.sections:
0594             if self.active_section==None or k.label==self.active_section:
0595                 for item in e:
0596                     # replace linefeeds in description
0597                     dlist=item.long.splitlines()
0598                     d=""
0599                     for l in dlist: 
0600                         if len(d):
0601                             d+=" - "
0602                         d+=l
0603                     self.nodes[index]=(item.name, str(item.size), item.timestamp, d)
0604                     self.nodes_keys[index]=item
0605                     index+=1
0606         self.item_list.ResetView(self.nodes, self.nodes_keys)
0607 
0608     def GetSelectedItems(self):
0609         if self.show_thumbnail:
0610             return [item for _,_,_,item in self.aggdisp.GetSelection()]
0611         res=[]
0612         sel=self.item_list.GetSelections()
0613         for sel_idx in sel:
0614             res.append(self.item_list.GetItemData(sel[sel_idx]))
0615         return res
0616 
0617     def GetAllItems(self):
0618         return [item for _,_,_,item in self.aggdisp.GetAllItems()]
0619 
0620     def CanSelectAll(self):
0621         return self.item_list.GetItemCount() > 0
0622 
0623     def OnSelectAll(self, _):
0624         self.aggdisp.SelectAll()
0625         self.item_list.SelectAll()
0626 
0627     def OnSave(self, _):
0628         # If one item is selected we ask for a filename to save.  If
0629         # multiple then we ask for a directory, and users don't get
0630         # the choice to affect the names of files.  Note that we don't
0631         # allow users to select a different format for the file - we
0632         # just copy it as is.
0633         items=self.GetSelectedItems()
0634         if len(items)==1:
0635             ext=getext(items[0].name)
0636             if ext=="": ext="*"
0637             else: ext="*."+ext
0638             with guihelper.WXDialogWrapper(wx.FileDialog(self, "Save item", wildcard=ext, defaultFile=items[0].name, style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR),
0639                                            True) as (dlg, retcode):
0640                 if retcode==wx.ID_OK:
0641                     file(dlg.GetPath(), "wb").write(self._data[items[0].datakey][items[0].key].mediadata)
0642                     if self._data[items[0].datakey][items[0].key].timestamp!=None:
0643                         os.utime(dlg.GetPath(), (self._data[items[0].datakey][items[0].key].timestamp, 
0644                                                  self._data[items[0].datakey][items[0].key].timestamp))
0645         else:
0646             with guihelper.WXDialogWrapper(wx.DirDialog(self, "Save items to", style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON),
0647                                            True) as (dlg, retcode):
0648                 if retcode==wx.ID_OK:
0649                     for item in items:
0650                         fname=item.name.encode(media_codec)
0651                         fname=os.path.join(dlg.GetPath(), basename(fname))
0652                         file(fname, 'wb').write(self._data[item.datakey][item.key].mediadata)
0653                         if self._data[item.datakey][item.key].timestamp!=None:
0654                             os.utime(fname, (self._data[item.datakey][item.key].timestamp, 
0655                                              self._data[item.datakey][item.key].timestamp))
0656 
0657     if guihelper.IsMSWindows():
0658         def OnCopy(self, _):
0659             items=self.GetSelectedItems()
0660             if not len(items):
0661                 # nothing selected
0662                 return
0663             file_names=wx.FileDataObject()
0664             for item in items:
0665                 me=self._data[self.database_key][item.key]
0666                 fname=self._gettempfile(me)
0667                 if not os.path.isfile(fname):
0668                     continue
0669                 file_names.AddFile(fname)
0670             if wx.TheClipboard.Open():
0671                 wx.TheClipboard.SetData(file_names)
0672                 wx.TheClipboard.Close()
0673         def CanCopy(self):
0674             return len(self.GetSelectedItems())
0675 
0676     if guihelper.IsGtk():
0677         # Gtk just pastes the file names as text onto the Clipboard
0678         def OnPaste(self, _=None):
0679             if not wx.TheClipboard.Open():
0680                 # can't access the clipboard
0681                 return
0682             if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT)):
0683                 file_names=wx.TextDataObject()
0684                 has_data=wx.TheClipboard.GetData(file_names)
0685             else:
0686                 has_data=False
0687             wx.TheClipboard.Close()
0688             if has_data:
0689                 # collect file names if any.
0690                 _names=[x for x in file_names.GetText().split('\n') \
0691                         if os.path.isfile(x) ]
0692                 if _names:
0693                     self.OnAddFiles(_names)
0694         def CanPaste(self):
0695             """ Return True if can accept clipboard data, False otherwise
0696             """
0697             if not wx.TheClipboard.Open():
0698                 return False
0699             r=wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT))
0700             if r:
0701                 file_names=wx.TextDataObject()
0702                 r=wx.TheClipboard.GetData(file_names)
0703                 if r:
0704                     for _name in file_names.GetText().split('\n'):
0705                         if not os.path.isfile(_name):
0706                             r=False
0707                             break
0708             wx.TheClipboard.Close()
0709             return r
0710     else:
0711         def OnPaste(self, _=None):
0712             if not wx.TheClipboard.Open():
0713                 # can't access the clipboard
0714                 return
0715             if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME)):
0716                 file_names=wx.FileDataObject()
0717                 has_data=wx.TheClipboard.GetData(file_names)
0718             else:
0719                 has_data=False
0720             wx.TheClipboard.Close()
0721             if has_data:
0722                 self.OnAddFiles(file_names.GetFilenames())
0723 
0724         def CanPaste(self):
0725             """ Return True if can accept clipboard data, False otherwise
0726             """
0727             if not wx.TheClipboard.Open():
0728                 return False
0729             r=wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME))
0730             wx.TheClipboard.Close()
0731             return r
0732 
0733     def CanDelete(self):
0734         if len(self.GetSelectedItems()):
0735             return True
0736         return False        
0737 
0738     def OnDelete(self,_):
0739         items=self.GetSelectedItems()
0740         for item in items:
0741             item.RemoveFromIndex()
0742         self.OnRefresh()
0743 
0744     def AddToIndex(self, file, origin, data, dict, timestamp=None, index=-1):
0745         # see if it is already in the origin
0746         if dict.has_key(self.database_key):
0747             for i in dict[self.database_key]:
0748                 # see if the media is already in the database
0749                 # if so update the existing entry, the display objects have references to the keys, if
0750                 # we delete items the keys go bad and we require a refresh
0751                 if dict[self.database_key][i].name==file and dict[self.database_key][i].origin==origin:
0752                     # update the index
0753                     if index>=0:
0754                         dict[self.database_key][i].index=index 
0755                     # update the timestamp
0756                     if timestamp!=None:
0757                         dict[self.database_key][i].timestamp=timestamp 
0758                     # if there is no data dont update the dict
0759                     # when indexes are updates the data is not provided
0760                     if data!=None and data!='':
0761                         dict[self.database_key][i].mediadata=data
0762                     return
0763         else:
0764             dict[self.database_key]={}
0765         entry=MediaEntry()
0766         entry.name=file
0767         entry.origin=origin
0768         entry.mediadata=data
0769         entry.index=index
0770         entry.timestamp=timestamp
0771         dict[self.database_key][entry.id]=entry
0772         self.modified=True
0773 
0774     def _fixupdb(self, db):
0775         # fixup the database to accommodate the new schema
0776         adjustflg=False
0777         for _,_name,_type in db.getcolumns(self.database_key):
0778             if _name=='mediadata' and _type!='indirectBLOB':
0779                 # old schema, need to adjust
0780                 adjustflg=True
0781                 break
0782         # adjust the table: replace the mediadata field
0783         if adjustflg:
0784             db._altertable(self.database_key, [('mediadata', 'indirectBLOB')],
0785                            ['mediadata'], 1)
0786 
0787     def _gettempfile(self, item):
0788         # store the the media data in a temporary file and return the full file
0789         # path
0790         filename=os.path.join(tempfile.gettempdir(),
0791                               item.name.encode(media_codec))
0792         file(filename, 'wb').write(item.mediadata)
0793         return filename
0794 
0795     def _filename(self, item):
0796         # return the filename associated with a media file
0797         if hasattr(item, 'origin'):
0798             _origin=item.origin
0799             _name=item.name
0800         else:
0801             _origin=item.get('origin', '')
0802             _name=item.get('name', '')
0803         relative_name=os.path.join(_origin,
0804                                    _name.encode(media_codec))
0805         return os.path.join(self.mainwindow.blob_path, relative_name)
0806 
0807     def _save_to_db(self, dict):
0808         db_rr={}
0809         for k,e in dict.items():
0810             db_rr[k]=MediaDataObject(e)
0811             # the media data does not go into the actual database, we store it 
0812             # in a regular file to minimise the database size, it 
0813             # gets very big if the data is stored in it and starts to get slow.
0814         database.ensurerecordtype(db_rr, mediaobjectfactory)
0815         self.mainwindow.database.savemajordict(self.database_key, db_rr)
0816 
0817     def _load_from_db(self, result):
0818         dict=self.mainwindow.database.\
0819                    getmajordictvalues(self.database_key,
0820                                       mediaobjectfactory)
0821         r={}
0822         for k,e in dict.items():
0823             ce=MediaEntry()
0824             ce.set_db_dict(e)
0825             if ce.mediadata is None:
0826                 # try reading the data from the "old" way
0827                 try:
0828                     ce.mediadata=file(self._filename(ce), 'rb').read()
0829                 except:
0830                     # abandon data that has no file it probably means that 
0831                     # blobs directory is corrupted, maybe the user deleted
0832                     # the file themselves
0833                     pass
0834             r[ce.id]=ce
0835         result.update({ self.database_key: r})
0836         return result
0837 
0838     def convert_to_dict(self, result, res=None):
0839         if res==None:
0840             res={}
0841         for rec in result[self.database_key]:
0842             fname=result[self.database_key][rec]['name']
0843             # legacy format does not always contain origin
0844             if result[self.database_key][rec].has_key('origin'):
0845                 origin=result[self.database_key][rec]['origin']
0846             else:
0847                 origin=self.default_origin
0848             data, timestamp=self.get_media_data(result, fname, origin)
0849             # None and '' are treated differently by viewer. data=='' means we could not read from 
0850             # the phone but we still want to display, 
0851             # None means we do not want to display like the builtin media
0852             if data=='': # no data read, see if we have this file in the dict already and use its data, 
0853                          # provides a workaround for phones that don't let us read the ringtones back 
0854                          # which we wrote in the first place
0855                 for i in self._data[self.database_key]:
0856                     if self._data[self.database_key][i].name==result[self.database_key][rec]['name'] \
0857                              and self._data[self.database_key][i].origin==result[self.database_key][rec]['origin'] \
0858                              and self._data[self.database_key][i].mediadata!=None:
0859                         data=self._data[self.database_key][i].mediadata
0860             self.AddToIndex(result[self.database_key][rec]['name'], origin, data, res, timestamp, rec)
0861         return res
0862 
0863     def get_media_data(self, result, name, origin):
0864         data=None
0865         timestamp=None # unix time
0866         if result.has_key(self.media_key):
0867             if result[self.media_key].has_key("new_media_version"):
0868                 if result[self.media_key].has_key(origin):
0869                     if result[self.media_key][origin].has_key(name):
0870                         data=result[self.media_key][origin][name]['data']
0871                         if result[self.media_key][origin][name].has_key('timestamp'):
0872                             timestamp=result[self.media_key][origin][name]['timestamp']
0873                 pass
0874             # old style data with no origin info, means that the filenames have
0875             # to be unique across all the origins for the widget
0876             elif result[self.media_key].has_key(name):
0877                 data=result[self.media_key][name]
0878         return data, timestamp
0879 
0880     def updateindex(self, index):
0881         self._data=self.convert_to_dict(index, self._data)
0882         # delete unused medias
0883         del_list=[]
0884         for i in self._data[self.database_key]:
0885             found=False
0886             for rec in index[self.database_key]:
0887                 if self._data[self.database_key][i].name==index[self.database_key][rec]['name'] \
0888                          and self._data[self.database_key][i].origin==index[self.database_key][rec]['origin']:
0889                     found=True
0890                     break
0891             if not found:
0892                 del_list.append(i)
0893         for i in del_list:
0894             del self._data[self.database_key][i]
0895         self.modified=True
0896 
0897     def populatefs(self, dict):
0898         res={}
0899         dict=self.convert_to_dict(dict)
0900         return self._populatefs(dict)
0901 
0902     def _populatefs(self, dict):
0903         self._save_to_db(dict.get(self.database_key, {}))
0904         return dict
0905 
0906     def populate(self, dict):
0907         if not dict.has_key('media_from_db'):
0908             # update the existing dict (the key are referenced from the display objects, doing a refresh causes
0909             # a long delay
0910             self._data=self.convert_to_dict(dict, self._data)
0911             # delete unused medias
0912             del_list=[]
0913             for i in self._data[self.database_key]:
0914                 found=False
0915                 for rec in dict[self.database_key]:
0916                     if self._data[self.database_key][i].name==dict[self.database_key][rec]['name'] \
0917                              and self._data[self.database_key][i].origin==dict[self.database_key][rec]['origin']:
0918                         found=True
0919                         break
0920                 if not found:
0921                     del_list.append(i)
0922             for i in del_list:
0923                 del self._data[self.database_key][i]
0924 
0925             self.modified=True
0926             self.OnRefresh()
0927         else:
0928             if dict[self.database_key]!=self._data[self.database_key]:
0929                 self._data[self.database_key]=dict[self.database_key].copy()
0930                 self.modified=True
0931                 self.OnRefresh()
0932                     
0933     def getfromfs(self, result):
0934         if self.mainwindow.database.doestableexist(self.database_key):
0935             result=self._load_from_db(result)
0936         else: # if there is no data in the database then try to read the legacy media
0937             res={}
0938             res=self.legacygetfromfs(res, self.media_key, self.database_key, self.CURRENTFILEVERSION) 
0939             # if we got anything save into the database and delete the old index file
0940             if res.has_key(self.database_key) and len(res[self.database_key])!=0:
0941                 result.update(self.convert_to_dict(res))
0942                 self._populatefs(result)
0943                 self.delete_old_media()
0944             else:
0945                 result=self._load_from_db(result)
0946         result['media_from_db']=1
0947         return result
0948 
0949     def legacygetfromfs(self, result, key, indexkey, currentversion):
0950         dict={}
0951         index_found=False
0952         if os.path.isdir(self.thedir):
0953             for file in os.listdir(self.thedir):
0954                 if file=='index.idx':
0955                     d={}
0956                     d['result']={}
0957                     common.readversionedindexfile(os.path.join(self.thedir, file), d, self.versionupgrade, currentversion)
0958                     result.update(d['result'])
0959                     index_found=True
0960                 elif file.lower() in self.skiplist:
0961                     # ignore windows detritus
0962                     continue
0963                 elif key is not None:
0964                     dict[file.decode(media_codec)]=open(os.path.join(self.thedir, file), "rb").read()
0965         if index_found:
0966             if key is not None:
0967                 result[key]=dict
0968             if indexkey not in result:
0969                 result[indexkey]={}
0970         return result
0971 
0972     def delete_old_media(self):
0973         # No longer do this since it messes up the virtual tables setup!
0974         pass
0975 
0976     def OnDropFiles(self, _, dummy, filenames):
0977         # There is a bug in that the most recently created tab
0978         # in the notebook that accepts filedrop receives these
0979         # files, not the most visible one.  We find the currently
0980         # viewed tab in the notebook and send the files there
0981         if self.__dragging:
0982             # I'm the drag source, forget 'bout it !
0983             return
0984         target=self # fallback
0985         t=self._tree.mw.GetCurrentActiveWidget()
0986         if isinstance(t, FileView):
0987             # changing target in dragndrop
0988             target=t
0989         target.OnAddFiles(filenames)
0990 
0991     def CanAdd(self):
0992         return True
0993 
0994     def OnAdd(self, _=None):
0995         with guihelper.WXDialogWrapper(wx.FileDialog(self, "Choose files", style=wx.OPEN|wx.MULTIPLE, wildcard=self.wildcard),
0996                                        True) as (dlg, retcode):
0997             if retcode==wx.ID_OK:
0998                 self.OnAddFiles(dlg.GetPaths())
0999 
1000     def CanRename(self):
1001         return len(self.GetSelectedItems())==1
1002     # subclass needs to define this
1003     media_notification_type=None
1004     def OnRename(self, _=None):
1005         items=self.GetSelectedItems()
1006         if len(items)!=1:
1007                # either none or more than 1 items selected
1008                return
1009         old_name=items[0].name
1010         with guihelper.WXDialogWrapper(wx.TextEntryDialog(self, "Enter a new name:", "Item Rename",
1011                                                           old_name),
1012                                        True) as (dlg, retcode):
1013             if retcode==wx.ID_OK:
1014                 new_name=dlg.GetValue()
1015                 if len(new_name) and new_name!=old_name:
1016                     items[0].name=new_name
1017                     items[0].RenameInIndex(new_name)
1018                     pubsub.publish(pubsub.MEDIA_NAME_CHANGED,
1019                                    data={ pubsub.media_change_type: self.media_notification_type,
1020                                           pubsub.media_old_name: old_name,
1021                                           pubsub.media_new_name: new_name })
1022           
1023     def OnAddFiles(self,_):
1024         raise NotImplementedError
1025 
1026     def OnReplace(self, _=None):
1027         items=self.GetSelectedItems()
1028         if len(items)!=1:
1029                # either none or more than 1 items selected
1030                return
1031         with guihelper.WXDialogWrapper(wx.FileDialog(self, "Choose file",
1032                                                      style=wx.OPEN, wildcard=self.wildcard),
1033                                        True) as (dlg, retcode):
1034             if retcode==wx.ID_OK:
1035                 self.ReplaceContents(items[0].name, items[0].origin, dlg.GetPath())
1036                 items[0].Refresh()
1037 
1038     def get_media_name_from_filename(self, filename, newext=''):
1039         path,filename=os.path.split(filename)
1040         # degrade to ascii
1041         degraded_fname=common.encode_with_degrade(filename, 'ascii', 'ignore')
1042         # decode with media codec in case it contains escape characters
1043         degraded_fname=degraded_fname.decode(media_codec)
1044         if not 'A' in self.filenamechars:
1045             degraded_fname=degraded_fname.lower()
1046         if not 'a' in self.filenamechars:
1047             degraded_fname=degraded_fname.upper()
1048         if len(newext):
1049             degraded_fname=stripext(degraded_fname)
1050         media_name="".join([x for x in degraded_fname if x in self.filenamechars])
1051         media_name=media_name.replace("  "," ").replace("  ", " ")  # remove double spaces
1052         if len(newext):
1053             media_name+='.'+newext
1054         if len(media_name)>self.maxlen:
1055             chop=len(media_name)-self.maxlen
1056             media_name=stripext(media_name)[:-chop].strip()+'.'+getext(media_name)
1057         return media_name
1058 
1059     def getdata(self,dict,want=NONE):
1060         items=None
1061         media_index={}
1062         media_data={}
1063         old_dict={}
1064         data_key=0
1065 
1066         if want==self.SELECTED:
1067             items=self.GetSelectedItems()
1068             if len(items)==0:
1069                 want=self.ALL
1070 
1071         if want==self.SELECTED:
1072             if items is not None:
1073                 media_data={}
1074                 i=0
1075                 for item in items:
1076                     me=self._data[item.datakey][item.key]
1077                     if me.mediadata!=None:
1078                         media_data[data_key]={'name': me.name, 'data': me.mediadata, 'origin': me.origin}
1079                         data_key+=1
1080 
1081         # convert into the old format
1082         index_cnt=-1
1083         for i in self._data[self.database_key]:
1084             me=self._data[self.database_key][i]
1085             # make sure the index is unique
1086             if me.index in media_index:
1087                 while index_cnt in media_index:
1088                     index_cnt-=1
1089                 index=index_cnt
1090             else:
1091                 index=me.index
1092             media_index[index]={'name': me.name, 'origin': me.origin} 
1093             if want==self.ALL and me.mediadata!=None:
1094                 media_data[data_key]={'name': me.name, 'data': me.mediadata, 'origin': me.origin}
1095                 data_key+=1
1096         old_dict[self.database_key]=media_index
1097         dict.update(old_dict)
1098         dict[self.media_key]=media_data
1099         return dict
1100 
1101     def CompareItems(self, a, b):
1102         s1=a.name.lower()
1103         s2=b.name.lower()
1104         if s1<s2:
1105             return -1
1106         if s1==s2:
1107             return 0
1108         return 1
1109 
1110     def log(self, log_str):
1111         self.mainwindow.log(log_str)
1112 
1113     def GetHelpID(self):
1114         return self.helpid
1115 
1116     def OnMediaInfo(self, msg):
1117         # return the list of strings (lines) describing this item
1118         client, name, origin=msg.data
1119         for _item in self.GetAllItems():
1120             if (origin is None or _item.origin==origin) and \
1121                _item.name==name:
1122                     pubsub.publish(pubsub.RESPONSE_MEDIA_INFO,
1123                                    { 'client': client,
1124                                      'canopen': bool(guihelper.GetOpenCommand(_item.mimetypes, _item.name)),
1125                                      'desc': _item.lines })
1126     def OnMediaOpen(self, msg):
1127         # Launch the specified item name
1128         name, origin=msg.data
1129         for _item in self.GetAllItems():
1130             if (origin is None or _item.origin==origin) and \
1131                _item.name==name:
1132                 return self._launch(_item)
1133 
1134 class FileViewDisplayItem(object):
1135 
1136     datakey="Someone forgot to set me"
1137     PADDING=3
1138 
1139     def __init__(self, view, key):
1140         self.view=view
1141         self.key=key
1142         self.thumbsize=10,10
1143         self.setvals()
1144         self.lastw=None
1145 
1146     def setvals(self):
1147         me=self.view._data[self.datakey][self.key]
1148         self.name=me.name
1149         self.origin=me.origin
1150         self.mimetypes=''
1151         self.short=''
1152         self.long=''
1153         self.timestamp=''
1154         self.thumb=None
1155         if me.mediadata!=None:
1156             self.size=len(me.mediadata)
1157             self.no_data=False
1158             if me.timestamp!=None and me.timestamp!=0:
1159                 try:
1160                     self.timestamp=time.strftime("%x %X", time.localtime(me.timestamp))
1161                 except: # unexplained errors sometimes, so skip timestamp if this fails
1162                     self.timestamp=''
1163             fileinfo=self.view.GetFileInfoString(me.mediadata)
1164             if fileinfo!=None:
1165                 self.short=fileinfo.shortdescription()
1166                 self.long=fileinfo.longdescription()
1167                 self.mimetypes=fileinfo.mimetypes
1168                 self.fileinfo=fileinfo
1169         else:
1170             self.size=0
1171             self.no_data=True
1172         self.selbbox=None
1173         self.lines=[self.name, self.short,
1174                     '%.1f kb' % (self.size/1024.0,)]
1175 
1176     def Draw(self, dc, width, height, selected):
1177         if self.thumb==None:
1178             try:
1179                 if self.size:
1180                     me=self.view._data[self.datakey][self.key]
1181                     self.thumb=self.view.GetItemThumbnail(me.mediadata, self.thumbnailsize[0], self.thumbnailsize[1], self.fileinfo)
1182                 else:
1183                     self.thumb=self.view.GetItemThumbnail(None, self.thumbnailsize[0], self.thumbnailsize[1])
1184             except:
1185                 self.thumb=self.view.GetItemThumbnail(None, self.thumbnailsize[0], self.thumbnailsize[1])
1186         redrawbbox=False
1187         if selected:
1188             if self.lastw!=width or self.selbbox is None:
1189                 redrawbbox=True
1190             else:
1191                 oldb=dc.GetBrush()
1192                 oldp=dc.GetPen()
1193                 dc.SetBrush(self.view.item_selection_brush)
1194                 dc.SetPen(self.view.item_selection_pen)
1195                 dc.DrawRectangle(*self.selbbox)
1196                 dc.SetBrush(oldb)
1197                 dc.SetPen(oldp)
1198         dc.DrawBitmap(self.thumb, self.PADDING+self.thumbnailsize[0]/2-self.thumb.GetWidth()/2, self.PADDING, True)
1199         xoff=self.PADDING+self.thumbnailsize[0]+self.PADDING
1200         yoff=self.PADDING*2
1201         widthavailable=width-xoff-self.PADDING
1202         maxw=0
1203         old=dc.GetFont()
1204         for i,line in enumerate(self.lines):
1205             dc.SetFont(self.view.item_line_font[i])
1206             w,h=DrawTextWithLimit(dc, xoff, yoff, line, widthavailable, self.view.item_guardspace, self.view.item_term)
1207             maxw=max(maxw,w)
1208             yoff+=h
1209         dc.SetFont(old)
1210         self.lastw=width
1211         self.selbbox=(0,0,xoff+maxw+self.PADDING,max(yoff+self.PADDING,self.thumb.GetHeight()+self.PADDING*2))
1212         if redrawbbox:
1213             return self.Draw(dc, width, height, selected)
1214         return self.selbbox
1215 
1216     def DisplayTooltip(self, parent, rect):
1217         res=["Name: "+self.name, "Origin: "+(self.origin, "default")[self.origin is None],
1218              'File size: %.1f kb (%d bytes)' % (self.size/1024.0, self.size), "\n"+self.datatype+" information:\n", self.long]
1219         # tipwindow takes screen coordinates so we have to transform
1220         x,y=parent.ClientToScreen(rect[0:2])
1221         return wx.TipWindow(parent, "\n".join(res), 1024, wx.Rect(x,y,rect[2], rect[3]))
1222 
1223     def RemoveFromIndex(self):
1224         del self.view._data[self.datakey][self.key]
1225         self.view.modified=True
1226         self.view.OnRefresh()
1227 
1228     def RenameInIndex(self, new_name):
1229         self.view._data[self.datakey][self.key].name=new_name
1230         self.view.modified=True
1231         self.view.OnRefresh()
1232 
1233     def ChangeOriginInIndex(self, new_origin):
1234         self.view._data[self.datakey][self.key].origin=new_origin
1235         self.view._data[self.datakey][self.key].index=-1
1236         self.view.modified=True
1237         self.view.OnRefresh()
1238 
1239     def Refresh(self):
1240         self.setvals()
1241         self.view.modified=True
1242         self.view.OnRefresh()
1243 

Generated by PyXR 0.9.4