2017-08-02 21:32:54 +00:00
import ClientConstants as CC
import ClientData
import ClientGUICommon
2017-11-29 21:48:23 +00:00
import ClientSerialisable
import HydrusData
2017-08-02 21:32:54 +00:00
import HydrusExceptions
2017-11-29 21:48:23 +00:00
import HydrusGlobals as HG
import HydrusSerialisable
import os
2017-08-02 21:32:54 +00:00
import wx
from wx . lib . mixins . listctrl import ListCtrlAutoWidthMixin
2018-04-11 22:30:40 +00:00
( ListCtrlEvent , EVT_LIST_CTRL ) = wx . lib . newevent . NewCommandEvent ( )
2017-12-06 22:06:56 +00:00
# This used to be ColumnSorterMixin, but it was crashing on sort-click on clients with many pages open
# I've disabled it for now because it was still catching people. The transition to BetterListCtrl will nuke the whole thing eventually.
class SaneListCtrl ( wx . ListCtrl , ListCtrlAutoWidthMixin ) :
2017-08-02 21:32:54 +00:00
def __init__ ( self , parent , height , columns , delete_key_callback = None , activation_callback = None ) :
num_columns = len ( columns )
wx . ListCtrl . __init__ ( self , parent , style = wx . LC_REPORT )
ListCtrlAutoWidthMixin . __init__ ( self )
self . itemDataMap = { }
self . _data_indices_to_sort_indices = { }
self . _data_indices_to_sort_indices_dirty = False
self . _next_data_index = 0
resize_column = 1
for ( i , ( name , width ) ) in enumerate ( columns ) :
self . InsertColumn ( i , name , width = width )
if width == - 1 :
resize_column = i + 1
self . setResizeColumn ( resize_column )
self . SetMinSize ( ( - 1 , height ) )
self . _delete_key_callback = delete_key_callback
self . _activation_callback = activation_callback
self . Bind ( wx . EVT_KEY_DOWN , self . EventKeyDown )
self . Bind ( wx . EVT_LIST_ITEM_ACTIVATED , self . EventItemActivated )
self . Bind ( wx . EVT_LIST_COL_BEGIN_DRAG , self . EventBeginColDrag )
def _GetIndexFromDataIndex ( self , data_index ) :
if self . _data_indices_to_sort_indices_dirty :
2018-01-03 22:37:30 +00:00
self . _data_indices_to_sort_indices = { self . GetItemData ( index ) : index for index in range ( self . GetItemCount ( ) ) }
2017-08-02 21:32:54 +00:00
self . _data_indices_to_sort_indices_dirty = False
try :
return self . _data_indices_to_sort_indices [ data_index ]
except KeyError :
raise HydrusExceptions . DataMissing ( ' Data not found! ' )
def Append ( self , display_tuple , sort_tuple ) :
index = wx . ListCtrl . Append ( self , display_tuple )
data_index = self . _next_data_index
self . SetItemData ( index , data_index )
self . itemDataMap [ data_index ] = list ( sort_tuple )
self . _data_indices_to_sort_indices [ data_index ] = index
self . _next_data_index + = 1
def DeleteItem ( self , * args , * * kwargs ) :
wx . ListCtrl . DeleteItem ( self , * args , * * kwargs )
self . _data_indices_to_sort_indices_dirty = True
def EventBeginColDrag ( self , event ) :
# resizeCol is not zero-indexed
if event . GetColumn ( ) == self . _resizeCol - 1 :
last_column = self . GetColumnCount ( )
if self . _resizeCol != last_column :
self . setResizeColumn ( last_column )
else :
event . Veto ( )
return
event . Skip ( )
def EventItemActivated ( self , event ) :
if self . _activation_callback is not None :
self . _activation_callback ( )
else :
event . Skip ( )
def EventKeyDown ( self , event ) :
( modifier , key ) = ClientData . ConvertKeyEventToSimpleTuple ( event )
if key in CC . DELETE_KEYS :
if self . _delete_key_callback is not None :
self . _delete_key_callback ( )
elif key in ( ord ( ' A ' ) , ord ( ' a ' ) ) and modifier == wx . ACCEL_CTRL :
self . SelectAll ( )
else :
event . Skip ( )
def GetAllSelected ( self ) :
indices = [ ]
i = self . GetFirstSelected ( )
while i != - 1 :
indices . append ( i )
i = self . GetNextSelected ( i )
return indices
def GetClientData ( self , index = None ) :
if index is None :
2018-01-03 22:37:30 +00:00
data_indicies = [ self . GetItemData ( index ) for index in range ( self . GetItemCount ( ) ) ]
2017-08-02 21:32:54 +00:00
datas = [ tuple ( self . itemDataMap [ data_index ] ) for data_index in data_indicies ]
return datas
else :
2018-01-03 22:37:30 +00:00
data_index = self . GetItemData ( index )
2017-08-02 21:32:54 +00:00
return tuple ( self . itemDataMap [ data_index ] )
def GetIndexFromClientData ( self , data , column_index = None ) :
for index in range ( self . GetItemCount ( ) ) :
client_data = self . GetClientData ( index )
if column_index is None :
comparison_data = client_data
else :
comparison_data = client_data [ column_index ]
if comparison_data == data :
return index
raise HydrusExceptions . DataMissing ( ' Data not found! ' )
def GetSecondarySortValues ( self , col , key1 , key2 ) :
# This overrides the ColumnSortedMixin. Just spam the whole tuple back.
return ( self . itemDataMap [ key1 ] , self . itemDataMap [ key2 ] )
def GetListCtrl ( self ) :
return self
def GetSelectedClientData ( self ) :
indices = self . GetAllSelected ( )
results = [ ]
for index in indices :
results . append ( self . GetClientData ( index ) )
return results
def HasClientData ( self , data , column_index = None ) :
try :
index = self . GetIndexFromClientData ( data , column_index )
return True
except HydrusExceptions . DataMissing :
return False
def OnSortOrderChanged ( self ) :
self . _data_indices_to_sort_indices_dirty = True
def RemoveAllSelected ( self ) :
indices = self . GetAllSelected ( )
self . RemoveIndices ( indices )
def RemoveIndices ( self , indices ) :
indices . sort ( )
indices . reverse ( ) # so we don't screw with the indices of deletees below
for index in indices :
self . DeleteItem ( index )
def SelectAll ( self ) :
currently_selected = set ( self . GetAllSelected ( ) )
currently_not_selected = [ index for index in range ( self . GetItemCount ( ) ) if index not in currently_selected ]
for index in currently_not_selected :
self . Select ( index )
def UpdateRow ( self , index , display_tuple , sort_tuple ) :
column = 0
for value in display_tuple :
2018-01-03 22:37:30 +00:00
self . SetItem ( index , column , value )
2017-08-02 21:32:54 +00:00
column + = 1
2018-01-03 22:37:30 +00:00
data_index = self . GetItemData ( index )
2017-08-02 21:32:54 +00:00
self . itemDataMap [ data_index ] = list ( sort_tuple )
class SaneListCtrlForSingleObject ( SaneListCtrl ) :
def __init__ ( self , * args , * * kwargs ) :
# this could one day just take column parameters that the user can pick
# it could just take obj in append or whatever and generate column tuples off that
self . _data_indices_to_objects = { }
self . _objects_to_data_indices = { }
SaneListCtrl . __init__ ( self , * args , * * kwargs )
def Append ( self , display_tuple , sort_tuple , obj ) :
self . _data_indices_to_objects [ self . _next_data_index ] = obj
self . _objects_to_data_indices [ obj ] = self . _next_data_index
SaneListCtrl . Append ( self , display_tuple , sort_tuple )
def GetIndexFromObject ( self , obj ) :
try :
data_index = self . _objects_to_data_indices [ obj ]
index = self . _GetIndexFromDataIndex ( data_index )
return index
except KeyError :
raise HydrusExceptions . DataMissing ( ' Data not found! ' )
def GetObject ( self , index ) :
2018-01-03 22:37:30 +00:00
data_index = self . GetItemData ( index )
2017-08-02 21:32:54 +00:00
return self . _data_indices_to_objects [ data_index ]
def GetObjects ( self , only_selected = False ) :
if only_selected :
indicies = self . GetAllSelected ( )
else :
indicies = range ( self . GetItemCount ( ) )
2018-01-03 22:37:30 +00:00
data_indicies = [ self . GetItemData ( index ) for index in indicies ]
2017-08-02 21:32:54 +00:00
datas = [ self . _data_indices_to_objects [ data_index ] for data_index in data_indicies ]
return datas
def HasObject ( self , obj ) :
try :
index = self . GetIndexFromObject ( obj )
return True
except HydrusExceptions . DataMissing :
return False
def SetNonDupeName ( self , obj ) :
# when column population is handled here, we can tuck this into normal append/update calls internally
2017-09-27 21:52:54 +00:00
current_names = { o . GetName ( ) for o in self . GetObjects ( ) if o is not obj }
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
HydrusSerialisable . SetNonDupeName ( obj , current_names )
2017-08-02 21:32:54 +00:00
def UpdateRow ( self , index , display_tuple , sort_tuple , obj ) :
SaneListCtrl . UpdateRow ( self , index , display_tuple , sort_tuple )
2018-01-03 22:37:30 +00:00
data_index = self . GetItemData ( index )
2017-08-02 21:32:54 +00:00
self . _data_indices_to_objects [ data_index ] = obj
self . _objects_to_data_indices [ obj ] = data_index
class SaneListCtrlPanel ( wx . Panel ) :
def __init__ ( self , parent ) :
wx . Panel . __init__ ( self , parent )
self . _vbox = wx . BoxSizer ( wx . VERTICAL )
self . _buttonbox = wx . BoxSizer ( wx . HORIZONTAL )
self . _listctrl = None
self . _button_infos = [ ]
def _SomeSelected ( self ) :
return self . _listctrl . GetSelectedItemCount ( ) > 0
def _UpdateButtons ( self ) :
for ( button , enabled_check_func ) in self . _button_infos :
if enabled_check_func ( ) :
button . Enable ( )
else :
button . Disable ( )
def AddButton ( self , label , clicked_func , enabled_only_on_selection = False , enabled_check_func = None ) :
button = ClientGUICommon . BetterButton ( self , label , clicked_func )
2018-01-03 22:37:30 +00:00
self . _buttonbox . Add ( button , CC . FLAGS_VCENTER )
2017-08-02 21:32:54 +00:00
if enabled_only_on_selection :
enabled_check_func = self . _SomeSelected
if enabled_check_func is not None :
self . _button_infos . append ( ( button , enabled_check_func ) )
def AddWindow ( self , window ) :
2018-01-03 22:37:30 +00:00
self . _buttonbox . Add ( window , CC . FLAGS_VCENTER )
2017-08-02 21:32:54 +00:00
def EventContentChanged ( self , event ) :
2018-01-03 22:37:30 +00:00
if not self . _listctrl :
return
2017-08-02 21:32:54 +00:00
self . _UpdateButtons ( )
event . Skip ( )
def EventSelectionChanged ( self , event ) :
2018-01-03 22:37:30 +00:00
if not self . _listctrl :
return
2017-08-02 21:32:54 +00:00
self . _UpdateButtons ( )
event . Skip ( )
def SetListCtrl ( self , listctrl ) :
self . _listctrl = listctrl
2018-01-03 22:37:30 +00:00
self . _vbox . Add ( self . _listctrl , CC . FLAGS_EXPAND_SIZER_BOTH_WAYS )
self . _vbox . Add ( self . _buttonbox , CC . FLAGS_BUTTON_SIZER )
2017-08-02 21:32:54 +00:00
self . SetSizer ( self . _vbox )
self . _listctrl . Bind ( wx . EVT_LIST_ITEM_SELECTED , self . EventSelectionChanged )
self . _listctrl . Bind ( wx . EVT_LIST_ITEM_DESELECTED , self . EventSelectionChanged )
self . _listctrl . Bind ( wx . EVT_LIST_INSERT_ITEM , self . EventContentChanged )
self . _listctrl . Bind ( wx . EVT_LIST_DELETE_ITEM , self . EventContentChanged )
self . _listctrl . Bind ( wx . EVT_LIST_DELETE_ALL_ITEMS , self . EventContentChanged )
2017-09-27 21:52:54 +00:00
2017-08-02 21:32:54 +00:00
class BetterListCtrl ( wx . ListCtrl , ListCtrlAutoWidthMixin ) :
def __init__ ( self , parent , name , height_num_chars , sizing_column_initial_width_num_chars , columns , data_to_tuples_func , delete_key_callback = None , activation_callback = None ) :
wx . ListCtrl . __init__ ( self , parent , style = wx . LC_REPORT )
ListCtrlAutoWidthMixin . __init__ ( self )
self . _data_to_tuples_func = data_to_tuples_func
self . _sort_column = 0
self . _sort_asc = True
# eventually have it look up 'name' in some options somewhere and see previous height, width, and column selection
# this thing should deal with missing entries but also have some filtered defaults for subs listctrl, which will have a bunch of possible columns
self . _indices_to_data_info = { }
self . _data_to_indices = { }
( total_width , height ) = ClientData . ConvertTextToPixels ( self , ( sizing_column_initial_width_num_chars , height_num_chars ) )
resize_column = 1
for ( i , ( name , width_num_chars ) ) in enumerate ( columns ) :
if width_num_chars == - 1 :
width = - 1
resize_column = i + 1
else :
width = ClientData . ConvertTextToPixelWidth ( self , width_num_chars )
total_width + = width
self . InsertColumn ( i , name , width = width )
self . setResizeColumn ( resize_column )
self . SetInitialSize ( ( total_width , height ) )
self . _delete_key_callback = delete_key_callback
self . _activation_callback = activation_callback
self . Bind ( wx . EVT_KEY_DOWN , self . EventKeyDown )
self . Bind ( wx . EVT_LIST_ITEM_ACTIVATED , self . EventItemActivated )
self . Bind ( wx . EVT_LIST_COL_BEGIN_DRAG , self . EventBeginColDrag )
self . Bind ( wx . EVT_LIST_COL_CLICK , self . EventColumnClick )
2017-08-30 20:27:47 +00:00
def _AddDataInfo ( self , data_info ) :
2017-08-02 21:32:54 +00:00
( data , display_tuple , sort_tuple ) = data_info
2017-08-09 21:33:51 +00:00
index = self . Append ( display_tuple )
2017-08-02 21:32:54 +00:00
self . _indices_to_data_info [ index ] = data_info
self . _data_to_indices [ data ] = index
def _GetSelected ( self ) :
indices = [ ]
i = self . GetFirstSelected ( )
while i != - 1 :
indices . append ( i )
i = self . GetNextSelected ( i )
return indices
2017-08-09 21:33:51 +00:00
def _RecalculateIndicesAfterDelete ( self ) :
2017-08-30 20:27:47 +00:00
indices_and_data_info = list ( self . _indices_to_data_info . items ( ) )
indices_and_data_info . sort ( )
2017-08-09 21:33:51 +00:00
self . _indices_to_data_info = { }
self . _data_to_indices = { }
2017-08-30 20:27:47 +00:00
for ( index , ( old_index , data_info ) ) in enumerate ( indices_and_data_info ) :
2017-08-09 21:33:51 +00:00
( data , display_tuple , sort_tuple ) = data_info
self . _data_to_indices [ data ] = index
self . _indices_to_data_info [ index ] = data_info
2017-08-02 21:32:54 +00:00
def _SortDataInfo ( self ) :
data_infos = list ( self . _indices_to_data_info . values ( ) )
def sort_key ( data_info ) :
( data , display_tuple , sort_tuple ) = data_info
2017-08-09 21:33:51 +00:00
return ( sort_tuple [ self . _sort_column ] , sort_tuple ) # add the sort tuple to get secondary sorting
2017-08-02 21:32:54 +00:00
data_infos . sort ( key = sort_key , reverse = not self . _sort_asc )
return data_infos
2017-08-09 21:33:51 +00:00
def _SortAndRefreshRows ( self ) :
2017-08-02 21:32:54 +00:00
2017-09-20 19:47:31 +00:00
selected_data_quick = set ( self . GetData ( only_selected = True ) )
2017-08-02 21:32:54 +00:00
2017-08-30 20:27:47 +00:00
selected_indices = self . _GetSelected ( )
for selected_index in selected_indices :
self . Select ( selected_index , False )
2017-08-02 21:32:54 +00:00
sorted_data_info = self . _SortDataInfo ( )
self . _indices_to_data_info = { }
self . _data_to_indices = { }
2017-08-30 20:27:47 +00:00
for ( index , data_info ) in enumerate ( sorted_data_info ) :
self . _indices_to_data_info [ index ] = data_info
2017-08-02 21:32:54 +00:00
( data , display_tuple , sort_tuple ) = data_info
2017-08-30 20:27:47 +00:00
self . _data_to_indices [ data ] = index
2017-08-02 21:32:54 +00:00
2017-08-30 20:27:47 +00:00
self . _UpdateRow ( index , display_tuple )
2017-09-20 19:47:31 +00:00
if data in selected_data_quick :
2017-08-30 20:27:47 +00:00
self . Select ( index )
2017-08-02 21:32:54 +00:00
2017-08-30 20:27:47 +00:00
def _UpdateRow ( self , index , display_tuple ) :
2017-08-02 21:32:54 +00:00
2017-08-30 20:27:47 +00:00
for ( column_index , value ) in enumerate ( display_tuple ) :
2018-01-03 22:37:30 +00:00
self . SetItem ( index , column_index , value )
2017-08-30 20:27:47 +00:00
2017-08-02 21:32:54 +00:00
2017-08-30 20:27:47 +00:00
def AddDatas ( self , datas ) :
for data in datas :
( display_tuple , sort_tuple ) = self . _data_to_tuples_func ( data )
self . _AddDataInfo ( ( data , display_tuple , sort_tuple ) )
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-08-02 21:32:54 +00:00
def DeleteDatas ( self , datas ) :
deletees = [ ( self . _data_to_indices [ data ] , data ) for data in datas ]
deletees . sort ( reverse = True )
for ( index , data ) in deletees :
self . DeleteItem ( index )
del self . _data_to_indices [ data ]
del self . _indices_to_data_info [ index ]
2017-08-09 21:33:51 +00:00
self . _RecalculateIndicesAfterDelete ( )
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-08-30 20:27:47 +00:00
2017-08-02 21:32:54 +00:00
def DeleteSelected ( self ) :
indices = self . _GetSelected ( )
indices . sort ( reverse = True )
for index in indices :
( data , display_tuple , sort_tuple ) = self . _indices_to_data_info [ index ]
self . DeleteItem ( index )
del self . _data_to_indices [ data ]
del self . _indices_to_data_info [ index ]
2017-08-09 21:33:51 +00:00
self . _RecalculateIndicesAfterDelete ( )
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-08-02 21:32:54 +00:00
def EventBeginColDrag ( self , event ) :
# resizeCol is not zero-indexed
if event . GetColumn ( ) == self . _resizeCol - 1 :
last_column = self . GetColumnCount ( )
if self . _resizeCol != last_column :
self . setResizeColumn ( last_column )
else :
event . Veto ( )
return
event . Skip ( )
def EventColumnClick ( self , event ) :
col = event . GetColumn ( )
if col == self . _sort_column :
self . _sort_asc = not self . _sort_asc
else :
self . _sort_column = col
self . _sort_asc = True
2017-08-09 21:33:51 +00:00
self . _SortAndRefreshRows ( )
2017-08-02 21:32:54 +00:00
def EventItemActivated ( self , event ) :
if self . _activation_callback is not None :
self . _activation_callback ( )
else :
event . Skip ( )
def EventKeyDown ( self , event ) :
( modifier , key ) = ClientData . ConvertKeyEventToSimpleTuple ( event )
if key in CC . DELETE_KEYS :
if self . _delete_key_callback is not None :
self . _delete_key_callback ( )
elif key in ( ord ( ' A ' ) , ord ( ' a ' ) ) and modifier == wx . ACCEL_CTRL :
self . SelectAll ( )
else :
event . Skip ( )
def GetData ( self , only_selected = False ) :
if only_selected :
indices = self . _GetSelected ( )
else :
indices = self . _indices_to_data_info . keys ( )
2017-09-20 19:47:31 +00:00
result = [ ]
2017-08-02 21:32:54 +00:00
for index in indices :
( data , display_tuple , sort_tuple ) = self . _indices_to_data_info [ index ]
2017-09-20 19:47:31 +00:00
result . append ( data )
2017-08-02 21:32:54 +00:00
return result
def HasData ( self , data ) :
return data in self . _data_to_indices
def HasSelected ( self ) :
return self . GetSelectedItemCount ( ) > 0
def SelectAll ( self ) :
currently_selected = set ( self . _GetSelected ( ) )
currently_not_selected = [ index for index in range ( self . GetItemCount ( ) ) if index not in currently_selected ]
for index in currently_not_selected :
self . Select ( index )
2017-11-15 22:35:49 +00:00
def SelectDatas ( self , datas ) :
for data in datas :
if data in self . _data_to_indices :
index = self . _data_to_indices [ data ]
self . Select ( index )
2017-12-20 22:55:48 +00:00
def SelectNone ( self ) :
currently_selected = set ( self . _GetSelected ( ) )
for index in currently_selected :
self . Select ( index , False )
2017-08-02 21:32:54 +00:00
def SetData ( self , datas ) :
2017-08-09 21:33:51 +00:00
datas = set ( datas )
existing_datas = set ( self . _data_to_indices . keys ( ) )
2017-08-02 21:32:54 +00:00
2017-08-09 21:33:51 +00:00
datas_to_add = datas . difference ( existing_datas )
datas_to_update = datas . intersection ( existing_datas )
datas_to_delete = existing_datas . difference ( datas )
2017-08-02 21:32:54 +00:00
2017-08-09 21:33:51 +00:00
if len ( datas_to_delete ) > 0 :
2017-08-02 21:32:54 +00:00
2017-08-09 21:33:51 +00:00
self . DeleteDatas ( datas_to_delete )
2017-08-02 21:32:54 +00:00
2017-08-09 21:33:51 +00:00
if len ( datas_to_update ) > 0 :
self . UpdateDatas ( datas_to_update )
2017-08-02 21:32:54 +00:00
2017-08-09 21:33:51 +00:00
if len ( datas_to_add ) > 0 :
2017-08-30 20:27:47 +00:00
self . AddDatas ( datas_to_add )
2017-08-09 21:33:51 +00:00
2017-12-13 22:33:07 +00:00
self . _SortAndRefreshRows ( )
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-08-02 21:32:54 +00:00
def Sort ( self , col = None , asc = None ) :
if col is not None :
self . _sort_column = col
if asc is not None :
self . _sort_asc = asc
2017-08-09 21:33:51 +00:00
self . _SortAndRefreshRows ( )
2017-08-02 21:32:54 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-08-02 21:32:54 +00:00
2017-09-06 20:18:20 +00:00
def UpdateDatas ( self , datas = None ) :
if datas is None :
datas = list ( self . _data_to_indices . keys ( ) )
2017-08-09 21:33:51 +00:00
for data in datas :
( display_tuple , sort_tuple ) = self . _data_to_tuples_func ( data )
data_info = ( data , display_tuple , sort_tuple )
index = self . _data_to_indices [ data ]
if data_info != self . _indices_to_data_info [ index ] :
self . _indices_to_data_info [ index ] = data_info
2017-08-30 20:27:47 +00:00
self . _UpdateRow ( index , display_tuple )
2017-08-09 21:33:51 +00:00
2018-04-11 22:30:40 +00:00
wx . QueueEvent ( self . GetEventHandler ( ) , ListCtrlEvent ( - 1 ) )
2017-09-27 21:52:54 +00:00
class BetterListCtrlPanel ( wx . Panel ) :
def __init__ ( self , parent ) :
wx . Panel . __init__ ( self , parent )
self . _vbox = wx . BoxSizer ( wx . VERTICAL )
self . _buttonbox = wx . BoxSizer ( wx . HORIZONTAL )
self . _listctrl = None
2017-11-29 21:48:23 +00:00
self . _permitted_object_types = [ ]
self . _import_add_callable = lambda x : None
2017-09-27 21:52:54 +00:00
self . _button_infos = [ ]
2018-02-21 21:59:37 +00:00
def _AddAllDefaults ( self , defaults_callable , add_callable ) :
defaults = defaults_callable ( )
for default in defaults :
add_callable ( default )
2018-04-25 22:07:52 +00:00
self . _listctrl . Sort ( )
2018-02-21 21:59:37 +00:00
2017-11-08 22:07:12 +00:00
def _AddButton ( self , button , enabled_only_on_selection = False , enabled_check_func = None ) :
2018-01-03 22:37:30 +00:00
self . _buttonbox . Add ( button , CC . FLAGS_VCENTER )
2017-11-08 22:07:12 +00:00
if enabled_only_on_selection :
enabled_check_func = self . _HasSelected
if enabled_check_func is not None :
self . _button_infos . append ( ( button , enabled_check_func ) )
2018-02-21 21:59:37 +00:00
def _AddSomeDefaults ( self , defaults_callable , add_callable ) :
defaults = defaults_callable ( )
selected = False
choice_tuples = [ ( default . GetName ( ) , default , selected ) for default in defaults ]
import ClientGUIDialogs
with ClientGUIDialogs . DialogCheckFromList ( self , ' select the defaults to add ' , choice_tuples ) as dlg :
if dlg . ShowModal ( ) == wx . ID_OK :
defaults_to_add = dlg . GetChecked ( )
for default in defaults_to_add :
add_callable ( default )
2018-04-25 22:07:52 +00:00
self . _listctrl . Sort ( )
2018-02-21 21:59:37 +00:00
2017-11-29 21:48:23 +00:00
def _Duplicate ( self ) :
dupe_data = self . _GetExportObject ( )
if dupe_data is not None :
dupe_data = dupe_data . Duplicate ( )
self . _ImportObject ( dupe_data )
def _ExportToClipboard ( self ) :
export_object = self . _GetExportObject ( )
if export_object is not None :
json = export_object . DumpToString ( )
HG . client_controller . pub ( ' clipboard ' , ' text ' , json )
def _ExportToPng ( self ) :
export_object = self . _GetExportObject ( )
if export_object is not None :
import ClientGUITopLevelWindows
import ClientGUISerialisable
with ClientGUITopLevelWindows . DialogNullipotent ( self , ' export to png ' ) as dlg :
panel = ClientGUISerialisable . PngExportPanel ( dlg , export_object )
dlg . SetPanel ( panel )
dlg . ShowModal ( )
2018-02-07 23:40:33 +00:00
def _ExportToPngs ( self ) :
export_object = self . _GetExportObject ( )
if export_object is None :
return
if not isinstance ( export_object , HydrusSerialisable . SerialisableList ) :
self . _ExportToPng ( )
return
import ClientGUITopLevelWindows
import ClientGUISerialisable
with ClientGUITopLevelWindows . DialogNullipotent ( self , ' export to png ' ) as dlg :
panel = ClientGUISerialisable . PngsExportPanel ( dlg , export_object )
dlg . SetPanel ( panel )
dlg . ShowModal ( )
2017-11-29 21:48:23 +00:00
def _GetExportObject ( self ) :
to_export = HydrusSerialisable . SerialisableList ( )
for obj in self . _listctrl . GetData ( only_selected = True ) :
to_export . append ( obj )
if len ( to_export ) == 0 :
return None
elif len ( to_export ) == 1 :
return to_export [ 0 ]
else :
return to_export
2017-09-27 21:52:54 +00:00
def _HasSelected ( self ) :
return self . _listctrl . HasSelected ( )
2017-11-29 21:48:23 +00:00
def _ImportFromClipboard ( self ) :
2017-12-13 22:33:07 +00:00
raw_text = HG . client_controller . GetClipboardText ( )
try :
2017-11-29 21:48:23 +00:00
2017-12-13 22:33:07 +00:00
obj = HydrusSerialisable . CreateFromString ( raw_text )
2017-11-29 21:48:23 +00:00
2017-12-13 22:33:07 +00:00
self . _ImportObject ( obj )
2017-11-29 21:48:23 +00:00
2017-12-13 22:33:07 +00:00
except Exception as e :
2017-11-29 21:48:23 +00:00
2017-12-13 22:33:07 +00:00
wx . MessageBox ( ' I could not understand what was in the clipboard ' )
2017-11-29 21:48:23 +00:00
2018-04-25 22:07:52 +00:00
self . _listctrl . Sort ( )
2017-11-29 21:48:23 +00:00
def _ImportFromPng ( self ) :
2018-02-07 23:40:33 +00:00
with wx . FileDialog ( self , ' select the png or pngs with the encoded data ' , style = wx . FD_OPEN | wx . FD_MULTIPLE , wildcard = ' PNG (*.png)|*.png ' ) as dlg :
2017-11-29 21:48:23 +00:00
if dlg . ShowModal ( ) == wx . ID_OK :
2018-02-07 23:40:33 +00:00
for path in dlg . GetPaths ( ) :
2017-11-29 21:48:23 +00:00
2018-02-07 23:40:33 +00:00
path = HydrusData . ToUnicode ( path )
2017-11-29 21:48:23 +00:00
2018-02-07 23:40:33 +00:00
try :
payload = ClientSerialisable . LoadFromPng ( path )
except Exception as e :
wx . MessageBox ( HydrusData . ToUnicode ( e ) )
return
2017-11-29 21:48:23 +00:00
2018-02-07 23:40:33 +00:00
try :
obj = HydrusSerialisable . CreateFromNetworkString ( payload )
self . _ImportObject ( obj )
except :
wx . MessageBox ( ' I could not understand what was encoded in the png! ' )
return
2017-11-29 21:48:23 +00:00
2018-04-25 22:07:52 +00:00
self . _listctrl . Sort ( )
2017-11-29 21:48:23 +00:00
def _ImportObject ( self , obj ) :
bad_object_types = set ( )
if isinstance ( obj , HydrusSerialisable . SerialisableList ) :
for sub_obj in obj :
self . _ImportObject ( sub_obj )
else :
if isinstance ( obj , self . _permitted_object_types ) :
self . _import_add_callable ( obj )
else :
bad_object_types . add ( type ( obj ) . __name__ )
if len ( bad_object_types ) > 0 :
message = ' The imported objects included these types: '
message + = os . linesep * 2
message + = os . linesep . join ( bad_object_types )
message + = os . linesep * 2
message + = ' Whereas this control only allows: '
message + = os . linesep * 2
message + = os . linesep . join ( ( o . __name__ for o in self . _permitted_object_types ) )
wx . MessageBox ( message )
2017-09-27 21:52:54 +00:00
def _UpdateButtons ( self ) :
for ( button , enabled_check_func ) in self . _button_infos :
if enabled_check_func ( ) :
button . Enable ( )
else :
button . Disable ( )
def AddButton ( self , label , clicked_func , enabled_only_on_selection = False , enabled_check_func = None ) :
button = ClientGUICommon . BetterButton ( self , label , clicked_func )
2017-11-08 22:07:12 +00:00
self . _AddButton ( button , enabled_only_on_selection = enabled_only_on_selection , enabled_check_func = enabled_check_func )
2017-09-27 21:52:54 +00:00
2017-11-15 22:35:49 +00:00
self . _UpdateButtons ( )
2017-11-08 22:07:12 +00:00
2018-02-21 21:59:37 +00:00
def AddDefaultsButton ( self , defaults_callable , add_callable ) :
import_menu_items = [ ]
all_call = HydrusData . Call ( self . _AddAllDefaults , defaults_callable , add_callable )
some_call = HydrusData . Call ( self . _AddSomeDefaults , defaults_callable , add_callable )
import_menu_items . append ( ( ' normal ' , ' add them all ' , ' Load all the defaults. ' , all_call ) )
import_menu_items . append ( ( ' normal ' , ' select from a list ' , ' Load some of the defaults. ' , some_call ) )
self . AddMenuButton ( ' add defaults ' , import_menu_items )
2017-11-29 21:48:23 +00:00
def AddImportExportButtons ( self , permitted_object_types , import_add_callable ) :
self . _permitted_object_types = permitted_object_types
self . _import_add_callable = import_add_callable
export_menu_items = [ ]
export_menu_items . append ( ( ' normal ' , ' to clipboard ' , ' Serialise the selected data and put it on your clipboard. ' , self . _ExportToClipboard ) )
export_menu_items . append ( ( ' normal ' , ' to png ' , ' Serialise the selected data and encode it to an image file you can easily share with other hydrus users. ' , self . _ExportToPng ) )
2018-02-07 23:40:33 +00:00
all_objs_are_named = False not in ( issubclass ( o , HydrusSerialisable . SerialisableBaseNamed ) for o in self . _permitted_object_types )
if all_objs_are_named :
export_menu_items . append ( ( ' normal ' , ' to pngs ' , ' Serialise the selected data and encode it to multiple image files you can easily share with other hydrus users. ' , self . _ExportToPngs ) )
2017-11-29 21:48:23 +00:00
import_menu_items = [ ]
import_menu_items . append ( ( ' normal ' , ' from clipboard ' , ' Load a data from text in your clipboard. ' , self . _ImportFromClipboard ) )
2018-02-07 23:40:33 +00:00
import_menu_items . append ( ( ' normal ' , ' from pngs ' , ' Load a data from an encoded png. ' , self . _ImportFromPng ) )
2017-11-29 21:48:23 +00:00
self . AddMenuButton ( ' export ' , export_menu_items , enabled_only_on_selection = True )
self . AddMenuButton ( ' import ' , import_menu_items )
self . AddButton ( ' duplicate ' , self . _Duplicate , enabled_only_on_selection = True )
2017-11-08 22:07:12 +00:00
def AddMenuButton ( self , label , menu_items , enabled_only_on_selection = False , enabled_check_func = None ) :
2017-09-27 21:52:54 +00:00
2017-11-08 22:07:12 +00:00
button = ClientGUICommon . MenuButton ( self , label , menu_items )
self . _AddButton ( button , enabled_only_on_selection = enabled_only_on_selection , enabled_check_func = enabled_check_func )
2017-11-15 22:35:49 +00:00
self . _UpdateButtons ( )
2017-11-08 22:07:12 +00:00
def AddSeparator ( self ) :
2018-01-03 22:37:30 +00:00
self . _buttonbox . Add ( ( 20 , 20 ) , CC . FLAGS_EXPAND_PERPENDICULAR )
2017-09-27 21:52:54 +00:00
def AddWindow ( self , window ) :
2018-01-03 22:37:30 +00:00
self . _buttonbox . Add ( window , CC . FLAGS_VCENTER )
2017-09-27 21:52:54 +00:00
def EventContentChanged ( self , event ) :
2018-01-17 22:52:10 +00:00
if not self . _listctrl :
return
2017-09-27 21:52:54 +00:00
self . _UpdateButtons ( )
event . Skip ( )
def EventSelectionChanged ( self , event ) :
2018-01-17 22:52:10 +00:00
if not self . _listctrl :
return
2017-09-27 21:52:54 +00:00
self . _UpdateButtons ( )
event . Skip ( )
2017-12-20 22:55:48 +00:00
def NewButtonRow ( self ) :
self . _buttonbox = wx . BoxSizer ( wx . HORIZONTAL )
2018-01-03 22:37:30 +00:00
self . _vbox . Add ( self . _buttonbox , CC . FLAGS_BUTTON_SIZER )
2017-12-20 22:55:48 +00:00
2017-09-27 21:52:54 +00:00
def SetListCtrl ( self , listctrl ) :
self . _listctrl = listctrl
2018-01-03 22:37:30 +00:00
self . _vbox . Add ( self . _listctrl , CC . FLAGS_EXPAND_SIZER_BOTH_WAYS )
self . _vbox . Add ( self . _buttonbox , CC . FLAGS_BUTTON_SIZER )
2017-09-27 21:52:54 +00:00
self . SetSizer ( self . _vbox )
self . _listctrl . Bind ( wx . EVT_LIST_ITEM_SELECTED , self . EventSelectionChanged )
self . _listctrl . Bind ( wx . EVT_LIST_ITEM_DESELECTED , self . EventSelectionChanged )
self . _listctrl . Bind ( wx . EVT_LIST_INSERT_ITEM , self . EventContentChanged )
self . _listctrl . Bind ( wx . EVT_LIST_DELETE_ITEM , self . EventContentChanged )
self . _listctrl . Bind ( wx . EVT_LIST_DELETE_ALL_ITEMS , self . EventContentChanged )