Compare commits
	
		
			31 Commits
		
	
	
		
			41f39ba8cc
			...
			c01e81af27
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c01e81af27 | |||
| f2090a7d46 | |||
| 9bd5697677 | |||
| dfce2f0125 | |||
| da9a8c024b | |||
| 95f790a7a4 | |||
| 8a3146fd03 | |||
| f51a860de5 | |||
| 4f9fe37613 | |||
| 9cde8345cf | |||
| ee123c4916 | |||
| eeef0a4330 | |||
| d0612a2b37 | |||
| 9697e8ca16 | |||
| efa42a301c | |||
| 75da08d081 | |||
| 49ed89201a | |||
| e96d9e682d | |||
| 206f67f2f0 | |||
| e929e9b742 | |||
| 0dece2cec9 | |||
| 982e586936 | |||
| 867c651a04 | |||
| f48d84a004 | |||
| dc9cae6d38 | |||
| 72f0236e58 | |||
| da63e6e44e | |||
| d3e42b3ae0 | |||
| bdd532060a | |||
| ded86b81ec | |||
| a7fbc6eadb | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,3 +1,4 @@ | |||||||
|  | docs/ | ||||||
| .idea/ | .idea/ | ||||||
| *.zip | *.zip | ||||||
|  |  | ||||||
| @@ -140,4 +141,3 @@ dmypy.json | |||||||
|  |  | ||||||
| # Cython debug symbols | # Cython debug symbols | ||||||
| cython_debug/ | cython_debug/ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,25 +40,3 @@ UI Targets: | |||||||
| <li>context_menu</li> | <li>context_menu</li> | ||||||
| <li>other</li> | <li>other</li> | ||||||
| </ul> | </ul> | ||||||
|  |  | ||||||
| ### Methods |  | ||||||
| ``` |  | ||||||
| # Must define and return a widget if "ui_target" is defined. |  | ||||||
| def get_ui_element(self): |  | ||||||
|     button = Gtk.Button(label=self.name) |  | ||||||
|     button.connect("button-release-event", self._do_download) |  | ||||||
|     return button |  | ||||||
|  |  | ||||||
| # Must define in plugin if "pass_fm_events" is set to "true" string. |  | ||||||
| def set_fm_event_system(self, fm_event_system): |  | ||||||
|     self._fm_event_system = fm_event_system |  | ||||||
|  |  | ||||||
| # Must define regardless if needed. Can just pass if plugin does stuff in its __init__ |  | ||||||
| def run(self): |  | ||||||
|     self._module_event_observer() |  | ||||||
|  |  | ||||||
| # Must define in plugin if "pass_ui_objects" is set and an array of valid glade UI IDs. |  | ||||||
| def set_ui_object_collection(self, ui_objects): |  | ||||||
|     self._ui_objects = ui_objects |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								plugins/archiver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Module | ||||||
|  | """ | ||||||
							
								
								
									
										3
									
								
								plugins/archiver/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Package | ||||||
|  | """ | ||||||
							
								
								
									
										164
									
								
								plugins/archiver/archiver.glade
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,164 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!-- Generated with glade 3.38.2 --> | ||||||
|  | <interface> | ||||||
|  |   <requires lib="gtk+" version="3.16"/> | ||||||
|  |   <object class="GtkTextBuffer" id="arc_command_buffer"> | ||||||
|  |     <property name="text" translatable="yes">$(which 7za || echo 7zr) a %o %N</property> | ||||||
|  |   </object> | ||||||
|  |   <object class="GtkFileChooserDialog" id="archiver_dialogue"> | ||||||
|  |     <property name="can-focus">False</property> | ||||||
|  |     <property name="modal">True</property> | ||||||
|  |     <property name="window-position">center</property> | ||||||
|  |     <property name="type-hint">dialog</property> | ||||||
|  |     <property name="gravity">center</property> | ||||||
|  |     <property name="do-overwrite-confirmation">True</property> | ||||||
|  |     <property name="select-multiple">True</property> | ||||||
|  |     <child internal-child="vbox"> | ||||||
|  |       <object class="GtkBox"> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="orientation">vertical</property> | ||||||
|  |         <property name="spacing">2</property> | ||||||
|  |         <child internal-child="action_area"> | ||||||
|  |           <object class="GtkButtonBox"> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="layout-style">end</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkButton" id="button21"> | ||||||
|  |                 <property name="label">gtk-cancel</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="receives-default">True</property> | ||||||
|  |                 <property name="use-stock">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">0</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkButton" id="button22"> | ||||||
|  |                 <property name="label">gtk-ok</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="receives-default">True</property> | ||||||
|  |                 <property name="use-stock">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">False</property> | ||||||
|  |             <property name="position">0</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkBox"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="orientation">vertical</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="homogeneous">True</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="label" translatable="yes">Compress Commands:</property> | ||||||
|  |                     <property name="xalign">0.20000000298023224</property> | ||||||
|  |                     <attributes> | ||||||
|  |                       <attribute name="gravity" value="west"/> | ||||||
|  |                     </attributes> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <placeholder/> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="label" translatable="yes">Archive Format:</property> | ||||||
|  |                     <property name="xalign">1</property> | ||||||
|  |                     <attributes> | ||||||
|  |                       <attribute name="gravity" value="east"/> | ||||||
|  |                     </attributes> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">2</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkComboBoxText"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="active">0</property> | ||||||
|  |                     <property name="active-id">0</property> | ||||||
|  |                     <items> | ||||||
|  |                       <item id="0" translatable="yes">7Zip (*.7z)</item> | ||||||
|  |                       <item id="1" translatable="yes">Zip (*.zip *.ZIP)</item> | ||||||
|  |                       <item id="2" translatable="yes">RAR (*.rar *.RAR)</item> | ||||||
|  |                       <item id="3" translatable="yes">Tar (*.tar)</item> | ||||||
|  |                       <item id="4" translatable="yes">Tar bzip2 (*.tar.bz2)</item> | ||||||
|  |                       <item id="5" translatable="yes">Tar Gzip (*.tar.gz *.tgz)</item> | ||||||
|  |                       <item id="6" translatable="yes">Tar xz (*.tar.xz *.txz)</item> | ||||||
|  |                       <item id="7" translatable="yes">Gzip (*.gz)</item> | ||||||
|  |                       <item id="8" translatable="yes">XZ (*.xz)</item> | ||||||
|  |                     </items> | ||||||
|  |                     <signal name="changed" handler="set_arc_buffer_text" swapped="no"/> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">3</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">0</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkTextView" id="arc_command"> | ||||||
|  |                 <property name="height-request">72</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="buffer">arc_command_buffer</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">2</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|  |     </child> | ||||||
|  |     <action-widgets> | ||||||
|  |       <action-widget response="-6">button21</action-widget> | ||||||
|  |       <action-widget response="-5">button22</action-widget> | ||||||
|  |     </action-widgets> | ||||||
|  |   </object> | ||||||
|  | </interface> | ||||||
							
								
								
									
										12
									
								
								plugins/archiver/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | { | ||||||
|  |     "manifest": { | ||||||
|  |         "name": "Archiver", | ||||||
|  |         "author": "ITDominator", | ||||||
|  |         "version": "0.0.1", | ||||||
|  |         "support": "", | ||||||
|  |         "requests": { | ||||||
|  |             "ui_target": "context_menu_plugins", | ||||||
|  |             "pass_fm_events": "true" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										129
									
								
								plugins/archiver/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,129 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import inspect | ||||||
|  | import shlex | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from plugins.plugin_base import PluginBase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(PluginBase): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.path               = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |         self._GLADE_FILE        = f"{self.path}/archiver.glade" | ||||||
|  |         self.name = "Archiver"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus | ||||||
|  |                                 #       where self.name should not be needed for message comms | ||||||
|  |         self._archiver_dialogue  = None | ||||||
|  |         self._arc_command_buffer = None | ||||||
|  |  | ||||||
|  |         # In compress commands: | ||||||
|  |         #    %n: First selected filename/dir to archive | ||||||
|  |         #    %N: All selected filenames/dirs to archive, or (with %O) a single filename | ||||||
|  |         #    %o: Resulting single archive file | ||||||
|  |         #    %O: Resulting archive per source file/directory (use changes %N meaning) | ||||||
|  |         # | ||||||
|  |         #  In extract commands: | ||||||
|  |         #    %x: Archive file to extract | ||||||
|  |         #    %g: Unique extraction target filename with optional subfolder | ||||||
|  |         #    %G: Unique extraction target filename, never with subfolder | ||||||
|  |         # | ||||||
|  |         #  In list commands: | ||||||
|  |         #      %x: Archive to list | ||||||
|  |         # | ||||||
|  |         #  Plus standard bash variables are accepted. | ||||||
|  |         self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N', | ||||||
|  |                                                                 'zip -r %o %N', | ||||||
|  |                                                                 'rar a -r %o %N', | ||||||
|  |                                                                 'tar -cvf %o %N', | ||||||
|  |                                                                 'tar -cvjf %o %N', | ||||||
|  |                                                                 'tar -cvzf %o %N', | ||||||
|  |                                                                 'tar -cvJf %o %N', | ||||||
|  |                                                                 'gzip -c %N > %O', | ||||||
|  |                                                                 'xz -cz %N > %O' | ||||||
|  |                                         ] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         self._builder           = Gtk.Builder() | ||||||
|  |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
|  |         classes  = [self] | ||||||
|  |         handlers = {} | ||||||
|  |         for c in classes: | ||||||
|  |             methods = None | ||||||
|  |             try: | ||||||
|  |                 methods = inspect.getmembers(c, predicate=inspect.ismethod) | ||||||
|  |                 handlers.update(methods) | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(repr(e)) | ||||||
|  |  | ||||||
|  |         self._builder.connect_signals(handlers) | ||||||
|  |  | ||||||
|  |         self._archiver_dialogue  = self._builder.get_object("archiver_dialogue") | ||||||
|  |         self._arc_command_buffer = self._builder.get_object("arc_command_buffer") | ||||||
|  |  | ||||||
|  |         item = Gtk.ImageMenuItem(self.name) | ||||||
|  |         item.set_image( Gtk.Image(stock=Gtk.STOCK_FLOPPY) ) | ||||||
|  |         item.connect("activate", self.show_archiver_dialogue) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def show_archiver_dialogue(self, widget=None, eve=None): | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |         state = self._fm_state | ||||||
|  |  | ||||||
|  |         self._archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) | ||||||
|  |         self._archiver_dialogue.set_current_folder(state.tab.get_current_directory()) | ||||||
|  |         self._archiver_dialogue.set_current_name("arc.7z") | ||||||
|  |  | ||||||
|  |         response = self._archiver_dialogue.run() | ||||||
|  |         if response == Gtk.ResponseType.OK: | ||||||
|  |             save_target = self._archiver_dialogue.get_filename() | ||||||
|  |             self.archive_files(save_target, state) | ||||||
|  |         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         self._archiver_dialogue.hide() | ||||||
|  |  | ||||||
|  |     def archive_files(self, save_target, state): | ||||||
|  |         paths       = [shlex.quote(p) for p in state.selected_files] | ||||||
|  |  | ||||||
|  |         sItr, eItr  = self._arc_command_buffer.get_bounds() | ||||||
|  |         pre_command = self._arc_command_buffer.get_text(sItr, eItr, False) | ||||||
|  |         pre_command = pre_command.replace("%o", shlex.quote(save_target)) | ||||||
|  |         pre_command = pre_command.replace("%N", ' '.join(paths)) | ||||||
|  |         command     = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" | ||||||
|  |         current_dir = state.tab.get_current_directory() | ||||||
|  |  | ||||||
|  |         state.tab.execute(shlex.split(command), start_dir=shlex.quote(current_dir)) | ||||||
|  |  | ||||||
|  |     def set_arc_buffer_text(self, widget=None, eve=None): | ||||||
|  |         sid = widget.get_active_id() | ||||||
|  |         self._arc_command_buffer.set_text(self.arc_commands[int(sid)]) | ||||||
							
								
								
									
										3
									
								
								plugins/disk_usage/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Module | ||||||
|  | """ | ||||||
							
								
								
									
										3
									
								
								plugins/disk_usage/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Package | ||||||
|  | """ | ||||||
							
								
								
									
										129
									
								
								plugins/disk_usage/du_usage.glade
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,129 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!-- Generated with glade 3.40.0 --> | ||||||
|  | <interface> | ||||||
|  |   <requires lib="gtk+" version="3.24"/> | ||||||
|  |   <object class="GtkListStore" id="du_store"> | ||||||
|  |     <columns> | ||||||
|  |       <!-- column-name Size --> | ||||||
|  |       <column type="gchararray"/> | ||||||
|  |       <!-- column-name Dir --> | ||||||
|  |       <column type="gchararray"/> | ||||||
|  |     </columns> | ||||||
|  |   </object> | ||||||
|  |   <object class="GtkDialog" id="du_dialog"> | ||||||
|  |     <property name="width-request">420</property> | ||||||
|  |     <property name="height-request">450</property> | ||||||
|  |     <property name="can-focus">False</property> | ||||||
|  |     <property name="modal">True</property> | ||||||
|  |     <property name="window-position">center</property> | ||||||
|  |     <property name="destroy-with-parent">True</property> | ||||||
|  |     <property name="type-hint">dialog</property> | ||||||
|  |     <property name="skip-taskbar-hint">True</property> | ||||||
|  |     <property name="skip-pager-hint">True</property> | ||||||
|  |     <property name="decorated">False</property> | ||||||
|  |     <property name="deletable">False</property> | ||||||
|  |     <property name="gravity">center</property> | ||||||
|  |     <child internal-child="vbox"> | ||||||
|  |       <object class="GtkBox"> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="orientation">vertical</property> | ||||||
|  |         <property name="spacing">2</property> | ||||||
|  |         <child internal-child="action_area"> | ||||||
|  |           <object class="GtkButtonBox"> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="layout-style">end</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkButton"> | ||||||
|  |                 <property name="label">gtk-close</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="receives-default">True</property> | ||||||
|  |                 <property name="use-stock">True</property> | ||||||
|  |                 <signal name="released" handler="_hide_du_menu" swapped="no"/> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">2</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">False</property> | ||||||
|  |             <property name="fill">False</property> | ||||||
|  |             <property name="position">0</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkBox"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="orientation">vertical</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkLabel" id="current_dir_lbl"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="margin-start">5</property> | ||||||
|  |                 <property name="margin-end">5</property> | ||||||
|  |                 <property name="margin-top">5</property> | ||||||
|  |                 <property name="margin-bottom">5</property> | ||||||
|  |                 <property name="label" translatable="yes">Current Directory:</property> | ||||||
|  |                 <property name="justify">center</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">0</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkScrolledWindow"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="shadow-type">in</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkTreeView"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="model">du_store</property> | ||||||
|  |                     <property name="headers-clickable">False</property> | ||||||
|  |                     <child internal-child="selection"> | ||||||
|  |                       <object class="GtkTreeSelection"/> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkTreeViewColumn"> | ||||||
|  |                         <property name="title" translatable="yes">Disk Usage</property> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCellRendererText"/> | ||||||
|  |                           <attributes> | ||||||
|  |                             <attribute name="text">0</attribute> | ||||||
|  |                           </attributes> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCellRendererText"/> | ||||||
|  |                           <attributes> | ||||||
|  |                             <attribute name="text">1</attribute> | ||||||
|  |                           </attributes> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="expand">True</property> | ||||||
|  |             <property name="fill">True</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|  |     </child> | ||||||
|  |   </object> | ||||||
|  | </interface> | ||||||
							
								
								
									
										12
									
								
								plugins/disk_usage/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | { | ||||||
|  |     "manifest": { | ||||||
|  |         "name": "Disk Usage", | ||||||
|  |         "author": "ITDominator", | ||||||
|  |         "version": "0.0.1", | ||||||
|  |         "support": "", | ||||||
|  |         "requests": { | ||||||
|  |             "ui_target": "context_menu_plugins", | ||||||
|  |             "pass_fm_events": "true" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										94
									
								
								plugins/disk_usage/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,94 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | import subprocess | ||||||
|  | import time | ||||||
|  | import inspect | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from plugins.plugin_base import PluginBase | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(PluginBase): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |  | ||||||
|  |         self.name        = "Disk Usage"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus | ||||||
|  |                                          #       where self.name should not be needed for message comms | ||||||
|  |  | ||||||
|  |         self.path        = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |         self._GLADE_FILE = f"{self.path}/du_usage.glade" | ||||||
|  |         self._du_dialog  = None | ||||||
|  |         self._du_store   = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         self._builder    = Gtk.Builder() | ||||||
|  |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
|  |         classes  = [self] | ||||||
|  |         handlers = {} | ||||||
|  |         for c in classes: | ||||||
|  |             methods = None | ||||||
|  |             try: | ||||||
|  |                 methods = inspect.getmembers(c, predicate=inspect.ismethod) | ||||||
|  |                 handlers.update(methods) | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(repr(e)) | ||||||
|  |  | ||||||
|  |         self._builder.connect_signals(handlers) | ||||||
|  |  | ||||||
|  |         self._du_dialog = self._builder.get_object("du_dialog") | ||||||
|  |         self._du_store  = self._builder.get_object("du_store") | ||||||
|  |         self._current_dir_lbl  = self._builder.get_object("current_dir_lbl") | ||||||
|  |  | ||||||
|  |         self._event_system.subscribe("show_du_menu", self._show_du_menu) | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         item = Gtk.ImageMenuItem(self.name) | ||||||
|  |         item.set_image( Gtk.Image(stock=Gtk.STOCK_HARDDISK) ) | ||||||
|  |         item.connect("activate", self._show_du_menu) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |     def _get_state(self, widget=None, eve=None): | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |  | ||||||
|  |     def _set_current_dir_lbl(self, widget=None, eve=None): | ||||||
|  |         self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") | ||||||
|  |  | ||||||
|  |     def _show_du_menu(self, widget=None, eve=None): | ||||||
|  |         self._fm_state = None | ||||||
|  |         self._get_state() | ||||||
|  |         self._set_current_dir_lbl() | ||||||
|  |         self.load_du_data() | ||||||
|  |         self._du_dialog.run() | ||||||
|  |  | ||||||
|  |     def load_du_data(self): | ||||||
|  |         self._du_store.clear() | ||||||
|  |  | ||||||
|  |         path     = self._fm_state.tab.get_current_directory() | ||||||
|  |         # NOTE: -h = human readable, -d = depth asigned to 1 | ||||||
|  |         command  = ["du", "-h", "-d", "1", path] | ||||||
|  |         proc     = subprocess.Popen(command, stdout=subprocess.PIPE) | ||||||
|  |         raw_data = proc.communicate()[0] | ||||||
|  |         data     = raw_data.decode("utf-8").strip()  # NOTE: Will return data AFTER completion (if any) | ||||||
|  |         parts    = data.split("\n") | ||||||
|  |  | ||||||
|  |         # NOTE: Last entry is curret dir. Move to top of list and pop off... | ||||||
|  |         size, file = parts[-1].split("\t") | ||||||
|  |         self._du_store.append([size, file.split("/")[-1]]) | ||||||
|  |         parts.pop() | ||||||
|  |  | ||||||
|  |         for part in parts: | ||||||
|  |             size, file = part.split("\t") | ||||||
|  |             self._du_store.append([size, file.split("/")[-1]]) | ||||||
|  |  | ||||||
|  |     def _hide_du_menu(self, widget=None, eve=None): | ||||||
|  |         self._du_dialog.hide() | ||||||
| @@ -1,15 +1,17 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <!-- Generated with glade 3.38.2 --> | <!-- Generated with glade 3.40.0 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.24"/> |   <requires lib="gtk+" version="3.24"/> | ||||||
|   <object class="GtkListStore" id="favorites_store"> |   <object class="GtkListStore" id="favorites_store"> | ||||||
|     <columns> |     <columns> | ||||||
|       <!-- column-name Favorites --> |       <!-- column-name Favorites --> | ||||||
|       <column type="gchararray"/> |       <column type="gchararray"/> | ||||||
|  |       <!-- column-name Path --> | ||||||
|  |       <column type="gchararray"/> | ||||||
|     </columns> |     </columns> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkDialog" id="favorites_dialog"> |   <object class="GtkDialog" id="favorites_dialog"> | ||||||
|     <property name="width-request">320</property> |     <property name="width-request">420</property> | ||||||
|     <property name="height-request">450</property> |     <property name="height-request">450</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="modal">True</property> |     <property name="modal">True</property> | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|             "ui_target": "main_menu_bttn_box_bar", |             "ui_target": "main_menu_bttn_box_bar", | ||||||
|             "pass_fm_events": "true", |             "pass_fm_events": "true", | ||||||
|             "pass_ui_objects": ["path_entry"], |             "pass_ui_objects": ["path_entry"], | ||||||
|             "bind_keys": [] |             "bind_keys": ["Favorites||show_favorites_menu:<Control>f"] | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, time, inspect, json | import os | ||||||
|  | import inspect | ||||||
|  | import json | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| @@ -10,19 +12,6 @@ from gi.repository import Gtk | |||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Threads WILL NOT die with parent's destruction. |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
| # NOTE: Threads WILL die with parent's destruction. |  | ||||||
| def daemon_threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin(PluginBase): | class Plugin(PluginBase): | ||||||
| @@ -38,18 +27,10 @@ class Plugin(PluginBase): | |||||||
|         self._favorites_dialog  = None |         self._favorites_dialog  = None | ||||||
|         self._favorites_store   = None |         self._favorites_store   = None | ||||||
|         self._favorites         = None |         self._favorites         = None | ||||||
|         self._state             = None |  | ||||||
|         self._selected          = None |         self._selected          = None | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_favorites_menu) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |  | ||||||
|  |  | ||||||
|         self._builder          = Gtk.Builder() |         self._builder          = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
| @@ -73,35 +54,43 @@ class Plugin(PluginBase): | |||||||
|             with open(self._FAVORITES_FILE) as f: |             with open(self._FAVORITES_FILE) as f: | ||||||
|                 self._favorites = json.load(f) |                 self._favorites = json.load(f) | ||||||
|                 for favorite in self._favorites: |                 for favorite in self._favorites: | ||||||
|                     self._favorites_store.append([favorite]) |                     display, path = favorite | ||||||
|  |                     self._favorites_store.append([display, path]) | ||||||
|         else: |         else: | ||||||
|             with open(self._FAVORITES_FILE, 'a') as f: |             with open(self._FAVORITES_FILE, 'a') as f: | ||||||
|                 f.write('[]') |                 f.write('[]') | ||||||
|  |  | ||||||
|  |         self._event_system.subscribe("show_favorites_menu", self._show_favorites_menu) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         button = Gtk.Button(label=self.name) | ||||||
|  |         button.connect("button-release-event", self._show_favorites_menu) | ||||||
|  |         return button | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _get_state(self, widget=None, eve=None): |     def _get_state(self, widget=None, eve=None): | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |         self._event_system.emit("get_current_state") | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         self._state         = self._event_message |  | ||||||
|         self._event_message = None |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def _set_current_dir_lbl(self, widget=None, eve=None): |     def _set_current_dir_lbl(self, widget=None, eve=None): | ||||||
|         self.wait_for_state() |         self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") | ||||||
|         self._current_dir_lbl.set_label(f"Current Directory:\n{self._state.tab.get_current_directory()}") |  | ||||||
|  |  | ||||||
|     def _add_to_favorite(self, state): |     def _add_to_favorite(self, state): | ||||||
|         current_directory = self._state.tab.get_current_directory() |         path    = self._fm_state.tab.get_current_directory() | ||||||
|         self._favorites_store.append([current_directory]) |         parts   = path.split("/") | ||||||
|         self._favorites.append(current_directory) |         display = '/'.join(parts[-3:]) if len(parts) > 3 else path | ||||||
|  |  | ||||||
|  |         self._favorites_store.append([display, path]) | ||||||
|  |         self._favorites.append([display, path]) | ||||||
|         self._save_favorites() |         self._save_favorites() | ||||||
|  |  | ||||||
|     def _remove_from_favorite(self, state): |     def _remove_from_favorite(self, state): | ||||||
|         path = self._favorites_store.get_value(self._selected, 0) |         path = self._favorites_store.get_value(self._selected, 1) | ||||||
|         self._favorites_store.remove(self._selected) |         self._favorites_store.remove(self._selected) | ||||||
|         self._favorites.remove(path) |  | ||||||
|  |         for i, f in enumerate(self._favorites): | ||||||
|  |             if f[1] == path: | ||||||
|  |                 self._favorites.remove( self._favorites[i] ) | ||||||
|  |  | ||||||
|         self._save_favorites() |         self._save_favorites() | ||||||
|  |  | ||||||
|     def _save_favorites(self): |     def _save_favorites(self): | ||||||
| @@ -109,13 +98,12 @@ class Plugin(PluginBase): | |||||||
|             json.dump(self._favorites, outfile, separators=(',', ':'), indent=4) |             json.dump(self._favorites, outfile, separators=(',', ':'), indent=4) | ||||||
|  |  | ||||||
|     def _set_selected_path(self, widget=None, eve=None): |     def _set_selected_path(self, widget=None, eve=None): | ||||||
|         path = self._favorites_store.get_value(self._selected, 0) |         path = self._favorites_store.get_value(self._selected, 1) | ||||||
|         self._ui_objects[0].set_text(path) |         self._ui_objects[0].set_text(path) | ||||||
|  |         self._set_current_dir_lbl() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _show_favorites_menu(self, widget=None, eve=None): |     def _show_favorites_menu(self, widget=None, eve=None): | ||||||
|         self._state = None |         self._fm_state = None | ||||||
|         self._get_state() |         self._get_state() | ||||||
|         self._set_current_dir_lbl() |         self._set_current_dir_lbl() | ||||||
|         self._favorites_dialog.run() |         self._favorites_dialog.run() | ||||||
| @@ -125,9 +113,5 @@ class Plugin(PluginBase): | |||||||
|  |  | ||||||
|     def _set_selected(self, user_data): |     def _set_selected(self, user_data): | ||||||
|         selected = user_data.get_selected()[1] |         selected = user_data.get_selected()[1] | ||||||
|         if selected: |         if selected and not self._selected == selected: | ||||||
|             self._selected = selected |             self._selected = selected | ||||||
|  |  | ||||||
|     def wait_for_state(self): |  | ||||||
|         while not self._state: |  | ||||||
|             pass |  | ||||||
|   | |||||||
| @@ -1,11 +1,18 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, time, pwd, grp | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import time | ||||||
|  | import pwd | ||||||
|  | import grp | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| from gi.repository import Gtk, GLib, Gio | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
| @@ -83,14 +90,7 @@ class Plugin(PluginBase): | |||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_properties_page) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |  | ||||||
|  |  | ||||||
|         self._builder           = Gtk.Builder() |         self._builder           = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
| @@ -105,14 +105,19 @@ class Plugin(PluginBase): | |||||||
|         self._file_owner    = self._builder.get_object("file_owner") |         self._file_owner    = self._builder.get_object("file_owner") | ||||||
|         self._file_group    = self._builder.get_object("file_group") |         self._file_group    = self._builder.get_object("file_group") | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         item = Gtk.ImageMenuItem(self.name) | ||||||
|  |         item.set_image( Gtk.Image(stock=Gtk.STOCK_PROPERTIES) ) | ||||||
|  |         item.connect("activate", self._show_properties_page) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |     @threaded | ||||||
|     def _show_properties_page(self, widget=None, eve=None): |     def _show_properties_page(self, widget=None, eve=None): | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |         event_system.emit("get_current_state") | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         state               = self._event_message |         state               = self._fm_state | ||||||
|         self._event_message = None |         self._event_message = None | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |         GLib.idle_add(self._process_changes, (state)) | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|         "version": "0.0.1", |         "version": "0.0.1", | ||||||
|         "support": "", |         "support": "", | ||||||
|         "requests": { |         "requests": { | ||||||
|             "ui_target": "context_menu", |             "ui_target": "context_menu_plugins", | ||||||
|             "pass_fm_events": "true" |             "pass_fm_events": "true" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,11 +1,18 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, inspect, requests, shutil | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import inspect | ||||||
|  | import requests | ||||||
|  | import shutil | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| gi.require_version('GdkPixbuf', '2.0') | gi.require_version('GdkPixbuf', '2.0') | ||||||
| from gi.repository import Gtk, GLib, GdkPixbuf | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import GdkPixbuf | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
| @@ -39,22 +46,13 @@ class Plugin(PluginBase): | |||||||
|         self._dialog                = None |         self._dialog                = None | ||||||
|         self._thumbnail_preview_img = None |         self._thumbnail_preview_img = None | ||||||
|         self._tmdb                  = scraper.get_tmdb_scraper() |         self._tmdb                  = scraper.get_tmdb_scraper() | ||||||
|         self._state                 = None |  | ||||||
|         self._overview              = None |         self._overview              = None | ||||||
|         self._file_name             = None |         self._file_name             = None | ||||||
|         self._file_location         = None |         self._file_location         = None | ||||||
|         self._trailer_link          = None |         self._trailer_link          = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_info_page) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |  | ||||||
|  |  | ||||||
|         self._builder           = Gtk.Builder() |         self._builder           = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
| @@ -78,21 +76,27 @@ class Plugin(PluginBase): | |||||||
|         self._file_hash             = self._builder.get_object("file_hash") |         self._file_hash             = self._builder.get_object("file_hash") | ||||||
|         self._trailer_link          = self._builder.get_object("trailer_link") |         self._trailer_link          = self._builder.get_object("trailer_link") | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         item = Gtk.ImageMenuItem(self.name) | ||||||
|  |         item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) | ||||||
|  |         item.connect("activate", self._show_info_page) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|     @threaded |     @threaded | ||||||
|     def _show_info_page(self, widget=None, eve=None): |     def _show_info_page(self, widget=None, eve=None): | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |         self._event_system.emit("get_current_state") | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         state               = self._event_message |         state               = self._fm_state | ||||||
|         self._event_message = None |         self._event_message = None | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |         GLib.idle_add(self._process_changes, (state)) | ||||||
|  |  | ||||||
|     def _process_changes(self, state): |     def _process_changes(self, state): | ||||||
|         self._state = None |         self._fm_state = None | ||||||
|  |  | ||||||
|         if len(state.selected_files) == 1: |         if len(state.selected_files) == 1: | ||||||
|             self._state = state |             self._fm_state = state | ||||||
|             self._set_ui_data() |             self._set_ui_data() | ||||||
|             response   = self._thumbnailer_dialog.run() |             response   = self._thumbnailer_dialog.run() | ||||||
|             if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: |             if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: | ||||||
| @@ -111,10 +115,11 @@ class Plugin(PluginBase): | |||||||
|         print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... |         print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... | ||||||
|  |  | ||||||
|     def get_video_data(self): |     def get_video_data(self): | ||||||
|         uri            = self._state.selected_files[0] |         uri            = self._fm_state.selected_files[0] | ||||||
|         path           = self._state.tab.get_current_directory() |         path           = self._fm_state.tab.get_current_directory() | ||||||
|         parts          = uri.split("/") |         parts          = uri.split("/") | ||||||
|         _title         = parts[ len(parts) - 1 ] |         _title         = parts[ len(parts) - 1 ] | ||||||
|  |         trailer        = None | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             title          = _title.split("(")[0].strip() |             title          = _title.split("(")[0].strip() | ||||||
| @@ -140,7 +145,6 @@ class Plugin(PluginBase): | |||||||
|                     raise Exception("No key found. Defering to none...") |                     raise Exception("No key found. Defering to none...") | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 print("No trailer found...") |                 print("No trailer found...") | ||||||
|                 trailer = None |  | ||||||
|  |  | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print(repr(e)) |             print(repr(e)) | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								plugins/movie_tv_info/tmdbscraper/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Module | ||||||
|  | """ | ||||||
| @@ -7,7 +7,7 @@ | |||||||
|         "requests": { |         "requests": { | ||||||
|             "ui_target": "context_menu", |             "ui_target": "context_menu", | ||||||
|             "pass_fm_events": "true", |             "pass_fm_events": "true", | ||||||
|             "bind_keys": ["Search||_show_grep_list_page:<Control>f"] |             "bind_keys": ["Search||show_search_page:<Control>s"] | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								plugins/searcher/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Mixins Module | ||||||
|  | """ | ||||||
							
								
								
									
										79
									
								
								plugins/searcher/mixins/file_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,79 @@ | |||||||
|  | # Python imports | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import signal | ||||||
|  | import json | ||||||
|  | import shlex | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from ..widgets.file_preview_widget import FilePreviewWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |  | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |  | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FileSearchMixin: | ||||||
|  |     def _run_find_file_query(self, widget=None, eve=None): | ||||||
|  |         self._handle_find_file_query(query=widget) | ||||||
|  |  | ||||||
|  |     # TODO: Merge this logic with nearly the exact same thing in grep_search_mixin | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _handle_find_file_query(self, widget=None, eve=None, query=None): | ||||||
|  |         # NOTE: Freeze IPC consumption | ||||||
|  |         self.pause_fifo_update = True | ||||||
|  |         self.search_query      = "" | ||||||
|  |  | ||||||
|  |         # NOTE: Kill the former process | ||||||
|  |         if self._list_proc: | ||||||
|  |             if self._list_proc.poll() == None: | ||||||
|  |                 self._list_proc.terminate() | ||||||
|  |                 while self._list_proc.poll() == None: | ||||||
|  |                     ... | ||||||
|  |  | ||||||
|  |             self._list_proc = None | ||||||
|  |  | ||||||
|  |         # NOTE: Clear children from ui and make sure ui thread redraws | ||||||
|  |         GLib.idle_add(self.reset_file_list_box) | ||||||
|  |  | ||||||
|  |         # NOTE: If query create new process and do all new loop. | ||||||
|  |         if query: | ||||||
|  |             self.pause_fifo_update = False | ||||||
|  |             GLib.idle_add(self._exec_find_file_query, query) | ||||||
|  |  | ||||||
|  |     def _exec_find_file_query(self, widget=None, eve=None): | ||||||
|  |         query = widget.get_text() | ||||||
|  |  | ||||||
|  |         if not query in ("", None): | ||||||
|  |             self.search_query = query | ||||||
|  |             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) | ||||||
|  |             command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] | ||||||
|  |             self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) | ||||||
|  |  | ||||||
|  |     def _load_file_ui(self, data): | ||||||
|  |         Gtk.main_iteration() | ||||||
|  |  | ||||||
|  |         if not data in ("", None): | ||||||
|  |             jdata  = json.loads( data ) | ||||||
|  |             target = jdata[0] | ||||||
|  |             file   = jdata[1] | ||||||
|  |  | ||||||
|  |             widget = FilePreviewWidget(target, file) | ||||||
|  |             self._file_list.add(widget) | ||||||
							
								
								
									
										83
									
								
								plugins/searcher/mixins/grep_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | |||||||
|  | # Python imports | ||||||
|  | import ctypes | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import signal | ||||||
|  | import json | ||||||
|  | import shlex | ||||||
|  | libgcc_s = ctypes.CDLL('libgcc_s.so.1') | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from ..widgets.grep_preview_widget import GrepPreviewWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |  | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |  | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GrepSearchMixin: | ||||||
|  |     def _run_grep_query(self, widget=None, eve=None): | ||||||
|  |         self._handle_grep_query(query=widget) | ||||||
|  |  | ||||||
|  |     # TODO: Merge this logic with nearly the exact same thing in file_search_mixin | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _handle_grep_query(self, widget=None, eve=None, query=None): | ||||||
|  |         # NOTE: Freeze IPC consumption | ||||||
|  |         self.pause_fifo_update = True | ||||||
|  |         self.grep_query        = "" | ||||||
|  |  | ||||||
|  |         # NOTE: Kill the former process | ||||||
|  |         if self._grep_proc: | ||||||
|  |             if self._grep_proc.poll() == None: | ||||||
|  |                 self._grep_proc.terminate() | ||||||
|  |                 while self._grep_proc.poll() == None: | ||||||
|  |                     ... | ||||||
|  |  | ||||||
|  |             self._grep_proc = None | ||||||
|  |  | ||||||
|  |         # NOTE: Clear children from ui and make sure ui thread redraws | ||||||
|  |         GLib.idle_add(self.reset_grep_box) | ||||||
|  |  | ||||||
|  |         # NOTE: If query create new process and do all new loop. | ||||||
|  |         if query: | ||||||
|  |             self.pause_fifo_update = False | ||||||
|  |             GLib.idle_add(self._exec_grep_query, query) | ||||||
|  |  | ||||||
|  |     def _exec_grep_query(self, widget=None, eve=None): | ||||||
|  |         query = widget.get_text() | ||||||
|  |         if not query.strip() in ("", None): | ||||||
|  |             self.grep_query = query | ||||||
|  |  | ||||||
|  |             target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) | ||||||
|  |             command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] | ||||||
|  |             self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) | ||||||
|  |  | ||||||
|  |     def _load_grep_ui(self, data): | ||||||
|  |         Gtk.main_iteration() | ||||||
|  |  | ||||||
|  |         if not data in ("", None): | ||||||
|  |             jdata = json.loads( data ) | ||||||
|  |             jkeys = jdata.keys() | ||||||
|  |             for key in jkeys: | ||||||
|  |                 sub_keys    = jdata[key].keys() | ||||||
|  |                 grep_result = jdata[key] | ||||||
|  |  | ||||||
|  |                 widget = GrepPreviewWidget(key, sub_keys, grep_result, self.grep_query) | ||||||
|  |                 self._grep_list.add(widget) | ||||||
| @@ -1,14 +1,21 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, multiprocessing, threading, subprocess, inspect, time, json | import os | ||||||
| from multiprocessing import Manager, Process | import threading | ||||||
|  | import inspect | ||||||
|  | import time | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| from gi.repository import Gtk, GLib, GObject | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
|  | from .mixins.file_search_mixin import FileSearchMixin | ||||||
|  | from .mixins.grep_search_mixin import GrepSearchMixin | ||||||
|  | from .utils.ipc_server import IPCServer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Threads WILL NOT die with parent's destruction. | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
| @@ -26,56 +33,7 @@ def daemon_threaded(fn): | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FilePreviewWidget(Gtk.LinkButton): | class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): | ||||||
|     def __init__(self, path, file): |  | ||||||
|         super(FilePreviewWidget, self).__init__() |  | ||||||
|         self.set_label(file) |  | ||||||
|         self.set_uri(f"file://{path}") |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GrepPreviewWidget(Gtk.Box): |  | ||||||
|     def __init__(self, path, sub_keys, data): |  | ||||||
|         super(GrepPreviewWidget, self).__init__() |  | ||||||
|         self.set_orientation(Gtk.Orientation.VERTICAL) |  | ||||||
|         self.line_color = "#e0cc64" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         _label = '/'.join( path.split("/")[-3:] ) |  | ||||||
|         title  = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) |  | ||||||
|  |  | ||||||
|         self.add(title) |  | ||||||
|         for key in sub_keys: |  | ||||||
|             line_num     = key |  | ||||||
|             text         = data[key] |  | ||||||
|             box          = Gtk.Box() |  | ||||||
|             number_label = Gtk.Label() |  | ||||||
|             text_view    = Gtk.Label(label=text[:-1]) |  | ||||||
|             label_text   = f"<span foreground='{self.line_color}'>{line_num}</span>" |  | ||||||
|  |  | ||||||
|             number_label.set_markup(label_text) |  | ||||||
|             number_label.set_margin_left(15) |  | ||||||
|             number_label.set_margin_right(5) |  | ||||||
|             number_label.set_margin_top(5) |  | ||||||
|             number_label.set_margin_bottom(5) |  | ||||||
|             text_view.set_margin_top(5) |  | ||||||
|             text_view.set_margin_bottom(5) |  | ||||||
|             text_view.set_line_wrap(True) |  | ||||||
|  |  | ||||||
|             box.add(number_label) |  | ||||||
|             box.add(text_view) |  | ||||||
|             self.add(box) |  | ||||||
|  |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| manager  = Manager() |  | ||||||
| grep_result_set  = manager.dict() |  | ||||||
| file_result_set  = manager.list() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugin(PluginBase): |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|  |  | ||||||
| @@ -86,20 +44,19 @@ class Plugin(PluginBase): | |||||||
|  |  | ||||||
|         self._search_dialog    = None |         self._search_dialog    = None | ||||||
|         self._active_path      = None |         self._active_path      = None | ||||||
|  |         self.file_list_parent  = None | ||||||
|  |         self.grep_list_parent  = None | ||||||
|         self._file_list        = None |         self._file_list        = None | ||||||
|         self._grep_list        = None |         self._grep_list        = None | ||||||
|         self._grep_proc        = None |         self._grep_proc        = None | ||||||
|         self._list_proc        = None |         self._list_proc        = None | ||||||
|  |         self.pause_fifo_update = False | ||||||
|  |         self.update_list_ui_buffer = () | ||||||
|  |         self.grep_query        = "" | ||||||
|  |         self.search_query      = "" | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_grep_list_page) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |  | ||||||
|  |  | ||||||
|         self._builder          = Gtk.Builder() |         self._builder          = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
| @@ -116,116 +73,65 @@ class Plugin(PluginBase): | |||||||
|         self._builder.connect_signals(handlers) |         self._builder.connect_signals(handlers) | ||||||
|  |  | ||||||
|         self._search_dialog = self._builder.get_object("search_dialog") |         self._search_dialog = self._builder.get_object("search_dialog") | ||||||
|         self._grep_list     = self._builder.get_object("grep_list") |         self.fsearch        = self._builder.get_object("fsearch") | ||||||
|         self._file_list     = self._builder.get_object("file_list") |  | ||||||
|  |  | ||||||
|         GObject.signal_new("update-file-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) |         self.grep_list_parent = self._builder.get_object("grep_list_parent") | ||||||
|         self._search_dialog.connect("update-file-ui-signal", self._load_file_ui) |         self.file_list_parent = self._builder.get_object("file_list_parent") | ||||||
|         GObject.signal_new("update-grep-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) |  | ||||||
|         self._search_dialog.connect("update-grep-ui-signal", self._load_grep_ui) |         self._event_system.subscribe("update-file-ui", self._load_file_ui) | ||||||
|  |         self._event_system.subscribe("update-grep-ui", self._load_grep_ui) | ||||||
|  |         self._event_system.subscribe("show_search_page", self._show_page) | ||||||
|  |  | ||||||
|  |  | ||||||
|     @daemon_threaded |         self.create_ipc_listener() | ||||||
|     def _show_grep_list_page(self, widget=None, eve=None): |  | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |  | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         state               = self._event_message |     def generate_reference_ui_element(self): | ||||||
|  |         item = Gtk.ImageMenuItem(self.name) | ||||||
|  |         item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) | ||||||
|  |         item.connect("activate", self._show_page) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _show_page(self, widget=None, eve=None): | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |  | ||||||
|  |         state               = self._fm_state | ||||||
|         self._event_message = None |         self._event_message = None | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_queries, (state)) |  | ||||||
|  |  | ||||||
|     def _process_queries(self, state): |  | ||||||
|         self._active_path   = state.tab.get_current_directory() |         self._active_path   = state.tab.get_current_directory() | ||||||
|         response            = self._search_dialog.run() |         response            = self._search_dialog.run() | ||||||
|         self._search_dialog.hide() |         self._search_dialog.hide() | ||||||
|  |  | ||||||
|  |     # TODO: Merge the below methods into some unified logic | ||||||
|     def _run_find_file_query(self, widget=None, eve=None): |     def reset_grep_box(self) -> None: | ||||||
|         if self._list_proc: |  | ||||||
|             self._list_proc.terminate() |  | ||||||
|             self._list_proc = None |  | ||||||
|             time.sleep(.2) |  | ||||||
|  |  | ||||||
|         del file_result_set[:] |  | ||||||
|         self.clear_children(self._file_list) |  | ||||||
|  |  | ||||||
|         query = widget.get_text() |  | ||||||
|         if query: |  | ||||||
|             self._list_proc = multiprocessing.Process(self._do_list_search(self._active_path, query)) |  | ||||||
|             self._list_proc.start() |  | ||||||
|  |  | ||||||
|     def _do_list_search(self, path, query): |  | ||||||
|         self._file_traverse_path(path, query) |  | ||||||
|         for target, file in file_result_set: |  | ||||||
|             widget = FilePreviewWidget(target, file) |  | ||||||
|             self._search_dialog.emit("update-file-ui-signal", (widget)) |  | ||||||
|  |  | ||||||
|     def _load_file_ui(self, parent=None, widget=None): |  | ||||||
|         self._file_list.add(widget) |  | ||||||
|  |  | ||||||
|     def _file_traverse_path(self, path, query): |  | ||||||
|         try: |         try: | ||||||
|             for file in os.listdir(path): |             child = self.grep_list_parent.get_children()[0] | ||||||
|                 target = os.path.join(path, file) |             self._grep_list = None | ||||||
|                 if os.path.isdir(target): |             self.grep_list_parent.remove(child) | ||||||
|                     self._file_traverse_path(target, query) |         except Exception: | ||||||
|                 else: |             ... | ||||||
|                     if query.lower() in file.lower(): |  | ||||||
|                         file_result_set.append([target, file]) |  | ||||||
|         except Exception as e: |  | ||||||
|             if debug: |  | ||||||
|                 print("Couldn't traverse to path. Might be permissions related...") |  | ||||||
|  |  | ||||||
|  |         self._grep_list = Gtk.Box() | ||||||
|  |         self._grep_list.set_orientation(Gtk.Orientation.VERTICAL) | ||||||
|  |         self.grep_list_parent.add(self._grep_list) | ||||||
|  |         self.grep_list_parent.show_all() | ||||||
|  |  | ||||||
|     def _run_grep_query(self, widget=None, eve=None): |         time.sleep(0.05) | ||||||
|         if self._grep_proc: |         Gtk.main_iteration() | ||||||
|             self._grep_proc.terminate() |  | ||||||
|             self._grep_proc = None |  | ||||||
|             time.sleep(.2) |  | ||||||
|  |  | ||||||
|         grep_result_set.clear() |     def reset_file_list_box(self) -> None: | ||||||
|         self.clear_children(self._grep_list) |  | ||||||
|  |  | ||||||
|         query = widget.get_text() |  | ||||||
|         if query: |  | ||||||
|             self._grep_proc = multiprocessing.Process(self._do_grep_search(self._active_path, query)) |  | ||||||
|             self._grep_proc.start() |  | ||||||
|  |  | ||||||
|     def _do_grep_search(self, path, query): |  | ||||||
|         self._grep_traverse_path(path, query) |  | ||||||
|  |  | ||||||
|         keys = grep_result_set.keys() |  | ||||||
|         for key in keys: |  | ||||||
|             sub_keys = grep_result_set[key].keys() |  | ||||||
|             widget   = GrepPreviewWidget(key, sub_keys, grep_result_set[key]) |  | ||||||
|             self._search_dialog.emit("update-grep-ui-signal", (widget)) |  | ||||||
|  |  | ||||||
|     def _load_grep_ui(self, parent=None, widget=None): |  | ||||||
|         self._grep_list.add(widget) |  | ||||||
|  |  | ||||||
|     def _grep_traverse_path(self, path, query): |  | ||||||
|         try: |         try: | ||||||
|             for file in os.listdir(path): |             child = self.file_list_parent.get_children()[0] | ||||||
|                 target = os.path.join(path, file) |             self._file_list = None | ||||||
|                 if os.path.isdir(target): |             self.file_list_parent.remove(child) | ||||||
|                     self._grep_traverse_path(target, query) |         except Exception: | ||||||
|                 else: |             ... | ||||||
|                     self._search_for_string(target, query) |  | ||||||
|         except Exception as e: |  | ||||||
|             if debug: |  | ||||||
|                 print("Couldn't traverse to path. Might be permissions related...") |  | ||||||
|  |  | ||||||
|     def _search_for_string(self, file, query): |         self._file_list = Gtk.Box() | ||||||
|         try: |         self._file_list.set_orientation(Gtk.Orientation.VERTICAL) | ||||||
|             with open(file, 'r') as fp: |         self.file_list_parent.add(self._file_list) | ||||||
|                 for i, line in enumerate(fp): |         self.file_list_parent.show_all() | ||||||
|                     if query in line: |  | ||||||
|                         if f"{file}" in grep_result_set.keys(): |         time.sleep(0.05) | ||||||
|                             grep_result_set[f"{file}"][f"{i+1}"] = line |         Gtk.main_iteration() | ||||||
|                         else: |  | ||||||
|                             grep_result_set[f"{file}"] = {} |  | ||||||
|                             grep_result_set[f"{file}"] = {f"{i+1}": line} |  | ||||||
|         except Exception as e: |  | ||||||
|             if debug: |  | ||||||
|                 print("Couldn't read file. Might be binary or other cause...") |  | ||||||
|   | |||||||
| @@ -74,7 +74,11 @@ | |||||||
|                 <property name="can-focus">False</property> |                 <property name="can-focus">False</property> | ||||||
|                 <property name="orientation">vertical</property> |                 <property name="orientation">vertical</property> | ||||||
|                 <child> |                 <child> | ||||||
|                   <object class="GtkSearchEntry"> |                   <object class="GtkBox"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkSearchEntry" id="fsearch"> | ||||||
|                         <property name="visible">True</property> |                         <property name="visible">True</property> | ||||||
|                         <property name="can-focus">True</property> |                         <property name="can-focus">True</property> | ||||||
|                         <property name="tooltip-text" translatable="yes">Query...</property> |                         <property name="tooltip-text" translatable="yes">Query...</property> | ||||||
| @@ -84,6 +88,28 @@ | |||||||
|                         <property name="placeholder-text" translatable="yes">Search for file...</property> |                         <property name="placeholder-text" translatable="yes">Search for file...</property> | ||||||
|                         <signal name="search-changed" handler="_run_find_file_query" swapped="no"/> |                         <signal name="search-changed" handler="_run_find_file_query" swapped="no"/> | ||||||
|                       </object> |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">True</property> | ||||||
|  |                         <property name="fill">True</property> | ||||||
|  |                         <property name="position">0</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkButton"> | ||||||
|  |                         <property name="label">gtk-stop</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="receives-default">True</property> | ||||||
|  |                         <property name="use-stock">True</property> | ||||||
|  |                         <signal name="released" handler="_handle_find_file_query" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">False</property> | ||||||
|  |                         <property name="fill">True</property> | ||||||
|  |                         <property name="position">1</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="expand">False</property> |                     <property name="expand">False</property> | ||||||
|                     <property name="fill">True</property> |                     <property name="fill">True</property> | ||||||
| @@ -102,7 +128,7 @@ | |||||||
|                         <property name="visible">True</property> |                         <property name="visible">True</property> | ||||||
|                         <property name="can-focus">False</property> |                         <property name="can-focus">False</property> | ||||||
|                         <child> |                         <child> | ||||||
|                           <object class="GtkBox" id="file_list"> |                           <object class="GtkBox" id="file_list_parent"> | ||||||
|                             <property name="visible">True</property> |                             <property name="visible">True</property> | ||||||
|                             <property name="can-focus">False</property> |                             <property name="can-focus">False</property> | ||||||
|                             <property name="orientation">vertical</property> |                             <property name="orientation">vertical</property> | ||||||
| @@ -139,6 +165,10 @@ | |||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">False</property> |                 <property name="can-focus">False</property> | ||||||
|                 <property name="orientation">vertical</property> |                 <property name="orientation">vertical</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkBox"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|                     <child> |                     <child> | ||||||
|                       <object class="GtkSearchEntry"> |                       <object class="GtkSearchEntry"> | ||||||
|                         <property name="visible">True</property> |                         <property name="visible">True</property> | ||||||
| @@ -150,6 +180,28 @@ | |||||||
|                         <property name="placeholder-text" translatable="yes">Query string in file...</property> |                         <property name="placeholder-text" translatable="yes">Query string in file...</property> | ||||||
|                         <signal name="search-changed" handler="_run_grep_query" swapped="no"/> |                         <signal name="search-changed" handler="_run_grep_query" swapped="no"/> | ||||||
|                       </object> |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">True</property> | ||||||
|  |                         <property name="fill">True</property> | ||||||
|  |                         <property name="position">0</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkButton"> | ||||||
|  |                         <property name="label">gtk-stop</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="receives-default">True</property> | ||||||
|  |                         <property name="use-stock">True</property> | ||||||
|  |                         <signal name="released" handler="_handle_grep_query" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">False</property> | ||||||
|  |                         <property name="fill">True</property> | ||||||
|  |                         <property name="position">1</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="expand">False</property> |                     <property name="expand">False</property> | ||||||
|                     <property name="fill">True</property> |                     <property name="fill">True</property> | ||||||
| @@ -168,12 +220,10 @@ | |||||||
|                         <property name="visible">True</property> |                         <property name="visible">True</property> | ||||||
|                         <property name="can-focus">False</property> |                         <property name="can-focus">False</property> | ||||||
|                         <child> |                         <child> | ||||||
|                           <object class="GtkBox" id="grep_list"> |                           <object class="GtkBox" id="grep_list_parent"> | ||||||
|                             <property name="visible">True</property> |                             <property name="visible">True</property> | ||||||
|                             <property name="can-focus">False</property> |                             <property name="can-focus">False</property> | ||||||
|                             <property name="orientation">vertical</property> |                             <property name="orientation">vertical</property> | ||||||
|                             <property name="spacing">5</property> |  | ||||||
|                             <property name="baseline-position">top</property> |  | ||||||
|                             <child> |                             <child> | ||||||
|                               <placeholder/> |                               <placeholder/> | ||||||
|                             </child> |                             </child> | ||||||
|   | |||||||
							
								
								
									
										90
									
								
								plugins/searcher/utils/ipc_server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,90 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, threading, pickle | ||||||
|  | from multiprocessing.connection import Listener, Client | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IPCServer: | ||||||
|  |     """ Create a listener so that other SolarFM instances send requests back to existing instance. """ | ||||||
|  |     def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"): | ||||||
|  |         self.is_ipc_alive     = False | ||||||
|  |         self._ipc_port        = 4848 | ||||||
|  |         self._ipc_address     = ipc_address | ||||||
|  |         self._conn_type       = conn_type | ||||||
|  |         self._ipc_authkey     = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') | ||||||
|  |         self._ipc_timeout     = 15.0 | ||||||
|  |  | ||||||
|  |         if conn_type == "socket": | ||||||
|  |             self._ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' | ||||||
|  |         elif conn_type == "full_network": | ||||||
|  |             self._ipc_address = '0.0.0.0' | ||||||
|  |         elif conn_type == "full_network_unsecured": | ||||||
|  |             self._ipc_authkey = None | ||||||
|  |             self._ipc_address = '0.0.0.0' | ||||||
|  |         elif conn_type == "local_network_unsecured": | ||||||
|  |             self._ipc_authkey = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def create_ipc_listener(self) -> None: | ||||||
|  |         if self._conn_type == "socket": | ||||||
|  |             if os.path.exists(self._ipc_address): | ||||||
|  |                 os.unlink(self._ipc_address) | ||||||
|  |  | ||||||
|  |             listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) | ||||||
|  |         elif "unsecured" not in self._conn_type: | ||||||
|  |             listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) | ||||||
|  |         else: | ||||||
|  |             listener = Listener((self._ipc_address, self._ipc_port)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         self.is_ipc_alive = True | ||||||
|  |         while True: | ||||||
|  |             conn = listener.accept() | ||||||
|  |  | ||||||
|  |             if not self.pause_fifo_update: | ||||||
|  |                 self.handle_message(conn) | ||||||
|  |             else: | ||||||
|  |                 conn.close() | ||||||
|  |  | ||||||
|  |         listener.close() | ||||||
|  |  | ||||||
|  |     def handle_message(self, conn) -> None: | ||||||
|  |         while True: | ||||||
|  |             msg  = conn.recv() | ||||||
|  |  | ||||||
|  |             if "SEARCH|" in msg: | ||||||
|  |                 file = msg.split("SEARCH|")[1].strip() | ||||||
|  |                 if file: | ||||||
|  |                     GLib.idle_add(self._load_file_ui, file, priority=GLib.PRIORITY_LOW) | ||||||
|  |  | ||||||
|  |             if "GREP|" in msg: | ||||||
|  |                 data = msg.split("GREP|")[1].strip() | ||||||
|  |                 if data: | ||||||
|  |                     GLib.idle_add(self._load_grep_ui, data, priority=GLib.PRIORITY_LOW) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             conn.close() | ||||||
|  |             break | ||||||
|  |  | ||||||
|  |     def send_ipc_message(self, message: str = "Empty Data...") -> None: | ||||||
|  |         try: | ||||||
|  |             if self._conn_type == "socket": | ||||||
|  |                 conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) | ||||||
|  |             elif "unsecured" not in self._conn_type: | ||||||
|  |                 conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) | ||||||
|  |             else: | ||||||
|  |                 conn = Client((self._ipc_address, self._ipc_port)) | ||||||
|  |  | ||||||
|  |             conn.send(message) | ||||||
|  |             conn.close() | ||||||
|  |         except ConnectionRefusedError as e: | ||||||
|  |             print("Connection refused...") | ||||||
|  |         except Exception as e: | ||||||
|  |             print(repr(e)) | ||||||
							
								
								
									
										152
									
								
								plugins/searcher/utils/search.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @@ -0,0 +1,152 @@ | |||||||
|  | #!/usr/bin/python3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | import traceback | ||||||
|  | import argparse | ||||||
|  | import threading | ||||||
|  | import json | ||||||
|  | import base64 | ||||||
|  | import time | ||||||
|  | import pickle | ||||||
|  | from setproctitle import setproctitle | ||||||
|  | from multiprocessing.connection import Client | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' | ||||||
|  | _ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') | ||||||
|  |  | ||||||
|  | filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \ | ||||||
|  |             (".txt", ".text", ".sh", ".cfg", ".conf", ".log") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def send_ipc_message(message) -> None: | ||||||
|  |     conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) | ||||||
|  |     conn.send(message) | ||||||
|  |     conn.close() | ||||||
|  |  | ||||||
|  |     # NOTE: Kinda important as this prevents overloading the UI thread | ||||||
|  |     time.sleep(0.05) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def file_search(path, query): | ||||||
|  |     try: | ||||||
|  |         for _path, _dir, _files in os.walk(path, topdown = True): | ||||||
|  |              for file in _files: | ||||||
|  |                  if query in file.lower(): | ||||||
|  |                      target = os.path.join(_path, file) | ||||||
|  |                      data = f"SEARCH|{json.dumps([target, file])}" | ||||||
|  |                      send_ipc_message(data) | ||||||
|  |     except Exception as e: | ||||||
|  |         print("Couldn't traverse to path. Might be permissions related...") | ||||||
|  |         traceback.print_exc() | ||||||
|  |  | ||||||
|  | def _search_for_string(file, query): | ||||||
|  |     b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') | ||||||
|  |     grep_result_set = {} | ||||||
|  |     padding = 15 | ||||||
|  |  | ||||||
|  |     with open(file, 'rb') as fp: | ||||||
|  |         # NOTE: I know there's an issue if there's a very large file with content | ||||||
|  |         #       all on one line will lower and dupe it. And, yes, it will only | ||||||
|  |         #       return one instance from the file. | ||||||
|  |         try: | ||||||
|  |             for i, raw in enumerate(fp): | ||||||
|  |                 line   = None | ||||||
|  |                 llower = raw.lower() | ||||||
|  |                 if not query in llower: | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |                 if len(raw) > 72: | ||||||
|  |                     start  = 0 | ||||||
|  |                     end    = len(raw) - 1 | ||||||
|  |                     index  = llower.index(query) | ||||||
|  |                     sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index | ||||||
|  |                     eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index | ||||||
|  |                     line   = raw[sindex:eindex] | ||||||
|  |                 else: | ||||||
|  |                     line = raw | ||||||
|  |  | ||||||
|  |                 b64_line = base64.urlsafe_b64encode(line).decode('utf-8') | ||||||
|  |                 if f"{b64_file}" in grep_result_set.keys(): | ||||||
|  |                     grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line | ||||||
|  |                 else: | ||||||
|  |                     grep_result_set[f"{b64_file}"] = {} | ||||||
|  |                     grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} | ||||||
|  |  | ||||||
|  |         except Exception as e: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             data = f"GREP|{json.dumps(grep_result_set)}" | ||||||
|  |             send_ipc_message(data) | ||||||
|  |         except Exception as e: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @daemon_threaded | ||||||
|  | def _search_for_string_threaded(file, query): | ||||||
|  |     _search_for_string(file, query) | ||||||
|  |  | ||||||
|  | def grep_search(path, query): | ||||||
|  |     try: | ||||||
|  |         for file in os.listdir(path): | ||||||
|  |             target = os.path.join(path, file) | ||||||
|  |             if os.path.isdir(target): | ||||||
|  |                 grep_search(target, query) | ||||||
|  |             else: | ||||||
|  |                 if target.lower().endswith(filter): | ||||||
|  |                     size = os.path.getsize(target) | ||||||
|  |                     if not size > 5000: | ||||||
|  |                         _search_for_string(target, query) | ||||||
|  |                     else: | ||||||
|  |                         _search_for_string_threaded(target, query) | ||||||
|  |  | ||||||
|  |     except Exception as e: | ||||||
|  |         print("Couldn't traverse to path. Might be permissions related...") | ||||||
|  |         traceback.print_exc() | ||||||
|  |  | ||||||
|  | def search(args): | ||||||
|  |     if args.type == "file_search": | ||||||
|  |         file_search(args.dir, args.query.lower()) | ||||||
|  |  | ||||||
|  |     if args.type == "grep_search": | ||||||
|  |         grep_search(args.dir, args.query.lower().encode("utf-8")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     try: | ||||||
|  |         setproctitle('SolarFM: File Search - Grepy') | ||||||
|  |  | ||||||
|  |         parser = argparse.ArgumentParser() | ||||||
|  |         # Add long and short arguments | ||||||
|  |         parser.add_argument("--type", "-t", default=None, help="Type of search to do.") | ||||||
|  |         parser.add_argument("--dir", "-d", default=None, help="Directory root for search type.") | ||||||
|  |         parser.add_argument("--query", "-q", default=None, help="Query search is working against.") | ||||||
|  |  | ||||||
|  |         # Read arguments (If any...) | ||||||
|  |         args = parser.parse_args() | ||||||
|  |         search(args) | ||||||
|  |     except Exception as e: | ||||||
|  |         traceback.print_exc() | ||||||
							
								
								
									
										3
									
								
								plugins/searcher/widgets/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Widgets Module | ||||||
|  | """ | ||||||
							
								
								
									
										16
									
								
								plugins/searcher/widgets/file_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FilePreviewWidget(Gtk.LinkButton): | ||||||
|  |     def __init__(self, path, file): | ||||||
|  |         super(FilePreviewWidget, self).__init__() | ||||||
|  |         self.set_label(file) | ||||||
|  |         self.set_uri(f"file://{path}") | ||||||
|  |         self.show_all() | ||||||
							
								
								
									
										60
									
								
								plugins/searcher/widgets/grep_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | |||||||
|  | # Python imports | ||||||
|  | import base64 | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GrepPreviewWidget(Gtk.Box): | ||||||
|  |     def __init__(self, _path, sub_keys, _data, _grep_query): | ||||||
|  |         super(GrepPreviewWidget, self).__init__() | ||||||
|  |  | ||||||
|  |         self.set_orientation(Gtk.Orientation.VERTICAL) | ||||||
|  |         line_color      = "#e0cc64" | ||||||
|  |         highlight_color = "#FBF719" | ||||||
|  |         grep_query      = _grep_query | ||||||
|  |  | ||||||
|  |         path  = self.decode_str(_path) | ||||||
|  |         lbl   = '/'.join( path.split("/")[-3:] ) | ||||||
|  |         title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=lbl) | ||||||
|  |  | ||||||
|  |         text_view = Gtk.TextView() | ||||||
|  |         buffer    = text_view.get_buffer() | ||||||
|  |         text_view.set_editable(False) | ||||||
|  |         text_view.set_monospace(True) | ||||||
|  |         text_view.set_wrap_mode(Gtk.WrapMode.NONE) | ||||||
|  |  | ||||||
|  |         for i, key in enumerate(sub_keys): | ||||||
|  |             line_num = self.make_utf8_line_num(line_color, key) | ||||||
|  |             itr      = buffer.get_end_iter() | ||||||
|  |             buffer.insert_markup(itr, line_num, len(line_num)) | ||||||
|  |  | ||||||
|  |             decoded  = f"\t{self.decode_str(_data[key])}" | ||||||
|  |             self.make_utf8_line_highlight(buffer, itr, i, highlight_color, decoded, grep_query) | ||||||
|  |  | ||||||
|  |         self.add(title) | ||||||
|  |         self.add(text_view) | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |     def decode_str(self, target): | ||||||
|  |         return base64.urlsafe_b64decode(target.encode('utf-8')).decode('utf-8') | ||||||
|  |  | ||||||
|  |     def make_utf8_line_num(self, color, target): | ||||||
|  |         return bytes(f"\n<span foreground='{color}'>{target}</span>", "utf-8").decode("utf-8") | ||||||
|  |  | ||||||
|  |     def make_utf8_line_highlight(self, buffer, itr, i, color, target, query): | ||||||
|  |         parts = re.split(r"(" + query + ")(?i)", target.replace("\n", "")) | ||||||
|  |         for part in parts: | ||||||
|  |             itr  = buffer.get_end_iter() | ||||||
|  |  | ||||||
|  |             if not query.lower() == part.lower() and not query.lower() in part.lower(): | ||||||
|  |                 buffer.insert(itr, part, length=len(part)) | ||||||
|  |             else: | ||||||
|  |                 new_s = f"<span foreground='#000000' background='{color}'>{part}</span>" | ||||||
|  |                 _part = bytes(new_s, "utf-8").decode("utf-8") | ||||||
|  |                 buffer.insert_markup(itr, _part, len(_part)) | ||||||
| @@ -1,5 +1,8 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, time | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import ime | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| @@ -33,14 +36,14 @@ class Plugin(PluginBase): | |||||||
|                                                     #       where self.name should not be needed for message comms |                                                     #       where self.name should not be needed for message comms | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |     def generate_reference_ui_element(self): | ||||||
|         button = Gtk.Button(label=self.name) |         button = Gtk.Button(label=self.name) | ||||||
|         button.connect("button-release-event", self.send_message) |         button.connect("button-release-event", self.send_message) | ||||||
|         return button |         return button | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |         ... | ||||||
|  |  | ||||||
|     def send_message(self, widget=None, eve=None): |     def send_message(self, widget=None, eve=None): | ||||||
|         message = "Hello, World!" |         message = "Hello, World!" | ||||||
|         self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) |         event_system.emit("display_message", ("warning", message, None)) | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								plugins/trasher/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Module | ||||||
|  | """ | ||||||
							
								
								
									
										3
									
								
								plugins/trasher/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Pligin Package | ||||||
|  | """ | ||||||
							
								
								
									
										16
									
								
								plugins/trasher/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     "manifest": { | ||||||
|  |         "name": "Trasher", | ||||||
|  |         "author": "ITDominator", | ||||||
|  |         "version": "0.0.1", | ||||||
|  |         "support": "", | ||||||
|  |         "requests": { | ||||||
|  |             "ui_target": "context_menu", | ||||||
|  |             "pass_fm_events": "true", | ||||||
|  |             "bind_keys": [ | ||||||
|  |                             "Trasher||delete_files:Delete", | ||||||
|  |                             "Trasher||trash_files:<Control>d" | ||||||
|  |                         ] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								plugins/trasher/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,143 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import inspect | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from plugins.plugin_base import PluginBase | ||||||
|  | from .xdgtrash import XDGTrash | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL NOT die with parent's destruction. | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  | # NOTE: Threads WILL die with parent's destruction. | ||||||
|  | def daemon_threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(PluginBase): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.path               = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |         self._GLADE_FILE        = f"{self.path}/trasher.glade" | ||||||
|  |         self.name               = "Trasher"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus | ||||||
|  |                                              #       where self.name should not be needed for message comms | ||||||
|  |         self.trashman           = XDGTrash() | ||||||
|  |         self.trash_files_path   = f"{GLib.get_user_data_dir()}/Trash/files" | ||||||
|  |         self.trash_info_path    = f"{GLib.get_user_data_dir()}/Trash/info" | ||||||
|  |  | ||||||
|  |         self.trashman.regenerate() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons) | ||||||
|  |         self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons) | ||||||
|  |         self._event_system.subscribe("delete_files", self.delete_files) | ||||||
|  |         self._event_system.subscribe("trash_files", self.trash_files) | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         trash_a = Gtk.MenuItem("Trash Actions") | ||||||
|  |         trash_menu = Gtk.Menu() | ||||||
|  |  | ||||||
|  |         self.restore = Gtk.MenuItem("Restore From Trash") | ||||||
|  |         self.restore.connect("activate", self.restore_trash_files) | ||||||
|  |  | ||||||
|  |         self.empty = Gtk.MenuItem("Empty Trash") | ||||||
|  |         self.empty.connect("activate", self.empty_trash) | ||||||
|  |  | ||||||
|  |         trash = Gtk.ImageMenuItem("Trash") | ||||||
|  |         trash.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) | ||||||
|  |         trash.connect("activate", self.trash_files) | ||||||
|  |         trash.set_always_show_image(True) | ||||||
|  |  | ||||||
|  |         go_to = Gtk.ImageMenuItem("Go To Trash") | ||||||
|  |         go_to.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) | ||||||
|  |         go_to.connect("activate", self.go_to_trash) | ||||||
|  |         go_to.set_always_show_image(True) | ||||||
|  |  | ||||||
|  |         delete = Gtk.ImageMenuItem("Delete") | ||||||
|  |         delete.set_image( Gtk.Image(stock=Gtk.STOCK_DELETE) ) | ||||||
|  |         delete.connect("activate", self.delete_files) | ||||||
|  |         delete.set_always_show_image(True) | ||||||
|  |  | ||||||
|  |         trash_a.set_submenu(trash_menu) | ||||||
|  |         trash_a.show_all() | ||||||
|  |         self._appen_menu_items(trash_menu, [self.restore, self.empty, trash, go_to, delete]) | ||||||
|  |  | ||||||
|  |         return trash_a | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _appen_menu_items(self, menu, items): | ||||||
|  |         for item in items: | ||||||
|  |             menu.append(item) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _show_trash_buttons(self): | ||||||
|  |         self.restore.show() | ||||||
|  |         self.empty.show() | ||||||
|  |  | ||||||
|  |     def _hide_trash_buttons(self): | ||||||
|  |         self.restore.hide() | ||||||
|  |         self.empty.hide() | ||||||
|  |  | ||||||
|  |     def delete_files(self, widget = None, eve = None): | ||||||
|  |         self._event_system.emit("do_hide_context_menu") | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |         state    = self._fm_state | ||||||
|  |         uris     = state.selected_files | ||||||
|  |         response = None | ||||||
|  |  | ||||||
|  |         state.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") | ||||||
|  |         for uri in uris: | ||||||
|  |             file = Gio.File.new_for_path(uri) | ||||||
|  |  | ||||||
|  |             if not response: | ||||||
|  |                 response = state.warning_alert.run() | ||||||
|  |                 state.warning_alert.hide() | ||||||
|  |             if response == Gtk.ResponseType.YES: | ||||||
|  |                 type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) | ||||||
|  |  | ||||||
|  |                 if type == Gio.FileType.DIRECTORY: | ||||||
|  |                     state.tab.delete_file( file.get_path() ) | ||||||
|  |                 else: | ||||||
|  |                     file.delete(cancellable=None) | ||||||
|  |             else: | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def trash_files(self, widget = None, eve = None, verbocity = False): | ||||||
|  |         self._event_system.emit("do_hide_context_menu") | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |         state = self._fm_state | ||||||
|  |         for uri in state.selected_files: | ||||||
|  |             self.trashman.trash(uri, verbocity) | ||||||
|  |  | ||||||
|  |     def restore_trash_files(self, widget = None, eve = None, verbocity = False): | ||||||
|  |         self._event_system.emit("do_hide_context_menu") | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |         state = self._fm_state | ||||||
|  |         for uri in state.selected_files: | ||||||
|  |             self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity) | ||||||
|  |  | ||||||
|  |     def empty_trash(self, widget = None, eve = None, verbocity = False): | ||||||
|  |         self._event_system.emit("do_hide_context_menu") | ||||||
|  |         self.trashman.empty(verbose = verbocity) | ||||||
|  |  | ||||||
|  |     def go_to_trash(self, widget = None, eve = None, verbocity = False): | ||||||
|  |         self._event_system.emit("do_hide_context_menu") | ||||||
|  |         self._event_system.emit("go_to_path", self.trash_files_path) | ||||||
| @@ -5,7 +5,7 @@ | |||||||
|         "version": "0.0.1", |         "version": "0.0.1", | ||||||
|         "support": "", |         "support": "", | ||||||
|         "requests": { |         "requests": { | ||||||
|             "ui_target": "context_menu", |             "ui_target": "context_menu_plugins", | ||||||
|             "pass_fm_events": "true" |             "pass_fm_events": "true" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,12 +1,20 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, subprocess, time, inspect, hashlib | import os | ||||||
|  | import threading | ||||||
|  | import subprocess | ||||||
|  | import time | ||||||
|  | import inspect | ||||||
|  | import hashlib | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  |  | ||||||
| # Gtk imports | # Gtk imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| gi.require_version('GdkPixbuf', '2.0') | gi.require_version('GdkPixbuf', '2.0') | ||||||
| from gi.repository import Gtk, GLib, Gio, GdkPixbuf | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import Gio | ||||||
|  | from gi.repository import GdkPixbuf | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from plugins.plugin_base import PluginBase | from plugins.plugin_base import PluginBase | ||||||
| @@ -42,17 +50,9 @@ class Plugin(PluginBase): | |||||||
|         self._file_name             = None |         self._file_name             = None | ||||||
|         self._file_location         = None |         self._file_location         = None | ||||||
|         self._file_hash             = None |         self._file_hash             = None | ||||||
|         self._state                 = None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |  | ||||||
|         button.connect("button-release-event", self._show_thumbnailer_page) |  | ||||||
|         return button |  | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |  | ||||||
|  |  | ||||||
|         self._builder           = Gtk.Builder() |         self._builder           = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
| @@ -75,23 +75,32 @@ class Plugin(PluginBase): | |||||||
|         self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") |         self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") | ||||||
|         self._file_hash             = self._builder.get_object("file_hash") |         self._file_hash             = self._builder.get_object("file_hash") | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|  |         pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True) | ||||||
|  |         icon   = Gtk.Image.new_from_pixbuf(pixbuf) | ||||||
|  |         item   = Gtk.ImageMenuItem(self.name) | ||||||
|  |  | ||||||
|  |         item.set_image( icon ) | ||||||
|  |         item.connect("activate", self._show_thumbnailer_page) | ||||||
|  |         item.set_always_show_image(True) | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |     @threaded | ||||||
|     def _show_thumbnailer_page(self, widget=None, eve=None): |     def _show_thumbnailer_page(self, widget=None, eve=None): | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |         self._event_system.emit("get_current_state") | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         state               = self._event_message |         state               = self._fm_state | ||||||
|         self._event_message = None |         self._event_message = None | ||||||
|  |  | ||||||
|         GLib.idle_add(self._process_changes, (state)) |         GLib.idle_add(self._process_changes, (state)) | ||||||
|  |  | ||||||
|     def _process_changes(self, state): |     def _process_changes(self, state): | ||||||
|         self._state = None |         self._fm_state = None | ||||||
|  |  | ||||||
|         if len(state.selected_files) == 1: |         if len(state.selected_files) == 1: | ||||||
|             if state.selected_files[0].lower().endswith(state.tab.fvideos): |             if state.selected_files[0].lower().endswith(state.tab.fvideos): | ||||||
|                 self._state = state |                 self._fm_state = state | ||||||
|                 self._set_ui_data() |                 self._set_ui_data() | ||||||
|                 response   = self._thumbnailer_dialog.run() |                 response   = self._thumbnailer_dialog.run() | ||||||
|                 if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: |                 if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: | ||||||
| @@ -103,32 +112,32 @@ class Plugin(PluginBase): | |||||||
|         file          = self._file_name.get_text() |         file          = self._file_name.get_text() | ||||||
|         dir           = self._file_location.get_text() |         dir           = self._file_location.get_text() | ||||||
|         file_hash     = self._file_hash.get_text() |         file_hash     = self._file_hash.get_text() | ||||||
|         hash_img_pth  = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" |         hash_img_pth  = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ... |             os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ... | ||||||
|  |  | ||||||
|             self._state.tab.create_thumbnail(dir, file, f"{scrub_percent}%") |             self._fm_state.tab.create_thumbnail(dir, file, f"{scrub_percent}%") | ||||||
|             preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) |             preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) | ||||||
|             self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) |             self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) | ||||||
|  |  | ||||||
|             img_pixbuf = self._state.tab.create_scaled_image(hash_img_pth) |             img_pixbuf = self._fm_state.tab.create_scaled_image(hash_img_pth) | ||||||
|             tree_pth   = self._state.icon_grid.get_selected_items()[0] |             tree_pth   = self._fm_state.icon_grid.get_selected_items()[0] | ||||||
|             itr        = self._state.store.get_iter(tree_pth) |             itr        = self._fm_state.store.get_iter(tree_pth) | ||||||
|             pixbuff    = self._state.store.get(itr, 0)[0] |             pixbuff    = self._fm_state.store.get(itr, 0)[0] | ||||||
|             self._state.store.set(itr, 0, img_pixbuf) |             self._fm_state.store.set(itr, 0, img_pixbuf) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print(repr(e)) |             print(repr(e)) | ||||||
|             print("Couldn't regenerate thumbnail!") |             print("Couldn't regenerate thumbnail!") | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _set_ui_data(self): |     def _set_ui_data(self): | ||||||
|         uri            = self._state.selected_files[0] |         uri            = self._fm_state.selected_files[0] | ||||||
|         path           = self._state.tab.get_current_directory() |         path           = self._fm_state.tab.get_current_directory() | ||||||
|         parts          = uri.split("/") |         parts          = uri.split("/") | ||||||
|  |  | ||||||
|         file_hash      = hashlib.sha256(str.encode(uri)).hexdigest() |         file_hash      = hashlib.sha256(str.encode(uri)).hexdigest() | ||||||
|         hash_img_pth   = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" |         hash_img_pth   = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" | ||||||
|         preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) |         preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) | ||||||
|  |  | ||||||
|         self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) |         self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) | ||||||
|   | |||||||
| @@ -33,21 +33,21 @@ class Plugin(PluginBase): | |||||||
|         self.name              = "Youtube Download"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |         self.name              = "Youtube Download"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus | ||||||
|                                                      #       where self.name should not be needed for message comms |                                                      #       where self.name should not be needed for message comms | ||||||
|  |  | ||||||
|  |     def generate_reference_ui_element(self): | ||||||
|     def get_ui_element(self): |  | ||||||
|         button = Gtk.Button(label=self.name) |         button = Gtk.Button(label=self.name) | ||||||
|         button.connect("button-release-event", self._do_download) |         button.connect("button-release-event", self._do_download) | ||||||
|         return button |         return button | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
|         self._module_event_observer() |         ... | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _do_download(self, widget=None, eve=None): | ||||||
|  |         self._event_system.emit("get_current_state") | ||||||
|  |  | ||||||
|  |         dir = self._fm_state.tab.get_current_directory() | ||||||
|  |         self._download(dir) | ||||||
|  |  | ||||||
|     @threaded |     @threaded | ||||||
|     def _do_download(self, widget=None, eve=None): |     def _download(self, dir): | ||||||
|         self._event_system.push_gui_event([self.name, "get_current_state", ()]) |         subprocess.Popen([f'{self.path}/download.sh', dir]) | ||||||
|         self.wait_for_fm_message() |  | ||||||
|  |  | ||||||
|         state = self._event_message |  | ||||||
|         subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) |  | ||||||
|         self._event_message = None |  | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ import builtins, threading | |||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from utils.event_system import EventSystem | from utils.event_system import EventSystem | ||||||
|  | from utils.endpoint_registry import EndpointRegistry | ||||||
|  | from utils.settings import Settings | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -23,32 +24,14 @@ def daemon_threaded_wrapper(fn): | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EndpointRegistry(): |  | ||||||
|     def __init__(self): |  | ||||||
|         self._endpoints = {} |  | ||||||
|  |  | ||||||
|     def register(self, rule, **options): |  | ||||||
|         def decorator(f): |  | ||||||
|             self._endpoints[rule] = f |  | ||||||
|             return f |  | ||||||
|  |  | ||||||
|         return decorator |  | ||||||
|  |  | ||||||
|     def get_endpoints(self): |  | ||||||
|         return self._endpoints |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Just reminding myself we can add to builtins two different ways... | # NOTE: Just reminding myself we can add to builtins two different ways... | ||||||
| # __builtins__.update({"event_system": Builtins()}) | # __builtins__.update({"event_system": Builtins()}) | ||||||
| builtins.app_name          = "SolarFM" | builtins.app_name          = "SolarFM" | ||||||
|  | builtins.settings          = Settings() | ||||||
|  | builtins.logger            = settings.get_logger() | ||||||
| builtins.event_system      = EventSystem() | builtins.event_system      = EventSystem() | ||||||
| builtins.endpoint_registry = EndpointRegistry() | builtins.endpoint_registry = EndpointRegistry() | ||||||
|  |  | ||||||
| builtins.threaded          = threaded_wrapper | builtins.threaded          = threaded_wrapper | ||||||
| builtins.daemon_threaded   = daemon_threaded_wrapper | builtins.daemon_threaded   = daemon_threaded_wrapper | ||||||
| builtins.event_sleep_time  = 0.05 | builtins.event_sleep_time  = 0.05 | ||||||
| builtins.trace_debug       = False |  | ||||||
| builtins.debug             = False |  | ||||||
| builtins.app_settings      = None |  | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
|  |  | ||||||
|  |  | ||||||
| # Python imports | # Python imports | ||||||
| import argparse, faulthandler, traceback | import argparse | ||||||
|  | import faulthandler | ||||||
|  | import traceback | ||||||
| from setproctitle import setproctitle | from setproctitle import setproctitle | ||||||
|  |  | ||||||
| import tracemalloc | import tracemalloc | ||||||
| @@ -15,25 +16,43 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  | from __builtins__ import * | ||||||
| from app import Application | from app import Application | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": |  | ||||||
|     """ Set process title, get arguments, and create GTK main thread. """ |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def run(): | ||||||
|     try: |     try: | ||||||
|         setproctitle('SolarFM') |         setproctitle(f"{app_name}") | ||||||
|         faulthandler.enable()  # For better debug info |         faulthandler.enable()  # For better debug info | ||||||
|  |  | ||||||
|         parser = argparse.ArgumentParser() |         parser = argparse.ArgumentParser() | ||||||
|         # Add long and short arguments |         # Add long and short arguments | ||||||
|  |         parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") | ||||||
|  |         parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") | ||||||
|  |         parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.") | ||||||
|  |  | ||||||
|         parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") |         parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") | ||||||
|         parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") |         parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") | ||||||
|  |  | ||||||
|         # Read arguments (If any...) |         # Read arguments (If any...) | ||||||
|         args, unknownargs = parser.parse_known_args() |         args, unknownargs = parser.parse_known_args() | ||||||
|  |  | ||||||
|  |         if args.debug == "true": | ||||||
|  |             settings.set_debug(True) | ||||||
|  |  | ||||||
|  |         if args.trace_debug == "true": | ||||||
|  |             settings.set_trace_debug(True) | ||||||
|  |  | ||||||
|  |         settings.do_dirty_start_check() | ||||||
|         Application(args, unknownargs) |         Application(args, unknownargs) | ||||||
|         Gtk.main() |         Gtk.main() | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         traceback.print_exc() |         traceback.print_exc() | ||||||
|         quit() |         quit() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     """ Set process title, get arguments, and create GTK main thread. """ | ||||||
|  |     run() | ||||||
|   | |||||||
| @@ -1,52 +1,51 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, inspect, time | import os | ||||||
|  | import inspect | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from __builtins__ import * |  | ||||||
| from utils.ipc_server import IPCServer | from utils.ipc_server import IPCServer | ||||||
| from utils.settings import Settings |  | ||||||
| from core.controller import Controller | from core.controller import Controller | ||||||
|  |  | ||||||
|  |  | ||||||
| class App_Launch_Exception(Exception): | class AppLaunchException(Exception): | ||||||
|     ... |     ... | ||||||
|  |  | ||||||
| class Controller_Start_Exceptio(Exception): | class ControllerStartException(Exception): | ||||||
|     ... |     ... | ||||||
|  |  | ||||||
|  |  | ||||||
| class Application(IPCServer): | class Application(IPCServer): | ||||||
|     """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ |     """ Inherit from IPCServer; create Controller classe; bind any signal(s) to Builder. """ | ||||||
|  |  | ||||||
|     def __init__(self, args, unknownargs): |     def __init__(self, args, unknownargs): | ||||||
|         super(Application, self).__init__() |         super(Application, self).__init__() | ||||||
|  |         self.args, self.unknownargs = args, unknownargs | ||||||
|  |  | ||||||
|         if not trace_debug: |         if not settings.is_trace_debug(): | ||||||
|  |             try: | ||||||
|                 self.create_ipc_listener() |                 self.create_ipc_listener() | ||||||
|             time.sleep(0.05) |             except Exception: | ||||||
|  |                 ... | ||||||
|  |  | ||||||
|             if not self.is_ipc_alive: |             if not self.is_ipc_alive: | ||||||
|                 if unknownargs: |                 for arg in unknownargs + [args.new_tab,]: | ||||||
|                     for arg in unknownargs: |  | ||||||
|                     if os.path.isdir(arg): |                     if os.path.isdir(arg): | ||||||
|                         message = f"FILE|{arg}" |                         message = f"FILE|{arg}" | ||||||
|                         self.send_ipc_message(message) |                         self.send_ipc_message(message) | ||||||
|  |  | ||||||
|                 if args.new_tab and os.path.isdir(args.new_tab): |                 raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...") | ||||||
|                     message = f"FILE|{args.new_tab}" |  | ||||||
|                     self.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 raise App_Launch_Exception(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         settings = Settings() |  | ||||||
|         settings.create_window() |         settings.create_window() | ||||||
|  |         self._load_controller_and_builder() | ||||||
|  |  | ||||||
|         controller = Controller(args, unknownargs, settings) |     def _load_controller_and_builder(self): | ||||||
|  |         controller = Controller(self.args, self.unknownargs) | ||||||
|         if not controller: |         if not controller: | ||||||
|             raise Controller_Start_Exceptio("Controller exited and doesn't exist...") |             raise ControllerStartException("Controller exited and doesn't exist...") | ||||||
|  |  | ||||||
|         # Gets the methods from the classes and sets to handler. |         # Gets the methods from the classes and sets to handler. | ||||||
|         # Then, builder connects to any signals it needs. |         # Then, builder connects to any signals it needs. | ||||||
| @@ -57,7 +56,7 @@ class Application(IPCServer): | |||||||
|             try: |             try: | ||||||
|                 methods = inspect.getmembers(c, predicate=inspect.ismethod) |                 methods = inspect.getmembers(c, predicate=inspect.ismethod) | ||||||
|                 handlers.update(methods) |                 handlers.update(methods) | ||||||
|             except Exception as e: |             except AppLaunchException as e: | ||||||
|                 print(repr(e)) |                 print(repr(e)) | ||||||
|  |  | ||||||
|         settings.get_builder().connect_signals(handlers) |         settings.get_builder().connect_signals(handlers) | ||||||
|   | |||||||
| @@ -7,80 +7,58 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk, GLib | from gi.repository import Gtk, GLib | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .mixins.exception_hook_mixin import ExceptionHookMixin |  | ||||||
| from .mixins.ui_mixin import UIMixin |  | ||||||
| from .signals.ipc_signals_mixin import IPCSignalsMixin |  | ||||||
| from .signals.keyboard_signals_mixin import KeyboardSignalsMixin |  | ||||||
| from .controller_data import Controller_Data | from .controller_data import Controller_Data | ||||||
|  | from .mixins.signals_mixins import SignalsMixins | ||||||
|  | from .ui import UI | ||||||
|  | from widgets.context_menu_widget import ContextMenuWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): | class Controller(UI, SignalsMixins, Controller_Data): | ||||||
|     """ Controller coordinates the mixins and is somewhat the root hub of it all. """ |     """ Controller coordinates the mixins and is somewhat the root hub of it all. """ | ||||||
|     def __init__(self, args, unknownargs, _settings): |     def __init__(self, args, unknownargs): | ||||||
|         self.setup_controller_data(_settings) |         self._subscribe_to_events() | ||||||
|         self.window.show() |         self.setup_controller_data() | ||||||
|  |  | ||||||
|         self.generate_windows(self.fm_controller_data) |         self.generate_windows(self.fm_controller_data) | ||||||
|  |  | ||||||
|  |         ContextMenuWidget().build_context_menu() | ||||||
|  |  | ||||||
|  |         if args.no_plugins == "false": | ||||||
|             self.plugins.launch_plugins() |             self.plugins.launch_plugins() | ||||||
|  |  | ||||||
|         if debug: |         for arg in unknownargs + [args.new_tab,]: | ||||||
|             self.window.set_interactive_debugging(True) |  | ||||||
|  |  | ||||||
|         if not trace_debug: |  | ||||||
|             self.gui_event_observer() |  | ||||||
|  |  | ||||||
|             if unknownargs: |  | ||||||
|                 for arg in unknownargs: |  | ||||||
|             if os.path.isdir(arg): |             if os.path.isdir(arg): | ||||||
|                 message = f"FILE|{arg}" |                 message = f"FILE|{arg}" | ||||||
|                         event_system.send_ipc_message(message) |                 event_system.emit("post_file_to_ipc", message) | ||||||
|  |  | ||||||
|             if args.new_tab and os.path.isdir(args.new_tab): |  | ||||||
|                 message = f"FILE|{args.new_tab}" |  | ||||||
|                 event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) | ||||||
|  |         event_system.subscribe("get_current_state", self.get_current_state) | ||||||
|  |         event_system.subscribe("display_message", self.display_message) | ||||||
|  |         event_system.subscribe("go_to_path", self.go_to_path) | ||||||
|  |         event_system.subscribe("do_hide_context_menu", self.do_hide_context_menu) | ||||||
|  |         event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls) | ||||||
|  |  | ||||||
|     def tear_down(self, widget=None, eve=None): |     def tear_down(self, widget=None, eve=None): | ||||||
|  |         if not settings.is_trace_debug(): | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|  |  | ||||||
|  |         settings.clear_pid() | ||||||
|         time.sleep(event_sleep_time) |         time.sleep(event_sleep_time) | ||||||
|         Gtk.main_quit() |         Gtk.main_quit() | ||||||
|  |  | ||||||
|  |  | ||||||
|     @daemon_threaded |  | ||||||
|     def gui_event_observer(self): |  | ||||||
|         while True: |  | ||||||
|             time.sleep(event_sleep_time) |  | ||||||
|             event = event_system.consume_gui_event() |  | ||||||
|             if event: |  | ||||||
|                 try: |  | ||||||
|                     sender_id, method_target, parameters = event |  | ||||||
|                     if sender_id: |  | ||||||
|                         method = getattr(self.__class__, "handle_gui_event_and_return_message") |  | ||||||
|                         GLib.idle_add(method, *(self, sender_id, method_target, parameters)) |  | ||||||
|                     else: |  | ||||||
|                         method = getattr(self.__class__, method_target) |  | ||||||
|                         GLib.idle_add(method, *(self, *parameters,)) |  | ||||||
|                 except Exception as e: |  | ||||||
|                     print(repr(e)) |  | ||||||
|  |  | ||||||
|     def handle_gui_event_and_return_message(self, sender, method_target, parameters): |  | ||||||
|         method = getattr(self.__class__, f"{method_target}") |  | ||||||
|         data   = method(*(self, *parameters)) |  | ||||||
|         event_system.push_module_event([sender, None, data]) |  | ||||||
|  |  | ||||||
|     def handle_plugin_key_event(self, sender, method_target, parameters=()): |  | ||||||
|         event_system.push_module_event([sender, method_target, parameters]) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def save_load_session(self, action="save_session"): |     def save_load_session(self, action="save_session"): | ||||||
|         wid, tid          = self.fm_controller.get_active_wid_and_tid() |         wid, tid          = self.fm_controller.get_active_wid_and_tid() | ||||||
|         tab               = self.get_fm_window(wid).get_tab_by_id(tid) |         tab               = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         save_load_dialog  = self.builder.get_object("save_load_dialog") |         save_load_dialog  = self.builder.get_object("save_load_dialog") | ||||||
|  |  | ||||||
|         if action == "save_session": |         if action == "save_session": | ||||||
|  |             if not settings.is_trace_debug(): | ||||||
|                 self.fm_controller.save_state() |                 self.fm_controller.save_state() | ||||||
|  |  | ||||||
|             return |             return | ||||||
|         elif action == "save_session_as": |         elif action == "save_session_as": | ||||||
|             save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) |             save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) | ||||||
| @@ -101,13 +79,13 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi | |||||||
|                 session_json = self.fm_controller.get_state_from_file(path) |                 session_json = self.fm_controller.get_state_from_file(path) | ||||||
|                 self.load_session(session_json) |                 self.load_session(session_json) | ||||||
|         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): |         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): | ||||||
|             pass |             ... | ||||||
|  |  | ||||||
|         save_load_dialog.hide() |         save_load_dialog.hide() | ||||||
|  |  | ||||||
|     def load_session(self, session_json): |     def load_session(self, session_json): | ||||||
|         if debug: |         if settings.is_debug(): | ||||||
|             self.logger.debug(f"Session Data: {session_json}") |             logger.debug(f"Session Data: {session_json}") | ||||||
|  |  | ||||||
|         self.ctrl_down  = False |         self.ctrl_down  = False | ||||||
|         self.shift_down = False |         self.shift_down = False | ||||||
| @@ -120,8 +98,12 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi | |||||||
|         gc.collect() |         gc.collect() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def do_action_from_menu_controls(self, widget, event_button): |     def do_action_from_menu_controls(self, widget, eve = None): | ||||||
|  |         if not isinstance(widget, str): | ||||||
|             action = widget.get_name() |             action = widget.get_name() | ||||||
|  |         else: | ||||||
|  |             action = widget | ||||||
|  |  | ||||||
|         self.hide_context_menu() |         self.hide_context_menu() | ||||||
|         self.hide_new_file_menu() |         self.hide_new_file_menu() | ||||||
|         self.hide_edit_file_menu() |         self.hide_edit_file_menu() | ||||||
| @@ -142,18 +124,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi | |||||||
|             self.copy_files() |             self.copy_files() | ||||||
|         if action == "paste": |         if action == "paste": | ||||||
|             self.paste_files() |             self.paste_files() | ||||||
|         if action == "archive": |  | ||||||
|             self.show_archiver_dialogue() |  | ||||||
|         if action == "delete": |  | ||||||
|             self.delete_files() |  | ||||||
|         if action == "trash": |  | ||||||
|             self.trash_files() |  | ||||||
|         if action == "go_to_trash": |  | ||||||
|             self.path_entry.set_text(self.trash_files_path) |  | ||||||
|         if action == "restore_from_trash": |  | ||||||
|             self.restore_trash_files() |  | ||||||
|         if action == "empty_trash": |  | ||||||
|             self.empty_trash() |  | ||||||
|         if action == "create": |         if action == "create": | ||||||
|             self.create_files() |             self.create_files() | ||||||
|         if action in ["save_session", "save_session_as", "load_session"]: |         if action in ["save_session", "save_session_as", "load_session"]: | ||||||
| @@ -162,7 +132,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @endpoint_registry.register(rule="go_home") |     @endpoint_registry.register(rule="go_home") | ||||||
|     def go_home(self, widget=None, eve=None): |     def go_home(self, widget=None, eve=None): | ||||||
|         self.builder.get_object("go_home").released() |         self.builder.get_object("go_home").released() | ||||||
| @@ -189,3 +158,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi | |||||||
|         wid, tid = self.fm_controller.get_active_wid_and_tid() |         wid, tid = self.fm_controller.get_active_wid_and_tid() | ||||||
|         tab      = self.get_fm_window(wid).get_tab_by_id(tid) |         tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) |         tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) | ||||||
|  |  | ||||||
|  |     def go_to_path(self, path): | ||||||
|  |         self.path_entry.set_text(path) | ||||||
|  |  | ||||||
|  |     def do_hide_context_menu(self): | ||||||
|  |         self.hide_context_menu() | ||||||
|   | |||||||
| @@ -4,12 +4,13 @@ from dataclasses import dataclass | |||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
| from gi.repository import GLib | from gi.repository import GLib | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from trasher.xdgtrash import XDGTrash |  | ||||||
| from shellfm.windows.controller import WindowController | from shellfm.windows.controller import WindowController | ||||||
| from plugins.plugins import Plugins | from plugins.plugins_controller import PluginsController | ||||||
|  |  | ||||||
|  |  | ||||||
| @dataclass(slots=True) | @dataclass(slots=True) | ||||||
| @@ -22,25 +23,22 @@ class State: | |||||||
|     selected_files: [] = None |     selected_files: [] = None | ||||||
|     to_copy_files:  [] = None |     to_copy_files:  [] = None | ||||||
|     to_cut_files:   [] = None |     to_cut_files:   [] = None | ||||||
|  |     warning_alert: type = None | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller_Data: | class Controller_Data: | ||||||
|     """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ |     """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ | ||||||
|     __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4" |     __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4" | ||||||
|  |  | ||||||
|     def setup_controller_data(self, _settings: type) -> None: |     def setup_controller_data(self) -> None: | ||||||
|         self.settings            = _settings |         self.builder             = settings.get_builder() | ||||||
|         self.builder             = self.settings.get_builder() |         self.keybindings         = settings.get_keybindings() | ||||||
|         self.logger              = self.settings.get_logger() |  | ||||||
|         self.keybindings         = self.settings.get_keybindings() |  | ||||||
|  |  | ||||||
|         self.trashman            = XDGTrash() |  | ||||||
|         self.fm_controller       = WindowController() |         self.fm_controller       = WindowController() | ||||||
|         self.plugins             = Plugins(_settings) |         self.plugins             = PluginsController() | ||||||
|         self.fm_controller_data  = self.fm_controller.get_state_from_file() |         self.fm_controller_data  = self.fm_controller.get_state_from_file() | ||||||
|         self.trashman.regenerate() |  | ||||||
|  |  | ||||||
|         self.window             = self.settings.get_main_window() |         self.window             = settings.get_main_window() | ||||||
|         self.window1            = self.builder.get_object("window_1") |         self.window1            = self.builder.get_object("window_1") | ||||||
|         self.window2            = self.builder.get_object("window_2") |         self.window2            = self.builder.get_object("window_2") | ||||||
|         self.window3            = self.builder.get_object("window_3") |         self.window3            = self.builder.get_object("window_3") | ||||||
| @@ -66,39 +64,14 @@ class Controller_Data: | |||||||
|  |  | ||||||
|         self.trash_files_path        = f"{GLib.get_user_data_dir()}/Trash/files" |         self.trash_files_path        = f"{GLib.get_user_data_dir()}/Trash/files" | ||||||
|         self.trash_info_path         = f"{GLib.get_user_data_dir()}/Trash/info" |         self.trash_info_path         = f"{GLib.get_user_data_dir()}/Trash/info" | ||||||
|         self.icon_theme              = self.settings.get_icon_theme() |         self.icon_theme              = settings.get_icon_theme() | ||||||
|  |  | ||||||
|         # In compress commands: |  | ||||||
|         #    %n: First selected filename/dir to archive |  | ||||||
|         #    %N: All selected filenames/dirs to archive, or (with %O) a single filename |  | ||||||
|         #    %o: Resulting single archive file |  | ||||||
|         #    %O: Resulting archive per source file/directory (use changes %N meaning) |  | ||||||
|         # |  | ||||||
|         #  In extract commands: |  | ||||||
|         #    %x: Archive file to extract |  | ||||||
|         #    %g: Unique extraction target filename with optional subfolder |  | ||||||
|         #    %G: Unique extraction target filename, never with subfolder |  | ||||||
|         # |  | ||||||
|         #  In list commands: |  | ||||||
|         #      %x: Archive to list |  | ||||||
|         # |  | ||||||
|         #  Plus standard bash variables are accepted. |  | ||||||
|         self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N', |  | ||||||
|                                                                 'zip -r %o %N', |  | ||||||
|                                                                 'rar a -r %o %N', |  | ||||||
|                                                                 'tar -cvf %o %N', |  | ||||||
|                                                                 'tar -cvjf %o %N', |  | ||||||
|                                                                 'tar -cvzf %o %N', |  | ||||||
|                                                                 'tar -cvJf %o %N', |  | ||||||
|                                                                 'gzip -c %N > %O', |  | ||||||
|                                                                 'xz -cz %N > %O' |  | ||||||
|                                         ] |  | ||||||
|  |  | ||||||
|         self.notebooks          = [self.window1, self.window2, self.window3, self.window4] |         self.notebooks          = [self.window1, self.window2, self.window3, self.window4] | ||||||
|         self.selected_files     = [] |         self.selected_files     = [] | ||||||
|         self.to_copy_files      = [] |         self.to_copy_files      = [] | ||||||
|         self.to_cut_files       = [] |         self.to_cut_files       = [] | ||||||
|         self.soft_update_lock   = {} |         self.soft_update_lock   = {} | ||||||
|  |         self.dnd_left_primed    = 0 | ||||||
|  |  | ||||||
|         self.single_click_open  = False |         self.single_click_open  = False | ||||||
|         self.is_pane1_hidden    = False |         self.is_pane1_hidden    = False | ||||||
| @@ -107,9 +80,6 @@ class Controller_Data: | |||||||
|         self.is_pane4_hidden    = False |         self.is_pane4_hidden    = False | ||||||
|  |  | ||||||
|         self.override_drop_dest = None |         self.override_drop_dest = None | ||||||
|         self.is_searching       = False |  | ||||||
|         self.search_icon_grid   = None |  | ||||||
|         self.search_tab         = None |  | ||||||
|  |  | ||||||
|         self.cancel_creation    = False |         self.cancel_creation    = False | ||||||
|         self.skip_edit          = False |         self.skip_edit          = False | ||||||
| @@ -118,14 +88,18 @@ class Controller_Data: | |||||||
|         self.shift_down         = False |         self.shift_down         = False | ||||||
|         self.alt_down           = False |         self.alt_down           = False | ||||||
|  |  | ||||||
|         self.success_color      = self.settings.get_success_color() |         self.success_color      = settings.get_success_color() | ||||||
|         self.warning_color      = self.settings.get_warning_color() |         self.warning_color      = settings.get_warning_color() | ||||||
|         self.error_color        = self.settings.get_error_color() |         self.error_color        = settings.get_error_color() | ||||||
|  |  | ||||||
|         # sys.excepthook = self.custom_except_hook |         # sys.excepthook = self.custom_except_hook | ||||||
|         self.window.connect("delete-event", self.tear_down) |         self.window.connect("delete-event", self.tear_down) | ||||||
|         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) |         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) | ||||||
|  |  | ||||||
|  |         self.window.show() | ||||||
|  |         if settings.is_debug(): | ||||||
|  |             self.window.set_interactive_debugging(True) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_current_state(self) -> State: |     def get_current_state(self) -> State: | ||||||
|         ''' |         ''' | ||||||
| @@ -142,7 +116,7 @@ class Controller_Data: | |||||||
|         state.tab            = self.get_fm_window(state.wid).get_tab_by_id(state.tid) |         state.tab            = self.get_fm_window(state.wid).get_tab_by_id(state.tid) | ||||||
|         state.icon_grid      = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") |         state.icon_grid      = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") | ||||||
|         state.store          = state.icon_grid.get_model() |         state.store          = state.icon_grid.get_model() | ||||||
|  |         state.warning_alert  = self.warning_alert | ||||||
|  |  | ||||||
|         selected_files       = state.icon_grid.get_selected_items() |         selected_files       = state.icon_grid.get_selected_items() | ||||||
|         if selected_files: |         if selected_files: | ||||||
| @@ -154,6 +128,7 @@ class Controller_Data: | |||||||
|         # if self.to_cut_files: |         # if self.to_cut_files: | ||||||
|         #     state.to_cut_files   = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) |         #     state.to_cut_files   = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) | ||||||
|  |  | ||||||
|  |         event_system.emit("update_state_info_plugins", state) | ||||||
|         return state |         return state | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,3 @@ | |||||||
| """ | """ | ||||||
| Mixins module |     Mixins module | ||||||
| """ | """ | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import traceback, time | import traceback | ||||||
|  | import time | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| from gi.repository import Gtk, GLib | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  |  | ||||||
| @@ -51,8 +53,3 @@ class ExceptionHookMixin: | |||||||
|                 f.write(text) |                 f.write(text) | ||||||
|  |  | ||||||
|         save_location_prompt.destroy() |         save_location_prompt.destroy() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_arc_buffer_text(self, widget=None, eve=None): |  | ||||||
|         sid = widget.get_active_id() |  | ||||||
|         self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) |  | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ | |||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| gi.require_version('Gdk', '3.0') | gi.require_version('Gdk', '3.0') | ||||||
| from gi.repository import Gtk, Gdk | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  |  | ||||||
| @@ -55,26 +56,6 @@ class ShowHideMixin: | |||||||
|         self.builder.get_object("about_page").hide() |         self.builder.get_object("about_page").hide() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def show_archiver_dialogue(self, widget=None, eve=None): |  | ||||||
|         wid, tid          = self.fm_controller.get_active_wid_and_tid() |  | ||||||
|         tab               = self.get_fm_window(wid).get_tab_by_id(tid) |  | ||||||
|         archiver_dialogue = self.builder.get_object("archiver_dialogue") |  | ||||||
|         archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) |  | ||||||
|         archiver_dialogue.set_current_folder(tab.get_current_directory()) |  | ||||||
|         archiver_dialogue.set_current_name("arc.7z") |  | ||||||
|  |  | ||||||
|         response = archiver_dialogue.run() |  | ||||||
|         if response == Gtk.ResponseType.OK: |  | ||||||
|             self.archive_files(archiver_dialogue) |  | ||||||
|         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         archiver_dialogue.hide() |  | ||||||
|  |  | ||||||
|     def hide_archiver_dialogue(self, widget=None, eve=None): |  | ||||||
|         self.builder.get_object("archiver_dialogue").hide() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def show_appchooser_menu(self, widget=None, eve=None): |     def show_appchooser_menu(self, widget=None, eve=None): | ||||||
|         appchooser_menu   = self.builder.get_object("appchooser_menu") |         appchooser_menu   = self.builder.get_object("appchooser_menu") | ||||||
|         appchooser_widget = self.builder.get_object("appchooser_widget") |         appchooser_widget = self.builder.get_object("appchooser_widget") | ||||||
| @@ -103,10 +84,10 @@ class ShowHideMixin: | |||||||
|         self.builder.get_object("plugin_controls").hide() |         self.builder.get_object("plugin_controls").hide() | ||||||
|  |  | ||||||
|     def show_context_menu(self, widget=None, eve=None): |     def show_context_menu(self, widget=None, eve=None): | ||||||
|         self.builder.get_object("context_menu_popup").run() |         self.builder.get_object("context_menu").popup_at_pointer(None) | ||||||
|  |  | ||||||
|     def hide_context_menu(self, widget=None, eve=None): |     def hide_context_menu(self, widget=None, eve=None): | ||||||
|         self.builder.get_object("context_menu_popup").hide() |         self.builder.get_object("context_menu").popdown() | ||||||
|  |  | ||||||
|     def show_new_file_menu(self, widget=None, eve=None): |     def show_new_file_menu(self, widget=None, eve=None): | ||||||
|         if widget: |         if widget: | ||||||
| @@ -135,6 +116,9 @@ class ShowHideMixin: | |||||||
|         if response == Gtk.ResponseType.CANCEL: |         if response == Gtk.ResponseType.CANCEL: | ||||||
|             self.cancel_edit = True |             self.cancel_edit = True | ||||||
|  |  | ||||||
|  |     def show_io_popup(self, widget=None, eve=None): | ||||||
|  |         self.builder.get_object("io_popup").popup() | ||||||
|  |  | ||||||
|     def hide_edit_file_menu(self, widget=None, eve=None): |     def hide_edit_file_menu(self, widget=None, eve=None): | ||||||
|         self.builder.get_object("edit_file_menu").hide() |         self.builder.get_object("edit_file_menu").hide() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Signals module | ||||||
|  | """ | ||||||
| @@ -1,18 +1,24 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, time, shlex | import os | ||||||
|  | import time | ||||||
|  | import shlex | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| from gi.repository import Gtk, GObject, GLib, Gio | from gi.repository import Gtk | ||||||
|  | from gi.repository import GObject | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import Gio | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
|  | from widgets.io_widget import IOWidget | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | class FileActionSignalsMixin: | ||||||
| class WidgetFileActionMixin: |     """docstring for FileActionSignalsMixin""" | ||||||
|     """docstring for WidgetFileActionMixin""" |  | ||||||
| 
 | 
 | ||||||
|     def sizeof_fmt(self, num, suffix="B"): |     def sizeof_fmt(self, num, suffix="B"): | ||||||
|         for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: |         for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: | ||||||
| @@ -40,8 +46,8 @@ class WidgetFileActionMixin: | |||||||
|         if tab.get_dir_watcher(): |         if tab.get_dir_watcher(): | ||||||
|             watcher = tab.get_dir_watcher() |             watcher = tab.get_dir_watcher() | ||||||
|             watcher.cancel() |             watcher.cancel() | ||||||
|             if debug: |             if settings.is_debug(): | ||||||
|                 self.logger.debug(f"Watcher Is Cancelled:  {watcher.is_cancelled()}") |                 logger.debug(f"Watcher Is Cancelled:  {watcher.is_cancelled()}") | ||||||
| 
 | 
 | ||||||
|         cur_dir = tab.get_current_directory() |         cur_dir = tab.get_current_directory() | ||||||
| 
 | 
 | ||||||
| @@ -59,8 +65,8 @@ class WidgetFileActionMixin: | |||||||
|         if eve_type in  [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, |         if eve_type in  [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, | ||||||
|                         Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, |                         Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, | ||||||
|                         Gio.FileMonitorEvent.MOVED_OUT]: |                         Gio.FileMonitorEvent.MOVED_OUT]: | ||||||
|             if debug: |             if settings.is_debug(): | ||||||
|                 self.logger.debug(eve_type) |                 logger.debug(eve_type) | ||||||
| 
 | 
 | ||||||
|             if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: |             if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: | ||||||
|                 self.update_on_soft_lock_end(data[0]) |                 self.update_on_soft_lock_end(data[0]) | ||||||
| @@ -102,24 +108,27 @@ class WidgetFileActionMixin: | |||||||
|             self.set_bottom_labels(tab) |             self.set_bottom_labels(tab) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def popup_search_files(self, wid, keyname): |  | ||||||
|         entry = self.builder.get_object(f"win{wid}_search_field") |  | ||||||
|         self.builder.get_object(f"win{wid}_search").popup() |  | ||||||
|         entry.set_text(keyname) |  | ||||||
|         entry.grab_focus_without_selecting() |  | ||||||
|         entry.set_position(-1) |  | ||||||
| 
 |  | ||||||
|     def do_file_search(self, widget, eve=None): |     def do_file_search(self, widget, eve=None): | ||||||
|  |         if not self.ctrl_down and not self.shift_down and not self.alt_down: | ||||||
|  |             target    = widget.get_name() | ||||||
|  |             notebook  = self.builder.get_object(target) | ||||||
|  |             page      = notebook.get_current_page() | ||||||
|  |             nth_page  = notebook.get_nth_page(page) | ||||||
|  |             icon_grid = nth_page.get_children()[0] | ||||||
|  | 
 | ||||||
|  |             wid, tid  = icon_grid.get_name().split("|") | ||||||
|  |             tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|             query     = widget.get_text().lower() |             query     = widget.get_text().lower() | ||||||
|         self.search_icon_grid.unselect_all() | 
 | ||||||
|         for i, file in enumerate(self.search_tab.get_files()): |             icon_grid.unselect_all() | ||||||
|  |             for i, file in enumerate(tab.get_files()): | ||||||
|                 if query and query in file[0].lower(): |                 if query and query in file[0].lower(): | ||||||
|                     path = Gtk.TreePath().new_from_indices([i]) |                     path = Gtk.TreePath().new_from_indices([i]) | ||||||
|                 self.search_icon_grid.select_path(path) |                     icon_grid.select_path(path) | ||||||
| 
 | 
 | ||||||
|         items = self.search_icon_grid.get_selected_items() |             items = icon_grid.get_selected_items() | ||||||
|         if len(items) > 0: |             if len(items) == 1: | ||||||
|             self.search_icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) |                 icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def open_files(self): |     def open_files(self): | ||||||
| @@ -143,19 +152,6 @@ class WidgetFileActionMixin: | |||||||
|             command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" |             command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" | ||||||
|             state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) |             state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) | ||||||
| 
 | 
 | ||||||
|     def archive_files(self, archiver_dialogue): |  | ||||||
|         state       = self.get_current_state() |  | ||||||
|         paths       = [shlex.quote(p) for p in self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)] |  | ||||||
| 
 |  | ||||||
|         save_target = archiver_dialogue.get_filename(); |  | ||||||
|         sItr, eItr  = self.arc_command_buffer.get_bounds() |  | ||||||
|         pre_command = self.arc_command_buffer.get_text(sItr, eItr, False) |  | ||||||
|         pre_command = pre_command.replace("%o", shlex.quote(save_target)) |  | ||||||
|         pre_command = pre_command.replace("%N", ' '.join(paths)) |  | ||||||
|         command     = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" |  | ||||||
| 
 |  | ||||||
|         state.tab.execute(shlex.split(command), start_dir=shlex.quote(state.tab.get_current_directory())) |  | ||||||
| 
 |  | ||||||
|     def rename_files(self): |     def rename_files(self): | ||||||
|         rename_label = self.builder.get_object("file_to_rename_label") |         rename_label = self.builder.get_object("file_to_rename_label") | ||||||
|         rename_input = self.builder.get_object("new_rename_fname") |         rename_input = self.builder.get_object("new_rename_fname") | ||||||
| @@ -208,45 +204,6 @@ class WidgetFileActionMixin: | |||||||
|         elif self.to_cut_files: |         elif self.to_cut_files: | ||||||
|             self.handle_files(self.to_cut_files, "move", target) |             self.handle_files(self.to_cut_files, "move", target) | ||||||
| 
 | 
 | ||||||
|     def delete_files(self): |  | ||||||
|         state    = self.get_current_state() |  | ||||||
|         uris     = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) |  | ||||||
|         response = None |  | ||||||
| 
 |  | ||||||
|         self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") |  | ||||||
|         for uri in uris: |  | ||||||
|             file = Gio.File.new_for_path(uri) |  | ||||||
| 
 |  | ||||||
|             if not response: |  | ||||||
|                 response = self.warning_alert.run() |  | ||||||
|                 self.warning_alert.hide() |  | ||||||
|             if response == Gtk.ResponseType.YES: |  | ||||||
|                 type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) |  | ||||||
| 
 |  | ||||||
|                 if type == Gio.FileType.DIRECTORY: |  | ||||||
|                     state.tab.delete_file( file.get_path() ) |  | ||||||
|                 else: |  | ||||||
|                     file.delete(cancellable=None) |  | ||||||
|             else: |  | ||||||
|                 break |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     def trash_files(self): |  | ||||||
|         state = self.get_current_state() |  | ||||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) |  | ||||||
|         for uri in uris: |  | ||||||
|             self.trashman.trash(uri, False) |  | ||||||
| 
 |  | ||||||
|     def restore_trash_files(self): |  | ||||||
|         state = self.get_current_state() |  | ||||||
|         uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) |  | ||||||
|         for uri in uris: |  | ||||||
|             self.trashman.restore(filename=uri.split("/")[-1], verbose=False) |  | ||||||
| 
 |  | ||||||
|     def empty_trash(self): |  | ||||||
|         self.trashman.empty(verbose=False) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     def create_files(self): |     def create_files(self): | ||||||
|         fname_field = self.builder.get_object("new_fname_field") |         fname_field = self.builder.get_object("new_fname_field") | ||||||
|         self.show_new_file_menu(fname_field) |         self.show_new_file_menu(fname_field) | ||||||
| @@ -355,7 +312,6 @@ class WidgetFileActionMixin: | |||||||
|                     file.make_directory(cancellable=None) |                     file.make_directory(cancellable=None) | ||||||
|                     continue |                     continue | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|                 type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) |                 type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) | ||||||
|                 if type == Gio.FileType.DIRECTORY: |                 if type == Gio.FileType.DIRECTORY: | ||||||
|                     wid, tid  = self.fm_controller.get_active_wid_and_tid() |                     wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
| @@ -369,10 +325,27 @@ class WidgetFileActionMixin: | |||||||
|                     if action == "move" or action == "rename": |                     if action == "move" or action == "rename": | ||||||
|                         tab.move_file(fPath, tPath) |                         tab.move_file(fPath, tPath) | ||||||
|                 else: |                 else: | ||||||
|  |                     io_widget = IOWidget(action, file) | ||||||
|  | 
 | ||||||
|                     if action == "copy": |                     if action == "copy": | ||||||
|                         file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) |                         file.copy_async(destination=target, | ||||||
|  |                                         flags=Gio.FileCopyFlags.BACKUP, | ||||||
|  |                                         io_priority=98, | ||||||
|  |                                         cancellable=io_widget.cancle_eve, | ||||||
|  |                                         progress_callback=io_widget.update_progress, | ||||||
|  |                                         callback=io_widget.finish_callback) | ||||||
|  | 
 | ||||||
|  |                         self.builder.get_object("io_list").add(io_widget) | ||||||
|                     if action == "move" or action == "rename": |                     if action == "move" or action == "rename": | ||||||
|                         file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) |                         file.move_async(destination=target, | ||||||
|  |                                         flags=Gio.FileCopyFlags.BACKUP, | ||||||
|  |                                         io_priority=98, | ||||||
|  |                                         cancellable=io_widget.cancle_eve, | ||||||
|  |                                         progress_callback=None, | ||||||
|  |                                         # NOTE: progress_callback here causes seg fault when set | ||||||
|  |                                         callback=io_widget.finish_callback) | ||||||
|  | 
 | ||||||
|  |                         self.builder.get_object("io_list").add(io_widget) | ||||||
| 
 | 
 | ||||||
|             except GObject.GError as e: |             except GObject.GError as e: | ||||||
|                 raise OSError(e) |                 raise OSError(e) | ||||||
| @@ -428,11 +401,11 @@ class WidgetFileActionMixin: | |||||||
|         target    = Gio.File.new_for_path(full_path) |         target    = Gio.File.new_for_path(full_path) | ||||||
|         start     = "-copy" |         start     = "-copy" | ||||||
| 
 | 
 | ||||||
|         if debug: |         if settings.is_debug(): | ||||||
|             self.logger.debug(f"Path:  {full_path}") |             logger.debug(f"Path:  {full_path}") | ||||||
|             self.logger.debug(f"Base Path:  {base_path}") |             logger.debug(f"Base Path:  {base_path}") | ||||||
|             self.logger.debug(f'Name:  {file_name}') |             logger.debug(f'Name:  {file_name}') | ||||||
|             self.logger.debug(f"Extension:  {extension}") |             logger.debug(f"Extension:  {extension}") | ||||||
| 
 | 
 | ||||||
|         i = 2 |         i = 2 | ||||||
|         while target.query_exists(): |         while target.query_exists(): | ||||||
| @@ -5,11 +5,12 @@ | |||||||
| # Application imports | # Application imports | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class IPCSignalsMixin: | class IPCSignalsMixin: | ||||||
|     """ IPCSignalsMixin handle messages from another starting solarfm process. """ |     """ IPCSignalsMixin handle messages from another starting solarfm process. """ | ||||||
| 
 | 
 | ||||||
|     def print_to_console(self, message=None): |     def print_to_console(self, message=None): | ||||||
|         print(self) |  | ||||||
|         print(message) |         print(message) | ||||||
| 
 | 
 | ||||||
|     def handle_file_from_ipc(self, path): |     def handle_file_from_ipc(self, path): | ||||||
| @@ -5,7 +5,8 @@ import re | |||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| gi.require_version('Gdk', '3.0') | gi.require_version('Gdk', '3.0') | ||||||
| from gi.repository import Gtk, Gdk | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| 
 | 
 | ||||||
| @@ -13,6 +14,8 @@ from gi.repository import Gtk, Gdk | |||||||
| valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") | valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class KeyboardSignalsMixin: | class KeyboardSignalsMixin: | ||||||
|     """ KeyboardSignalsMixin keyboard hooks controller. """ |     """ KeyboardSignalsMixin keyboard hooks controller. """ | ||||||
| 
 | 
 | ||||||
| @@ -21,7 +24,6 @@ class KeyboardSignalsMixin: | |||||||
|         self.ctrl_down    = False |         self.ctrl_down    = False | ||||||
|         self.shift_down   = False |         self.shift_down   = False | ||||||
|         self.alt_down     = False |         self.alt_down     = False | ||||||
|         self.is_searching = False |  | ||||||
| 
 | 
 | ||||||
|     def on_global_key_press_controller(self, eve, user_data): |     def on_global_key_press_controller(self, eve, user_data): | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |         keyname = Gdk.keyval_name(user_data.keyval).lower() | ||||||
| @@ -52,30 +54,18 @@ class KeyboardSignalsMixin: | |||||||
|                 return True |                 return True | ||||||
|             except Exception: |             except Exception: | ||||||
|                 # Must be plugins scope or we forgot to add method to file manager scope |                 # Must be plugins scope or we forgot to add method to file manager scope | ||||||
|                 sender, method_target = mapping.split("||") |                 sender, eve_type = mapping.split("||") | ||||||
|                 self.handle_plugin_key_event(sender, method_target) |                 self.handle_plugin_key_event(sender, eve_type) | ||||||
|         else: |         else: | ||||||
|             if debug: |             if settings.is_debug(): | ||||||
|                 print(f"on_global_key_release_controller > key > {keyname}") |                 print(f"on_global_key_release_controller > key > {keyname}") | ||||||
| 
 | 
 | ||||||
|             if self.ctrl_down: |             if self.ctrl_down: | ||||||
|                 if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: |                 if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: | ||||||
|                     self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() |                     self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() | ||||||
| 
 | 
 | ||||||
|             if re.fullmatch(valid_keyvalue_pat, keyname): |     def handle_plugin_key_event(self, sender, eve_type): | ||||||
|                 if not self.is_searching and not self.ctrl_down \ |         event_system.emit(eve_type) | ||||||
|                     and not self.shift_down and not self.alt_down: |  | ||||||
|                         focused_obj = self.window.get_focus() |  | ||||||
|                         if isinstance(focused_obj, Gtk.IconView): |  | ||||||
|                             self.is_searching     = True |  | ||||||
|                             state                 = self.get_current_state() |  | ||||||
|                             self.search_tab       = state.tab |  | ||||||
|                             self.search_icon_grid = state.icon_grid |  | ||||||
| 
 |  | ||||||
|                             self.unset_keys_and_data() |  | ||||||
|                             self.popup_search_files(state.wid, keyname) |  | ||||||
|                             return True |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|     def keyboard_close_tab(self): |     def keyboard_close_tab(self): | ||||||
|         wid, tid  = self.fm_controller.get_active_wid_and_tid() |         wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
| @@ -88,5 +78,6 @@ class KeyboardSignalsMixin: | |||||||
| 
 | 
 | ||||||
|         self.get_fm_window(wid).delete_tab_by_id(tid) |         self.get_fm_window(wid).delete_tab_by_id(tid) | ||||||
|         notebook.remove_page(page) |         notebook.remove_page(page) | ||||||
|  |         if not trace_debug: | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|         self.set_window_title() |         self.set_window_title() | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | from .exception_hook_mixin import ExceptionHookMixin | ||||||
|  | from .signals.file_action_signals_mixin import FileActionSignalsMixin | ||||||
|  | from .signals.ipc_signals_mixin import IPCSignalsMixin | ||||||
|  | from .signals.keyboard_signals_mixin import KeyboardSignalsMixin | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin): | ||||||
|  |     ... | ||||||
| @@ -1,3 +1,3 @@ | |||||||
| """ | """ | ||||||
| UI module |     UI module | ||||||
| """ | """ | ||||||
|   | |||||||
| @@ -6,48 +6,22 @@ import gi | |||||||
|  |  | ||||||
| gi.require_version("Gtk", "3.0") | gi.require_version("Gtk", "3.0") | ||||||
| gi.require_version('Gdk', '3.0') | gi.require_version('Gdk', '3.0') | ||||||
| from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import GLib | ||||||
|  | from gi.repository import Gio | ||||||
|  | from gi.repository import GdkPixbuf | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  | from widgets.tab_header_widget import TabHeaderWidget | ||||||
|  | from widgets.icon_grid_widget import IconGridWidget | ||||||
|  | from widgets.icon_tree_widget import IconTreeWidget | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid... |  | ||||||
| # Can possibly use this to dynamicly load icons instead... |  | ||||||
| class Icon(Gtk.HBox): |  | ||||||
|     def __init__(self, tab, dir, file): |  | ||||||
|         super(Icon, self).__init__() |  | ||||||
|  |  | ||||||
|         self.load_icon(tab, dir, file) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def load_icon(self, tab, dir, file): |  | ||||||
|         icon = tab.create_icon(dir, file) |  | ||||||
|  |  | ||||||
|         if not icon: |  | ||||||
|             path = f"{dir}/{file}" |  | ||||||
|             icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0]) |  | ||||||
|  |  | ||||||
|         if not icon: |  | ||||||
|             icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) |  | ||||||
|  |  | ||||||
|         self.add(Gtk.Image.new_from_pixbuf(icon)) |  | ||||||
|         self.show_all() |  | ||||||
|  |  | ||||||
|     def get_system_thumbnail(self, file, size): |  | ||||||
|         try: |  | ||||||
|             gio_file  = Gio.File.new_for_path(file) |  | ||||||
|             info      = gio_file.query_info('standard::icon' , 0, None) |  | ||||||
|             icon      = info.get_icon().get_names()[0] |  | ||||||
|             icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() |  | ||||||
|             return GdkPixbuf.Pixbuf.new_from_file(icon_path) |  | ||||||
|         except Exception as e: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GridMixin: | class GridMixin: | ||||||
|     """docstring for WidgetMixin""" |     """docstring for GridMixin""" | ||||||
|  |  | ||||||
|     def load_store(self, tab, store, save_state=False): |     def load_store(self, tab, store, save_state=False): | ||||||
|         store.clear() |         store.clear() | ||||||
| @@ -61,7 +35,7 @@ class GridMixin: | |||||||
|             self.create_icon(i, tab, store, dir, file[0]) |             self.create_icon(i, tab, store, dir, file[0]) | ||||||
|  |  | ||||||
|         # NOTE: Not likely called often from here but it could be useful |         # NOTE: Not likely called often from here but it could be useful | ||||||
|         if save_state: |         if save_state and not trace_debug: | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|  |  | ||||||
|     @threaded |     @threaded | ||||||
| @@ -86,133 +60,59 @@ class GridMixin: | |||||||
|             info      = gio_file.query_info('standard::icon' , 0, None) |             info      = gio_file.query_info('standard::icon' , 0, None) | ||||||
|             icon      = info.get_icon().get_names()[0] |             icon      = info.get_icon().get_names()[0] | ||||||
|             icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() |             icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() | ||||||
|  |  | ||||||
|             return GdkPixbuf.Pixbuf.new_from_file(icon_path) |             return GdkPixbuf.Pixbuf.new_from_file(icon_path) | ||||||
|         except Exception as e: |         except Exception: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_tab_widget(self, tab): |     def create_tab_widget(self, tab): | ||||||
|         tab_widget = Gtk.ButtonBox() |         return TabHeaderWidget(tab, self.close_tab) | ||||||
|         label = Gtk.Label() |  | ||||||
|         tid   = Gtk.Label() |  | ||||||
|         close = Gtk.Button() |  | ||||||
|         icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE) |  | ||||||
|  |  | ||||||
|         label.set_label(f"{tab.get_end_of_path()}") |  | ||||||
|         label.set_width_chars(len(tab.get_end_of_path())) |  | ||||||
|         label.set_xalign(0.0) |  | ||||||
|         tid.set_label(f"{tab.get_id()}") |  | ||||||
|  |  | ||||||
|         close.add(icon) |  | ||||||
|         tab_widget.add(label) |  | ||||||
|         tab_widget.add(close) |  | ||||||
|         tab_widget.add(tid) |  | ||||||
|  |  | ||||||
|         close.connect("released", self.close_tab) |  | ||||||
|         tab_widget.show_all() |  | ||||||
|         tid.hide() |  | ||||||
|         return tab_widget |  | ||||||
|  |  | ||||||
|     def create_scroll_and_store(self, tab, wid, use_tree_view=False): |     def create_scroll_and_store(self, tab, wid, use_tree_view=False): | ||||||
|  |         scroll = Gtk.ScrolledWindow() | ||||||
|  |  | ||||||
|         if not use_tree_view: |         if not use_tree_view: | ||||||
|             scroll, store = self.create_icon_grid_widget(tab, wid) |             grid = self.create_icon_grid_widget() | ||||||
|         else: |         else: | ||||||
|             # TODO: Fix global logic to make the below work too |             # TODO: Fix global logic to make the below work too | ||||||
|             scroll, store = self.create_icon_tree_widget(tab, wid) |             grid = self.create_icon_tree_widget() | ||||||
|  |  | ||||||
|         return scroll, store |  | ||||||
|  |  | ||||||
|     def create_icon_grid_widget(self, tab, wid): |  | ||||||
|         scroll = Gtk.ScrolledWindow() |  | ||||||
|         grid   = Gtk.IconView() |  | ||||||
|         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) |  | ||||||
|  |  | ||||||
|         grid.set_model(store) |  | ||||||
|         grid.set_pixbuf_column(0) |  | ||||||
|         grid.set_text_column(1) |  | ||||||
|  |  | ||||||
|         grid.set_item_orientation(1) |  | ||||||
|         grid.set_selection_mode(3) |  | ||||||
|         grid.set_item_width(96) |  | ||||||
|         grid.set_item_padding(8) |  | ||||||
|         grid.set_margin(12) |  | ||||||
|         grid.set_row_spacing(18) |  | ||||||
|         grid.set_columns(-1) |  | ||||||
|         grid.set_spacing(12) |  | ||||||
|         grid.set_column_spacing(18) |  | ||||||
|  |  | ||||||
|         grid.connect("button_release_event", self.grid_icon_single_click) |  | ||||||
|         grid.connect("item-activated",       self.grid_icon_double_click) |  | ||||||
|         grid.connect("selection-changed",    self.grid_set_selected_items) |  | ||||||
|         grid.connect("drag-data-get",        self.grid_on_drag_set) |  | ||||||
|         grid.connect("drag-data-received",   self.grid_on_drag_data_received) |  | ||||||
|         grid.connect("drag-motion",          self.grid_on_drag_motion) |  | ||||||
|  |  | ||||||
|         URI_TARGET_TYPE  = 80 |  | ||||||
|         uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) |  | ||||||
|         targets          = [ uri_target ] |  | ||||||
|         action           = Gdk.DragAction.COPY |  | ||||||
|         grid.enable_model_drag_dest(targets, action) |  | ||||||
|         grid.enable_model_drag_source(0, targets, action) |  | ||||||
|  |  | ||||||
|         grid.show_all() |  | ||||||
|         scroll.add(grid) |         scroll.add(grid) | ||||||
|         grid.set_name(f"{wid}|{tab.get_id()}") |  | ||||||
|         scroll.set_name(f"{wid}|{tab.get_id()}") |         scroll.set_name(f"{wid}|{tab.get_id()}") | ||||||
|  |         grid.set_name(f"{wid}|{tab.get_id()}") | ||||||
|         self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) |         self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) | ||||||
|         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) |         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) | ||||||
|         return scroll, store |  | ||||||
|  |  | ||||||
|     def create_icon_tree_widget(self, tab, wid): |         return scroll, grid.get_store() | ||||||
|         scroll = Gtk.ScrolledWindow() |  | ||||||
|         grid   = Gtk.TreeView() |  | ||||||
|         store  = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) |  | ||||||
|         column = Gtk.TreeViewColumn("Icons") |  | ||||||
|         icon   = Gtk.CellRendererPixbuf() |  | ||||||
|         name   = Gtk.CellRendererText() |  | ||||||
|         selec  = grid.get_selection() |  | ||||||
|  |  | ||||||
|         grid.set_model(store) |     def create_icon_grid_widget(self): | ||||||
|         selec.set_mode(3) |         grid = IconGridWidget() | ||||||
|         column.pack_start(icon, False) |         grid._setup_additional_signals( | ||||||
|         column.pack_start(name, True) |             self.grid_icon_single_click, | ||||||
|         column.add_attribute(icon, "pixbuf", 0) |             self.grid_icon_double_click, | ||||||
|         column.add_attribute(name, "text", 1) |             self.grid_set_selected_items, | ||||||
|         column.set_expand(False) |             self.grid_on_drag_set, | ||||||
|         column.set_sizing(2) |             self.grid_on_drag_data_received, | ||||||
|         column.set_min_width(120) |             self.grid_on_drag_motion | ||||||
|         column.set_max_width(74) |         ) | ||||||
|  |  | ||||||
|         grid.append_column(column) |         return grid | ||||||
|         grid.set_search_column(1) |  | ||||||
|         grid.set_rubber_banding(True) |  | ||||||
|         grid.set_headers_visible(False) |  | ||||||
|         grid.set_enable_tree_lines(False) |  | ||||||
|  |  | ||||||
|         grid.connect("button_release_event", self.grid_icon_single_click) |     def create_icon_tree_widget(self): | ||||||
|         grid.connect("row-activated",        self.grid_icon_double_click) |         grid = IconTreeWidget() | ||||||
|         grid.connect("drag-data-get",        self.grid_on_drag_set) |         grid._setup_additional_signals( | ||||||
|         grid.connect("drag-data-received",   self.grid_on_drag_data_received) |             self.grid_icon_single_click, | ||||||
|         grid.connect("drag-motion",          self.grid_on_drag_motion) |             self.grid_icon_double_click, | ||||||
|  |             self.grid_on_drag_set, | ||||||
|  |             self.grid_on_drag_data_received, | ||||||
|  |             self.grid_on_drag_motion | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         URI_TARGET_TYPE  = 80 |  | ||||||
|         uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) |  | ||||||
|         targets          = [ uri_target ] |  | ||||||
|         action           = Gdk.DragAction.COPY |  | ||||||
|         grid.enable_model_drag_dest(targets, action) |  | ||||||
|         grid.enable_model_drag_source(0, targets, action) |  | ||||||
|  |  | ||||||
|         grid.show_all() |  | ||||||
|         scroll.add(grid) |  | ||||||
|         grid.set_name(f"{wid}|{tab.get_id()}") |  | ||||||
|         scroll.set_name(f"{wid}|{tab.get_id()}") |  | ||||||
|         self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) |  | ||||||
|         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) |  | ||||||
|         grid.columns_autosize() |         grid.columns_autosize() | ||||||
|  |         return grid | ||||||
|         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) |  | ||||||
|         return scroll, store |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_store_and_label_from_notebook(self, notebook, _name): |     def get_store_and_label_from_notebook(self, notebook, _name): | ||||||
|         icon_grid = None |         icon_grid = None | ||||||
|   | |||||||
| @@ -56,4 +56,5 @@ class PaneMixin: | |||||||
|     def _save_state(self, state, pane_index): |     def _save_state(self, state, pane_index): | ||||||
|         window = self.fm_controller.get_window_by_index(pane_index - 1) |         window = self.fm_controller.get_window_by_index(pane_index - 1) | ||||||
|         window.set_is_hidden(state) |         window.set_is_hidden(state) | ||||||
|  |         if not settings.is_trace_debug(): | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ class TabMixin(GridMixin): | |||||||
|         notebook    = self.builder.get_object(f"window_{wid}") |         notebook    = self.builder.get_object(f"window_{wid}") | ||||||
|         path_entry  = self.builder.get_object(f"path_entry") |         path_entry  = self.builder.get_object(f"path_entry") | ||||||
|         tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") |         tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") | ||||||
|         tab.logger  = self.logger |         tab.logger  = logger | ||||||
|  |  | ||||||
|         tab.set_wid(wid) |         tab.set_wid(wid) | ||||||
|         if not path: |         if not path: | ||||||
| @@ -35,6 +35,7 @@ class TabMixin(GridMixin): | |||||||
|         tab_widget    = self.create_tab_widget(tab) |         tab_widget    = self.create_tab_widget(tab) | ||||||
|         scroll, store = self.create_scroll_and_store(tab, wid) |         scroll, store = self.create_scroll_and_store(tab, wid) | ||||||
|         index         = notebook.append_page(scroll, tab_widget) |         index         = notebook.append_page(scroll, tab_widget) | ||||||
|  |         notebook.set_tab_detachable(scroll, True) | ||||||
|  |  | ||||||
|         self.fm_controller.set_wid_and_tid(wid, tab.get_id()) |         self.fm_controller.set_wid_and_tid(wid, tab.get_id()) | ||||||
|         path_entry.set_text(tab.get_current_directory()) |         path_entry.set_text(tab.get_current_directory()) | ||||||
| @@ -63,9 +64,14 @@ class TabMixin(GridMixin): | |||||||
|         watcher.cancel() |         watcher.cancel() | ||||||
|         self.get_fm_window(wid).delete_tab_by_id(tid) |         self.get_fm_window(wid).delete_tab_by_id(tid) | ||||||
|         notebook.remove_page(page) |         notebook.remove_page(page) | ||||||
|  |         if not settings.is_trace_debug(): | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|         self.set_window_title() |         self.set_window_title() | ||||||
|  |  | ||||||
|  |     # NOTE: Not actually getting called even tho set in the glade file... | ||||||
|  |     def on_tab_dnded(self, notebook, page, x, y): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|     def on_tab_reorder(self, child, page_num, new_index): |     def on_tab_reorder(self, child, page_num, new_index): | ||||||
|         wid, tid = page_num.get_name().split("|") |         wid, tid = page_num.get_name().split("|") | ||||||
|         window   = self.get_fm_window(wid) |         window   = self.get_fm_window(wid) | ||||||
| @@ -80,6 +86,7 @@ class TabMixin(GridMixin): | |||||||
|  |  | ||||||
|         tab = window.get_tab_by_id(tid) |         tab = window.get_tab_by_id(tid) | ||||||
|         self.set_file_watcher(tab) |         self.set_file_watcher(tab) | ||||||
|  |         if not settings.is_trace_debug(): | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|  |  | ||||||
|     def on_tab_switch_update(self, notebook, content=None, index=None): |     def on_tab_switch_update(self, notebook, content=None, index=None): | ||||||
| @@ -115,6 +122,7 @@ class TabMixin(GridMixin): | |||||||
|         tab_label.set_label(tab.get_end_of_path()) |         tab_label.set_label(tab.get_end_of_path()) | ||||||
|         self.set_window_title() |         self.set_window_title() | ||||||
|         self.set_file_watcher(tab) |         self.set_file_watcher(tab) | ||||||
|  |         if not settings.is_trace_debug(): | ||||||
|             self.fm_controller.save_state() |             self.fm_controller.save_state() | ||||||
|  |  | ||||||
|     def do_action_from_bar_controls(self, widget, eve=None): |     def do_action_from_bar_controls(self, widget, eve=None): | ||||||
| @@ -127,7 +135,9 @@ class TabMixin(GridMixin): | |||||||
|         if action == "create_tab": |         if action == "create_tab": | ||||||
|             dir = tab.get_current_directory() |             dir = tab.get_current_directory() | ||||||
|             self.create_tab(wid, None, dir) |             self.create_tab(wid, None, dir) | ||||||
|  |             if not settings.is_trace_debug(): | ||||||
|                 self.fm_controller.save_state() |                 self.fm_controller.save_state() | ||||||
|  |  | ||||||
|             return |             return | ||||||
|         if action == "go_up": |         if action == "go_up": | ||||||
|             tab.pop_from_path() |             tab.pop_from_path() | ||||||
|   | |||||||
| @@ -6,12 +6,17 @@ from os.path import isdir | |||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gdk', '3.0') | gi.require_version('Gdk', '3.0') | ||||||
| from gi.repository import Gdk, Gio | from gi.repository import Gdk | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .tab_mixin import TabMixin | from .tab_mixin import TabMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WindowException(Exception): | ||||||
|  |     ... | ||||||
|  |  | ||||||
|  |  | ||||||
| class WindowMixin(TabMixin): | class WindowMixin(TabMixin): | ||||||
|     """docstring for WindowMixin""" |     """docstring for WindowMixin""" | ||||||
|  |  | ||||||
| @@ -20,8 +25,8 @@ class WindowMixin(TabMixin): | |||||||
|             for j, value in enumerate(session_json): |             for j, value in enumerate(session_json): | ||||||
|                 i = j + 1 |                 i = j + 1 | ||||||
|                 notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") |                 notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") | ||||||
|                 is_hidden = True if value[0]["window"]["isHidden"] == "True" else False |                 is_hidden = True if value["window"]["isHidden"] == "True" else False | ||||||
|                 tabs      = value[0]["window"]["tabs"] |                 tabs      = value["window"]["tabs"] | ||||||
|                 self.fm_controller.create_window() |                 self.fm_controller.create_window() | ||||||
|                 notebook_tggl_button.set_active(True) |                 notebook_tggl_button.set_active(True) | ||||||
|  |  | ||||||
| @@ -46,7 +51,7 @@ class WindowMixin(TabMixin): | |||||||
|  |  | ||||||
|                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) | ||||||
|                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |                 icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) | ||||||
|             except Exception as e: |             except WindowException as e: | ||||||
|                 print("\n:  The saved session might be missing window data!  :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") |                 print("\n:  The saved session might be missing window data!  :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") | ||||||
|                 print(repr(e)) |                 print(repr(e)) | ||||||
|         else: |         else: | ||||||
| @@ -88,12 +93,11 @@ class WindowMixin(TabMixin): | |||||||
|         formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) |         formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) | ||||||
|         formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) ) |         formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) ) | ||||||
|  |  | ||||||
|  |         # NOTE: Hides empty trash and other desired buttons based on context. | ||||||
|         if self.trash_files_path == current_directory: |         if self.trash_files_path == current_directory: | ||||||
|             self.builder.get_object("restore_from_trash").show() |             event_system.emit("show_trash_buttons") | ||||||
|             self.builder.get_object("empty_trash").show() |  | ||||||
|         else: |         else: | ||||||
|             self.builder.get_object("restore_from_trash").hide() |             event_system.emit("hide_trash_buttons") | ||||||
|             self.builder.get_object("empty_trash").hide() |  | ||||||
|  |  | ||||||
|         # If something selected |         # If something selected | ||||||
|         self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") |         self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") | ||||||
| @@ -108,8 +112,8 @@ class WindowMixin(TabMixin): | |||||||
|                                                         cancellable=None) |                                                         cancellable=None) | ||||||
|                     file_size = file_info.get_size() |                     file_size = file_info.get_size() | ||||||
|                     combined_size += file_size |                     combined_size += file_size | ||||||
|                 except Exception as e: |                 except WindowException as e: | ||||||
|                     if debug: |                     if settings.is_debug(): | ||||||
|                         print(repr(e)) |                         print(repr(e)) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -156,10 +160,31 @@ class WindowMixin(TabMixin): | |||||||
|         path_entry.set_text(tab.get_current_directory()) |         path_entry.set_text(tab.get_current_directory()) | ||||||
|  |  | ||||||
|     def grid_set_selected_items(self, icons_grid): |     def grid_set_selected_items(self, icons_grid): | ||||||
|         self.selected_files = icons_grid.get_selected_items() |         items = icons_grid.get_selected_items() | ||||||
|  |         size  = len(items) | ||||||
|  |  | ||||||
|     def grid_cursor_toggled(self, icons_grid): |         if size == 1: | ||||||
|         print("wat...") |             # NOTE: If already in selection, likely dnd else not so wont readd | ||||||
|  |             if items[0] in self.selected_files: | ||||||
|  |                 self.dnd_left_primed += 1 | ||||||
|  |                 # NOTE: If in selection but trying to just select an already selected item. | ||||||
|  |                 if self.dnd_left_primed > 1: | ||||||
|  |                     self.dnd_left_primed = 0 | ||||||
|  |                     self.selected_files.clear() | ||||||
|  |                     return | ||||||
|  |  | ||||||
|  |                 # NOTE: Likely trying dnd, just readd to selection the former set. | ||||||
|  |                 #       Prevents losing highlighting of grid selected. | ||||||
|  |                 for path in self.selected_files: | ||||||
|  |                     icons_grid.select_path(path) | ||||||
|  |  | ||||||
|  |                 return | ||||||
|  |  | ||||||
|  |         if size > 0: | ||||||
|  |             self.selected_files = icons_grid.get_selected_items() | ||||||
|  |         else: | ||||||
|  |             self.dnd_left_primed = 0 | ||||||
|  |             self.selected_files.clear() | ||||||
|  |  | ||||||
|     def grid_icon_single_click(self, icons_grid, eve): |     def grid_icon_single_click(self, icons_grid, eve): | ||||||
|         try: |         try: | ||||||
| @@ -169,14 +194,16 @@ class WindowMixin(TabMixin): | |||||||
|             self.set_path_text(wid, tid) |             self.set_path_text(wid, tid) | ||||||
|             self.set_window_title() |             self.set_window_title() | ||||||
|  |  | ||||||
|  |  | ||||||
|             if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1:   # l-click |             if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1:   # l-click | ||||||
|  |                 if self.ctrl_down: | ||||||
|  |                     self.dnd_left_primed = 0 | ||||||
|  |  | ||||||
|                 if self.single_click_open: # FIXME: need to find a way to pass the model index |                 if self.single_click_open: # FIXME: need to find a way to pass the model index | ||||||
|                     self.grid_icon_double_click(icons_grid) |                     self.grid_icon_double_click(icons_grid) | ||||||
|             elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click |             elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click | ||||||
|                 self.show_context_menu() |                 self.show_context_menu() | ||||||
|  |  | ||||||
|         except Exception as e: |         except WindowException as e: | ||||||
|             print(repr(e)) |             print(repr(e)) | ||||||
|             self.display_message(self.error_color, f"{repr(e)}") |             self.display_message(self.error_color, f"{repr(e)}") | ||||||
|  |  | ||||||
| @@ -205,7 +232,7 @@ class WindowMixin(TabMixin): | |||||||
|                 self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid) |                 self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid) | ||||||
|             else: |             else: | ||||||
|                 self.open_files() |                 self.open_files() | ||||||
|         except Exception as e: |         except WindowException as e: | ||||||
|             traceback.print_exc() |             traceback.print_exc() | ||||||
|             self.display_message(self.error_color, f"{repr(e)}") |             self.display_message(self.error_color, f"{repr(e)}") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .show_hide_mixin import ShowHideMixin |  | ||||||
| from .ui.widget_file_action_mixin import WidgetFileActionMixin |  | ||||||
| from .ui.pane_mixin import PaneMixin |  | ||||||
| from .ui.window_mixin import WindowMixin |  | ||||||
| from .show_hide_mixin import ShowHideMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): |  | ||||||
|     pass |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Signals module |  | ||||||
| """ |  | ||||||
							
								
								
									
										12
									
								
								src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .mixins.show_hide_mixin import ShowHideMixin | ||||||
|  | from .mixins.ui.pane_mixin import PaneMixin | ||||||
|  | from .mixins.ui.window_mixin import WindowMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UI(PaneMixin, WindowMixin, ShowHideMixin): | ||||||
|  |     ... | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, json | import os | ||||||
|  | import json | ||||||
| from os.path import join | from os.path import join | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
| @@ -56,7 +57,8 @@ class ManifestProcessor: | |||||||
|             if requests["ui_target"] in  [ |             if requests["ui_target"] in  [ | ||||||
|                                             "none", "other", "main_Window", "main_menu_bar", |                                             "none", "other", "main_Window", "main_menu_bar", | ||||||
|                                             "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list", |                                             "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list", | ||||||
|                                             "context_menu", "window_1", "window_2", "window_3", "window_4" |                                             "context_menu", "context_menu_plugins", "window_1", | ||||||
|  |                                             "window_2", "window_3", "window_4" | ||||||
|                                         ]: |                                         ]: | ||||||
|                 if requests["ui_target"] == "other": |                 if requests["ui_target"] == "other": | ||||||
|                     if "ui_target_id" in keys: |                     if "ui_target_id" in keys: | ||||||
|   | |||||||
| @@ -1,11 +1,16 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, time | import os | ||||||
|  | import time | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PluginBaseException(Exception): | ||||||
|  |     ... | ||||||
|  |  | ||||||
|  |  | ||||||
| class PluginBase: | class PluginBase: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.name               = "Example Plugin"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus |         self.name               = "Example Plugin"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus | ||||||
| @@ -13,43 +18,48 @@ class PluginBase: | |||||||
|  |  | ||||||
|         self._builder           = None |         self._builder           = None | ||||||
|         self._ui_objects        = None |         self._ui_objects        = None | ||||||
|  |         self._fm_state          = None | ||||||
|         self._event_system      = None |         self._event_system      = None | ||||||
|         self._event_sleep_time  = .5 |  | ||||||
|         self._event_message     = None |  | ||||||
|  |  | ||||||
|     def set_fm_event_system(self, fm_event_system): |     def set_fm_event_system(self, fm_event_system): | ||||||
|  |         """ | ||||||
|  |             Requests Key:  'pass_fm_events': "true" | ||||||
|  |             Must define in plugin if "pass_fm_events" is set to "true" string. | ||||||
|  |         """ | ||||||
|         self._event_system = fm_event_system |         self._event_system = fm_event_system | ||||||
|  |  | ||||||
|     def set_ui_object_collection(self, ui_objects): |     def set_ui_object_collection(self, ui_objects): | ||||||
|  |         """ | ||||||
|  |             Requests Key:  "pass_ui_objects": [""] | ||||||
|  |             Request reference to a UI component. Will be passed back as array to plugin. | ||||||
|  |             Must define in plugin if set and an array of valid glade UI IDs is given. | ||||||
|  |         """ | ||||||
|         self._ui_objects = ui_objects |         self._ui_objects = ui_objects | ||||||
|  |  | ||||||
|     def wait_for_fm_message(self): |  | ||||||
|         while not self._event_message: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def clear_children(self, widget: type) -> None: |     def clear_children(self, widget: type) -> None: | ||||||
|         ''' Clear children of a gtk widget. ''' |         """ Clear children of a gtk widget. """ | ||||||
|         for child in widget.get_children(): |         for child in widget.get_children(): | ||||||
|             widget.remove(child) |             widget.remove(child) | ||||||
|  |  | ||||||
|     @daemon_threaded |     def subscribe_to_events(self): | ||||||
|     def _module_event_observer(self): |         self._event_system.subscribe("update_state_info_plugins", self._update_fm_state_info) | ||||||
|         while True: |  | ||||||
|             time.sleep(self._event_sleep_time) |  | ||||||
|             event = self._event_system.read_module_event() |  | ||||||
|             if event: |  | ||||||
|                 try: |  | ||||||
|                     if event[0] == self.name: |  | ||||||
|                         target_id, method_target, data = self._event_system.consume_module_event() |  | ||||||
|  |  | ||||||
|                         if not method_target: |     def _update_fm_state_info(self, state): | ||||||
|                             self._event_message = data |         self._fm_state = state | ||||||
|                         else: |  | ||||||
|                             method = getattr(self.__class__, f"{method_target}") |     def generate_reference_ui_element(self): | ||||||
|                             if data: |         """ | ||||||
|                                 data = method(*(self, *data)) |             Requests Key:  'ui_target': "plugin_control_list", | ||||||
|                             else: |             Must define regardless if needed and can 'pass' if plugin doesn't use it. | ||||||
|                                 method(*(self,)) |             Must return a widget if "ui_target" is set. | ||||||
|                 except Exception as e: |         """ | ||||||
|                     print(repr(e)) |         raise PluginBaseException("Method hasn't been overriden...") | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         """ | ||||||
|  |             Must define regardless if needed and can 'pass' if plugin doesn't need it. | ||||||
|  |             Is intended to be used to setup internal signals or custom Gtk Builders/UI logic. | ||||||
|  |         """ | ||||||
|  |         raise PluginBaseException("Method hasn't been overriden...") | ||||||
|   | |||||||
| @@ -1,14 +1,20 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, sys, importlib, traceback | import os | ||||||
| from os.path import join, isdir | import sys | ||||||
|  | import importlib | ||||||
|  | import traceback | ||||||
|  | from os.path import join | ||||||
|  | from os.path import isdir | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| from gi.repository import Gtk, Gio | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gio | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .manifest import PluginInfo, ManifestProcessor | from .manifest import PluginInfo | ||||||
|  | from .manifest import ManifestProcessor | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -17,17 +23,16 @@ class InvalidPluginException(Exception): | |||||||
|     ... |     ... | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Plugins: | class PluginsController: | ||||||
|     """Plugins controller""" |     """PluginsController controller""" | ||||||
| 
 | 
 | ||||||
|     def __init__(self, settings: type): |     def __init__(self): | ||||||
|         path                      = os.path.dirname(os.path.realpath(__file__)) |         path                      = os.path.dirname(os.path.realpath(__file__)) | ||||||
|         sys.path.insert(0, path)  # NOTE: I think I'm not using this correctly... |         sys.path.insert(0, path)  # NOTE: I think I'm not using this correctly... | ||||||
| 
 | 
 | ||||||
|         self._settings            = settings |         self._builder             = settings.get_builder() | ||||||
|         self._builder             = self._settings.get_builder() |         self._plugins_path        = settings.get_plugins_path() | ||||||
|         self._plugins_path        = self._settings.get_plugins_path() |         self._keybindings         = settings.get_keybindings() | ||||||
|         self._keybindings         = self._settings.get_keybindings() |  | ||||||
| 
 | 
 | ||||||
|         self._plugins_dir_watcher = None |         self._plugins_dir_watcher = None | ||||||
|         self._plugin_collection   = [] |         self._plugin_collection   = [] | ||||||
| @@ -72,20 +77,30 @@ class Plugins: | |||||||
| 
 | 
 | ||||||
|     def load_plugin_module(self, path, folder, target): |     def load_plugin_module(self, path, folder, target): | ||||||
|         os.chdir(path) |         os.chdir(path) | ||||||
|         spec   = importlib.util.spec_from_file_location(folder, target, submodule_search_locations=path) | 
 | ||||||
|  |         locations = [] | ||||||
|  |         self.collect_search_locations(path, locations) | ||||||
|  | 
 | ||||||
|  |         spec   = importlib.util.spec_from_file_location(folder, target, submodule_search_locations = locations) | ||||||
|         module = importlib.util.module_from_spec(spec) |         module = importlib.util.module_from_spec(spec) | ||||||
|         sys.modules[folder] = module |         sys.modules[folder] = module | ||||||
|         spec.loader.exec_module(module) |         spec.loader.exec_module(module) | ||||||
| 
 | 
 | ||||||
|         return module |         return module | ||||||
| 
 | 
 | ||||||
|  |     def collect_search_locations(self, path, locations): | ||||||
|  |         locations.append(path) | ||||||
|  |         for file in os.listdir(path): | ||||||
|  |             _path = os.path.join(path, file) | ||||||
|  |             if os.path.isdir(_path): | ||||||
|  |                 self.collect_search_locations(_path, locations) | ||||||
| 
 | 
 | ||||||
|     def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []): |     def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []): | ||||||
|         plugin.reference = module.Plugin() |         plugin.reference = module.Plugin() | ||||||
|         keys             = loading_data.keys() |         keys             = loading_data.keys() | ||||||
| 
 | 
 | ||||||
|         if "ui_target" in keys: |         if "ui_target" in keys: | ||||||
|             loading_data["ui_target"].add( plugin.reference.get_ui_element() ) |             loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() ) | ||||||
|             loading_data["ui_target"].show_all() |             loading_data["ui_target"].show_all() | ||||||
| 
 | 
 | ||||||
|         if "pass_ui_objects" in keys: |         if "pass_ui_objects" in keys: | ||||||
| @@ -93,6 +108,7 @@ class Plugins: | |||||||
| 
 | 
 | ||||||
|         if "pass_fm_events" in keys: |         if "pass_fm_events" in keys: | ||||||
|             plugin.reference.set_fm_event_system(event_system) |             plugin.reference.set_fm_event_system(event_system) | ||||||
|  |             plugin.reference.subscribe_to_events() | ||||||
| 
 | 
 | ||||||
|         if "bind_keys" in keys: |         if "bind_keys" in keys: | ||||||
|             self._keybindings.append_bindings( loading_data["bind_keys"] ) |             self._keybindings.append_bindings( loading_data["bind_keys"] ) | ||||||
| @@ -16,100 +16,100 @@ def threaded(fn): | |||||||
|  |  | ||||||
| class WindowController: | class WindowController: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         USER_HOME               = path.expanduser('~') |         USER_HOME: str              = path.expanduser('~') | ||||||
|         CONFIG_PATH             = USER_HOME   + "/.config/solarfm" |         CONFIG_PATH: str            = f"{USER_HOME}/.config/solarfm" | ||||||
|         self._session_file      = CONFIG_PATH + "/session.json" |         self._session_file: srr     = f"{CONFIG_PATH}/session.json" | ||||||
|  |  | ||||||
|         self._event_sleep_time  = 1 |         self._event_sleep_time: int = 1 | ||||||
|         self._active_window_id  = "" |         self._active_window_id: str = "" | ||||||
|         self._active_tab_id     = "" |         self._active_tab_id: str    = "" | ||||||
|         self._windows           = [] |         self._windows: list         = [] | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_wid_and_tid(self, wid, tid): |     def set_wid_and_tid(self, wid: int, tid: int) -> None: | ||||||
|         self._active_window_id = str(wid) |         self._active_window_id = str(wid) | ||||||
|         self._active_tab_id    = str(tid) |         self._active_tab_id    = str(tid) | ||||||
|  |  | ||||||
|     def get_active_wid_and_tid(self): |     def get_active_wid_and_tid(self) -> list: | ||||||
|         return self._active_window_id, self._active_tab_id |         return self._active_window_id, self._active_tab_id | ||||||
|  |  | ||||||
|     def create_window(self): |     def create_window(self) -> Window: | ||||||
|         window = Window() |         window = Window() | ||||||
|         window.set_nickname(f"window_{str(len(self._windows) + 1)}") |         window.set_nickname(f"window_{str(len(self._windows) + 1)}") | ||||||
|         self._windows.append(window) |         self._windows.append(window) | ||||||
|         return window |         return window | ||||||
|  |  | ||||||
|  |  | ||||||
|     def add_tab_for_window(self, win_id): |     def add_tab_for_window(self, win_id: str) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 return window.create_tab() |                 return window.create_tab() | ||||||
|  |  | ||||||
|     def add_tab_for_window_by_name(self, name): |     def add_tab_for_window_by_name(self, name: str) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_name() == name: |             if window.get_name() == name: | ||||||
|                 return window.create_tab() |                 return window.create_tab() | ||||||
|  |  | ||||||
|     def add_tab_for_window_by_nickname(self, nickname): |     def add_tab_for_window_by_nickname(self, nickname: str) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_nickname() == nickname: |             if window.get_nickname() == nickname: | ||||||
|                 return window.create_tab() |                 return window.create_tab() | ||||||
|  |  | ||||||
|     def pop_window(self): |     def pop_window(self) -> None: | ||||||
|         self._windows.pop() |         self._windows.pop() | ||||||
|  |  | ||||||
|     def delete_window_by_id(self, win_id): |     def delete_window_by_id(self, win_id: str) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 self._windows.remove(window) |                 self._windows.remove(window) | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|     def delete_window_by_name(self, name): |     def delete_window_by_name(self, name: str) -> str: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_name() == name: |             if window.get_name() == name: | ||||||
|                 self._windows.remove(window) |                 self._windows.remove(window) | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|     def delete_window_by_nickname(self, nickname): |     def delete_window_by_nickname(self, nickname: str) -> str: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_nickname() == nickname: |             if window.get_nickname() == nickname: | ||||||
|                 self._windows.remove(window) |                 self._windows.remove(window) | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|     def get_window_by_id(self, win_id): |     def get_window_by_id(self, win_id: str) -> Window: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 return window |                 return window | ||||||
|  |  | ||||||
|         raise(f"No Window by ID {win_id} found!") |         raise(f"No Window by ID {win_id} found!") | ||||||
|  |  | ||||||
|     def get_window_by_name(self, name): |     def get_window_by_name(self, name: str) -> Window: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_name() == name: |             if window.get_name() == name: | ||||||
|                 return window |                 return window | ||||||
|  |  | ||||||
|         raise(f"No Window by Name {name} found!") |         raise(f"No Window by Name {name} found!") | ||||||
|  |  | ||||||
|     def get_window_by_nickname(self, nickname): |     def get_window_by_nickname(self, nickname: str) -> Window: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_nickname() == nickname: |             if window.get_nickname() == nickname: | ||||||
|                 return window |                 return window | ||||||
|  |  | ||||||
|         raise(f"No Window by Nickname {nickname} found!") |         raise(f"No Window by Nickname {nickname} found!") | ||||||
|  |  | ||||||
|     def get_window_by_index(self, index): |     def get_window_by_index(self, index: int) -> Window: | ||||||
|         return self._windows[index] |         return self._windows[index] | ||||||
|  |  | ||||||
|     def get_all_windows(self): |     def get_all_windows(self) -> list: | ||||||
|         return self._windows |         return self._windows | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_window_nickname(self, win_id = None, nickname = ""): |     def set_window_nickname(self, win_id: str = None, nickname: str = "") -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 window.set_nickname(nickname) |                 window.set_nickname(nickname) | ||||||
|  |  | ||||||
|     def list_windows(self): |     def list_windows(self) -> None: | ||||||
|         print("\n[  ----  Windows  ----  ]\n") |         print("\n[  ----  Windows  ----  ]\n") | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             print(f"\nID: {window.get_id()}") |             print(f"\nID: {window.get_id()}") | ||||||
| @@ -121,18 +121,18 @@ class WindowController: | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def list_files_from_tabs_of_window(self, win_id): |     def list_files_from_tabs_of_window(self, win_id: str) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 window.list_files_from_tabs() |                 window.list_files_from_tabs() | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|     def get_tabs_count(self, win_id): |     def get_tabs_count(self, win_id: str) -> int: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 return window.get_tabs_count() |                 return window.get_tabs_count() | ||||||
|  |  | ||||||
|     def get_tabs_from_window(self, win_id): |     def get_tabs_from_window(self, win_id: str) -> list: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             if window.get_id() == win_id: |             if window.get_id() == win_id: | ||||||
|                 return window.get_all_tabs() |                 return window.get_all_tabs() | ||||||
| @@ -140,13 +140,13 @@ class WindowController: | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def unload_tabs_and_windows(self): |     def unload_tabs_and_windows(self) -> None: | ||||||
|         for window in self._windows: |         for window in self._windows: | ||||||
|             window.get_all_tabs().clear() |             window.get_all_tabs().clear() | ||||||
|  |  | ||||||
|         self._windows.clear() |         self._windows.clear() | ||||||
|  |  | ||||||
|     def save_state(self, session_file = None): |     def save_state(self, session_file: str = None) -> None: | ||||||
|         if not session_file: |         if not session_file: | ||||||
|             session_file = self._session_file |             session_file = self._session_file | ||||||
|  |  | ||||||
| @@ -158,7 +158,6 @@ class WindowController: | |||||||
|                     tabs.append(tab.get_current_directory()) |                     tabs.append(tab.get_current_directory()) | ||||||
|  |  | ||||||
|                 windows.append( |                 windows.append( | ||||||
|                     [ |  | ||||||
|                     { |                     { | ||||||
|                         'window':{ |                         'window':{ | ||||||
|                             "ID": window.get_id(), |                             "ID": window.get_id(), | ||||||
| @@ -168,7 +167,6 @@ class WindowController: | |||||||
|                             'tabs': tabs |                             'tabs': tabs | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     ] |  | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|             with open(session_file, 'w') as outfile: |             with open(session_file, 'w') as outfile: | ||||||
| @@ -176,7 +174,7 @@ class WindowController: | |||||||
|         else: |         else: | ||||||
|             raise Exception("Window data corrupted! Can not save session!") |             raise Exception("Window data corrupted! Can not save session!") | ||||||
|  |  | ||||||
|     def get_state_from_file(self, session_file = None): |     def get_state_from_file(self, session_file: str = None) -> dict: | ||||||
|         if not session_file: |         if not session_file: | ||||||
|             session_file = self._session_file |             session_file = self._session_file | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,10 +2,17 @@ | |||||||
| import os, subprocess, threading, hashlib | import os, subprocess, threading, hashlib | ||||||
| from os.path import isfile | from os.path import isfile | ||||||
|  |  | ||||||
| # Gtk imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| gi.require_version('GdkPixbuf', '2.0') | gi.require_version('GdkPixbuf', '2.0') | ||||||
| from gi.repository import GdkPixbuf | from gi.repository import GdkPixbuf, GLib | ||||||
|  |  | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     from PIL import Image as PImage | ||||||
|  | except Exception as e: | ||||||
|  |     PImage = None | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .mixins.desktopiconmixin import DesktopIconMixin | from .mixins.desktopiconmixin import DesktopIconMixin | ||||||
| @@ -35,11 +42,14 @@ class Icon(DesktopIconMixin, VideoIconMixin): | |||||||
|                 thumbnl = self.parse_desktop_files(full_path) |                 thumbnl = self.parse_desktop_files(full_path) | ||||||
|  |  | ||||||
|             return thumbnl |             return thumbnl | ||||||
|         except Exception as e: |         except Exception: | ||||||
|  |             ... | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def create_thumbnail(self, dir, file, scrub_percent = "65%"): |     def create_thumbnail(self, dir, file, scrub_percent = "65%"): | ||||||
|         full_path = f"{dir}/{file}" |         full_path = f"{dir}/{file}" | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest() |             file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest() | ||||||
|             hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" |             hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" | ||||||
| @@ -54,6 +64,7 @@ class Icon(DesktopIconMixin, VideoIconMixin): | |||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print("Thumbnail generation issue:") |             print("Thumbnail generation issue:") | ||||||
|             print( repr(e) ) |             print( repr(e) ) | ||||||
|  |  | ||||||
|         return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") |         return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -61,24 +72,41 @@ class Icon(DesktopIconMixin, VideoIconMixin): | |||||||
|         if not wxh: |         if not wxh: | ||||||
|             wxh = self.video_icon_wh |             wxh = self.video_icon_wh | ||||||
|  |  | ||||||
|  |         if path: | ||||||
|             try: |             try: | ||||||
|                 if path.lower().endswith(".gif"): |                 if path.lower().endswith(".gif"): | ||||||
|                     return  GdkPixbuf.PixbufAnimation.new_from_file(path) \ |                     return  GdkPixbuf.PixbufAnimation.new_from_file(path) \ | ||||||
|                                                         .get_static_image() \ |                                                         .get_static_image() \ | ||||||
|                                                         .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) |                                                         .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) | ||||||
|             else: |                 elif path.lower().endswith(".webp") and PImage: | ||||||
|  |                     return self.image2pixbuf(path, wxh) | ||||||
|  |  | ||||||
|                 return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) |                 return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 print("Image Scaling Issue:") |                 print("Image Scaling Issue:") | ||||||
|                 print( repr(e) ) |                 print( repr(e) ) | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |     def image2pixbuf(self, path, wxh): | ||||||
|  |         """Convert Pillow image to GdkPixbuf""" | ||||||
|  |         im   = PImage.open(path) | ||||||
|  |         data = im.tobytes() | ||||||
|  |         data = GLib.Bytes.new(data) | ||||||
|  |         w, h = im.size | ||||||
|  |  | ||||||
|  |         pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, | ||||||
|  |                                                             False, 8, w, h, w * 3) | ||||||
|  |  | ||||||
|  |         return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2 | ||||||
|  |  | ||||||
|     def create_from_file(self, path): |     def create_from_file(self, path): | ||||||
|         try: |         try: | ||||||
|             return GdkPixbuf.Pixbuf.new_from_file(path) |             return GdkPixbuf.Pixbuf.new_from_file(path) | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print("Image from file Issue:") |             print("Image from file Issue:") | ||||||
|             print( repr(e) ) |             print( repr(e) ) | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def return_generic_icon(self): |     def return_generic_icon(self): | ||||||
|   | |||||||
| @@ -3,6 +3,11 @@ import os, subprocess, hashlib | |||||||
| from os.path import isfile | from os.path import isfile | ||||||
|  |  | ||||||
| # Gtk imports | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  |  | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .xdg.DesktopEntry import DesktopEntry | from .xdg.DesktopEntry import DesktopEntry | ||||||
| @@ -36,8 +41,13 @@ class DesktopIconMixin: | |||||||
|             elif os.path.exists(icon): |             elif os.path.exists(icon): | ||||||
|                 return self.create_scaled_image(icon, self.sys_icon_wh) |                 return self.create_scaled_image(icon, self.sys_icon_wh) | ||||||
|             else: |             else: | ||||||
|                 alt_icon_path = "" |                 gio_icon = Gio.Icon.new_for_string(icon) | ||||||
|  |                 gicon    = Gtk.Image.new_from_gicon(gio_icon, 32) | ||||||
|  |                 pixbuf   = gicon.get_pixbuf() | ||||||
|  |                 if pixbuf: | ||||||
|  |                     return pixbuf | ||||||
|  |  | ||||||
|  |                 alt_icon_path = "" | ||||||
|                 for dir in self.ICON_DIRS: |                 for dir in self.ICON_DIRS: | ||||||
|                     alt_icon_path = self.traverse_icons_folder(dir, icon) |                     alt_icon_path = self.traverse_icons_folder(dir, icon) | ||||||
|                     if alt_icon_path != "": |                     if alt_icon_path != "": | ||||||
| @@ -50,13 +60,8 @@ class DesktopIconMixin: | |||||||
|             return None |             return None | ||||||
|  |  | ||||||
|     def traverse_icons_folder(self, path, icon): |     def traverse_icons_folder(self, path, icon): | ||||||
|         alt_icon_path = "" |  | ||||||
|  |  | ||||||
|         for (dirpath, dirnames, filenames) in os.walk(path): |         for (dirpath, dirnames, filenames) in os.walk(path): | ||||||
|             for file in filenames: |             for file in filenames: | ||||||
|                 appNM = "application-x-" + icon |                 appNM = "application-x-" + icon | ||||||
|                 if icon in file or appNM in file: |                 if icon in file or appNM in file: | ||||||
|                     alt_icon_path = dirpath + "/" + file |                     return f"{dirpath}/{file}" | ||||||
|                     break |  | ||||||
|  |  | ||||||
|         return alt_icon_path |  | ||||||
|   | |||||||
| @@ -322,10 +322,8 @@ def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", | |||||||
|                     icon_cache[tmp] = [time.time(), icon] |                     icon_cache[tmp] = [time.time(), icon] | ||||||
|                     return icon |                     return icon | ||||||
|             except UnicodeDecodeError as e: |             except UnicodeDecodeError as e: | ||||||
|                 if debug: |                 ... | ||||||
|                     raise e |  | ||||||
|                 else: |  | ||||||
|                     pass |  | ||||||
|  |  | ||||||
|     # we haven't found anything? "hicolor" is our fallback |     # we haven't found anything? "hicolor" is our fallback | ||||||
|     if theme != "hicolor": |     if theme != "hicolor": | ||||||
|   | |||||||
| @@ -7,20 +7,20 @@ import os | |||||||
|  |  | ||||||
|  |  | ||||||
| class Path: | class Path: | ||||||
|     def get_home(self): |     def get_home(self) -> str: | ||||||
|         return os.path.expanduser("~") + self.subpath |         return os.path.expanduser("~") + self.subpath | ||||||
|  |  | ||||||
|     def get_path(self): |     def get_path(self) -> str: | ||||||
|         return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" |         return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" | ||||||
|  |  | ||||||
|     def get_path_list(self): |     def get_path_list(self) -> list: | ||||||
|         return self.path |         return self.path | ||||||
|  |  | ||||||
|     def push_to_path(self, dir): |     def push_to_path(self, dir: str): | ||||||
|         self.path.append(dir) |         self.path.append(dir) | ||||||
|         self.load_directory() |         self.load_directory() | ||||||
|  |  | ||||||
|     def pop_from_path(self): |     def pop_from_path(self) -> None: | ||||||
|         try: |         try: | ||||||
|             self.path.pop() |             self.path.pop() | ||||||
|  |  | ||||||
| @@ -32,9 +32,9 @@ class Path: | |||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|     def set_path(self, path): |     def set_path(self, path: str) -> bool: | ||||||
|         if path == self.get_path(): |         if path == self.get_path(): | ||||||
|             return |             return False | ||||||
|  |  | ||||||
|         if os.path.isdir(path): |         if os.path.isdir(path): | ||||||
|             self.path = list( filter(None, path.replace("\\", "/").split('/')) ) |             self.path = list( filter(None, path.replace("\\", "/").split('/')) ) | ||||||
| @@ -43,7 +43,7 @@ class Path: | |||||||
|  |  | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     def set_path_with_sub_path(self, sub_path): |     def set_path_with_sub_path(self, sub_path: str) -> bool: | ||||||
|         path = os.path.join(self.get_home(), sub_path) |         path = os.path.join(self.get_home(), sub_path) | ||||||
|         if path == self.get_path(): |         if path == self.get_path(): | ||||||
|             return False |             return False | ||||||
| @@ -55,7 +55,7 @@ class Path: | |||||||
|  |  | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     def set_to_home(self): |     def set_to_home(self) -> None: | ||||||
|         home = os.path.expanduser("~") + self.subpath |         home = os.path.expanduser("~") + self.subpath | ||||||
|         path = list( filter(None, home.replace("\\", "/").split('/')) ) |         path = list( filter(None, home.replace("\\", "/").split('/')) ) | ||||||
|         self.path = path |         self.path = path | ||||||
|   | |||||||
| @@ -21,24 +21,24 @@ from .path import Path | |||||||
| class Tab(Settings, FileHandler, Launcher, Icon, Path): | class Tab(Settings, FileHandler, Launcher, Icon, Path): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.logger             = None |         self.logger             = None | ||||||
|         self._id_length   = 10 |         self._id_length: int    = 10 | ||||||
|  |  | ||||||
|         self._id          = "" |         self._id: str           = "" | ||||||
|         self._wid         = None |         self._wid: str          = None | ||||||
|         self._dir_watcher       = None |         self._dir_watcher       = None | ||||||
|         self._hide_hidden = self.HIDE_HIDDEN_FILES |         self._hide_hidden: bool = self.HIDE_HIDDEN_FILES | ||||||
|         self._files       = [] |         self._files: list       = [] | ||||||
|         self._dirs        = [] |         self._dirs: list        = [] | ||||||
|         self._vids        = [] |         self._vids: list        = [] | ||||||
|         self._images      = [] |         self._images: list      = [] | ||||||
|         self._desktop     = [] |         self._desktop: list     = [] | ||||||
|         self._ungrouped   = [] |         self._ungrouped: list   = [] | ||||||
|         self._hidden      = [] |         self._hidden: list      = [] | ||||||
|  |  | ||||||
|         self._generate_id() |         self._generate_id() | ||||||
|         self.set_to_home() |         self.set_to_home() | ||||||
|  |  | ||||||
|     def load_directory(self): |     def load_directory(self) -> None: | ||||||
|         path            = self.get_path() |         path            = self.get_path() | ||||||
|         self._dirs      = [] |         self._dirs      = [] | ||||||
|         self._vids      = [] |         self._vids      = [] | ||||||
| @@ -97,7 +97,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|             return False |             return False | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_not_hidden_count(self): |     def get_not_hidden_count(self) -> int: | ||||||
|         return len(self._files)    + \ |         return len(self._files)    + \ | ||||||
|                 len(self._dirs)    + \ |                 len(self._dirs)    + \ | ||||||
|                 len(self._vids)    + \ |                 len(self._vids)    + \ | ||||||
| @@ -105,13 +105,13 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|                 len(self._desktop) + \ |                 len(self._desktop) + \ | ||||||
|                 len(self._ungrouped) |                 len(self._ungrouped) | ||||||
|  |  | ||||||
|     def get_hidden_count(self): |     def get_hidden_count(self) -> int: | ||||||
|         return len(self._hidden) |         return len(self._hidden) | ||||||
|  |  | ||||||
|     def get_files_count(self): |     def get_files_count(self) -> int: | ||||||
|         return len(self._files) |         return len(self._files) | ||||||
|  |  | ||||||
|     def get_path_part_from_hash(self, hash): |     def get_path_part_from_hash(self, hash: str) -> str: | ||||||
|         files = self.get_files() |         files = self.get_files() | ||||||
|         file  = None |         file  = None | ||||||
|  |  | ||||||
| @@ -122,7 +122,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|  |  | ||||||
|         return file |         return file | ||||||
|  |  | ||||||
|     def get_files_formatted(self): |     def get_files_formatted(self) -> dict: | ||||||
|         files     = self._hash_set(self._files), |         files     = self._hash_set(self._files), | ||||||
|         dirs      = self._hash_set(self._dirs), |         dirs      = self._hash_set(self._dirs), | ||||||
|         videos    = self.get_videos(), |         videos    = self.get_videos(), | ||||||
| @@ -154,7 +154,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|         return data |         return data | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_gtk_icon_str_combo(self): |     def get_gtk_icon_str_combo(self) -> list: | ||||||
|         data = [] |         data = [] | ||||||
|         dir  = self.get_current_directory() |         dir  = self.get_current_directory() | ||||||
|         for file in self._files: |         for file in self._files: | ||||||
| @@ -163,57 +163,57 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|  |  | ||||||
|         return data |         return data | ||||||
|  |  | ||||||
|     def get_current_directory(self): |     def get_current_directory(self) -> str: | ||||||
|         return self.get_path() |         return self.get_path() | ||||||
|  |  | ||||||
|     def get_current_sub_path(self): |     def get_current_sub_path(self) -> str: | ||||||
|         path = self.get_path() |         path = self.get_path() | ||||||
|         home = f"{self.get_home()}/" |         home = f"{self.get_home()}/" | ||||||
|         return path.replace(home, "") |         return path.replace(home, "") | ||||||
|  |  | ||||||
|     def get_end_of_path(self): |     def get_end_of_path(self) -> str: | ||||||
|         parts = self.get_current_directory().split("/") |         parts = self.get_current_directory().split("/") | ||||||
|         size  = len(parts) |         size  = len(parts) | ||||||
|         return parts[size - 1] |         return parts[size - 1] | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_hiding_hidden(self, state): |     def set_hiding_hidden(self, state: bool) -> None: | ||||||
|         self._hide_hidden = state |         self._hide_hidden = state | ||||||
|  |  | ||||||
|     def is_hiding_hidden(self): |     def is_hiding_hidden(self) -> bool: | ||||||
|         return self._hide_hidden |         return self._hide_hidden | ||||||
|  |  | ||||||
|     def get_dot_dots(self): |     def get_dot_dots(self) -> list: | ||||||
|         return self._hash_set(['.', '..']) |         return self._hash_set(['.', '..']) | ||||||
|  |  | ||||||
|     def get_files(self): |     def get_files(self) -> list: | ||||||
|         return self._hash_set(self._files) |         return self._hash_set(self._files) | ||||||
|  |  | ||||||
|     def get_dirs(self): |     def get_dirs(self) -> list: | ||||||
|         return self._hash_set(self._dirs) |         return self._hash_set(self._dirs) | ||||||
|  |  | ||||||
|     def get_videos(self): |     def get_videos(self) -> list: | ||||||
|         return self._hash_set(self._vids) |         return self._hash_set(self._vids) | ||||||
|  |  | ||||||
|     def get_images(self): |     def get_images(self) -> list: | ||||||
|         return self._hash_set(self._images) |         return self._hash_set(self._images) | ||||||
|  |  | ||||||
|     def get_desktops(self): |     def get_desktops(self) -> list: | ||||||
|         return self._hash_set(self._desktop) |         return self._hash_set(self._desktop) | ||||||
|  |  | ||||||
|     def get_ungrouped(self): |     def get_ungrouped(self) -> list: | ||||||
|         return self._hash_set(self._ungrouped) |         return self._hash_set(self._ungrouped) | ||||||
|  |  | ||||||
|     def get_hidden(self): |     def get_hidden(self) -> list: | ||||||
|         return self._hash_set(self._hidden) |         return self._hash_set(self._hidden) | ||||||
|  |  | ||||||
|     def get_id(self): |     def get_id(self) -> str: | ||||||
|         return self._id |         return self._id | ||||||
|  |  | ||||||
|     def set_wid(self, _wid): |     def set_wid(self, _wid: str) -> None: | ||||||
|         self._wid = _wid |         self._wid = _wid | ||||||
|  |  | ||||||
|     def get_wid(self): |     def get_wid(self) -> str: | ||||||
|         return self._wid |         return self._wid | ||||||
|  |  | ||||||
|     def set_dir_watcher(self, watcher): |     def set_dir_watcher(self, watcher): | ||||||
| @@ -228,19 +228,19 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|     def _natural_keys(self, text): |     def _natural_keys(self, text): | ||||||
|         return [ self._atoi(c) for c in re.split('(\d+)',text) ] |         return [ self._atoi(c) for c in re.split('(\d+)',text) ] | ||||||
|  |  | ||||||
|     def _hash_text(self, text): |     def _hash_text(self, text) -> str: | ||||||
|         return hashlib.sha256(str.encode(text)).hexdigest()[:18] |         return hashlib.sha256(str.encode(text)).hexdigest()[:18] | ||||||
|  |  | ||||||
|     def _hash_set(self, arry): |     def _hash_set(self, arry: list) -> list: | ||||||
|         data = [] |         data = [] | ||||||
|         for arr in arry: |         for arr in arry: | ||||||
|             data.append([arr, self._hash_text(arr)]) |             data.append([arr, self._hash_text(arr)]) | ||||||
|         return data |         return data | ||||||
|  |  | ||||||
|     def _random_with_N_digits(self, n): |     def _random_with_N_digits(self, n: int) -> int: | ||||||
|         range_start = 10**(n-1) |         range_start = 10**(n-1) | ||||||
|         range_end = (10**n)-1 |         range_end = (10**n)-1 | ||||||
|         return randint(range_start, range_end) |         return randint(range_start, range_end) | ||||||
|  |  | ||||||
|     def _generate_id(self): |     def _generate_id(self) -> str: | ||||||
|         self._id = str(self._random_with_N_digits(self._id_length)) |         self._id = str(self._random_with_N_digits(self._id_length)) | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| # System import | # System import | ||||||
| import os, threading, subprocess, shlex | import os, threading, subprocess | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class Settings: | |||||||
|     FFMPG_THUMBNLR    = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary |     FFMPG_THUMBNLR    = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary | ||||||
|     REMUX_FOLDER      = f"{USER_HOME}/.remuxs"             # Remuxed files folder |     REMUX_FOLDER      = f"{USER_HOME}/.remuxs"             # Remuxed files folder | ||||||
|  |  | ||||||
|     ICON_DIRS         = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,] |     ICON_DIRS         = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"] | ||||||
|     BASE_THUMBS_PTH   = f"{USER_HOME}/.thumbnails"         # Used for thumbnail generation |     BASE_THUMBS_PTH   = f"{USER_HOME}/.thumbnails"         # Used for thumbnail generation | ||||||
|     ABS_THUMBS_PTH    = f"{BASE_THUMBS_PTH}/normal"        # Used for thumbnail generation |     ABS_THUMBS_PTH    = f"{BASE_THUMBS_PTH}/normal"        # Used for thumbnail generation | ||||||
|     STEAM_ICONS_PTH   = f"{BASE_THUMBS_PTH}/steam_icons" |     STEAM_ICONS_PTH   = f"{BASE_THUMBS_PTH}/steam_icons" | ||||||
|   | |||||||
| @@ -11,62 +11,68 @@ from .tabs.tab import Tab | |||||||
|  |  | ||||||
| class Window: | class Window: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self._id_length = 10 |         self._id_length: int  = 10 | ||||||
|         self._id        = "" |         self._id: str         = "" | ||||||
|         self._name      = "" |         self._name: str       = "" | ||||||
|         self._nickname  = "" |         self._nickname:str    = "" | ||||||
|         self._isHidden  = False |         self._isHidden: bool  = False | ||||||
|         self._tabs      = [] |         self._active_tab: int = 0 | ||||||
|  |         self._tabs: list      = [] | ||||||
|  |  | ||||||
|         self._generate_id() |         self._generate_id() | ||||||
|         self._set_name() |         self._set_name() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_tab(self): |     def create_tab(self) -> Tab: | ||||||
|         tab = Tab() |         tab = Tab() | ||||||
|         self._tabs.append(tab) |         self._tabs.append(tab) | ||||||
|         return tab |         return tab | ||||||
|  |  | ||||||
|     def pop_tab(self): |     def pop_tab(self) -> None: | ||||||
|         self._tabs.pop() |         self._tabs.pop() | ||||||
|  |  | ||||||
|     def delete_tab_by_id(self, tid): |     def delete_tab_by_id(self, tid: str): | ||||||
|         for tab in self._tabs: |         for tab in self._tabs: | ||||||
|             if tab.get_id() == tid: |             if tab.get_id() == tid: | ||||||
|                 self._tabs.remove(tab) |                 self._tabs.remove(tab) | ||||||
|                 break |                 break | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_tab_by_id(self, tid): |     def get_tab_by_id(self, tid: str) -> Tab: | ||||||
|         for tab in self._tabs: |         for tab in self._tabs: | ||||||
|             if tab.get_id() == tid: |             if tab.get_id() == tid: | ||||||
|                 return tab |                 return tab | ||||||
|  |  | ||||||
|     def get_tab_by_index(self, index): |     def get_tab_by_index(self, index) -> Tab: | ||||||
|         return self._tabs[index] |         return self._tabs[index] | ||||||
|  |  | ||||||
|     def get_tabs_count(self): |     def get_tabs_count(self) -> int: | ||||||
|         return len(self._tabs) |         return len(self._tabs) | ||||||
|  |  | ||||||
|     def get_all_tabs(self): |     def get_all_tabs(self) -> list: | ||||||
|         return self._tabs |         return self._tabs | ||||||
|  |  | ||||||
|     def get_id(self): |     def get_id(self) -> str: | ||||||
|         return self._id |         return self._id | ||||||
|  |  | ||||||
|     def get_name(self): |     def get_name(self) -> str: | ||||||
|         return self._name |         return self._name | ||||||
|  |  | ||||||
|     def get_nickname(self): |     def get_nickname(self) -> str: | ||||||
|         return self._nickname |         return self._nickname | ||||||
|  |  | ||||||
|     def is_hidden(self): |     def is_hidden(self) -> bool: | ||||||
|         return self._isHidden |         return self._isHidden | ||||||
|  |  | ||||||
|     def list_files_from_tabs(self): |     def list_files_from_tabs(self) -> None: | ||||||
|         for tab in self._tabs: |         for tab in self._tabs: | ||||||
|             print(tab.get_files()) |             print(tab.get_files()) | ||||||
|  |  | ||||||
|  |     def set_active_tab(self, index: int): | ||||||
|  |         self._active_tab = index | ||||||
|  |  | ||||||
|  |     def get_active_tab(self) -> Tab: | ||||||
|  |         return self._tabs[self._active_tab] | ||||||
|  |  | ||||||
|     def set_nickname(self, nickname): |     def set_nickname(self, nickname): | ||||||
|         self._nickname = f"{nickname}" |         self._nickname = f"{nickname}" | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| """ |  | ||||||
| Trasher module |  | ||||||
| """ |  | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EndpointRegistry(): | ||||||
|  |     def __init__(self): | ||||||
|  |         self._endpoints = {} | ||||||
|  |  | ||||||
|  |     def register(self, rule, **options): | ||||||
|  |         def decorator(f): | ||||||
|  |             self._endpoints[rule] = f | ||||||
|  |             return f | ||||||
|  |  | ||||||
|  |         return decorator | ||||||
|  |  | ||||||
|  |     def get_endpoints(self): | ||||||
|  |         return self._endpoints | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
|  | from collections import defaultdict | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| @@ -7,57 +8,23 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventSystemPushException(Exception): |  | ||||||
|     ... |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventSystem: | class EventSystem: | ||||||
|     """ Inheret IPCServerMixin. Create an pub/sub systems. """ |     """ Create event system. """ | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         # NOTE: The format used is list of ['who', target, (data,)] Where: |         self.subscribers = defaultdict(list) | ||||||
|         #             who is the sender or target ID and is used for context and control flow, |  | ||||||
|         #             method_target is the method to call, |  | ||||||
|         #             data is the method parameters OR message data to give |  | ||||||
|         #       Where data may be any kind of data |  | ||||||
|         self._gui_events    = [] |  | ||||||
|         self._module_events = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Makeshift "events" system FIFO |     def subscribe(self, event_type, fn): | ||||||
|     def _pop_gui_event(self) -> None: |         self.subscribers[event_type].append(fn) | ||||||
|         if len(self._gui_events) > 0: |  | ||||||
|             return self._gui_events.pop(0) |  | ||||||
|         return None |  | ||||||
|  |  | ||||||
|     def _pop_module_event(self) -> None: |     def emit(self, event_type, data = None): | ||||||
|         if len(self._module_events) > 0: |         if event_type in self.subscribers: | ||||||
|             return self._module_events.pop(0) |             for fn in self.subscribers[event_type]: | ||||||
|         return None |                 if data: | ||||||
|  |                     if hasattr(data, '__iter__') and not type(data) is str: | ||||||
|  |                         fn(*data) | ||||||
|     def push_gui_event(self, event: list) -> None: |                     else: | ||||||
|         if len(event) == 3: |                         fn(data) | ||||||
|             self._gui_events.append(event) |                 else: | ||||||
|             return None |                     fn() | ||||||
|  |  | ||||||
|         raise EventSystemPushException("Invald event format! Please do:  ['sender_id': str, method_target: method, (data,): any]") |  | ||||||
|  |  | ||||||
|     def push_module_event(self, event: list) -> None: |  | ||||||
|         if len(event) == 3: |  | ||||||
|             self._module_events.append(event) |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|         raise EventSystemPushException("Invald event format! Please do:  ['target_id': str, method_target: method, (data,): any]") |  | ||||||
|  |  | ||||||
|     def read_gui_event(self) -> list: |  | ||||||
|         return self._gui_events[0] if self._gui_events else None |  | ||||||
|  |  | ||||||
|     def read_module_event(self) -> list: |  | ||||||
|         return self._module_events[0] if self._module_events else None |  | ||||||
|  |  | ||||||
|     def consume_gui_event(self) -> list: |  | ||||||
|         return self._pop_gui_event() |  | ||||||
|  |  | ||||||
|     def consume_module_event(self) -> list: |  | ||||||
|         return self._pop_module_event() |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, threading, time | import os, threading, time | ||||||
| from multiprocessing.connection import Listener, Client | from multiprocessing.connection import Client | ||||||
|  | from multiprocessing.connection import Listener | ||||||
|  |  | ||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| @@ -29,12 +30,16 @@ class IPCServer: | |||||||
|         elif conn_type == "local_network_unsecured": |         elif conn_type == "local_network_unsecured": | ||||||
|             self._ipc_authkey = None |             self._ipc_authkey = None | ||||||
|  |  | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         event_system.subscribe("post_file_to_ipc", self.send_ipc_message) | ||||||
|  |  | ||||||
|     @daemon_threaded |  | ||||||
|     def create_ipc_listener(self) -> None: |     def create_ipc_listener(self) -> None: | ||||||
|         if self._conn_type == "socket": |         if self._conn_type == "socket": | ||||||
|             if os.path.exists(self._ipc_address): |             if os.path.exists(self._ipc_address) and settings.is_dirty_start(): | ||||||
|                 return |                 os.unlink(self._ipc_address) | ||||||
|  |  | ||||||
|             listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) |             listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) | ||||||
|         elif "unsecured" not in self._conn_type: |         elif "unsecured" not in self._conn_type: | ||||||
| @@ -44,23 +49,27 @@ class IPCServer: | |||||||
|  |  | ||||||
|  |  | ||||||
|         self.is_ipc_alive = True |         self.is_ipc_alive = True | ||||||
|  |         self._run_ipc_loop(listener) | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _run_ipc_loop(self, listener) -> None: | ||||||
|         while True: |         while True: | ||||||
|             conn       = listener.accept() |             conn       = listener.accept() | ||||||
|             start_time = time.perf_counter() |             start_time = time.perf_counter() | ||||||
|             self.handle_message(conn, start_time) |             self._handle_ipc_message(conn, start_time) | ||||||
|  |  | ||||||
|         listener.close() |         listener.close() | ||||||
|  |  | ||||||
|     def handle_message(self, conn, start_time) -> None: |     def _handle_ipc_message(self, conn, start_time) -> None: | ||||||
|         while True: |         while True: | ||||||
|             msg = conn.recv() |             msg = conn.recv() | ||||||
|             if debug: |             if settings.is_debug(): | ||||||
|                 print(msg) |                 print(msg) | ||||||
|  |  | ||||||
|             if "FILE|" in msg: |             if "FILE|" in msg: | ||||||
|                 file = msg.split("FILE|")[1].strip() |                 file = msg.split("FILE|")[1].strip() | ||||||
|                 if file: |                 if file: | ||||||
|                     event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) |                     event_system.emit("handle_file_from_ipc", file) | ||||||
|  |  | ||||||
|                 conn.close() |                 conn.close() | ||||||
|                 break |                 break | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, logging | import os | ||||||
|  | import logging | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,16 +1,15 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, json | import os | ||||||
|  | import json | ||||||
| from os import path | from os import path | ||||||
|  |  | ||||||
| # Gtk imports | # Gtk imports | ||||||
| import gi, cairo | import gi, cairo | ||||||
| gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | ||||||
| gi.require_version('Gdk', '3.0') | gi.require_version('Gdk', '3.0') | ||||||
|  |  | ||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
| from gi.repository import Gdk | from gi.repository import Gdk | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from .logger import Logger | from .logger import Logger | ||||||
| from .keybindings import Keybindings | from .keybindings import Keybindings | ||||||
| @@ -31,6 +30,8 @@ class Settings: | |||||||
|         self._KEY_BINDINGS  = f"{self._CONFIG_PATH}/key-bindings.json" |         self._KEY_BINDINGS  = f"{self._CONFIG_PATH}/key-bindings.json" | ||||||
|         self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" |         self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" | ||||||
|         self._WINDOW_ICON   = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" |         self._WINDOW_ICON   = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" | ||||||
|  |         self._CONTEXT_MENU  = f"{self._CONFIG_PATH}/contexct_menu.json" | ||||||
|  |         self._PID_FILE      = f"{self._CONFIG_PATH}/{app_name.lower()}.pid" | ||||||
|         self._ICON_THEME    = Gtk.IconTheme.get_default() |         self._ICON_THEME    = Gtk.IconTheme.get_default() | ||||||
|  |  | ||||||
|         if not os.path.exists(self._CONFIG_PATH): |         if not os.path.exists(self._CONFIG_PATH): | ||||||
| @@ -40,6 +41,8 @@ class Settings: | |||||||
|  |  | ||||||
|         if not os.path.exists(self._GLADE_FILE): |         if not os.path.exists(self._GLADE_FILE): | ||||||
|             self._GLADE_FILE    = f"{self._USR_SOLARFM}/Main_Window.glade" |             self._GLADE_FILE    = f"{self._USR_SOLARFM}/Main_Window.glade" | ||||||
|  |         if not os.path.exists(self._CONTEXT_MENU): | ||||||
|  |             self._CONTEXT_MENU    = f"{self._USR_SOLARFM}/contexct_menu.json" | ||||||
|         if not os.path.exists(self._KEY_BINDINGS): |         if not os.path.exists(self._KEY_BINDINGS): | ||||||
|             self._KEY_BINDINGS  = f"{self._USR_SOLARFM}/key-bindings.json" |             self._KEY_BINDINGS  = f"{self._USR_SOLARFM}/key-bindings.json" | ||||||
|         if not os.path.exists(self._CSS_FILE): |         if not os.path.exists(self._CSS_FILE): | ||||||
| @@ -58,11 +61,54 @@ class Settings: | |||||||
|             keybindings = json.load(file)["keybindings"] |             keybindings = json.load(file)["keybindings"] | ||||||
|             self._keybindings.configure(keybindings) |             self._keybindings.configure(keybindings) | ||||||
|  |  | ||||||
|  |         with open(self._CONTEXT_MENU) as file: | ||||||
|  |             self._context_menu_data = json.load(file) | ||||||
|  |  | ||||||
|         self._main_window    = None |         self._main_window    = None | ||||||
|         self._logger         = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger() |         self._logger         = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger() | ||||||
|         self._builder        = Gtk.Builder() |         self._builder        = Gtk.Builder() | ||||||
|         self._builder.add_from_file(self._GLADE_FILE) |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
|  |         self._trace_debug   = False | ||||||
|  |         self._debug         = False | ||||||
|  |         self._dirty_start   = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def do_dirty_start_check(self): | ||||||
|  |         if not os.path.exists(self._PID_FILE): | ||||||
|  |             self._write_new_pid() | ||||||
|  |         else: | ||||||
|  |             with open(self._PID_FILE, "r") as _pid: | ||||||
|  |                 pid = _pid.readline().strip() | ||||||
|  |                 if pid not in ("", None): | ||||||
|  |                     self._check_alive_status(int(pid)) | ||||||
|  |                 else: | ||||||
|  |                     self._write_new_pid() | ||||||
|  |  | ||||||
|  |     """ Check For the existence of a unix pid. """ | ||||||
|  |     def _check_alive_status(self, pid): | ||||||
|  |         print(f"PID Found: {pid}") | ||||||
|  |         try: | ||||||
|  |             os.kill(pid, 0) | ||||||
|  |         except OSError: | ||||||
|  |             print(f"{app_name} is starting dirty...") | ||||||
|  |             self._dirty_start = True | ||||||
|  |             self._write_new_pid() | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.") | ||||||
|  |  | ||||||
|  |     def _write_new_pid(self): | ||||||
|  |         pid = os.getpid() | ||||||
|  |         self._write_pid(pid) | ||||||
|  |  | ||||||
|  |     def _clean_pid(self): | ||||||
|  |         os.unlink(self._PID_FILE) | ||||||
|  |  | ||||||
|  |     def _write_pid(self, pid): | ||||||
|  |         with open(self._PID_FILE, "w") as _pid: | ||||||
|  |             _pid.write(f"{pid}") | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_window(self) -> None: |     def create_window(self) -> None: | ||||||
|         # Get window and connect signals |         # Get window and connect signals | ||||||
| @@ -101,6 +147,8 @@ class Settings: | |||||||
|  |  | ||||||
|         return monitors |         return monitors | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_context_menu_data(self) -> Gtk.Builder:  return self._context_menu_data | ||||||
|     def get_main_window(self)   -> Gtk.ApplicationWindow: return self._main_window |     def get_main_window(self)   -> Gtk.ApplicationWindow: return self._main_window | ||||||
|     def get_builder(self)       -> Gtk.Builder:  return self._builder |     def get_builder(self)       -> Gtk.Builder:  return self._builder | ||||||
|     def get_logger(self)        -> Logger:       return self._logger |     def get_logger(self)        -> Logger:       return self._logger | ||||||
| @@ -111,3 +159,15 @@ class Settings: | |||||||
|     def get_success_color(self) -> str: return self._success_color |     def get_success_color(self) -> str: return self._success_color | ||||||
|     def get_warning_color(self) -> str: return self._warning_color |     def get_warning_color(self) -> str: return self._warning_color | ||||||
|     def get_error_color(self)   -> str: return self._error_color |     def get_error_color(self)   -> str: return self._error_color | ||||||
|  |  | ||||||
|  |     def is_trace_debug(self)    -> str: return self._trace_debug | ||||||
|  |     def is_debug(self)          -> str: return self._debug | ||||||
|  |     def is_dirty_start(self)    -> bool: return self._dirty_start | ||||||
|  |     def clear_pid(self): self._clean_pid() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def set_trace_debug(self, trace_debug): | ||||||
|  |         self._trace_debug = trace_debug | ||||||
|  |  | ||||||
|  |     def set_debug(self, debug): | ||||||
|  |         self._debug = debug | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Widgets module | ||||||
|  | """ | ||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ContextMenuWidget(Gtk.Menu): | ||||||
|  |     """docstring for ContextMenuWidget""" | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         super(ContextMenuWidget, self).__init__() | ||||||
|  |         self._builder           = settings.get_builder() | ||||||
|  |         self._context_menu_data = settings.get_context_menu_data() | ||||||
|  |         self._window            = settings.get_main_window() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def make_submenu(self, name, data, keys): | ||||||
|  |         menu      = Gtk.Menu() | ||||||
|  |         menu_item = Gtk.MenuItem(name) | ||||||
|  |  | ||||||
|  |         for key in keys: | ||||||
|  |             if isinstance(data, dict): | ||||||
|  |                 entry = self.make_menu_item(key, data[key]) | ||||||
|  |             elif isinstance(data, list): | ||||||
|  |                 entry = self.make_menu_item(key, data) | ||||||
|  |             else: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |             menu.append(entry) | ||||||
|  |  | ||||||
|  |         menu_item.set_submenu(menu) | ||||||
|  |         return menu_item | ||||||
|  |  | ||||||
|  |     def make_menu_item(self, name, data) -> Gtk.MenuItem: | ||||||
|  |         if isinstance(data, dict): | ||||||
|  |             return self.make_submenu(name, data, data.keys()) | ||||||
|  |         elif isinstance(data, list): | ||||||
|  |             entry = Gtk.ImageMenuItem(name) | ||||||
|  |             icon  = getattr(Gtk, f"{data[0]}") | ||||||
|  |             entry.set_image( Gtk.Image(stock=icon) ) | ||||||
|  |             entry.set_always_show_image(True) | ||||||
|  |             entry.connect("activate", self._emit, (data[1])) | ||||||
|  |             return entry | ||||||
|  |  | ||||||
|  |     def build_context_menu(self) -> None: | ||||||
|  |         data          = self._context_menu_data | ||||||
|  |         dkeys         = data.keys() | ||||||
|  |         plugins_entry = None | ||||||
|  |  | ||||||
|  |         for dkey in dkeys: | ||||||
|  |             entry = self.make_menu_item(dkey, data[dkey]) | ||||||
|  |             self.append(entry) | ||||||
|  |             if dkey == "Plugins": | ||||||
|  |                 plugins_entry = entry | ||||||
|  |  | ||||||
|  |         self.attach_to_widget(self._window, None) | ||||||
|  |         self.show_all() | ||||||
|  |         self._builder.expose_object("context_menu", self) | ||||||
|  |         if plugins_entry: | ||||||
|  |             self._builder.expose_object("context_menu_plugins", plugins_entry.get_submenu()) | ||||||
|  |  | ||||||
|  |     def _emit(self, menu_item, type): | ||||||
|  |         event_system.emit("do_action_from_menu_controls", type) | ||||||
| @@ -0,0 +1,74 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import GdkPixbuf | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IconGridWidget(Gtk.IconView): | ||||||
|  |     """docstring for IconGridWidget""" | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         super(IconGridWidget, self).__init__() | ||||||
|  |  | ||||||
|  |         self._store = None | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._set_up_dnd() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |     def get_store(self): | ||||||
|  |         return self._store | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_pixbuf_column(0) | ||||||
|  |         self.set_text_column(1) | ||||||
|  |  | ||||||
|  |         self.set_item_orientation(1) | ||||||
|  |         self.set_selection_mode(3) | ||||||
|  |         self.set_item_width(96) | ||||||
|  |         self.set_item_padding(8) | ||||||
|  |         self.set_margin(12) | ||||||
|  |         self.set_row_spacing(18) | ||||||
|  |         self.set_columns(-1) | ||||||
|  |         self.set_spacing(12) | ||||||
|  |         self.set_column_spacing(18) | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _setup_additional_signals(self, grid_icon_single_click, | ||||||
|  |                                         grid_icon_double_click, | ||||||
|  |                                         grid_set_selected_items, | ||||||
|  |                                         grid_on_drag_set, | ||||||
|  |                                         grid_on_drag_data_received, | ||||||
|  |                                         grid_on_drag_motion): | ||||||
|  |  | ||||||
|  |         self.connect("button_release_event", grid_icon_single_click) | ||||||
|  |         self.connect("item-activated",       grid_icon_double_click) | ||||||
|  |         self.connect("selection-changed",    grid_set_selected_items) | ||||||
|  |         self.connect("drag-data-get",        grid_on_drag_set) | ||||||
|  |         self.connect("drag-data-received",   grid_on_drag_data_received) | ||||||
|  |         self.connect("drag-motion",          grid_on_drag_motion) | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         self._store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) | ||||||
|  |         self.set_model(self._store) | ||||||
|  |  | ||||||
|  |     def _set_up_dnd(self): | ||||||
|  |         URI_TARGET_TYPE  = 80 | ||||||
|  |         uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) | ||||||
|  |         targets          = [ uri_target ] | ||||||
|  |         action           = Gdk.DragAction.COPY | ||||||
|  |         self.enable_model_drag_dest(targets, action) | ||||||
|  |         self.enable_model_drag_source(0, targets, action) | ||||||
| @@ -0,0 +1,81 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import GdkPixbuf | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IconTreeWidget(Gtk.TreeView): | ||||||
|  |     """docstring for IconTreeWidget""" | ||||||
|  |  | ||||||
|  |     def __init__(self): | ||||||
|  |         super(IconTreeWidget, self).__init__() | ||||||
|  |  | ||||||
|  |         self._store = None | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._set_up_dnd() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |     def get_store(self): | ||||||
|  |         return self._store | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_search_column(1) | ||||||
|  |         self.set_rubber_banding(True) | ||||||
|  |         self.set_headers_visible(False) | ||||||
|  |         self.set_enable_tree_lines(False) | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _setup_additional_signals(self, grid_icon_single_click, | ||||||
|  |                                         grid_icon_double_click, | ||||||
|  |                                         grid_on_drag_set, | ||||||
|  |                                         grid_on_drag_data_received, | ||||||
|  |                                         grid_on_drag_motion): | ||||||
|  |  | ||||||
|  |         self.connect("button_release_event", self.grid_icon_single_click) | ||||||
|  |         self.connect("row-activated",        self.grid_icon_double_click) | ||||||
|  |         self.connect("drag-data-get",        self.grid_on_drag_set) | ||||||
|  |         self.connect("drag-data-received",   self.grid_on_drag_data_received) | ||||||
|  |         self.connect("drag-motion",          self.grid_on_drag_motion) | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) | ||||||
|  |         column = Gtk.TreeViewColumn("Icons") | ||||||
|  |         icon   = Gtk.CellRendererPixbuf() | ||||||
|  |         name   = Gtk.CellRendererText() | ||||||
|  |         selec  = self.get_selection() | ||||||
|  |  | ||||||
|  |         self.set_model(store) | ||||||
|  |         selec.set_mode(3) | ||||||
|  |  | ||||||
|  |         column.pack_start(icon, False) | ||||||
|  |         column.pack_start(name, True) | ||||||
|  |         column.add_attribute(icon, "pixbuf", 0) | ||||||
|  |         column.add_attribute(name, "text", 1) | ||||||
|  |         column.set_expand(False) | ||||||
|  |         column.set_sizing(2) | ||||||
|  |         column.set_min_width(120) | ||||||
|  |         column.set_max_width(74) | ||||||
|  |  | ||||||
|  |         self.append_column(column) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _set_up_dnd(self): | ||||||
|  |         URI_TARGET_TYPE  = 80 | ||||||
|  |         uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) | ||||||
|  |         targets          = [ uri_target ] | ||||||
|  |         action           = Gdk.DragAction.COPY | ||||||
|  |         self.enable_model_drag_dest(targets, action) | ||||||
|  |         self.enable_model_drag_source(0, targets, action) | ||||||
| @@ -0,0 +1,79 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IOWidget(Gtk.Box): | ||||||
|  |     """docstring for IOWidget""" | ||||||
|  |  | ||||||
|  |     def __init__(self, action, file): | ||||||
|  |         super(IOWidget, self).__init__() | ||||||
|  |         self._action    = action | ||||||
|  |         self._file      = file | ||||||
|  |         self._basename  = self._file.get_basename() | ||||||
|  |  | ||||||
|  |         self.cancle_eve = Gio.Cancellable.new() | ||||||
|  |         self.progress   = None | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_orientation(1) | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         stats         = Gtk.Box() | ||||||
|  |         label         = Gtk.Label() | ||||||
|  |         cncl_button   = Gtk.Button(label="Cancel") | ||||||
|  |         del_button    = Gtk.Button(label="Clear") | ||||||
|  |         self.progress = Gtk.ProgressBar() | ||||||
|  |  | ||||||
|  |         label.set_label(self._basename) | ||||||
|  |         self.progress.set_show_text(True) | ||||||
|  |         self.progress.set_text(f"{self._action.upper()}ING") | ||||||
|  |         stats.set_orientation(0) | ||||||
|  |  | ||||||
|  |         stats.pack_end(del_button, False, False, 5) | ||||||
|  |         del_button.connect("clicked", self.delete_self, ()) | ||||||
|  |  | ||||||
|  |         if not self._action in ("create", "rename"): | ||||||
|  |             stats.pack_end(cncl_button, False, False, 5) | ||||||
|  |             cncl_button.connect("clicked", self.do_cancel, *(self, self.cancle_eve)) | ||||||
|  |  | ||||||
|  |         stats.add(self.progress) | ||||||
|  |         self.add(label) | ||||||
|  |         self.add(stats) | ||||||
|  |  | ||||||
|  |     def do_cancel(self, widget, container, eve): | ||||||
|  |         print(f"Canceling: [{self._action}] of {self._basename} ...") | ||||||
|  |         eve.cancel() | ||||||
|  |  | ||||||
|  |     def update_progress(self, current, total, eve=None): | ||||||
|  |         self.progress.set_fraction(current/total) | ||||||
|  |  | ||||||
|  |     def finish_callback(self, file, task=None, eve=None): | ||||||
|  |         if self._action == "move" or self._action == "rename": | ||||||
|  |             status = self._file.move_finish(task) | ||||||
|  |         if self._action == "copy": | ||||||
|  |             status = self._file.copy_finish(task) | ||||||
|  |  | ||||||
|  |         if status: | ||||||
|  |             self.delete_self() | ||||||
|  |         else: | ||||||
|  |             print(f"{self._action} of {self._basename} failed...") | ||||||
|  |  | ||||||
|  |     def delete_self(self, widget=None, eve=None): | ||||||
|  |         self.get_parent().remove(self) | ||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TabHeaderWidget(Gtk.ButtonBox): | ||||||
|  |     """docstring for TabHeaderWidget""" | ||||||
|  |  | ||||||
|  |     def __init__(self, tab, close_tab): | ||||||
|  |         super(TabHeaderWidget, self).__init__() | ||||||
|  |         self._tab       = tab | ||||||
|  |         self._close_tab = close_tab # NOTE: Close method in tab_mixin | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._setup_signals() | ||||||
|  |         self._load_widgets() | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_orientation(0) | ||||||
|  |  | ||||||
|  |     def _setup_signals(self): | ||||||
|  |         ... | ||||||
|  |  | ||||||
|  |     def _load_widgets(self): | ||||||
|  |         label = Gtk.Label() | ||||||
|  |         tid   = Gtk.Label() | ||||||
|  |         close = Gtk.Button() | ||||||
|  |         icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE) | ||||||
|  |  | ||||||
|  |         label.set_label(f"{self._tab.get_end_of_path()}") | ||||||
|  |         label.set_width_chars(len(self._tab.get_end_of_path())) | ||||||
|  |         label.set_xalign(0.0) | ||||||
|  |         tid.set_label(f"{self._tab.get_id()}") | ||||||
|  |  | ||||||
|  |         close.connect("released", self._close_tab) | ||||||
|  |  | ||||||
|  |         close.add(icon) | ||||||
|  |         self.add(label) | ||||||
|  |         self.add(close) | ||||||
|  |         self.add(tid) | ||||||
|  |  | ||||||
|  |         self.show_all() | ||||||
|  |         tid.hide() | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <!-- Generated with glade 3.38.2 --> | <!-- Generated with glade 3.40.0 --> | ||||||
| <interface> | <interface> | ||||||
|   <requires lib="gtk+" version="3.22"/> |   <requires lib="gtk+" version="3.22"/> | ||||||
|   <object class="GtkAboutDialog" id="about_page"> |   <object class="GtkAboutDialog" id="about_page"> | ||||||
| @@ -452,180 +452,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|       <action-widget response="-5">appchooser_select_btn</action-widget> |       <action-widget response="-5">appchooser_select_btn</action-widget> | ||||||
|     </action-widgets> |     </action-widgets> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkTextBuffer" id="arc_command_buffer"> |  | ||||||
|     <property name="text" translatable="yes">$(which 7za || echo 7zr) a %o %N</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkFileChooserDialog" id="archiver_dialogue"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="modal">True</property> |  | ||||||
|     <property name="window-position">center</property> |  | ||||||
|     <property name="type-hint">dialog</property> |  | ||||||
|     <property name="gravity">center</property> |  | ||||||
|     <property name="do-overwrite-confirmation">True</property> |  | ||||||
|     <property name="select-multiple">True</property> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="orientation">vertical</property> |  | ||||||
|         <property name="spacing">2</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox"> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="button21"> |  | ||||||
|                 <property name="label">gtk-cancel</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkButton" id="button22"> |  | ||||||
|                 <property name="label">gtk-ok</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="receives-default">True</property> |  | ||||||
|                 <property name="use-stock">True</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkBox"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">False</property> |  | ||||||
|                 <property name="homogeneous">True</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Compress Commands:</property> |  | ||||||
|                     <property name="xalign">0.20000000298023224</property> |  | ||||||
|                     <attributes> |  | ||||||
|                       <attribute name="gravity" value="west"/> |  | ||||||
|                     </attributes> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">0</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <placeholder/> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Archive Format:</property> |  | ||||||
|                     <property name="xalign">1</property> |  | ||||||
|                     <attributes> |  | ||||||
|                       <attribute name="gravity" value="east"/> |  | ||||||
|                     </attributes> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">2</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkComboBoxText"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="active">0</property> |  | ||||||
|                     <property name="active-id">0</property> |  | ||||||
|                     <items> |  | ||||||
|                       <item id="0" translatable="yes">7Zip (*.7z)</item> |  | ||||||
|                       <item id="1" translatable="yes">Zip (*.zip *.ZIP)</item> |  | ||||||
|                       <item id="2" translatable="yes">RAR (*.rar *.RAR)</item> |  | ||||||
|                       <item id="3" translatable="yes">Tar (*.tar)</item> |  | ||||||
|                       <item id="4" translatable="yes">Tar bzip2 (*.tar.bz2)</item> |  | ||||||
|                       <item id="5" translatable="yes">Tar Gzip (*.tar.gz *.tgz)</item> |  | ||||||
|                       <item id="6" translatable="yes">Tar xz (*.tar.xz *.txz)</item> |  | ||||||
|                       <item id="7" translatable="yes">Gzip (*.gz)</item> |  | ||||||
|                       <item id="8" translatable="yes">XZ (*.xz)</item> |  | ||||||
|                     </items> |  | ||||||
|                     <signal name="changed" handler="set_arc_buffer_text" swapped="no"/> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="expand">False</property> |  | ||||||
|                     <property name="fill">True</property> |  | ||||||
|                     <property name="position">3</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">0</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkTextView" id="arc_command"> |  | ||||||
|                 <property name="height-request">72</property> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="buffer">arc_command_buffer</property> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">True</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">1</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">2</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <action-widgets> |  | ||||||
|       <action-widget response="-6">button21</action-widget> |  | ||||||
|       <action-widget response="-5">button22</action-widget> |  | ||||||
|     </action-widgets> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkImage" id="archive_img"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="stock">gtk-save-as</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkImage" id="create_img"> |   <object class="GtkImage" id="create_img"> | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="stock">gtk-new</property> |     <property name="stock">gtk-new</property> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkImage" id="exec_in_term_img"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="stock">gtk-execute</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkImage" id="image1"> |   <object class="GtkImage" id="image1"> | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
| @@ -651,23 +482,18 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="stock">gtk-execute</property> |     <property name="stock">gtk-execute</property> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkTextBuffer" id="message_buffer"/> |   <object class="GtkImage" id="io_img"> | ||||||
|   <object class="GtkImage" id="open_with_img"> |  | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="stock">gtk-open</property> |     <property name="stock">gtk-stop</property> | ||||||
|   </object> |   </object> | ||||||
|  |   <object class="GtkTextBuffer" id="message_buffer"/> | ||||||
|   <object class="GtkImage" id="rename_img"> |   <object class="GtkImage" id="rename_img"> | ||||||
|     <property name="visible">True</property> |     <property name="visible">True</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="stock">gtk-edit</property> |     <property name="stock">gtk-edit</property> | ||||||
|     <property name="icon_size">3</property> |     <property name="icon_size">3</property> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkImage" id="rename_img2"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="stock">gtk-edit</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkFileChooserDialog" id="save_load_dialog"> |   <object class="GtkFileChooserDialog" id="save_load_dialog"> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="type-hint">dialog</property> |     <property name="type-hint">dialog</property> | ||||||
| @@ -961,17 +787,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                             <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |                             <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> | ||||||
|                           </object> |                           </object> | ||||||
|                         </child> |                         </child> | ||||||
|                         <child> |  | ||||||
|                           <object class="GtkImageMenuItem"> |  | ||||||
|                             <property name="label">gtk-delete</property> |  | ||||||
|                             <property name="name">delete</property> |  | ||||||
|                             <property name="visible">True</property> |  | ||||||
|                             <property name="can-focus">False</property> |  | ||||||
|                             <property name="use-underline">True</property> |  | ||||||
|                             <property name="use-stock">True</property> |  | ||||||
|                             <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                           </object> |  | ||||||
|                         </child> |  | ||||||
|                       </object> |                       </object> | ||||||
|                     </child> |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
| @@ -1014,7 +829,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                 <property name="spacing">5</property> |                 <property name="spacing">5</property> | ||||||
|                 <property name="layout-style">start</property> |                 <property name="layout-style">start</property> | ||||||
|                 <child> |                 <child> | ||||||
|                   <object class="GtkButton" id="plugins_buttoin"> |                   <object class="GtkButton" id="plugins_button"> | ||||||
|                     <property name="label" translatable="yes">Plugins</property> |                     <property name="label" translatable="yes">Plugins</property> | ||||||
|                     <property name="visible">True</property> |                     <property name="visible">True</property> | ||||||
|                     <property name="can-focus">True</property> |                     <property name="can-focus">True</property> | ||||||
| @@ -1091,6 +906,22 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <property name="position">4</property> |                     <property name="position">4</property> | ||||||
|                   </packing> |                   </packing> | ||||||
|                 </child> |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkButton" id="io_button"> | ||||||
|  |                     <property name="label" translatable="yes">I/O</property> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="receives-default">True</property> | ||||||
|  |                     <property name="image">io_img</property> | ||||||
|  |                     <property name="always-show-image">True</property> | ||||||
|  |                     <signal name="released" handler="show_io_popup" swapped="no"/> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">True</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">5</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|               </object> |               </object> | ||||||
|               <packing> |               <packing> | ||||||
|                 <property name="expand">False</property> |                 <property name="expand">False</property> | ||||||
| @@ -1243,6 +1074,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <property name="margin-bottom">5</property> |                     <property name="margin-bottom">5</property> | ||||||
|                     <property name="show-border">False</property> |                     <property name="show-border">False</property> | ||||||
|                     <property name="scrollable">True</property> |                     <property name="scrollable">True</property> | ||||||
|  |                     <property name="group-name">sfm_windows</property> | ||||||
|  |                     <signal name="create-window" handler="on_tab_dnded" swapped="no"/> | ||||||
|                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> |                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> | ||||||
|                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> |                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> | ||||||
|                     <child> |                     <child> | ||||||
| @@ -1263,6 +1096,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <child type="tab"> |                     <child type="tab"> | ||||||
|                       <placeholder/> |                       <placeholder/> | ||||||
|                     </child> |                     </child> | ||||||
|  |                     <child type="action-end"> | ||||||
|  |                       <object class="GtkSearchEntry" id="win1_search_field"> | ||||||
|  |                         <property name="name">window_1</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="primary-icon-name">edit-find-symbolic</property> | ||||||
|  |                         <property name="primary-icon-activatable">False</property> | ||||||
|  |                         <property name="primary-icon-sensitive">False</property> | ||||||
|  |                         <property name="placeholder-text" translatable="yes">Search...</property> | ||||||
|  |                         <signal name="changed" handler="do_file_search" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="tab-fill">False</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="resize">False</property> |                     <property name="resize">False</property> | ||||||
| @@ -1281,6 +1129,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <property name="margin-bottom">5</property> |                     <property name="margin-bottom">5</property> | ||||||
|                     <property name="show-border">False</property> |                     <property name="show-border">False</property> | ||||||
|                     <property name="scrollable">True</property> |                     <property name="scrollable">True</property> | ||||||
|  |                     <property name="group-name">sfm_windows</property> | ||||||
|  |                     <signal name="create-window" handler="on_tab_dnded" swapped="no"/> | ||||||
|                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> |                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> | ||||||
|                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> |                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> | ||||||
|                     <child> |                     <child> | ||||||
| @@ -1301,6 +1151,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <child type="tab"> |                     <child type="tab"> | ||||||
|                       <placeholder/> |                       <placeholder/> | ||||||
|                     </child> |                     </child> | ||||||
|  |                     <child type="action-end"> | ||||||
|  |                       <object class="GtkSearchEntry" id="win2_search_field"> | ||||||
|  |                         <property name="name">window_2</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="primary-icon-name">edit-find-symbolic</property> | ||||||
|  |                         <property name="primary-icon-activatable">False</property> | ||||||
|  |                         <property name="primary-icon-sensitive">False</property> | ||||||
|  |                         <property name="placeholder-text" translatable="yes">Search...</property> | ||||||
|  |                         <signal name="changed" handler="do_file_search" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="tab-fill">False</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="resize">False</property> |                     <property name="resize">False</property> | ||||||
| @@ -1333,6 +1198,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <property name="margin-bottom">5</property> |                     <property name="margin-bottom">5</property> | ||||||
|                     <property name="show-border">False</property> |                     <property name="show-border">False</property> | ||||||
|                     <property name="scrollable">True</property> |                     <property name="scrollable">True</property> | ||||||
|  |                     <property name="group-name">sfm_windows</property> | ||||||
|  |                     <signal name="create-window" handler="on_tab_dnded" swapped="no"/> | ||||||
|                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> |                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> | ||||||
|                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> |                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> | ||||||
|                     <child> |                     <child> | ||||||
| @@ -1353,6 +1220,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <child type="tab"> |                     <child type="tab"> | ||||||
|                       <placeholder/> |                       <placeholder/> | ||||||
|                     </child> |                     </child> | ||||||
|  |                     <child type="action-end"> | ||||||
|  |                       <object class="GtkSearchEntry" id="win3_search_field"> | ||||||
|  |                         <property name="name">window_3</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="primary-icon-name">edit-find-symbolic</property> | ||||||
|  |                         <property name="primary-icon-activatable">False</property> | ||||||
|  |                         <property name="primary-icon-sensitive">False</property> | ||||||
|  |                         <property name="placeholder-text" translatable="yes">Search...</property> | ||||||
|  |                         <signal name="changed" handler="do_file_search" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="tab-fill">False</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="resize">False</property> |                     <property name="resize">False</property> | ||||||
| @@ -1370,6 +1252,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <property name="margin-bottom">5</property> |                     <property name="margin-bottom">5</property> | ||||||
|                     <property name="show-border">False</property> |                     <property name="show-border">False</property> | ||||||
|                     <property name="scrollable">True</property> |                     <property name="scrollable">True</property> | ||||||
|  |                     <property name="group-name">sfm_windows</property> | ||||||
|  |                     <signal name="create-window" handler="on_tab_dnded" swapped="no"/> | ||||||
|                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> |                     <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/> | ||||||
|                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> |                     <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/> | ||||||
|                     <child> |                     <child> | ||||||
| @@ -1390,6 +1274,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|                     <child type="tab"> |                     <child type="tab"> | ||||||
|                       <placeholder/> |                       <placeholder/> | ||||||
|                     </child> |                     </child> | ||||||
|  |                     <child type="action-end"> | ||||||
|  |                       <object class="GtkSearchEntry" id="win4_search_field"> | ||||||
|  |                         <property name="name">window_4</property> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="primary-icon-name">edit-find-symbolic</property> | ||||||
|  |                         <property name="primary-icon-activatable">False</property> | ||||||
|  |                         <property name="primary-icon-sensitive">False</property> | ||||||
|  |                         <property name="placeholder-text" translatable="yes">Search...</property> | ||||||
|  |                         <signal name="changed" handler="do_file_search" swapped="no"/> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="tab-fill">False</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|                   </object> |                   </object> | ||||||
|                   <packing> |                   <packing> | ||||||
|                     <property name="resize">False</property> |                     <property name="resize">False</property> | ||||||
| @@ -1954,6 +1853,23 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|       <class name="alert-border"/> |       <class name="alert-border"/> | ||||||
|     </style> |     </style> | ||||||
|   </object> |   </object> | ||||||
|  |   <object class="GtkPopover" id="io_popup"> | ||||||
|  |     <property name="can-focus">False</property> | ||||||
|  |     <property name="relative-to">io_button</property> | ||||||
|  |     <property name="position">bottom</property> | ||||||
|  |     <child> | ||||||
|  |       <object class="GtkBox" id="io_list"> | ||||||
|  |         <property name="width-request">320</property> | ||||||
|  |         <property name="height-request">320</property> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="orientation">vertical</property> | ||||||
|  |         <child> | ||||||
|  |           <placeholder/> | ||||||
|  |         </child> | ||||||
|  |       </object> | ||||||
|  |     </child> | ||||||
|  |   </object> | ||||||
|   <object class="GtkPopover" id="message_popup_widget"> |   <object class="GtkPopover" id="message_popup_widget"> | ||||||
|     <property name="width-request">320</property> |     <property name="width-request">320</property> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
| @@ -2221,7 +2137,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|   </object> |   </object> | ||||||
|   <object class="GtkPopover" id="plugin_controls"> |   <object class="GtkPopover" id="plugin_controls"> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="relative-to">plugins_buttoin</property> |     <property name="relative-to">plugins_button</property> | ||||||
|     <signal name="button-release-event" handler="hide_plugins_popup" swapped="no"/> |     <signal name="button-release-event" handler="hide_plugins_popup" swapped="no"/> | ||||||
|     <signal name="key-release-event" handler="hide_plugins_popup" swapped="no"/> |     <signal name="key-release-event" handler="hide_plugins_popup" swapped="no"/> | ||||||
|     <child> |     <child> | ||||||
| @@ -2235,503 +2151,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe | |||||||
|       </object> |       </object> | ||||||
|     </child> |     </child> | ||||||
|   </object> |   </object> | ||||||
|   <object class="GtkPopover" id="win1_search"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="margin-start">5</property> |  | ||||||
|     <property name="margin-end">5</property> |  | ||||||
|     <property name="margin-top">5</property> |  | ||||||
|     <property name="margin-bottom">5</property> |  | ||||||
|     <property name="relative-to">window_1</property> |  | ||||||
|     <property name="position">bottom</property> |  | ||||||
|     <property name="constrain-to">none</property> |  | ||||||
|     <signal name="closed" handler="stop_file_searching" swapped="no"/> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkEntry" id="win1_search_field"> |  | ||||||
|         <property name="width-request">52</property> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">True</property> |  | ||||||
|         <signal name="changed" handler="do_file_search" swapped="no"/> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <style> |  | ||||||
|       <class name="search-border"/> |  | ||||||
|     </style> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkPopover" id="win2_search"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="margin-start">5</property> |  | ||||||
|     <property name="margin-end">5</property> |  | ||||||
|     <property name="margin-top">5</property> |  | ||||||
|     <property name="margin-bottom">5</property> |  | ||||||
|     <property name="relative-to">window_2</property> |  | ||||||
|     <property name="position">bottom</property> |  | ||||||
|     <property name="constrain-to">none</property> |  | ||||||
|     <signal name="closed" handler="stop_file_searching" swapped="no"/> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkEntry" id="win2_search_field"> |  | ||||||
|         <property name="width-request">96</property> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">True</property> |  | ||||||
|         <signal name="changed" handler="do_file_search" swapped="no"/> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <style> |  | ||||||
|       <class name="search-border"/> |  | ||||||
|     </style> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkPopover" id="win3_search"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="margin-start">5</property> |  | ||||||
|     <property name="margin-end">5</property> |  | ||||||
|     <property name="margin-top">5</property> |  | ||||||
|     <property name="margin-bottom">5</property> |  | ||||||
|     <property name="relative-to">window_3</property> |  | ||||||
|     <property name="constrain-to">none</property> |  | ||||||
|     <signal name="closed" handler="stop_file_searching" swapped="no"/> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkEntry" id="win3_search_field"> |  | ||||||
|         <property name="width-request">96</property> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">True</property> |  | ||||||
|         <signal name="changed" handler="do_file_search" swapped="no"/> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <style> |  | ||||||
|       <class name="search-border"/> |  | ||||||
|     </style> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkPopover" id="win4_search"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="margin-start">5</property> |  | ||||||
|     <property name="margin-end">5</property> |  | ||||||
|     <property name="margin-top">5</property> |  | ||||||
|     <property name="margin-bottom">5</property> |  | ||||||
|     <property name="relative-to">window_4</property> |  | ||||||
|     <property name="constrain-to">none</property> |  | ||||||
|     <signal name="closed" handler="stop_file_searching" swapped="no"/> |  | ||||||
|     <child> |  | ||||||
|       <object class="GtkEntry" id="win4_search_field"> |  | ||||||
|         <property name="width-request">96</property> |  | ||||||
|         <property name="visible">True</property> |  | ||||||
|         <property name="can-focus">True</property> |  | ||||||
|         <signal name="changed" handler="do_file_search" swapped="no"/> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <style> |  | ||||||
|       <class name="search-border"/> |  | ||||||
|     </style> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkImage" id="trash_img"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="icon-name">user-trash</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkImage" id="trash_img2"> |  | ||||||
|     <property name="visible">True</property> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="icon-name">user-trash</property> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkDialog" id="context_menu_popup"> |  | ||||||
|     <property name="can-focus">False</property> |  | ||||||
|     <property name="resizable">False</property> |  | ||||||
|     <property name="window-position">mouse</property> |  | ||||||
|     <property name="type-hint">splashscreen</property> |  | ||||||
|     <property name="skip-pager-hint">True</property> |  | ||||||
|     <property name="decorated">False</property> |  | ||||||
|     <property name="deletable">False</property> |  | ||||||
|     <property name="gravity">static</property> |  | ||||||
|     <signal name="focus-out-event" handler="hide_context_menu" swapped="no"/> |  | ||||||
|     <child internal-child="vbox"> |  | ||||||
|       <object class="GtkBox"> |  | ||||||
|         <property name="can-focus">False</property> |  | ||||||
|         <property name="orientation">vertical</property> |  | ||||||
|         <property name="spacing">2</property> |  | ||||||
|         <child internal-child="action_area"> |  | ||||||
|           <object class="GtkButtonBox"> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="layout-style">end</property> |  | ||||||
|             <child> |  | ||||||
|               <placeholder/> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <placeholder/> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">False</property> |  | ||||||
|             <property name="position">0</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|         <child> |  | ||||||
|           <object class="GtkBox" id="context_menu"> |  | ||||||
|             <property name="visible">True</property> |  | ||||||
|             <property name="can-focus">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkExpander"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="margin-bottom">5</property> |  | ||||||
|                 <property name="expanded">True</property> |  | ||||||
|                 <property name="label-fill">True</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="orientation">vertical</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-open</property> |  | ||||||
|                         <property name="name">open</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Open...</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Open With</property> |  | ||||||
|                         <property name="name">open_with</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="image">open_with_img</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-execute</property> |  | ||||||
|                         <property name="name">execute</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">2</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Execute in Terminal</property> |  | ||||||
|                         <property name="name">execute_in_terminal</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="image">exec_in_term_img</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">3</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|                 <child type="label"> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="hexpand">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Open</property> |  | ||||||
|                     <property name="justify">center</property> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">4</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkExpander"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="margin-bottom">5</property> |  | ||||||
|                 <property name="expanded">True</property> |  | ||||||
|                 <property name="label-fill">True</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="orientation">vertical</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-new</property> |  | ||||||
|                         <property name="name">create</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">New File/Folder...</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Rename</property> |  | ||||||
|                         <property name="name">rename</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Rename...</property> |  | ||||||
|                         <property name="image">rename_img2</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-cut</property> |  | ||||||
|                         <property name="name">cut</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Cut...</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">2</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-copy</property> |  | ||||||
|                         <property name="name">copy</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Copy...</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">3</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-paste</property> |  | ||||||
|                         <property name="name">paste</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Paste...</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">4</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Archive</property> |  | ||||||
|                         <property name="name">archive</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Archive...</property> |  | ||||||
|                         <property name="image">archive_img</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">5</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|                 <child type="label"> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="hexpand">False</property> |  | ||||||
|                     <property name="label" translatable="yes">File Actions</property> |  | ||||||
|                     <property name="justify">center</property> |  | ||||||
|                     <property name="ellipsize">end</property> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">5</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkExpander"> |  | ||||||
|                 <property name="visible">True</property> |  | ||||||
|                 <property name="can-focus">True</property> |  | ||||||
|                 <property name="margin-top">5</property> |  | ||||||
|                 <property name="margin-bottom">10</property> |  | ||||||
|                 <property name="label-fill">True</property> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkBox"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="orientation">vertical</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton" id="restore_from_trash"> |  | ||||||
|                         <property name="label" translatable="yes">Restore From Trash</property> |  | ||||||
|                         <property name="name">restore_from_trash</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Restore From Trash...</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">0</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton" id="empty_trash"> |  | ||||||
|                         <property name="label" translatable="yes">Empty Trash</property> |  | ||||||
|                         <property name="name">empty_trash</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Empty Trash...</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">1</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Trash</property> |  | ||||||
|                         <property name="name">trash</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Move to Trash...</property> |  | ||||||
|                         <property name="margin-top">20</property> |  | ||||||
|                         <property name="image">trash_img</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">2</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label" translatable="yes">Go To Trash</property> |  | ||||||
|                         <property name="name">go_to_trash</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Go To Trash...</property> |  | ||||||
|                         <property name="image">trash_img2</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">3</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkButton"> |  | ||||||
|                         <property name="label">gtk-delete</property> |  | ||||||
|                         <property name="name">delete</property> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">True</property> |  | ||||||
|                         <property name="receives-default">True</property> |  | ||||||
|                         <property name="tooltip-text" translatable="yes">Delete...</property> |  | ||||||
|                         <property name="margin-top">20</property> |  | ||||||
|                         <property name="use-stock">True</property> |  | ||||||
|                         <property name="always-show-image">True</property> |  | ||||||
|                         <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> |  | ||||||
|                       </object> |  | ||||||
|                       <packing> |  | ||||||
|                         <property name="expand">False</property> |  | ||||||
|                         <property name="fill">True</property> |  | ||||||
|                         <property name="position">4</property> |  | ||||||
|                       </packing> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|                 <child type="label"> |  | ||||||
|                   <object class="GtkLabel"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">False</property> |  | ||||||
|                     <property name="hexpand">False</property> |  | ||||||
|                     <property name="label" translatable="yes">Trash</property> |  | ||||||
|                     <property name="justify">center</property> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|               </object> |  | ||||||
|               <packing> |  | ||||||
|                 <property name="expand">False</property> |  | ||||||
|                 <property name="fill">True</property> |  | ||||||
|                 <property name="position">6</property> |  | ||||||
|               </packing> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|           <packing> |  | ||||||
|             <property name="expand">False</property> |  | ||||||
|             <property name="fill">True</property> |  | ||||||
|             <property name="position">1</property> |  | ||||||
|           </packing> |  | ||||||
|         </child> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|   </object> |  | ||||||
|   <object class="GtkMessageDialog" id="warning_alert"> |   <object class="GtkMessageDialog" id="warning_alert"> | ||||||
|     <property name="can-focus">False</property> |     <property name="can-focus">False</property> | ||||||
|     <property name="resizable">False</property> |     <property name="resizable">False</property> | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								user_config/usr/share/solarfm/contexct_menu.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | { | ||||||
|  |     "Open Actions": { | ||||||
|  |         "Open":      ["STOCK_OPEN", "open"], | ||||||
|  |         "Open With": ["STOCK_OPEN", "open_with"], | ||||||
|  |         "Execute":   ["STOCK_EXECUTE", "execute"], | ||||||
|  |         "Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"] | ||||||
|  |     }, | ||||||
|  |     "File Actions": { | ||||||
|  |         "New":    ["STOCK_ADD", "create"], | ||||||
|  |         "Rename": ["STOCK_EDIT", "rename"], | ||||||
|  |         "Cut":    ["STOCK_CUT", "cut"], | ||||||
|  |         "Copy":   ["STOCK_COPY", "copy"], | ||||||
|  |         "Paste":  ["STOCK_PASTE", "paste"] | ||||||
|  |     }, | ||||||
|  |     "Plugins": {} | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/3g2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/3gp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/ai.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/air.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/asf.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/avi.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/bib.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/cls.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/csv.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/deb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/djvu.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/dmg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/doc.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.5 KiB |