Module fileview
[hide private]
[frames] | no frames]

Source Code for Module fileview

   1  #!/usr/bin/env python 
   2   
   3  ### BITPIM 
   4  ### 
   5  ### Copyright (C) 2003-2006 Roger Binns <rogerb@rogerbinns.com> 
   6  ### 
   7  ### This program is free software; you can redistribute it and/or modify 
   8  ### it under the terms of the BitPim license as detailed in the LICENSE file. 
   9  ### 
  10  ### $Id: fileview.py 4732 2009-03-06 03:53:02Z hjelmn $ 
  11   
  12   
  13  ### 
  14  ### File viewer 
  15  ### 
  16  from __future__ import with_statement 
  17  import os 
  18  import copy 
  19  import cStringIO 
  20  import time 
  21  import base64 
  22  import phone_media_codec 
  23  import wx 
  24  import guihelper 
  25  import aggregatedisplay 
  26  import pubsub 
  27  import common 
  28  import widgets 
  29  import guiwidgets 
  30  import shutil 
  31  import database 
  32  import helpids 
  33  import tempfile 
  34   
  35   
  36  basename=common.basename 
  37  stripext=common.stripext 
  38  getext=common.getext 
39 40 41 #------------------------------------------------------------------------------- 42 -class MediaDataObject(database.basedataobject):
43 # modified_datatime is unix time 44 _knownproperties=['name', 'origin', 'index', 'timestamp'] 45 _knownlistproperties=database.basedataobject._knownlistproperties.copy() 46 _knownlistproperties.update( { 'mediadata': ['data'] })
47 - def __init__(self, data=None):
48 if data is None or not isinstance(data, MediaEntry): 49 return; 50 self.update(data.get_db_dict())
51 mediaobjectfactory=database.dataobjectfactory(MediaDataObject)
52 53 #------------------------------------------------------------------------------- 54 -class MediaEntry(object):
55 _id_index=0 56 _max_id_index=999
57 - def __init__(self):
58 self._data={ 'serials': [] } 59 self._create_id()
60
61 - def get(self):
62 res=copy.deepcopy(self._data, None, {}) 63 # account for the medidadata field 64 if res.has_key('mediadata'): 65 if res['mediadata'] is not None: 66 res['mediadata']=[{'data': buffer(res['mediadata']) }] 67 else: 68 del res['mediadata'] 69 return res
70 - def set(self, d):
71 self._data={} 72 self._data.update(d) 73 # check for mediadata 74 if d.get('mediadata', None) is not None: 75 self._data['mediadata']=str(d['mediadata'][0]['data'])
76
77 - def get_db_dict(self):
78 return self.get()
79 - def set_db_dict(self, d):
80 self.set(d)
81
82 - def _create_id(self):
83 "Create a BitPim serial for this entry" 84 self._data.setdefault("serials", []).append(\ 85 {"sourcetype": "bitpim", 86 "id": '%.3f%03d'%(time.time(), MediaEntry._id_index) }) 87 if MediaEntry._id_index<MediaEntry._max_id_index: 88 MediaEntry._id_index+=1 89 else: 90 MediaEntry._id_index=0
91 - def _get_id(self):
92 s=self._data.get('serials', []) 93 for n in s: 94 if n.get('sourcetype', None)=='bitpim': 95 return n.get('id', None) 96 return None
97 - def _set_id(self, id):
98 s=self._data.get('serials', []) 99 for n in s: 100 if n.get('sourcetype', None)=='bitpim': 101 n['id']=id 102 return 103 self._data['serials'].append({'sourcetype': 'bitpim', 'id': id } )
104 id=property(fget=_get_id, fset=_set_id) 105
106 - def _set_or_del(self, key, v, v_list=[]):
107 if v is None or v in v_list: 108 if self._data.has_key(key): 109 del self._data[key] 110 else: 111 self._data[key]=v
112
113 - def _get_origin(self):
114 return self._data.get('origin', '')
115 - def _set_origin(self, v):
116 if v is None: 117 if self._data.has_key('origin'): 118 del self._data['origin'] 119 return 120 if not isinstance(v, (str, unicode)): 121 raise TypeError,'not a string or unicode type' 122 self._data['origin']=v
123 origin=property(fget=_get_origin, fset=_set_origin) 124
125 - def _get_mediadata(self):
126 return self._data.get('mediadata', None)
127 - def _set_mediadata(self, v):
128 if v is not None: 129 self._set_or_del('mediadata', v, [])
130 mediadata=property(fget=_get_mediadata, fset=_set_mediadata) 131
132 - def _get_name(self):
133 return self._data.get('name', '')
134 - def _set_name(self, v):
135 self._set_or_del('name', v, ('',))
136 name=property(fget=_get_name, fset=_set_name) 137
138 - def _get_index(self):
139 return self._data.get('index', -1)
140 - def _set_index(self, v):
141 self._set_or_del('index', v, ('',))
142 index=property(fget=_get_index, fset=_set_index) 143
144 - def _get_timestamp(self):
145 return self._data.get('timestamp', None)
146 - def _set_timestamp(self, v):
147 if v is not None: 148 if not isinstance(v, (int, float)): 149 raise TypeError('timestamp property is an int arg') 150 v=int(v) 151 self._set_or_del('timestamp', v)
152 timestamp=property(fget=_get_timestamp, fset=_set_timestamp)
153
154 -def DrawTextWithLimit(dc, x, y, text, widthavailable, guardspace, term="..."):
155 """Draws text and if it will overflow the width available, truncates and puts ... at the end 156 157 @param x: start position for text 158 @param y: start position for text 159 @param text: the string to draw 160 @param widthavailable: the total amount of space available 161 @param guardspace: if the text is longer than widthavailable then this amount of space is 162 reclaimed from the right handside and term put there instead. Consequently 163 this value should be at least the width of term 164 @param term: the string that is placed in the guardspace if it gets truncated. Make sure guardspace 165 is at least the width of this string! 166 @returns: The extent of the text that was drawn in the end as a tuple of (width, height) 167 """ 168 w,h=dc.GetTextExtent(text) 169 if w<widthavailable: 170 dc.DrawText(text,x,y) 171 return w,h 172 extents=dc.GetPartialTextExtents(text) 173 limit=widthavailable-guardspace 174 # find out how many chars in we have to go before hitting limit 175 for i,offset in enumerate(extents): 176 if offset>limit: 177 break 178 # back off 1 in case the new text's a tad long 179 if i: 180 i-=1 181 text=text[:i]+term 182 w,h=dc.GetTextExtent(text) 183 assert w<=widthavailable 184 dc.DrawText(text, x, y) 185 return w,h
186 187 media_codec=phone_media_codec.codec_name
188 -class MyFileDropTarget(wx.FileDropTarget):
189 - def __init__(self, target, drag_over=False, enter_leave=False):
190 wx.FileDropTarget.__init__(self) 191 self.target=target 192 self.drag_over=drag_over 193 self.enter_leave=enter_leave
194
195 - def OnDropFiles(self, x, y, filenames):
196 return self.target.OnDropFiles(x,y,filenames)
197
198 - def OnDragOver(self, x, y, d):
199 if self.drag_over: 200 return self.target.OnDragOver(x,y,d) 201 return wx.FileDropTarget.OnDragOver(self, x, y, d)
202
203 - def OnEnter(self, x, y, d):
204 if self.enter_leave: 205 return self.target.OnEnter(x,y,d) 206 return wx.FileDropTarget.OnEnter(self, x, y, d)
207
208 - def OnLeave(self):
209 if self.enter_leave: 210 return self.target.OnLeave() 211 return wx.FileDropTarget.OnLeave(self)
212
213 -class FileView(wx.Panel, widgets.BitPimWidget):
214 215 # Various DC objects used for drawing the items. We have to calculate them in the constructor as 216 # the app object hasn't been constructed when this file is imported. 217 item_selection_brush=None 218 item_selection_pen=None 219 item_line_font=None 220 item_term="..." 221 item_guardspace=None 222 # Files we should ignore 223 skiplist= ( 'desktop.ini', 'thumbs.db', 'zbthumbnail.info' ) 224 database_key="" 225 226 # how much data do we want in call to getdata 227 NONE=0 228 SELECTED=1 229 ALL=2 230 231 # maximum length of a filename 232 maxlen=-1 # set via phone profile 233 # acceptable characters in a filename 234 filenamechars=None # set via phone profile 235 # Default Help page, children can override 236 helpid=helpids.ID_TAB_MEDIA 237
238 - def __init__(self, mainwindow, parent, media_root, watermark=None):
239 wx.Panel.__init__(self,parent,style=wx.CLIP_CHILDREN) 240 # adjust the DB to accommodate the new schema if necessary 241 self._fixupdb(mainwindow.database) 242 # item attributes 243 if self.item_selection_brush is None: 244 self.item_selection_brush=wx.TheBrushList.FindOrCreateBrush("MEDIUMPURPLE2", wx.SOLID) 245 self.item_selection_pen=wx.ThePenList.FindOrCreatePen("MEDIUMPURPLE2", 1, wx.SOLID) 246 f1=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.BOLD) 247 f2=wx.TheFontList.FindOrCreateFont(10, wx.SWISS, wx.NORMAL, wx.NORMAL) 248 self.item_line_font=[f1, f2, f2, f2] 249 dc=wx.MemoryDC() 250 dc.SelectObject(wx.EmptyBitmap(100,100)) 251 self.item_guardspace=dc.GetTextExtent(self.item_term)[0] 252 del dc 253 254 # no redraw ickiness 255 # wx.EVT_ERASE_BACKGROUND(self, lambda evt: None) 256 257 self.parent=parent 258 self.mainwindow=mainwindow 259 self.thedir=None 260 self.wildcard="I forgot to set wildcard in derived class|*" 261 self.__dragging=False 262 self._in_context_menu=False 263 self.media_root=media_root 264 self.show_thumbnail=True 265 self.active_section="" 266 # origins that should not be used for phonebook 267 self.excluded_origins=() 268 269 # use the aggregatedisplay to do the actual item display 270 self.aggdisp=aggregatedisplay.Display(self, self, watermark) # we are our own datasource 271 self.vbs=wx.BoxSizer(wx.VERTICAL) 272 273 ### toolbar 274 self.tb=wx.ToolBar(self, -1, style=wx.TB_3DBUTTONS|wx.TB_HORIZONTAL) 275 self.tb.SetToolBitmapSize(wx.Size(18,18)) 276 sz=self.tb.GetToolBitmapSize() 277 278 # list and thumbnail tools 279 self.tb.AddRadioLabelTool(guihelper.ID_FILEVIEW_THUMBNAIL, "Thumbnail", 280 wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_THUMB_VIEW, wx.ART_TOOLBAR, sz), 281 wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_THUMB_VIEW, wx.ART_TOOLBAR, sz), 282 "Show Thumbnails", "Show items as thumbnails") 283 self.tb.AddRadioLabelTool(guihelper.ID_FILEVIEW_LIST, "List", 284 wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_LIST_VIEW, wx.ART_TOOLBAR, sz), 285 wx.ArtProvider.GetBitmap(guihelper.ART_MEDIA_LIST_VIEW, wx.ART_TOOLBAR, sz), 286 "Show List", "Show items in a list") 287 self.vbs.Add(self.tb, 0, wx.EXPAND|wx.ALL, 1) 288 self.aggr_sizer=self.vbs.Add(self.aggdisp, 1, wx.EXPAND|wx.ALL, 2) 289 290 # main list 291 column_info=self.GetColumnNames() 292 self.item_list=guiwidgets.BitPimListCtrl(self, column_info) 293 self.nodes={} 294 self.nodes_keys={} 295 self.item_list.ResetView(self.nodes, self.nodes_keys) 296 self.item_sizer=self.vbs.Add(self.item_list, 1, wx.EXPAND|wx.ALL, 2) 297 self.item_sizer.Show(False) 298 self.note=self.vbs.Add(wx.StaticText(self, -1, ' Note: Click column headings to sort data'), 0, wx.ALIGN_CENTRE|wx.BOTTOM, 10) 299 self.note.Show(False) 300 self.SetSizer(self.vbs) 301 timerid=wx.NewId() 302 self.thetimer=wx.Timer(self, timerid) 303 wx.EVT_TIMER(self, timerid, self.OnTooltipTimer) 304 self.motionpos=None 305 wx.EVT_MOUSE_EVENTS(self.aggdisp, self.OnMouseEvent) 306 self.tipwindow=None 307 if True: # guihelper.IsMSWindows() or guihelper.IsGtk(): 308 # turn on drag-and-drag for all platforms 309 wx.EVT_MOTION(self.aggdisp, self.OnStartDrag) 310 311 # Menus 312 313 self.itemmenu=wx.Menu() 314 self.itemmenu.Append(guihelper.ID_FV_OPEN, "Open") 315 self.itemmenu.Append(guihelper.ID_FV_SAVE, "Save ...") 316 self.itemmenu.AppendSeparator() 317 if guihelper.IsMSWindows(): 318 self.itemmenu.Append(guihelper.ID_FV_COPY, "Copy") 319 self.itemmenu.Append(guihelper.ID_FV_DELETE, "Delete") 320 self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename") 321 self.movemenu=wx.Menu() 322 self.itemmenu.AppendMenu(guihelper.ID_FV_MOVE, "Move to", self.movemenu) 323 self.itemmenu.AppendSeparator() 324 self.itemmenu.Append(guihelper.ID_FV_REPLACE, "Replace") 325 # self.itemmenu.Append(guihelper.ID_FV_RENAME, "Rename") 326 self.itemmenu.Append(guihelper.ID_FV_REFRESH, "Refresh") 327 328 self.bgmenu=wx.Menu() 329 self.bgmenu.Append(guihelper.ID_FV_ADD, "Add ...") 330 self.bgmenu.Append(guihelper.ID_FV_PASTE, "Paste") 331 self.bgmenu.Append(guihelper.ID_FV_REFRESH, "Refresh") 332 333 wx.EVT_MENU(self.tb, guihelper.ID_FILEVIEW_THUMBNAIL, self.OnThumbnailView) 334 wx.EVT_MENU(self.tb, guihelper.ID_FILEVIEW_LIST, self.OnListView) 335 336 337 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_OPEN, self.OnLaunch) 338 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_SAVE, self.OnSave) 339 if guihelper.IsMSWindows(): 340 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_COPY, self.OnCopy) 341 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_DELETE, self.OnDelete) 342 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_RENAME, self.OnRename) 343 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_REPLACE, self.OnReplace) 344 wx.EVT_MENU(self.itemmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh()) 345 wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_ADD, self.OnAdd) 346 wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_PASTE, self.OnPaste) 347 wx.EVT_MENU(self.bgmenu, guihelper.ID_FV_REFRESH, lambda evt: self.OnRefresh) 348 349 wx.EVT_RIGHT_UP(self.aggdisp, self.OnRightClick) 350 wx.EVT_LIST_ITEM_RIGHT_CLICK(self.item_list, self.item_list.GetId(), self.OnRightClick) 351 aggregatedisplay.EVT_ACTIVATE(self.aggdisp, self.aggdisp.GetId(), self.OnLaunch) 352 wx.EVT_LIST_ITEM_ACTIVATED(self.item_list, self.item_list.GetId(), self.OnLaunch) 353 354 self.droptarget=MyFileDropTarget(self) 355 self.SetDropTarget(self.droptarget) 356 wx.EVT_SIZE(self, self.OnSize) 357 wx.EVT_IDLE(self, self.OnIdle) 358 wx.EVT_KEY_DOWN(self.aggdisp, self.OnKeyDown) 359 wx.EVT_KEY_UP(self.aggdisp, self.OnKeyUp) 360 self.tb.Realize() 361 pubsub.subscribe(self.OnMediaInfo, pubsub.REQUEST_MEDIA_INFO) 362 pubsub.subscribe(self.OnMediaOpen, pubsub.REQUEST_MEDIA_OPEN)
363
364 - def OnIdle(self, _):
365 "Save out changed data" 366 if self.modified: 367 self.modified=False 368 self._populatefs(self._data) 369 self.OnListRequest() # broadcast changes
370
371 - def OnKeyDown(self, evt):
372 if guihelper.IsGtk(): 373 if evt.GetKeyCode()==wx.WXK_SHIFT: 374 self._shift_down=True 375 else: 376 self._shift_down=evt.ShiftDown() 377 evt.Skip()
378
379 - def OnKeyUp(self, evt):
380 if guihelper.IsGtk(): 381 if evt.GetKeyCode()==wx.WXK_SHIFT: 382 self._shift_down=False 383 else: 384 self._shift_down=evt.ShiftDown() 385 evt.Skip()
386
387 - def OnThumbnailView(self, _):
388 self.thetimer.Stop() 389 self.show_thumbnail=True 390 self.item_sizer.Show(False) 391 self.note.Show(False) 392 self.aggr_sizer.Show(True) 393 self.aggdisp.SetFocus() 394 self.vbs.Layout()
395
396 - def OnListView(self, _):
397 self.thetimer.Stop() 398 self.show_thumbnail=False 399 self.aggr_sizer.Show(False) 400 # resize to hide the thumbnails otherwise it still gets the mouse scroll events. 401 self.aggdisp.SetSize((1,1)) 402 self.item_sizer.Show(True) 403 self.item_list.SetFocus() 404 self.note.Show(True) 405 self.vbs.Layout()
406
407 - def OnSelected(self, node):
408 self.active_section=self.media_root.GetNodeName(self, node) 409 self.aggdisp.SetActiveSection(self.active_section) 410 self.MakeMoveMenu() 411 self.OnRefreshList()
412
413 - def GetRightClickMenuItems(self, node):
414 # we set these values so that the event hander knows only to save 415 # this origin rather than all media, we clear these values after 416 # the menu is dismissed (see OnRightClickMenuExit below) 417 self.media_root.widget_to_save=self 418 self.media_root.origin_to_save=self.active_section 419 result=[] 420 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITADDENTRY, "Add to %s" % self.active_section, "Add a new media items")) 421 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITDELETEENTRY, "Delete Selected", "Delete Selected Items")) 422 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITSELECTALL, "Select All", "Select All Items")) 423 if guihelper.IsMSWindows(): 424 result.append((widgets.BitPimWidget.MENU_NORMAL, guihelper.ID_EDITCOPY, "Copy", "Copy Selected Items")) 425 result.append((widgets.BitPimWidget.MENU_SPACER, 0, "", "")) 426 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")) 427 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")) 428 return result
429
430 - def OnRightClickMenuExit(self):
431 # clear these values now that the event has been processed 432 self.media_root.widget_to_save=None 433 self.media_root.origin_to_save=""
434
435 - def MakeMoveMenu(self):
436 # redo the move menu 437 menuItems = self.movemenu.GetMenuItems() 438 for i, menuItem in enumerate(menuItems): 439 self.Unbind(wx.EVT_MENU, id=menuItem.GetId()) 440 self.movemenu.DeleteItem(menuItem) 441 442 # get the list of origins 443 origins=self.media_root.GetNodeList(self) 444 origins.remove(self.active_section) 445 if len(origins): 446 for origin in origins: 447 mid=wx.NewId() 448 self.movemenu.Append(mid, origin) 449 wx.EVT_MENU(self, mid, self.OnMoveItem)
450
451 - def GetColumnNames(self):
452 columns=[] 453 columns.append(("Name", 120, False)) 454 columns.append(("Size/bytes", 80, True)) 455 columns.append(("Date Modified", 120, False)) 456 columns.append(("File Details", 380, False)) 457 return columns
458
459 - def OnSize(self, evt):
460 # stop the tool tip from poping up when we're resizing! 461 if self.thetimer.IsRunning(): 462 self.thetimer.Stop() 463 evt.Skip()
464
465 - def OnRightClick(self, evt):
466 """Popup the right click context menu 467 468 @param widget: which widget to popup in 469 @param position: position in widget 470 @param onitem: True if the context menu is for an item 471 """ 472 if len(self.GetSelectedItems()): 473 menu=self.itemmenu 474 item=self.GetSelectedItems()[0] 475 single=len(self.GetSelectedItems())==1 476 menu.Enable(guihelper.ID_FV_RENAME, single) 477 # we always launch on mac 478 if not guihelper.IsMac(): 479 menu.FindItemById(guihelper.ID_FV_OPEN).Enable(guihelper.GetOpenCommand(item.mimetypes, item.name) is not None) 480 else: 481 menu=self.bgmenu 482 menu.Enable(guihelper.ID_FV_PASTE, self.CanPaste()) 483 if menu is None: 484 return 485 # we're putting up the context menu, quit the tool tip timer. 486 self._in_context_menu=True 487 self.aggdisp.PopupMenu(menu, evt.GetPosition()) 488 self._in_context_menu=False
489
490 - def OnMoveItem(self, evt):
491 new_origin=None 492 items=self.GetSelectedItems() 493 new_origin=self.movemenu.FindItemById(evt.GetId()).GetLabel() 494 for item in items: 495 if new_origin!=None and new_origin in self.media_root.GetNodeList(self): 496 # make sure this name is not already used 497 for i in self._data[self.database_key]: 498 if self._data[self.database_key][i].origin==new_origin and \ 499 self._data[self.database_key][i].name==item.name: 500 wx.MessageBox("A file with the same name already exists in %s!" % new_origin, "Move Error", wx.OK|wx.ICON_EXCLAMATION) 501 return 502 wx.BeginBusyCursor() 503 item.ChangeOriginInIndex(new_origin) 504 self.OnRefresh() 505 wx.EndBusyCursor()
506
507 - def _launch(self, item):
508 # Open/Launch the specified item 509 me=self._data[self.database_key][item.key] 510 fname=self._gettempfile(me) 511 if guihelper.IsMac(): 512 import findertools 513 findertools.launch(fname) 514 return 515 cmd=guihelper.GetOpenCommand(item.mimetypes, fname) 516 if cmd is None: 517 wx.Bell() 518 else: 519 wx.Execute(cmd, wx.EXEC_ASYNC)
520 521 @guihelper.BusyWrapper
522 - def OnLaunch(self, _):
523 self._launch(self.GetSelectedItems()[0])
524 525 if True: # guihelper.IsMSWindows() or guihelper.IsGtk(): 526 # drag-and-drop files should work on all platforms
527 - def OnStartDrag(self, evt):
528 evt.Skip() 529 if not evt.LeftIsDown(): 530 return 531 items=self.GetSelectedItems() 532 if not len(items): 533 return 534 drag_source=wx.DropSource(self) 535 file_names=wx.FileDataObject() 536 for item in items: 537 me=self._data[self.database_key][item.key] 538 fname=self._gettempfile(me) 539 if not os.path.isfile(fname): 540 continue 541 file_names.AddFile(fname) 542 drag_source.SetData(file_names) 543 self.__dragging=True 544 res=drag_source.DoDragDrop(wx.Drag_AllowMove) 545 self.__dragging=False 546 # check of any of the files have been removed, 547 # can't trust result returned by DoDragDrop 548 for item in items: 549 me=self._data[self.database_key][item.key] 550 fname=self._gettempfile(me) 551 if not os.path.isfile(fname): 552 item.RemoveFromIndex()
553
554 - def OnMouseEvent(self, evt):
555 self.motionpos=evt.GetPosition() 556 # on windows if we quickly move the mouse out of bitpim window we never get an event and we will pop up 557 # the tooltip when we should not, so we check the position after the timeout and see if it has moved. 558 self.abs_mouse_pos=wx.GetMousePosition() 559 evt.Skip() 560 self.thetimer.Stop() 561 if evt.AltDown() or evt.MetaDown() or evt.ControlDown() or \ 562 evt.ShiftDown() or evt.Dragging() or evt.IsButton() or \ 563 self._in_context_menu or not self.show_thumbnail: 564 return 565 self.thetimer.Start(1750, wx.TIMER_ONE_SHOT)
566
567 - def OnTooltipTimer(self, _):
568 if self._in_context_menu or not self.show_thumbnail or \ 569 wx.GetApp().critical.isSet(): 570 # we're putting up a context menu or main app is busy, forget this 571 return 572 # see if we have moved 573 if self.abs_mouse_pos!=wx.GetMousePosition(): 574 return 575 x,y=self.aggdisp.CalcUnscrolledPosition(*self.motionpos) 576 res=self.aggdisp.HitTest(x,y) 577 if res.item is not None: 578 try: self.tipwindow.Destroy() 579 except: pass 580 self.tipwindow=res.item.DisplayTooltip(self.aggdisp, res.itemrectscrolled)
581
582 - def OnRefresh(self):
583 # update aggregate view 584 self.aggdisp.UpdateItems() 585 self.OnRefreshList() 586 self.media_root.DoMediaSummary()
587
588 - def OnRefreshList(self):
589 # update list view 590 self.nodes={} 591 self.nodes_keys={} 592 index=0 593 for k,e in self.sections: 594 if self.active_section==None or k.label==self.active_section: 595 for item in e: 596 # replace linefeeds in description 597 dlist=item.long.splitlines() 598 d="" 599 for l in dlist: 600 if len(d): 601 d+=" - " 602 d+=l 603 self.nodes[index]=(item.name, str(item.size), item.timestamp, d) 604 self.nodes_keys[index]=item 605 index+=1 606 self.item_list.ResetView(self.nodes, self.nodes_keys)
607
608 - def GetSelectedItems(self):
609 if self.show_thumbnail: 610 return [item for _,_,_,item in self.aggdisp.GetSelection()] 611 res=[] 612 sel=self.item_list.GetSelections() 613 for sel_idx in sel: 614 res.append(self.item_list.GetItemData(sel[sel_idx])) 615 return res
616
617 - def GetAllItems(self):
618 return [item for _,_,_,item in self.aggdisp.GetAllItems()]
619
620 - def CanSelectAll(self):
621 return self.item_list.GetItemCount() > 0
622
623 - def OnSelectAll(self, _):
624 self.aggdisp.SelectAll() 625 self.item_list.SelectAll()
626
627 - def OnSave(self, _):
628 # If one item is selected we ask for a filename to save. If 629 # multiple then we ask for a directory, and users don't get 630 # the choice to affect the names of files. Note that we don't 631 # allow users to select a different format for the file - we 632 # just copy it as is. 633 items=self.GetSelectedItems() 634 if len(items)==1: 635 ext=getext(items[0].name) 636 if ext=="": ext="*" 637 else: ext="*."+ext 638 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Save item", wildcard=ext, defaultFile=items[0].name, style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR), 639 True) as (dlg, retcode): 640 if retcode==wx.ID_OK: 641 file(dlg.GetPath(), "wb").write(self._data[items[0].datakey][items[0].key].mediadata) 642 if self._data[items[0].datakey][items[0].key].timestamp!=None: 643 os.utime(dlg.GetPath(), (self._data[items[0].datakey][items[0].key].timestamp, 644 self._data[items[0].datakey][items[0].key].timestamp)) 645 else: 646 with guihelper.WXDialogWrapper(wx.DirDialog(self, "Save items to", style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON), 647 True) as (dlg, retcode): 648 if retcode==wx.ID_OK: 649 for item in items: 650 fname=item.name.encode(media_codec) 651 fname=os.path.join(dlg.GetPath(), basename(fname)) 652 file(fname, 'wb').write(self._data[item.datakey][item.key].mediadata) 653 if self._data[item.datakey][item.key].timestamp!=None: 654 os.utime(fname, (self._data[item.datakey][item.key].timestamp, 655 self._data[item.datakey][item.key].timestamp))
656 657 if guihelper.IsMSWindows():
658 - def OnCopy(self, _):
659 items=self.GetSelectedItems() 660 if not len(items): 661 # nothing selected 662 return 663 file_names=wx.FileDataObject() 664 for item in items: 665 me=self._data[self.database_key][item.key] 666 fname=self._gettempfile(me) 667 if not os.path.isfile(fname): 668 continue 669 file_names.AddFile(fname) 670 if wx.TheClipboard.Open(): 671 wx.TheClipboard.SetData(file_names) 672 wx.TheClipboard.Close()
673 - def CanCopy(self):
674 return len(self.GetSelectedItems())
675 676 if guihelper.IsGtk(): 677 # Gtk just pastes the file names as text onto the Clipboard
678 - def OnPaste(self, _=None):
679 if not wx.TheClipboard.Open(): 680 # can't access the clipboard 681 return 682 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT)): 683 file_names=wx.TextDataObject() 684 has_data=wx.TheClipboard.GetData(file_names) 685 else: 686 has_data=False 687 wx.TheClipboard.Close() 688 if has_data: 689 # collect file names if any. 690 _names=[x for x in file_names.GetText().split('\n') \ 691 if os.path.isfile(x) ] 692 if _names: 693 self.OnAddFiles(_names)
694 - def CanPaste(self):
695 """ Return True if can accept clipboard data, False otherwise 696 """ 697 if not wx.TheClipboard.Open(): 698 return False 699 r=wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_UNICODETEXT)) 700 if r: 701 file_names=wx.TextDataObject() 702 r=wx.TheClipboard.GetData(file_names) 703 if r: 704 for _name in file_names.GetText().split('\n'): 705 if not os.path.isfile(_name): 706 r=False 707 break 708 wx.TheClipboard.Close() 709 return r
710 else:
711 - def OnPaste(self, _=None):
712 if not wx.TheClipboard.Open(): 713 # can't access the clipboard 714 return 715 if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME)): 716 file_names=wx.FileDataObject() 717 has_data=wx.TheClipboard.GetData(file_names) 718 else: 719 has_data=False 720 wx.TheClipboard.Close() 721 if has_data: 722 self.OnAddFiles(file_names.GetFilenames())
723
724 - def CanPaste(self):
725 """ Return True if can accept clipboard data, False otherwise 726 """ 727 if not wx.TheClipboard.Open(): 728 return False 729 r=wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_FILENAME)) 730 wx.TheClipboard.Close() 731 return r
732
733 - def CanDelete(self):
734 if len(self.GetSelectedItems()): 735 return True 736 return False
737
738 - def OnDelete(self,_):
739 items=self.GetSelectedItems() 740 for item in items: 741 item.RemoveFromIndex() 742 self.OnRefresh()
743
744 - def AddToIndex(self, file, origin, data, dict, timestamp=None, index=-1):
745 # see if it is already in the origin 746 if dict.has_key(self.database_key): 747 for i in dict[self.database_key]: 748 # see if the media is already in the database 749 # if so update the existing entry, the display objects have references to the keys, if 750 # we delete items the keys go bad and we require a refresh 751 if dict[self.database_key][i].name==file and dict[self.database_key][i].origin==origin: 752 # update the index 753 if index>=0: 754 dict[self.database_key][i].index=index 755 # update the timestamp 756 if timestamp!=None: 757 dict[self.database_key][i].timestamp=timestamp 758 # if there is no data dont update the dict 759 # when indexes are updates the data is not provided 760 if data!=None and data!='': 761 dict[self.database_key][i].mediadata=data 762 return 763 else: 764 dict[self.database_key]={} 765 entry=MediaEntry() 766 entry.name=file 767 entry.origin=origin 768 entry.mediadata=data 769 entry.index=index 770 entry.timestamp=timestamp 771 dict[self.database_key][entry.id]=entry 772 self.modified=True
773
774 - def _fixupdb(self, db):
775 # fixup the database to accommodate the new schema 776 adjustflg=False 777 for _,_name,_type in db.getcolumns(self.database_key): 778 if _name=='mediadata' and _type!='indirectBLOB': 779 # old schema, need to adjust 780 adjustflg=True 781 break 782 # adjust the table: replace the mediadata field 783 if adjustflg: 784 db._altertable(self.database_key, [('mediadata', 'indirectBLOB')], 785 ['mediadata'], 1)
786
787 - def _gettempfile(self, item):
788 # store the the media data in a temporary file and return the full file 789 # path 790 filename=os.path.join(tempfile.gettempdir(), 791 item.name.encode(media_codec)) 792 file(filename, 'wb').write(item.mediadata) 793 return filename
794
795 - def _filename(self, item):
796 # return the filename associated with a media file 797 if hasattr(item, 'origin'): 798 _origin=item.origin 799 _name=item.name 800 else: 801 _origin=item.get('origin', '') 802 _name=item.get('name', '') 803 relative_name=os.path.join(_origin, 804 _name.encode(media_codec)) 805 return os.path.join(self.mainwindow.blob_path, relative_name)
806
807 - def _save_to_db(self, dict):
808 db_rr={} 809 for k,e in dict.items(): 810 db_rr[k]=MediaDataObject(e) 811 # the media data does not go into the actual database, we store it 812 # in a regular file to minimise the database size, it 813 # gets very big if the data is stored in it and starts to get slow. 814 database.ensurerecordtype(db_rr, mediaobjectfactory) 815 self.mainwindow.database.savemajordict(self.database_key, db_rr)
816
817 - def _load_from_db(self, result):
818 dict=self.mainwindow.database.\ 819 getmajordictvalues(self.database_key, 820 mediaobjectfactory) 821 r={} 822 for k,e in dict.items(): 823 ce=MediaEntry() 824 ce.set_db_dict(e) 825 if ce.mediadata is None: 826 # try reading the data from the "old" way 827 try: 828 ce.mediadata=file(self._filename(ce), 'rb').read() 829 except: 830 # abandon data that has no file it probably means that 831 # blobs directory is corrupted, maybe the user deleted 832 # the file themselves 833 pass 834 r[ce.id]=ce 835 result.update({ self.database_key: r}) 836 return result
837
838 - def convert_to_dict(self, result, res=None):
839 if res==None: 840 res={} 841 for rec in result[self.database_key]: 842 fname=result[self.database_key][rec]['name'] 843 # legacy format does not always contain origin 844 if result[self.database_key][rec].has_key('origin'): 845 origin=result[self.database_key][rec]['origin'] 846 else: 847 origin=self.default_origin 848 data, timestamp=self.get_media_data(result, fname, origin) 849 # None and '' are treated differently by viewer. data=='' means we could not read from 850 # the phone but we still want to display, 851 # None means we do not want to display like the builtin media 852 if data=='': # no data read, see if we have this file in the dict already and use its data, 853 # provides a workaround for phones that don't let us read the ringtones back 854 # which we wrote in the first place 855 for i in self._data[self.database_key]: 856 if self._data[self.database_key][i].name==result[self.database_key][rec]['name'] \ 857 and self._data[self.database_key][i].origin==result[self.database_key][rec]['origin'] \ 858 and self._data[self.database_key][i].mediadata!=None: 859 data=self._data[self.database_key][i].mediadata 860 self.AddToIndex(result[self.database_key][rec]['name'], origin, data, res, timestamp, rec) 861 return res
862
863 - def get_media_data(self, result, name, origin):
864 data=None 865 timestamp=None # unix time 866 if result.has_key(self.media_key): 867 if result[self.media_key].has_key("new_media_version"): 868 if result[self.media_key].has_key(origin): 869 if result[self.media_key][origin].has_key(name): 870 data=result[self.media_key][origin][name]['data'] 871 if result[self.media_key][origin][name].has_key('timestamp'): 872 timestamp=result[self.media_key][origin][name]['timestamp'] 873 pass 874 # old style data with no origin info, means that the filenames have 875 # to be unique across all the origins for the widget 876 elif result[self.media_key].has_key(name): 877 data=result[self.media_key][name] 878 return data, timestamp
879
880 - def updateindex(self, index):
881 self._data=self.convert_to_dict(index, self._data) 882 # delete unused medias 883 del_list=[] 884 for i in self._data[self.database_key]: 885 found=False 886 for rec in index[self.database_key]: 887 if self._data[self.database_key][i].name==index[self.database_key][rec]['name'] \ 888 and self._data[self.database_key][i].origin==index[self.database_key][rec]['origin']: 889 found=True 890 break 891 if not found: 892 del_list.append(i) 893 for i in del_list: 894 del self._data[self.database_key][i] 895 self.modified=True
896
897 - def populatefs(self, dict):
898 res={} 899 dict=self.convert_to_dict(dict) 900 return self._populatefs(dict)
901
902 - def _populatefs(self, dict):
903 self._save_to_db(dict.get(self.database_key, {})) 904 return dict
905
906 - def populate(self, dict):
907 if not dict.has_key('media_from_db'): 908 # update the existing dict (the key are referenced from the display objects, doing a refresh causes 909 # a long delay 910 self._data=self.convert_to_dict(dict, self._data) 911 # delete unused medias 912 del_list=[] 913 for i in self._data[self.database_key]: 914 found=False 915 for rec in dict[self.database_key]: 916 if self._data[self.database_key][i].name==dict[self.database_key][rec]['name'] \ 917 and self._data[self.database_key][i].origin==dict[self.database_key][rec]['origin']: 918 found=True 919 break 920 if not found: 921 del_list.append(i) 922 for i in del_list: 923 del self._data[self.database_key][i] 924 925 self.modified=True 926 self.OnRefresh() 927 else: 928 if dict[self.database_key]!=self._data[self.database_key]: 929 self._data[self.database_key]=dict[self.database_key].copy() 930 self.modified=True 931 self.OnRefresh()
932
933 - def getfromfs(self, result):
934 if self.mainwindow.database.doestableexist(self.database_key): 935 result=self._load_from_db(result) 936 else: # if there is no data in the database then try to read the legacy media 937 res={} 938 res=self.legacygetfromfs(res, self.media_key, self.database_key, self.CURRENTFILEVERSION) 939 # if we got anything save into the database and delete the old index file 940 if res.has_key(self.database_key) and len(res[self.database_key])!=0: 941 result.update(self.convert_to_dict(res)) 942 self._populatefs(result) 943 self.delete_old_media() 944 else: 945 result=self._load_from_db(result) 946 result['media_from_db']=1 947 return result
948
949 - def legacygetfromfs(self, result, key, indexkey, currentversion):
950 dict={} 951 index_found=False 952 if os.path.isdir(self.thedir): 953 for file in os.listdir(self.thedir): 954 if file=='index.idx': 955 d={} 956 d['result']={} 957 common.readversionedindexfile(os.path.join(self.thedir, file), d, self.versionupgrade, currentversion) 958 result.update(d['result']) 959 index_found=True 960 elif file.lower() in self.skiplist: 961 # ignore windows detritus 962 continue 963 elif key is not None: 964 dict[file.decode(media_codec)]=open(os.path.join(self.thedir, file), "rb").read() 965 if index_found: 966 if key is not None: 967 result[key]=dict 968 if indexkey not in result: 969 result[indexkey]={} 970 return result
971
972 - def delete_old_media(self):
973 # No longer do this since it messes up the virtual tables setup! 974 pass
975
976 - def OnDropFiles(self, _, dummy, filenames):
977 # There is a bug in that the most recently created tab 978 # in the notebook that accepts filedrop receives these 979 # files, not the most visible one. We find the currently 980 # viewed tab in the notebook and send the files there 981 if self.__dragging: 982 # I'm the drag source, forget 'bout it ! 983 return 984 target=self # fallback 985 t=self._tree.mw.GetCurrentActiveWidget() 986 if isinstance(t, FileView): 987 # changing target in dragndrop 988 target=t 989 target.OnAddFiles(filenames)
990
991 - def CanAdd(self):
992 return True
993
994 - def OnAdd(self, _=None):
995 with guihelper.WXDialogWrapper(wx.FileDialog(self, "Choose files", style=wx.OPEN|wx.MULTIPLE, wildcard=self.wildcard), 996 True) as (dlg, retcode): 997 if retcode==wx.ID_OK: 998 self.OnAddFiles(dlg.GetPaths())
999
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 media_name=media_name.replace(".m4a",".mp4") # change file extension of mpeg4 files 1053 if len(newext): 1054 media_name+='.'+newext 1055 if len(media_name)>self.maxlen: 1056 chop=len(media_name)-self.maxlen 1057 media_name=stripext(media_name)[:-chop].strip()+'.'+getext(media_name) 1058 return media_name
1059
1060 - def getdata(self,dict,want=NONE):
1061 items=None 1062 media_index={} 1063 media_data={} 1064 old_dict={} 1065 data_key=0 1066 1067 if want==self.SELECTED: 1068 items=self.GetSelectedItems() 1069 if len(items)==0: 1070 want=self.ALL 1071 1072 if want==self.SELECTED: 1073 if items is not None: 1074 media_data={} 1075 i=0 1076 for item in items: 1077 me=self._data[item.datakey][item.key] 1078 if me.mediadata!=None: 1079 media_data[data_key]={'name': me.name, 'data': me.mediadata, 'origin': me.origin} 1080 data_key+=1 1081 1082 # convert into the old format 1083 index_cnt=-1 1084 for i in self._data[self.database_key]: 1085 me=self._data[self.database_key][i] 1086 # make sure the index is unique 1087 if me.index in media_index: 1088 while index_cnt in media_index: 1089 index_cnt-=1 1090 index=index_cnt 1091 else: 1092 index=me.index 1093 media_index[index]={'name': me.name, 'origin': me.origin} 1094 if want==self.ALL and me.mediadata!=None: 1095 media_data[data_key]={'name': me.name, 'data': me.mediadata, 'origin': me.origin} 1096 data_key+=1 1097 old_dict[self.database_key]=media_index 1098 dict.update(old_dict) 1099 dict[self.media_key]=media_data 1100 return dict
1101
1102 - def CompareItems(self, a, b):
1103 s1=a.name.lower() 1104 s2=b.name.lower() 1105 if s1<s2: 1106 return -1 1107 if s1==s2: 1108 return 0 1109 return 1
1110
1111 - def log(self, log_str):
1112 self.mainwindow.log(log_str)
1113
1114 - def GetHelpID(self):
1115 return self.helpid
1116
1117 - def OnMediaInfo(self, msg):
1118 # return the list of strings (lines) describing this item 1119 client, name, origin=msg.data 1120 for _item in self.GetAllItems(): 1121 if (origin is None or _item.origin==origin) and \ 1122 _item.name==name: 1123 pubsub.publish(pubsub.RESPONSE_MEDIA_INFO, 1124 { 'client': client, 1125 'canopen': bool(guihelper.GetOpenCommand(_item.mimetypes, _item.name)), 1126 'desc': _item.lines })
1127 - def OnMediaOpen(self, msg):
1128 # Launch the specified item name 1129 name, origin=msg.data 1130 for _item in self.GetAllItems(): 1131 if (origin is None or _item.origin==origin) and \ 1132 _item.name==name: 1133 return self._launch(_item)
1134
1135 -class FileViewDisplayItem(object):
1136 1137 datakey="Someone forgot to set me" 1138 PADDING=3 1139
1140 - def __init__(self, view, key):
1141 self.view=view 1142 self.key=key 1143 self.thumbsize=10,10 1144 self.setvals() 1145 self.lastw=None
1146
1147 - def setvals(self):
1148 me=self.view._data[self.datakey][self.key] 1149 self.name=me.name 1150 self.origin=me.origin 1151 self.mimetypes='' 1152 self.short='' 1153 self.long='' 1154 self.timestamp='' 1155 self.thumb=None 1156 if me.mediadata!=None: 1157 self.size=len(me.mediadata) 1158 self.no_data=False 1159 if me.timestamp!=None and me.timestamp!=0: 1160 try: 1161 self.timestamp=time.strftime("%x %X", time.localtime(me.timestamp)) 1162 except: # unexplained errors sometimes, so skip timestamp if this fails 1163 self.timestamp='' 1164 fileinfo=self.view.GetFileInfoString(me.mediadata) 1165 if fileinfo!=None: 1166 self.short=fileinfo.shortdescription() 1167 self.long=fileinfo.longdescription() 1168 self.mimetypes=fileinfo.mimetypes 1169 self.fileinfo=fileinfo 1170 else: 1171 self.size=0 1172 self.no_data=True 1173 self.selbbox=None 1174 self.lines=[self.name, self.short, 1175 '%.1f kb' % (self.size/1024.0,)]
1176
1177 - def Draw(self, dc, width, height, selected):
1178 if self.thumb==None: 1179 try: 1180 if self.size: 1181 me=self.view._data[self.datakey][self.key] 1182 self.thumb=self.view.GetItemThumbnail(me.mediadata, self.thumbnailsize[0], self.thumbnailsize[1], self.fileinfo) 1183 else: 1184 self.thumb=self.view.GetItemThumbnail(None, self.thumbnailsize[0], self.thumbnailsize[1]) 1185 except: 1186 self.thumb=self.view.GetItemThumbnail(None, self.thumbnailsize[0], self.thumbnailsize[1]) 1187 redrawbbox=False 1188 if selected: 1189 if self.lastw!=width or self.selbbox is None: 1190 redrawbbox=True 1191 else: 1192 oldb=dc.GetBrush() 1193 oldp=dc.GetPen() 1194 dc.SetBrush(self.view.item_selection_brush) 1195 dc.SetPen(self.view.item_selection_pen) 1196 dc.DrawRectangle(*self.selbbox) 1197 dc.SetBrush(oldb) 1198 dc.SetPen(oldp) 1199 dc.DrawBitmap(self.thumb, self.PADDING+self.thumbnailsize[0]/2-self.thumb.GetWidth()/2, self.PADDING, True) 1200 xoff=self.PADDING+self.thumbnailsize[0]+self.PADDING 1201 yoff=self.PADDING*2 1202 widthavailable=width-xoff-self.PADDING 1203 maxw=0 1204 old=dc.GetFont() 1205 for i,line in enumerate(self.lines): 1206 dc.SetFont(self.view.item_line_font[i]) 1207 w,h=DrawTextWithLimit(dc, xoff, yoff, line, widthavailable, self.view.item_guardspace, self.view.item_term) 1208 maxw=max(maxw,w) 1209 yoff+=h 1210 dc.SetFont(old) 1211 self.lastw=width 1212 self.selbbox=(0,0,xoff+maxw+self.PADDING,max(yoff+self.PADDING,self.thumb.GetHeight()+self.PADDING*2)) 1213 if redrawbbox: 1214 return self.Draw(dc, width, height, selected) 1215 return self.selbbox
1216
1217 - def DisplayTooltip(self, parent, rect):
1218 res=["Name: "+self.name, "Origin: "+(self.origin, "default")[self.origin is None], 1219 'File size: %.1f kb (%d bytes)' % (self.size/1024.0, self.size), "\n"+self.datatype+" information:\n", self.long] 1220 # tipwindow takes screen coordinates so we have to transform 1221 x,y=parent.ClientToScreen(rect[0:2]) 1222 return wx.TipWindow(parent, "\n".join(res), 1024, wx.Rect(x,y,rect[2], rect[3]))
1223
1224 - def RemoveFromIndex(self):
1225 del self.view._data[self.datakey][self.key] 1226 self.view.modified=True 1227 self.view.OnRefresh()
1228
1229 - def RenameInIndex(self, new_name):
1230 self.view._data[self.datakey][self.key].name=new_name 1231 self.view.modified=True 1232 self.view.OnRefresh()
1233
1234 - def ChangeOriginInIndex(self, new_origin):
1235 self.view._data[self.datakey][self.key].origin=new_origin 1236 self.view._data[self.datakey][self.key].index=-1 1237 self.view.modified=True 1238 self.view.OnRefresh()
1239
1240 - def Refresh(self):
1241 self.setvals() 1242 self.view.modified=True 1243 self.view.OnRefresh()
1244