Compare commits
	
		
			42 Commits
		
	
	
		
			7737e3ad6d
			...
			6bd4d97db2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6bd4d97db2 | |||
| 92d8069f3a | |||
| b1bf8785c6 | |||
| bcc04dda3c | |||
| 8f64066049 | |||
| 111c535876 | |||
| 793621745a | |||
| ee086f67f4 | |||
| e4656c771a | |||
| 9442453d43 | |||
| 0539fa41f0 | |||
| 2278cdc0c3 | |||
| 22c9fa301b | |||
| 1fc1609b0a | |||
| 09a85abb79 | |||
| 51ac26048c | |||
| c3f637b5fd | |||
| 45ca8abbdd | |||
| 4aeaffdd44 | |||
| 8eccdfce7c | |||
| 8e242f5475 | |||
| 32f061ff76 | |||
| 8f1c1848fd | |||
| 7e5d603eb9 | |||
| 674dac5918 | |||
| bddcc8e3e6 | |||
| 56b8ee6117 | |||
| 312a782a87 | |||
| 7b4bbd7c2b | |||
| f79aa4e852 | |||
| 918eec1053 | |||
| 07714c9cd4 | |||
| 2622600e92 | |||
| 6eed25efd6 | |||
| eafc8613e6 | |||
| f77becc21c | |||
| 67c13d264a | |||
| 52aa14dcb4 | |||
| 7534bf141e | |||
| ca855712b1 | |||
| 95c6f79627 | |||
| a863dbc586 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | .idea/ | ||||||
|  | *.zip | ||||||
|  |  | ||||||
| # ---> Python | # ---> Python | ||||||
| # Byte-compiled / optimized / DLL files | # Byte-compiled / optimized / DLL files | ||||||
| __pycache__/ | __pycache__/ | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| # SolarFM |  | ||||||
|  |  | ||||||
| # SolarFM | # SolarFM | ||||||
| SolarFM is a Gtk+ Python file manager. | SolarFM is a Gtk+ Python file manager. | ||||||
|  |  | ||||||
| @@ -14,6 +12,11 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn | |||||||
| # TODO | # TODO | ||||||
| <ul> | <ul> | ||||||
| <li>Add simpleish plugin system to run bash/python scripts.</li> | <li>Add simpleish plugin system to run bash/python scripts.</li> | ||||||
|  | <li>Add simpleish preview plugin for various file types.</li> | ||||||
|  | <li>Add simpleish file chmod, chown, stats, etc plugin for file management.</li> | ||||||
|  | <li>Add simpleish search plugin to do recursive search and show.</li> | ||||||
|  | <li>Add simpleish bulk-renamer.</li> | ||||||
|  | <li>Add a basic favorites manager plugin.</li> | ||||||
| </ul> | </ul> | ||||||
|  |  | ||||||
| # Images | # Images | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										61
									
								
								plugins/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								plugins/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | ### Note | ||||||
|  | Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with. | ||||||
|  | Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Manifest Example (All are required even if empty.) | ||||||
|  | ``` | ||||||
|  | class Manifest: | ||||||
|  |     path: str     = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |     name: str     = "Example Plugin" | ||||||
|  |     author: str   = "John Doe" | ||||||
|  |     version: str  = "0.0.1" | ||||||
|  |     support: str  = "" | ||||||
|  |     requests: {}  = { | ||||||
|  |         'ui_target': "plugin_control_list", | ||||||
|  |         'pass_fm_events': "true" | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Requests | ||||||
|  | ``` | ||||||
|  | requests: {}  = { | ||||||
|  |     'ui_target': "plugin_control_list", | ||||||
|  |     'ui_target_id': "<some other Gtk Glade ID>"          # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options... | ||||||
|  |     'pass_fm_events': "true"                             # If empty or not present will be ignored. | ||||||
|  |     'bind_keys': [f"{name}||send_message:<Control>f"], | ||||||
|  |                   f"{name}||do_save:<Control>s"]         # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right. | ||||||
|  |  | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | UI Targets: | ||||||
|  | <ul> | ||||||
|  | <li>main_Window</li> | ||||||
|  | <li>main_menu_bar</li> | ||||||
|  | <li>path_menu_bar</li> | ||||||
|  | <li>plugin_control_list</li> | ||||||
|  | <li>window_(1-4)</li> | ||||||
|  | <li>context_menu</li> | ||||||
|  | <li>other</li> | ||||||
|  | </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() | ||||||
|  |  | ||||||
|  | ``` | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| ### Note |  | ||||||
| Copy the example and rename it to your desired name. The Main class and passed in arguments are required. You don't necessarily need to use the passed in socket_id or event_system. |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import sys, traceback, threading, inspect, os, time |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Main: |  | ||||||
|     def __init__(self, socket_id, event_system): |  | ||||||
|         self._socket_id    = socket_id |  | ||||||
|         self._event_system = event_system |  | ||||||
|         self._gtk_plug     = Gtk.Plug.new(self._socket_id) |  | ||||||
|         self.start_loop() |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def start_loop(self): |  | ||||||
|         i      = 0 |  | ||||||
|         cycles = 5 |  | ||||||
|         alive  = True |  | ||||||
|         while alive: |  | ||||||
|             if i == cycles: |  | ||||||
|                 alive = False |  | ||||||
|  |  | ||||||
|             self._event_system.push_gui_event(["some_type", "display_message", ("warning", str(i), None)]) |  | ||||||
|             i += 1 |  | ||||||
|  |  | ||||||
|             time.sleep(1) |  | ||||||
							
								
								
									
										685
									
								
								plugins/file_properties/file_properties.glade
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										685
									
								
								plugins/file_properties/file_properties.glade
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,685 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <!-- Generated with glade 3.38.2 --> | ||||||
|  | <interface> | ||||||
|  |   <requires lib="gtk+" version="3.16"/> | ||||||
|  |   <object class="GtkDialog" id="file_properties_dialog"> | ||||||
|  |     <property name="can-focus">False</property> | ||||||
|  |     <property name="border-width">6</property> | ||||||
|  |     <property name="title" translatable="yes">File Properties</property> | ||||||
|  |     <property name="modal">True</property> | ||||||
|  |     <property name="window-position">center-on-parent</property> | ||||||
|  |     <property name="default-width">420</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="gravity">center</property> | ||||||
|  |     <signal name="response" handler="on_filePropertiesDlg_response" swapped="no"/> | ||||||
|  |     <child internal-child="vbox"> | ||||||
|  |       <object class="GtkBox" id="dialog_vbox"> | ||||||
|  |         <property name="visible">True</property> | ||||||
|  |         <property name="can-focus">False</property> | ||||||
|  |         <property name="spacing">12</property> | ||||||
|  |         <child internal-child="action_area"> | ||||||
|  |           <object class="GtkButtonBox" id="dialog_action_area"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="layout-style">end</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkButton" id="cancel_button"> | ||||||
|  |                 <property name="label">gtk-cancel</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="can-default">True</property> | ||||||
|  |                 <property name="receives-default">False</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="ok_button"> | ||||||
|  |                 <property name="label">gtk-ok</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">True</property> | ||||||
|  |                 <property name="can-default">True</property> | ||||||
|  |                 <property name="receives-default">False</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="pack-type">end</property> | ||||||
|  |             <property name="position">0</property> | ||||||
|  |           </packing> | ||||||
|  |         </child> | ||||||
|  |         <child> | ||||||
|  |           <object class="GtkNotebook" id="notebook"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">True</property> | ||||||
|  |             <property name="border-width">6</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkAlignment" id="alignment2"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="top-padding">6</property> | ||||||
|  |                 <property name="bottom-padding">6</property> | ||||||
|  |                 <property name="left-padding">12</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkTable" id="general_table"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="border-width">4</property> | ||||||
|  |                     <property name="n-rows">7</property> | ||||||
|  |                     <property name="n-columns">2</property> | ||||||
|  |                     <property name="column-spacing">12</property> | ||||||
|  |                     <property name="row-spacing">6</property> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label_filename"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>File _Name:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="mnemonic-widget">file_name</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkEntry" id="file_name"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label20"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>_Location:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="mnemonic-widget">file_location</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">1</property> | ||||||
|  |                         <property name="bottom-attach">2</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkEntry" id="file_location"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">1</property> | ||||||
|  |                         <property name="bottom-attach">2</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label_target"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>Link _Target:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="mnemonic-widget">file_target</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">2</property> | ||||||
|  |                         <property name="bottom-attach">3</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkEntry" id="file_target"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">2</property> | ||||||
|  |                         <property name="bottom-attach">3</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label4"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>Type:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                         <property name="yalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">3</property> | ||||||
|  |                         <property name="bottom-attach">4</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options">GTK_FILL</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="mime_type"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="selectable">True</property> | ||||||
|  |                         <property name="ellipsize">end</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                         <property name="yalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">3</property> | ||||||
|  |                         <property name="bottom-attach">4</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label5"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>Size:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">4</property> | ||||||
|  |                         <property name="bottom-attach">5</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="file_size"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="selectable">True</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">4</property> | ||||||
|  |                         <property name="bottom-attach">5</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label7"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>_Modified:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">5</property> | ||||||
|  |                         <property name="bottom-attach">6</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkLabel" id="label13"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="label" translatable="yes"><b>_Accessed:</b></property> | ||||||
|  |                         <property name="use-markup">True</property> | ||||||
|  |                         <property name="use-underline">True</property> | ||||||
|  |                         <property name="xalign">0</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="top-attach">6</property> | ||||||
|  |                         <property name="bottom-attach">7</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkEntry" id="mtime"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">5</property> | ||||||
|  |                         <property name="bottom-attach">6</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkEntry" id="atime"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="left-attach">1</property> | ||||||
|  |                         <property name="right-attach">2</property> | ||||||
|  |                         <property name="top-attach">6</property> | ||||||
|  |                         <property name="bottom-attach">7</property> | ||||||
|  |                         <property name="x-options">GTK_FILL</property> | ||||||
|  |                         <property name="y-options"/> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |             </child> | ||||||
|  |             <child type="tab"> | ||||||
|  |               <object class="GtkLabel" id="label1"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="label" translatable="yes">_Info</property> | ||||||
|  |                 <property name="use-underline">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="tab-fill">False</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkAlignment" id="alignment1"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="top-padding">6</property> | ||||||
|  |                 <property name="bottom-padding">6</property> | ||||||
|  |                 <property name="left-padding">12</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkVBox" id="vbox1"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="spacing">6</property> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkTable" id="table3"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="border-width">2</property> | ||||||
|  |                         <property name="n-rows">2</property> | ||||||
|  |                         <property name="n-columns">2</property> | ||||||
|  |                         <property name="column-spacing">12</property> | ||||||
|  |                         <property name="row-spacing">6</property> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkLabel" id="owner_label"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="label" translatable="yes"><b>_Owner:</b></property> | ||||||
|  |                             <property name="use-markup">True</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="mnemonic-widget">file_owner</property> | ||||||
|  |                             <property name="xalign">0</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkLabel" id="group_label"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="label" translatable="yes"><b>_Group:</b></property> | ||||||
|  |                             <property name="use-markup">True</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="mnemonic-widget">file_group</property> | ||||||
|  |                             <property name="xalign">0</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkEntry" id="file_owner"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">1</property> | ||||||
|  |                             <property name="right-attach">2</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkEntry" id="file_group"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">1</property> | ||||||
|  |                             <property name="right-attach">2</property> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">False</property> | ||||||
|  |                         <property name="fill">False</property> | ||||||
|  |                         <property name="position">0</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkHSeparator" id="hseparator1"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">False</property> | ||||||
|  |                         <property name="fill">False</property> | ||||||
|  |                         <property name="position">1</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkTable" id="table2"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">False</property> | ||||||
|  |                         <property name="border-width">4</property> | ||||||
|  |                         <property name="n-rows">3</property> | ||||||
|  |                         <property name="n-columns">5</property> | ||||||
|  |                         <property name="column-spacing">12</property> | ||||||
|  |                         <property name="row-spacing">6</property> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkLabel" id="label17"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="label" translatable="yes"><b>Owner:</b></property> | ||||||
|  |                             <property name="use-markup">True</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="xalign">0</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkLabel" id="label18"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="label" translatable="yes"><b>Group:</b></property> | ||||||
|  |                             <property name="use-markup">True</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="xalign">0</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkLabel" id="label19"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="label" translatable="yes"><b>Other:</b></property> | ||||||
|  |                             <property name="use-markup">True</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="xalign">0</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="top-attach">2</property> | ||||||
|  |                             <property name="bottom-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="owner_r"> | ||||||
|  |                             <property name="label" translatable="yes">Read</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">1</property> | ||||||
|  |                             <property name="right-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="group_r"> | ||||||
|  |                             <property name="label" translatable="yes">Read</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">1</property> | ||||||
|  |                             <property name="right-attach">2</property> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="others_r"> | ||||||
|  |                             <property name="label" translatable="yes">Read</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">1</property> | ||||||
|  |                             <property name="right-attach">2</property> | ||||||
|  |                             <property name="top-attach">2</property> | ||||||
|  |                             <property name="bottom-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="owner_w"> | ||||||
|  |                             <property name="label" translatable="yes">Write</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">2</property> | ||||||
|  |                             <property name="right-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="group_w"> | ||||||
|  |                             <property name="label" translatable="yes">Write</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">2</property> | ||||||
|  |                             <property name="right-attach">3</property> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="others_w"> | ||||||
|  |                             <property name="label" translatable="yes">Write</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">2</property> | ||||||
|  |                             <property name="right-attach">3</property> | ||||||
|  |                             <property name="top-attach">2</property> | ||||||
|  |                             <property name="bottom-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="owner_x"> | ||||||
|  |                             <property name="label" translatable="yes">Execute</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">3</property> | ||||||
|  |                             <property name="right-attach">4</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="group_x"> | ||||||
|  |                             <property name="label" translatable="yes">Execute</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">3</property> | ||||||
|  |                             <property name="right-attach">4</property> | ||||||
|  |                             <property name="top-attach">1</property> | ||||||
|  |                             <property name="bottom-attach">2</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkCheckButton" id="others_x"> | ||||||
|  |                             <property name="label" translatable="yes">Execute</property> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">True</property> | ||||||
|  |                             <property name="receives-default">False</property> | ||||||
|  |                             <property name="border-width">2</property> | ||||||
|  |                             <property name="use-underline">True</property> | ||||||
|  |                             <property name="draw-indicator">True</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">3</property> | ||||||
|  |                             <property name="right-attach">4</property> | ||||||
|  |                             <property name="top-attach">2</property> | ||||||
|  |                             <property name="bottom-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options"/> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkVSeparator" id="vseparator1"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                           </object> | ||||||
|  |                           <packing> | ||||||
|  |                             <property name="left-attach">4</property> | ||||||
|  |                             <property name="right-attach">5</property> | ||||||
|  |                             <property name="bottom-attach">3</property> | ||||||
|  |                             <property name="x-options">GTK_FILL</property> | ||||||
|  |                             <property name="y-options">GTK_FILL</property> | ||||||
|  |                           </packing> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="expand">False</property> | ||||||
|  |                         <property name="fill">True</property> | ||||||
|  |                         <property name="position">2</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child type="tab"> | ||||||
|  |               <object class="GtkLabel" id="label2"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="label" translatable="yes">_Permissions</property> | ||||||
|  |                 <property name="use-underline">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |                 <property name="tab-fill">False</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">cancel_button</action-widget> | ||||||
|  |       <action-widget response="-5">ok_button</action-widget> | ||||||
|  |     </action-widgets> | ||||||
|  |   </object> | ||||||
|  | </interface> | ||||||
							
								
								
									
										283
									
								
								plugins/file_properties/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								plugins/file_properties/plugin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,283 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, threading, subprocess, time, pwd, grp | ||||||
|  | from datetime import datetime | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk, GLib, Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 Manifest: | ||||||
|  |     path: str     = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |     name: str     = "Properties" | ||||||
|  |     author: str   = "ITDominator" | ||||||
|  |     version: str  = "0.0.1" | ||||||
|  |     support: str  = "" | ||||||
|  |     requests: {}  = { | ||||||
|  |         'ui_target': "context_menu", | ||||||
|  |         'pass_fm_events': "true" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | class Properties: | ||||||
|  |     file_uri: str      = None | ||||||
|  |     file_name: str     = None | ||||||
|  |     file_location: str = None | ||||||
|  |     file_target: str   = None | ||||||
|  |     mime_type: str     = None | ||||||
|  |     file_size: str     = None | ||||||
|  |     mtime: int         = None | ||||||
|  |     atime: int         = None | ||||||
|  |     file_owner: str    = None | ||||||
|  |     file_group: str    = None | ||||||
|  |     chmod_stat: str    = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(Manifest): | ||||||
|  |     def __init__(self): | ||||||
|  |         self._GLADE_FILE        = f"{self.path}/file_properties.glade" | ||||||
|  |         self._builder           = None | ||||||
|  |         self._properties_dialog = None | ||||||
|  |  | ||||||
|  |         self._event_system      = None | ||||||
|  |         self._event_sleep_time  = .5 | ||||||
|  |         self._event_message     = None | ||||||
|  |  | ||||||
|  |         self._file_name         = None | ||||||
|  |         self._file_location     = None | ||||||
|  |         self._file_target       = None | ||||||
|  |         self._mime_type         = None | ||||||
|  |         self._file_size         = None | ||||||
|  |         self._mtime             = None | ||||||
|  |         self._atime             = None | ||||||
|  |         self._file_owner        = None | ||||||
|  |         self._file_group        = None | ||||||
|  |  | ||||||
|  |         self._chmod_map: {} = { | ||||||
|  |                                 "7": "rwx", | ||||||
|  |                                 "6": "rw", | ||||||
|  |                                 "5": "rx", | ||||||
|  |                                 "4": "r", | ||||||
|  |                                 "3": "wx", | ||||||
|  |                                 "2": "w", | ||||||
|  |                                 "1": "x", | ||||||
|  |                                 "0": "" | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         self._chmod_map_counter: {} = { | ||||||
|  |                                  "rwx": "7", | ||||||
|  |                                  "rw":  "6", | ||||||
|  |                                  "rx":  "5", | ||||||
|  |                                  "r":   "4", | ||||||
|  |                                  "wx":  "3", | ||||||
|  |                                  "w":   "2", | ||||||
|  |                                  "x":   "1", | ||||||
|  |                                  "":    "0" | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_ui_element(self): | ||||||
|  |         self._builder           = Gtk.Builder() | ||||||
|  |         self._builder.add_from_file(self._GLADE_FILE) | ||||||
|  |  | ||||||
|  |         self._properties_dialog = self._builder.get_object("file_properties_dialog") | ||||||
|  |         self._file_name     = self._builder.get_object("file_name") | ||||||
|  |         self._file_location = self._builder.get_object("file_location") | ||||||
|  |         self._file_target   = self._builder.get_object("file_target") | ||||||
|  |         self._mime_type     = self._builder.get_object("mime_type") | ||||||
|  |         self._file_size     = self._builder.get_object("file_size") | ||||||
|  |         self._mtime         = self._builder.get_object("mtime") | ||||||
|  |         self._atime         = self._builder.get_object("atime") | ||||||
|  |         self._file_owner    = self._builder.get_object("file_owner") | ||||||
|  |         self._file_group    = self._builder.get_object("file_group") | ||||||
|  |  | ||||||
|  |         button = Gtk.Button(label=self.name) | ||||||
|  |         button.connect("button-release-event", self._show_properties_page) | ||||||
|  |         return button | ||||||
|  |  | ||||||
|  |     def set_fm_event_system(self, fm_event_system): | ||||||
|  |         self._event_system = fm_event_system | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         self._module_event_observer() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @threaded | ||||||
|  |     def _show_properties_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 | ||||||
|  |         self._event_message = None | ||||||
|  |  | ||||||
|  |         GLib.idle_add(self._process_changes, (state)) | ||||||
|  |  | ||||||
|  |     def _process_changes(self, state): | ||||||
|  |         if len(state.selected_files) == 1: | ||||||
|  |             uri  = state.selected_files[0] | ||||||
|  |             path = state.tab.get_current_directory() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             properties = self._set_ui_data(uri, path) | ||||||
|  |             response   = self._properties_dialog.run() | ||||||
|  |             if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: | ||||||
|  |                 self._properties_dialog.hide() | ||||||
|  |  | ||||||
|  |             self._update_file(properties) | ||||||
|  |             self._properties_dialog.hide() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _update_file(self, properties): | ||||||
|  |         chmod_stat = self._get_check_boxes() | ||||||
|  |  | ||||||
|  |         if chmod_stat is not properties.chmod_stat: | ||||||
|  |             try: | ||||||
|  |                 print("\nNew chmod flags...") | ||||||
|  |                 print(f"Old:  {''.join(properties.chmod_stat)}") | ||||||
|  |                 print(f"New:  {chmod_stat}") | ||||||
|  |  | ||||||
|  |                 command = ["chmod", f"{chmod_stat}", properties.file_uri] | ||||||
|  |                 with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: | ||||||
|  |                     result = proc.stdout.read().decode("UTF-8").strip() | ||||||
|  |                     print(result) | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(f"Couldn't chmod\nFile:  {properties.file_uri}") | ||||||
|  |                 print( repr(e) ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         owner = self._file_owner.get_text() | ||||||
|  |         group = self._file_group.get_text() | ||||||
|  |         if owner is not properties.file_owner or group is not properties.file_group: | ||||||
|  |             try: | ||||||
|  |                 print("\nNew owner/group flags...") | ||||||
|  |                 print(f"Old:\n\tOwner: {properties.file_owner}\n\tGroup: {properties.file_group}") | ||||||
|  |                 print(f"New:\n\tOwner: {owner}\n\tGroup: {group}") | ||||||
|  |  | ||||||
|  |                 uid = pwd.getpwnam(owner).pw_uid | ||||||
|  |                 gid = grp.getgrnam(group).gr_gid | ||||||
|  |                 os.chown(properties.file_uri, uid, gid) | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(f"Couldn't chmod\nFile:  {properties.file_uri}") | ||||||
|  |                 print( repr(e) ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _set_ui_data(self, uri, path): | ||||||
|  |         properties = Properties() | ||||||
|  |         file_info  = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed", | ||||||
|  |                                                             flags=Gio.FileQueryInfoFlags.NONE, | ||||||
|  |                                                             cancellable=None) | ||||||
|  |  | ||||||
|  |         is_symlink               = file_info.get_attribute_as_string("standard::is-symlink") | ||||||
|  |         properties.file_uri      = uri | ||||||
|  |         properties.file_target   = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else "" | ||||||
|  |         properties.file_name     = file_info.get_display_name() | ||||||
|  |         properties.file_location = path | ||||||
|  |         properties.mime_type     = file_info.get_content_type() | ||||||
|  |         properties.file_size     = self._sizeof_fmt(file_info.get_size()) | ||||||
|  |         properties.mtime         = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::changed")) ).strftime("%A, %B %d, %Y %I:%M:%S") | ||||||
|  |         properties.atime         = datetime.fromtimestamp( int(file_info.get_attribute_as_string("time::access")) ).strftime("%A, %B %d, %Y %I:%M:%S") | ||||||
|  |         properties.file_owner    = file_info.get_attribute_as_string("owner::user") | ||||||
|  |         properties.file_group    = file_info.get_attribute_as_string("owner::group") | ||||||
|  |  | ||||||
|  |         # NOTE: Read = 4,  Write = 2,  Exec = 1 | ||||||
|  |         command = ["stat", "-c", "%a", uri] | ||||||
|  |         with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: | ||||||
|  |             properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip()) | ||||||
|  |             owner  = self._chmod_map[f"{properties.chmod_stat[0]}"] | ||||||
|  |             group  = self._chmod_map[f"{properties.chmod_stat[1]}"] | ||||||
|  |             others = self._chmod_map[f"{properties.chmod_stat[2]}"] | ||||||
|  |  | ||||||
|  |             self._reset_check_boxes() | ||||||
|  |             self._set_check_boxes([["owner", owner], ["group", group], ["others", others]]) | ||||||
|  |  | ||||||
|  |         self._file_name.set_text(properties.file_name) | ||||||
|  |         self._file_location.set_text(properties.file_location) | ||||||
|  |         self._file_target.set_text(properties.file_target) | ||||||
|  |         self._mime_type.set_label(properties.mime_type) | ||||||
|  |         self._file_size.set_label(properties.file_size) | ||||||
|  |         self._mtime.set_text(properties.mtime) | ||||||
|  |         self._atime.set_text(properties.atime) | ||||||
|  |         self._file_owner.set_text(properties.file_owner) | ||||||
|  |         self._file_group.set_text(properties.file_group) | ||||||
|  |  | ||||||
|  |         return properties | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _get_check_boxes(self): | ||||||
|  |         perms = [[], [], []] | ||||||
|  |  | ||||||
|  |         for i, target in enumerate(["owner", "group", "others"]): | ||||||
|  |             for type in ["r", "w", "x"]: | ||||||
|  |                 is_active = self._builder.get_object(f"{target}_{type}").get_active() | ||||||
|  |                 if is_active: | ||||||
|  |                     perms[i].append(type) | ||||||
|  |  | ||||||
|  |         digits = [] | ||||||
|  |         for perm in perms: | ||||||
|  |             digits.append(self._chmod_map_counter[ ''.join(perm) ]) | ||||||
|  |  | ||||||
|  |         return ''.join(digits) | ||||||
|  |  | ||||||
|  |     def _set_check_boxes(self, targets): | ||||||
|  |         for name, target in targets: | ||||||
|  |             for type in list(target): | ||||||
|  |                 obj = f"{name}_{type}" | ||||||
|  |                 self._builder.get_object(obj).set_active(True) | ||||||
|  |  | ||||||
|  |     def _reset_check_boxes(self): | ||||||
|  |         for target in ["owner", "group", "others"]: | ||||||
|  |             for type in ["r", "w", "x"]: | ||||||
|  |                 self._builder.get_object(f"{target}_{type}").set_active(False) | ||||||
|  |  | ||||||
|  |     def _sizeof_fmt(self, num, suffix="B"): | ||||||
|  |         for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: | ||||||
|  |             if abs(num) < 1024.0: | ||||||
|  |                 return f"{num:3.1f} {unit}{suffix}" | ||||||
|  |             num /= 1024.0 | ||||||
|  |         return f"{num:.1f} Yi{suffix}" | ||||||
|  |  | ||||||
|  |     def wait_for_fm_message(self): | ||||||
|  |         while not self._event_message: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _module_event_observer(self): | ||||||
|  |         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: | ||||||
|  |                             self._event_message = data | ||||||
|  |                         else: | ||||||
|  |                             method = getattr(self.__class__, f"{method_target}") | ||||||
|  |                             if data: | ||||||
|  |                                 data = method(*(self, *data)) | ||||||
|  |                             else: | ||||||
|  |                                 method(*(self,)) | ||||||
|  |                 except Exception as e: | ||||||
|  |                     print(repr(e)) | ||||||
							
								
								
									
										88
									
								
								plugins/template/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								plugins/template/plugin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, threading, subprocess, time | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 Manifest: | ||||||
|  |     path: str     = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |     name: str     = "Example Plugin" | ||||||
|  |     author: str   = "John Doe" | ||||||
|  |     version: str  = "0.0.1" | ||||||
|  |     support: str  = "" | ||||||
|  |     requests: {}  = { | ||||||
|  |         'ui_target': "plugin_control_list", | ||||||
|  |         'pass_fm_events': "true", | ||||||
|  |         'bind_keys': [f"{name}||send_message:<Control>f"] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(Manifest): | ||||||
|  |     def __init__(self): | ||||||
|  |         self._event_system      = None | ||||||
|  |         self._event_sleep_time  = .5 | ||||||
|  |         self._event_message     = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_ui_element(self): | ||||||
|  |         button = Gtk.Button(label=self.name) | ||||||
|  |         button.connect("button-release-event", self.send_message) | ||||||
|  |         return button | ||||||
|  |  | ||||||
|  |     def set_fm_event_system(self, fm_event_system): | ||||||
|  |         self._event_system = fm_event_system | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         self._module_event_observer() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def send_message(self, widget=None, eve=None): | ||||||
|  |         message = "Hello, World!" | ||||||
|  |         print("here") | ||||||
|  |         self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def wait_for_fm_message(self): | ||||||
|  |         while not self._event_message: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _module_event_observer(self): | ||||||
|  |         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: | ||||||
|  |                             self._event_message = data | ||||||
|  |                         else: | ||||||
|  |                             method = getattr(self.__class__, f"{method_target}") | ||||||
|  |                             if data: | ||||||
|  |                                 data = method(*(self, *data)) | ||||||
|  |                             else: | ||||||
|  |                                 method(*(self,)) | ||||||
|  |                 except Exception as e: | ||||||
|  |                     print(repr(e)) | ||||||
							
								
								
									
										18
									
								
								plugins/youtube_download/download.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								plugins/youtube_download/download.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # . CONFIG.sh | ||||||
|  |  | ||||||
|  | # set -o xtrace       ## To debug scripts | ||||||
|  | # set -o errexit      ## To exit on error | ||||||
|  | # set -o errunset     ## To exit if a variable is referenced but not set | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function main() { | ||||||
|  |     cd "$(dirname "")" | ||||||
|  |     echo "Working Dir: " $(pwd) | ||||||
|  |     source "/home/abaddon/Portable_Apps/py-venvs/yt-dlp-venv/venv/bin/activate" | ||||||
|  |  | ||||||
|  |     LINK=`xclip -selection clipboard -o` | ||||||
|  |     yt-dlp --write-sub --embed-sub --sub-langs en -o "${1}/%(title)s.%(ext)s" "${LINK}" | ||||||
|  | } | ||||||
|  | main "$@"; | ||||||
							
								
								
									
										92
									
								
								plugins/youtube_download/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								plugins/youtube_download/plugin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, threading, subprocess, time | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # 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 Manifest: | ||||||
|  |     path: str     = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |     name: str     = "Youtube Download" | ||||||
|  |     author: str   = "ITDominator" | ||||||
|  |     version: str  = "0.0.1" | ||||||
|  |     support: str  = "" | ||||||
|  |     requests: {}  = { | ||||||
|  |         'ui_target': "plugin_control_list", | ||||||
|  |         'pass_fm_events': "true" | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin(Manifest): | ||||||
|  |     def __init__(self): | ||||||
|  |         self._event_system     = None | ||||||
|  |         self._event_sleep_time = .5 | ||||||
|  |         self._event_message    = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_ui_element(self): | ||||||
|  |         button = Gtk.Button(label=self.name) | ||||||
|  |         button.connect("button-release-event", self._do_download) | ||||||
|  |         return button | ||||||
|  |  | ||||||
|  |     def set_fm_event_system(self, fm_event_system): | ||||||
|  |         self._event_system = fm_event_system | ||||||
|  |  | ||||||
|  |     def run(self): | ||||||
|  |         self._module_event_observer() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @threaded | ||||||
|  |     def _do_download(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 | ||||||
|  |         subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) | ||||||
|  |         self._event_message = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def wait_for_fm_message(self): | ||||||
|  |         while not self._event_message: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     @daemon_threaded | ||||||
|  |     def _module_event_observer(self): | ||||||
|  |         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: | ||||||
|  |                             self._event_message = data | ||||||
|  |                         else: | ||||||
|  |                             method = getattr(self.__class__, f"{method_target}") | ||||||
|  |                             if data: | ||||||
|  |                                 data = method(*(self, *data)) | ||||||
|  |                             else: | ||||||
|  |                                 method(*(self,)) | ||||||
|  |                 except Exception as e: | ||||||
|  |                     print(repr(e)) | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| # set -o xtrace       ## To debug scripts |  | ||||||
| # set -o errexit      ## To exit on error |  | ||||||
| # set -o errunset     ## To exit if a variable is referenced but not set |  | ||||||
|  |  | ||||||
|  |  | ||||||
| function main() { |  | ||||||
|     find . -name "__pycache__" -exec rm -rf $1 {} \; |  | ||||||
|     find . -name "*.pyc" -exec rm -rf $1 {} \; |  | ||||||
| } |  | ||||||
| main |  | ||||||
| @@ -4,24 +4,25 @@ import builtins | |||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from signal_classes import IPCServerMixin | from ipc_server import IPCServer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Builtins(IPCServerMixin): | class EventSystem(IPCServer): | ||||||
|     """Docstring for __builtins__ extender""" |     """ Inheret IPCServerMixin. Create an pub/sub systems. """ | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         # NOTE: The format used is list of [type, target, data] |         super(EventSystem, self).__init__() | ||||||
|  |  | ||||||
|  |         # NOTE: The format used is list of [type, target, (data,)] Where: | ||||||
|  |         #             type is useful context for control flow, | ||||||
|  |         #             target is the method to call, | ||||||
|  |         #             data is the method parameters to give | ||||||
|         #       Where data may be any kind of data |         #       Where data may be any kind of data | ||||||
|         self._gui_events    = [] |         self._gui_events    = [] | ||||||
|         self._fm_events     = [] |         self._module_events = [] | ||||||
|         self.is_ipc_alive   = False |  | ||||||
|         self.ipc_authkey    = b'solarfm-ipc' |  | ||||||
|         self.ipc_address    = '127.0.0.1' |  | ||||||
|         self.ipc_port       = 4848 |  | ||||||
|         self.ipc_timeout    = 15.0 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Makeshift fake "events" type system FIFO |     # Makeshift fake "events" type system FIFO | ||||||
| @@ -30,9 +31,9 @@ class Builtins(IPCServerMixin): | |||||||
|             return self._gui_events.pop(0) |             return self._gui_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def _pop_fm_event(self): |     def _pop_module_event(self): | ||||||
|         if len(self._fm_events) > 0: |         if len(self._module_events) > 0: | ||||||
|             return self._fm_events.pop(0) |             return self._module_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -41,33 +42,33 @@ class Builtins(IPCServerMixin): | |||||||
|             self._gui_events.append(event) |             self._gui_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, data]") |         raise Exception("Invald event format! Please do:  [type, target, (data,)]") | ||||||
|  |  | ||||||
|     def push_fm_event(self, event): |     def push_module_event(self, event): | ||||||
|         if len(event) == 3: |         if len(event) == 3: | ||||||
|             self._fm_events.append(event) |             self._module_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, data]") |         raise Exception("Invald event format! Please do:  [type, target, (data,)]") | ||||||
|  |  | ||||||
|     def read_gui_event(self): |     def read_gui_event(self): | ||||||
|         return self._gui_events[0] |         return self._gui_events[0] | ||||||
|  |  | ||||||
|     def read_fm_event(self): |     def read_module_event(self): | ||||||
|         return self._fm_events[0] |         return self._module_events[0] | ||||||
|  |  | ||||||
|     def consume_gui_event(self): |     def consume_gui_event(self): | ||||||
|         return self._pop_gui_event() |         return self._pop_gui_event() | ||||||
|  |  | ||||||
|     def consume_fm_event(self): |     def consume_module_event(self): | ||||||
|         return self._pop_fm_event() |         return self._pop_module_event() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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.event_system      = Builtins() | builtins.event_system      = EventSystem() | ||||||
| builtins.event_sleep_time  = 0.2 | builtins.event_sleep_time  = 0.2 | ||||||
| builtins.debug             = False | builtins.debug             = False | ||||||
| builtins.trace_debug       = False | builtins.trace_debug       = False | ||||||
|   | |||||||
| @@ -1,54 +1,3 @@ | |||||||
| # Python imports | """ | ||||||
| import os, inspect, time | Base module | ||||||
|  | """ | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from utils import Settings |  | ||||||
| from signal_classes import Controller |  | ||||||
| from __builtins__ import Builtins |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Main(Builtins): |  | ||||||
|     def __init__(self, args, unknownargs): |  | ||||||
|         if not debug: |  | ||||||
|             event_system.create_ipc_server() |  | ||||||
|  |  | ||||||
|         time.sleep(0.2) |  | ||||||
|         if not trace_debug: |  | ||||||
|             if not event_system.is_ipc_alive: |  | ||||||
|                 if unknownargs: |  | ||||||
|                     for arg in unknownargs: |  | ||||||
|                         if os.path.isdir(arg): |  | ||||||
|                             message = f"FILE|{arg}" |  | ||||||
|                             event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 if args.new_tab and os.path.isdir(args.new_tab): |  | ||||||
|                     message = f"FILE|{args.new_tab}" |  | ||||||
|                     event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 raise Exception("IPC Server Exists: Will send path(s) to it and close...") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         settings = Settings() |  | ||||||
|         settings.create_window() |  | ||||||
|  |  | ||||||
|         controller = Controller(args, unknownargs, settings) |  | ||||||
|         if not controller: |  | ||||||
|             raise Exception("Controller exited and doesn't exist...") |  | ||||||
|  |  | ||||||
|         # Gets the methods from the classes and sets to handler. |  | ||||||
|         # Then, builder connects to any signals it needs. |  | ||||||
|         classes  = [controller] |  | ||||||
|         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)) |  | ||||||
|  |  | ||||||
|         settings.builder.connect_signals(handlers) |  | ||||||
|   | |||||||
| @@ -15,10 +15,12 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from __init__ import Main | from app import Application | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|  |     """ Set process title, get arguments, and create GTK main thread. """ | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         # import web_pdb |         # import web_pdb | ||||||
|         # web_pdb.set_trace() |         # web_pdb.set_trace() | ||||||
| @@ -33,7 +35,7 @@ if __name__ == "__main__": | |||||||
|         # Read arguments (If any...) |         # Read arguments (If any...) | ||||||
|         args, unknownargs = parser.parse_known_args() |         args, unknownargs = parser.parse_known_args() | ||||||
|  |  | ||||||
|         Main(args, unknownargs) |         Application(args, unknownargs) | ||||||
|         Gtk.main() |         Gtk.main() | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         traceback.print_exc() |         traceback.print_exc() | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, inspect, time | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from utils.settings import Settings | ||||||
|  | from context.controller import Controller | ||||||
|  | from __builtins__ import EventSystem | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Application(EventSystem): | ||||||
|  |     """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ | ||||||
|  |  | ||||||
|  |     def __init__(self, args, unknownargs): | ||||||
|  |         if not trace_debug: | ||||||
|  |             event_system.create_ipc_server() | ||||||
|  |             time.sleep(0.1) | ||||||
|  |  | ||||||
|  |             if not event_system.is_ipc_alive: | ||||||
|  |                 if unknownargs: | ||||||
|  |                     for arg in unknownargs: | ||||||
|  |                         if os.path.isdir(arg): | ||||||
|  |                             message = f"FILE|{arg}" | ||||||
|  |                             event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |                 if args.new_tab and os.path.isdir(args.new_tab): | ||||||
|  |                     message = f"FILE|{args.new_tab}" | ||||||
|  |                     event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |                 raise Exception("IPC Server Exists: Will send path(s) to it and close...") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         settings = Settings() | ||||||
|  |         settings.create_window() | ||||||
|  |  | ||||||
|  |         controller = Controller(args, unknownargs, settings) | ||||||
|  |         if not controller: | ||||||
|  |             raise Exception("Controller exited and doesn't exist...") | ||||||
|  |  | ||||||
|  |         # Gets the methods from the classes and sets to handler. | ||||||
|  |         # Then, builder connects to any signals it needs. | ||||||
|  |         classes  = [controller] | ||||||
|  |         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)) | ||||||
|  |  | ||||||
|  |         settings.builder.connect_signals(handlers) | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Gtk Bound Signal Module | ||||||
|  | """ | ||||||
							
								
								
									
										171
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/context/controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, gc, threading, time | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk, GLib | ||||||
|  |  | ||||||
|  | # 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): | ||||||
|  |     """ Controller coordinates the mixins and is somewhat the root hub of it all. """ | ||||||
|  |     def __init__(self, args, unknownargs, _settings): | ||||||
|  |         self.setup_controller_data(_settings) | ||||||
|  |         self.window.show() | ||||||
|  |  | ||||||
|  |         self.generate_windows(self.state) | ||||||
|  |         self.plugins.launch_plugins() | ||||||
|  |  | ||||||
|  |         if debug: | ||||||
|  |             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): | ||||||
|  |                         message = f"FILE|{arg}" | ||||||
|  |                         event_system.send_ipc_message(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 tear_down(self, widget=None, eve=None): | ||||||
|  |         event_system.send_ipc_message("close server") | ||||||
|  |         self.fm_controller.save_state() | ||||||
|  |         time.sleep(event_sleep_time) | ||||||
|  |         Gtk.main_quit() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @threaded | ||||||
|  |     def gui_event_observer(self): | ||||||
|  |         while True: | ||||||
|  |             time.sleep(event_sleep_time) | ||||||
|  |             event = event_system.consume_gui_event() | ||||||
|  |             if event: | ||||||
|  |                 try: | ||||||
|  |                     type, target, data = event | ||||||
|  |                     if type: | ||||||
|  |                         method = getattr(self.__class__, "handle_gui_event_and_set_message") | ||||||
|  |                         GLib.idle_add(method, *(self, type, target, data)) | ||||||
|  |                     else: | ||||||
|  |                         method = getattr(self.__class__, target) | ||||||
|  |                         GLib.idle_add(method, *(self, *data,)) | ||||||
|  |                 except Exception as e: | ||||||
|  |                     print(repr(e)) | ||||||
|  |  | ||||||
|  |     def handle_gui_event_and_set_message(self, type, target, parameters): | ||||||
|  |         method = getattr(self.__class__, f"{target}") | ||||||
|  |         data   = method(*(self, *parameters)) | ||||||
|  |         self.plugins.send_message_to_plugin(type, data) | ||||||
|  |  | ||||||
|  |     def open_terminal(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) | ||||||
|  |         dir      = tab.get_current_directory() | ||||||
|  |         tab.execute(f"{tab.terminal_app}", dir) | ||||||
|  |  | ||||||
|  |     def save_load_session(self, action="save_session"): | ||||||
|  |         wid, tid          = self.fm_controller.get_active_wid_and_tid() | ||||||
|  |         tab               = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |         save_load_dialog  = self.builder.get_object("save_load_dialog") | ||||||
|  |  | ||||||
|  |         if action == "save_session": | ||||||
|  |             self.fm_controller.save_state() | ||||||
|  |             return | ||||||
|  |         elif action == "save_session_as": | ||||||
|  |             save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) | ||||||
|  |         elif action == "load_session": | ||||||
|  |             save_load_dialog.set_action(Gtk.FileChooserAction.OPEN) | ||||||
|  |         else: | ||||||
|  |             raise Exception(f"Unknown action given:  {action}") | ||||||
|  |  | ||||||
|  |         save_load_dialog.set_current_folder(tab.get_current_directory()) | ||||||
|  |         save_load_dialog.set_current_name("session.json") | ||||||
|  |         response = save_load_dialog.run() | ||||||
|  |         if response == Gtk.ResponseType.OK: | ||||||
|  |             if action == "save_session_as": | ||||||
|  |                 path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}" | ||||||
|  |                 self.fm_controller.save_state(path) | ||||||
|  |             elif action == "load_session": | ||||||
|  |                 path         = f"{save_load_dialog.get_file().get_path()}" | ||||||
|  |                 session_json = self.fm_controller.load_state(path) | ||||||
|  |                 self.load_session(session_json) | ||||||
|  |         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |         save_load_dialog.hide() | ||||||
|  |  | ||||||
|  |     def load_session(self, session_json): | ||||||
|  |         if debug: | ||||||
|  |             print(f"Session Data: {session_json}") | ||||||
|  |  | ||||||
|  |         self.ctrl_down  = False | ||||||
|  |         self.shift_down = False | ||||||
|  |         self.alt_down   = False | ||||||
|  |         for notebook in self.notebooks: | ||||||
|  |             self.clear_children(notebook) | ||||||
|  |  | ||||||
|  |         self.fm_controller.unload_tabs_and_windows() | ||||||
|  |         self.generate_windows(session_json) | ||||||
|  |         gc.collect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def do_action_from_menu_controls(self, widget, event_button): | ||||||
|  |         action = widget.get_name() | ||||||
|  |         self.hide_context_menu() | ||||||
|  |         self.hide_new_file_menu() | ||||||
|  |         self.hide_edit_file_menu() | ||||||
|  |  | ||||||
|  |         if action == "open": | ||||||
|  |             self.open_files() | ||||||
|  |         if action == "open_with": | ||||||
|  |             self.show_appchooser_menu() | ||||||
|  |         if action == "execute": | ||||||
|  |             self.execute_files() | ||||||
|  |         if action == "execute_in_terminal": | ||||||
|  |             self.execute_files(in_terminal=True) | ||||||
|  |         if action == "rename": | ||||||
|  |             self.rename_files() | ||||||
|  |         if action == "cut": | ||||||
|  |             self.to_copy_files.clear() | ||||||
|  |             self.cut_files() | ||||||
|  |         if action == "copy": | ||||||
|  |             self.to_cut_files.clear() | ||||||
|  |             self.copy_files() | ||||||
|  |         if action == "paste": | ||||||
|  |             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": | ||||||
|  |             self.show_new_file_menu() | ||||||
|  |         if action in ["save_session", "save_session_as", "load_session"]: | ||||||
|  |             self.save_load_session(action) | ||||||
| @@ -1,26 +1,25 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import signal | import sys, os, signal | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| from gi.repository import GLib | from gi.repository import GLib | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from trasher.xdgtrash import XDGTrash | from trasher.xdgtrash import XDGTrash | ||||||
| from shellfm import WindowController | from shellfm.windows.controller import WindowController | ||||||
| from plugins import Plugins | from plugins.plugins import Plugins | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Controller_Data: | class Controller_Data: | ||||||
|     def has_method(self, o, name): |     """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ | ||||||
|         return callable(getattr(o, name, None)) |  | ||||||
| 
 | 
 | ||||||
|     def setup_controller_data(self, _settings): |     def setup_controller_data(self, _settings): | ||||||
|         self.trashman           = XDGTrash() |         self.trashman           = XDGTrash() | ||||||
|         self.window_controller  = WindowController() |         self.fm_controller      = WindowController() | ||||||
|         self.plugins            = Plugins(_settings) |         self.plugins            = Plugins(_settings) | ||||||
|         self.state              = self.window_controller.load_state() |         self.state              = self.fm_controller.load_state() | ||||||
|         self.trashman.regenerate() |         self.trashman.regenerate() | ||||||
| 
 | 
 | ||||||
|         self.settings           = _settings |         self.settings           = _settings | ||||||
| @@ -32,18 +31,19 @@ class Controller_Data: | |||||||
|         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") | ||||||
|         self.window4            = self.builder.get_object("window_4") |         self.window4            = self.builder.get_object("window_4") | ||||||
|         self.message_widget     = self.builder.get_object("message_widget") |         self.message_popup_widget = self.builder.get_object("message_popup_widget") | ||||||
|         self.message_view       = self.builder.get_object("message_view") |         self.message_text_view  = self.builder.get_object("message_text_view") | ||||||
|         self.message_buffer     = self.builder.get_object("message_buffer") |         self.message_buffer     = self.builder.get_object("message_buffer") | ||||||
|         self.arc_command_buffer = self.builder.get_object("arc_command_buffer") |         self.arc_command_buffer = self.builder.get_object("arc_command_buffer") | ||||||
| 
 | 
 | ||||||
|  |         self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") | ||||||
|         self.warning_alert      = self.builder.get_object("warning_alert") |         self.warning_alert      = self.builder.get_object("warning_alert") | ||||||
|         self.edit_file_menu     = self.builder.get_object("edit_file_menu") |         self.edit_file_menu     = self.builder.get_object("edit_file_menu") | ||||||
|         self.file_exists_dialog = self.builder.get_object("file_exists_dialog") |         self.file_exists_dialog = self.builder.get_object("file_exists_dialog") | ||||||
|         self.exists_file_label  = self.builder.get_object("exists_file_label") |         self.exists_file_label  = self.builder.get_object("exists_file_label") | ||||||
|         self.exists_file_field  = self.builder.get_object("exists_file_field") |         self.exists_file_field  = self.builder.get_object("exists_file_field") | ||||||
|         self.path_menu          = self.builder.get_object("path_menu") |         self.path_menu          = self.builder.get_object("path_menu") | ||||||
|         self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") |         self.path_entry         = self.builder.get_object("path_entry") | ||||||
| 
 | 
 | ||||||
|         self.bottom_size_label       = self.builder.get_object("bottom_size_label") |         self.bottom_size_label       = self.builder.get_object("bottom_size_label") | ||||||
|         self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") |         self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") | ||||||
| @@ -82,6 +82,7 @@ class Controller_Data: | |||||||
|         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.single_click_open  = False |         self.single_click_open  = False | ||||||
|         self.is_pane1_hidden    = False |         self.is_pane1_hidden    = False | ||||||
| @@ -91,19 +92,66 @@ class Controller_Data: | |||||||
| 
 | 
 | ||||||
|         self.override_drop_dest = None |         self.override_drop_dest = None | ||||||
|         self.is_searching       = False |         self.is_searching       = False | ||||||
|         self.search_iconview    = None |         self.search_icon_grid   = None | ||||||
|         self.search_view        = None |         self.search_tab         = None | ||||||
| 
 | 
 | ||||||
|         self.skip_edit          = False |         self.skip_edit          = False | ||||||
|         self.cancel_edit        = False |         self.cancel_edit        = False | ||||||
|         self.ctrlDown          = False |         self.ctrl_down          = False | ||||||
|         self.shiftDown         = False |         self.shift_down         = False | ||||||
|         self.altDown           = False |         self.alt_down           = False | ||||||
| 
 |  | ||||||
|         self.success           = "#88cc27" |  | ||||||
|         self.warning           = "#ffa800" |  | ||||||
|         self.error             = "#ff0000" |  | ||||||
| 
 | 
 | ||||||
|  |         self.success_color      = self.settings.get_success_color() | ||||||
|  |         self.warning_color      = self.settings.get_warning_color() | ||||||
|  |         self.error_color        = self.settings.get_error_color() | ||||||
| 
 | 
 | ||||||
|  |         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) | ||||||
|  | 
 | ||||||
|  |     def get_current_state(self): | ||||||
|  |         ''' | ||||||
|  |         Returns the state info most useful for any given context and action intent. | ||||||
|  | 
 | ||||||
|  |                 Parameters: | ||||||
|  |                         a (obj): self | ||||||
|  | 
 | ||||||
|  |                 Returns: | ||||||
|  |                         wid, tid, tab, icon_grid, store | ||||||
|  |         ''' | ||||||
|  |         wid, tid     = self.fm_controller.get_active_wid_and_tid() | ||||||
|  |         tab          = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |         icon_grid    = self.builder.get_object(f"{wid}|{tid}|icon_grid") | ||||||
|  |         store        = icon_grid.get_model() | ||||||
|  |         return wid, tid, tab, icon_grid, store | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def clear_console(self): | ||||||
|  |         ''' Clears the terminal screen. ''' | ||||||
|  |         os.system('cls' if os.name == 'nt' else 'clear') | ||||||
|  | 
 | ||||||
|  |     def call_method(self, _method_name, data = None): | ||||||
|  |         ''' | ||||||
|  |         Calls a method from scope of class. | ||||||
|  | 
 | ||||||
|  |                 Parameters: | ||||||
|  |                         a (obj): self | ||||||
|  |                         b (str): method name to be called | ||||||
|  |                         c (*): Data (if any) to be passed to the method. | ||||||
|  |                                 Note: It must be structured according to the given methods requirements. | ||||||
|  | 
 | ||||||
|  |                 Returns: | ||||||
|  |                         Return data is that which the calling method gives. | ||||||
|  |         ''' | ||||||
|  |         method_name = str(_method_name) | ||||||
|  |         method      = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") | ||||||
|  |         return method(data) if data else method() | ||||||
|  | 
 | ||||||
|  |     def has_method(self, obj, name): | ||||||
|  |         ''' Checks if a given method exists. ''' | ||||||
|  |         return callable(getattr(obj, name, None)) | ||||||
|  | 
 | ||||||
|  |     def clear_children(self, widget): | ||||||
|  |         ''' Clear children of a gtk widget. ''' | ||||||
|  |         for child in widget.get_children(): | ||||||
|  |             widget.remove(child) | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Mixins module | ||||||
|  | """ | ||||||
| @@ -0,0 +1,62 @@ | |||||||
|  | # Python imports | ||||||
|  | import traceback, threading, time | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk, GLib | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ExceptionHookMixin: | ||||||
|  |     """ ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """ | ||||||
|  |  | ||||||
|  |     def custom_except_hook(self, exec_type, value, _traceback): | ||||||
|  |         trace     = ''.join(traceback.format_tb(_traceback)) | ||||||
|  |         data      = f"Exec Type:  {exec_type}  <-->  Value:  {value}\n\n{trace}\n\n\n\n" | ||||||
|  |         start_itr = self.message_buffer.get_start_iter() | ||||||
|  |         self.message_buffer.place_cursor(start_itr) | ||||||
|  |         self.display_message(self.error, data) | ||||||
|  |  | ||||||
|  |     def display_message(self, type, text, seconds=None): | ||||||
|  |         self.message_buffer.insert_at_cursor(text) | ||||||
|  |         self.message_popup_widget.popup() | ||||||
|  |         if seconds: | ||||||
|  |             self.hide_message_timeout(seconds) | ||||||
|  |  | ||||||
|  |     @threaded | ||||||
|  |     def hide_message_timeout(self, seconds=3): | ||||||
|  |         time.sleep(seconds) | ||||||
|  |         GLib.idle_add(self.message_popup_widget.popdown) | ||||||
|  |  | ||||||
|  |     def save_debug_alerts(self, widget=None, eve=None): | ||||||
|  |         start_itr, end_itr   = self.message_buffer.get_bounds() | ||||||
|  |         save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ | ||||||
|  |                                                         action  = Gtk.FileChooserAction.SAVE, \ | ||||||
|  |                                                         buttons = (Gtk.STOCK_CANCEL, \ | ||||||
|  |                                                                     Gtk.ResponseType.CANCEL, \ | ||||||
|  |                                                                     Gtk.STOCK_SAVE, \ | ||||||
|  |                                                                     Gtk.ResponseType.OK)) | ||||||
|  |  | ||||||
|  |         text = self.message_buffer.get_text(start_itr, end_itr, False) | ||||||
|  |         resp = save_location_prompt.run() | ||||||
|  |         if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): | ||||||
|  |             pass | ||||||
|  |         elif resp == Gtk.ResponseType.OK: | ||||||
|  |             target = save_location_prompt.get_filename(); | ||||||
|  |             with open(target, "w") as f: | ||||||
|  |                 f.write(text) | ||||||
|  |  | ||||||
|  |         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,14 +4,14 @@ | |||||||
| 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, Gio | from gi.repository import Gtk, Gdk | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ShowHideMixin: | class ShowHideMixin: | ||||||
|     def show_messages_popup(self, type, text, seconds=None): |     def show_messages_popup(self, type, text, seconds=None): | ||||||
|         self.message_widget.popup() |         self.message_popup_widget.popup() | ||||||
| 
 | 
 | ||||||
|     def stop_file_searching(self, widget=None, eve=None): |     def stop_file_searching(self, widget=None, eve=None): | ||||||
|         self.is_searching = False |         self.is_searching = False | ||||||
| @@ -48,7 +48,7 @@ class ShowHideMixin: | |||||||
|     def show_about_page(self, widget=None, eve=None): |     def show_about_page(self, widget=None, eve=None): | ||||||
|         about_page = self.builder.get_object("about_page") |         about_page = self.builder.get_object("about_page") | ||||||
|         response   = about_page.run() |         response   = about_page.run() | ||||||
|         if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): |         if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: | ||||||
|             self.hide_about_page() |             self.hide_about_page() | ||||||
| 
 | 
 | ||||||
|     def hide_about_page(self, widget=None, eve=None): |     def hide_about_page(self, widget=None, eve=None): | ||||||
| @@ -56,11 +56,11 @@ class ShowHideMixin: | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def show_archiver_dialogue(self, widget=None, eve=None): |     def show_archiver_dialogue(self, widget=None, eve=None): | ||||||
|         wid, tid          = self.window_controller.get_active_data() |         wid, tid          = self.fm_controller.get_active_wid_and_tid() | ||||||
|         view              = self.get_fm_window(wid).get_view_by_id(tid) |         tab               = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         archiver_dialogue = self.builder.get_object("archiver_dialogue") |         archiver_dialogue = self.builder.get_object("archiver_dialogue") | ||||||
|         archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) |         archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) | ||||||
|         archiver_dialogue.set_current_folder(view.get_current_directory()) |         archiver_dialogue.set_current_folder(tab.get_current_directory()) | ||||||
|         archiver_dialogue.set_current_name("arc.7z") |         archiver_dialogue.set_current_name("arc.7z") | ||||||
| 
 | 
 | ||||||
|         response = archiver_dialogue.run() |         response = archiver_dialogue.run() | ||||||
| @@ -96,6 +96,12 @@ class ShowHideMixin: | |||||||
|         dialog.response(Gtk.ResponseType.OK) |         dialog.response(Gtk.ResponseType.OK) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     def show_plugins_popup(self, widget=None, eve=None): | ||||||
|  |         self.builder.get_object("plugin_list").popup() | ||||||
|  | 
 | ||||||
|  |     def hide_plugins_popup(self, widget=None, eve=None): | ||||||
|  |         self.builder.get_object("plugin_list").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").run() |         self.builder.get_object("context_menu").run() | ||||||
| 
 | 
 | ||||||
| @@ -131,7 +137,7 @@ class ShowHideMixin: | |||||||
| 
 | 
 | ||||||
|     def hide_edit_file_menu_enter_key(self, widget=None, eve=None): |     def hide_edit_file_menu_enter_key(self, widget=None, eve=None): | ||||||
|         keyname = Gdk.keyval_name(eve.keyval).lower() |         keyname = Gdk.keyval_name(eve.keyval).lower() | ||||||
|         if "return" in keyname or "enter" in keyname: |         if keyname in ["return", "enter"]: | ||||||
|             self.builder.get_object("edit_file_menu").hide() |             self.builder.get_object("edit_file_menu").hide() | ||||||
| 
 | 
 | ||||||
|     def hide_edit_file_menu_skip(self, widget=None, eve=None): |     def hide_edit_file_menu_skip(self, widget=None, eve=None): | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | UI module | ||||||
|  | """ | ||||||
| @@ -39,8 +39,6 @@ class PaneMixin: | |||||||
|     def toggle_notebook_pane(self, widget, eve=None): |     def toggle_notebook_pane(self, widget, eve=None): | ||||||
|         name        = widget.get_name() |         name        = widget.get_name() | ||||||
|         pane_index  = int(name[-1]) |         pane_index  = int(name[-1]) | ||||||
|         pane        = None |  | ||||||
| 
 |  | ||||||
|         master_pane = self.builder.get_object("pane_master") |         master_pane = self.builder.get_object("pane_master") | ||||||
|         pane        = self.builder.get_object("pane_top") if pane_index in [1, 2] else self.builder.get_object("pane_bottom") |         pane        = self.builder.get_object("pane_top") if pane_index in [1, 2] else self.builder.get_object("pane_bottom") | ||||||
| 
 | 
 | ||||||
| @@ -50,16 +48,12 @@ class PaneMixin: | |||||||
|             self._save_state(state, pane_index) |             self._save_state(state, pane_index) | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         child = None |         child = pane.get_child1() if pane_index in [1, 3] else pane.get_child2() | ||||||
|         if pane_index in [1, 3]: |  | ||||||
|             child = pane.get_child1() |  | ||||||
|         elif pane_index in [2, 4]: |  | ||||||
|             child = pane.get_child2() |  | ||||||
| 
 | 
 | ||||||
|         self.toggle_pane(child) |         self.toggle_pane(child) | ||||||
|         self._save_state(state, pane_index) |         self._save_state(state, pane_index) | ||||||
| 
 | 
 | ||||||
|     def _save_state(self, state, pane_index): |     def _save_state(self, state, pane_index): | ||||||
|         window = self.window_controller.get_window_by_index(pane_index - 1) |         window = self.fm_controller.get_window_by_index(pane_index - 1) | ||||||
|         window.isHidden = state |         window.set_is_hidden(state) | ||||||
|         self.window_controller.save_state() |         self.fm_controller.save_state() | ||||||
| @@ -0,0 +1,202 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .widget_mixin import WidgetMixin | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TabMixin(WidgetMixin): | ||||||
|  |     """docstring for TabMixin""" | ||||||
|  |  | ||||||
|  |     def create_tab(self, wid, path=None): | ||||||
|  |         notebook    = self.builder.get_object(f"window_{wid}") | ||||||
|  |         path_entry  = self.builder.get_object(f"path_entry") | ||||||
|  |         tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") | ||||||
|  |         tab.logger  = self.logger | ||||||
|  |  | ||||||
|  |         tab.set_wid(wid) | ||||||
|  |         if path: tab.set_path(path) | ||||||
|  |  | ||||||
|  |         tab_widget    = self.create_tab_widget(tab) | ||||||
|  |         scroll, store = self.create_icon_grid_widget(tab, wid) | ||||||
|  |         # TODO: Fix global logic to make the below work too | ||||||
|  |         # scroll, store = self.create_icon_tree_widget(tab, wid) | ||||||
|  |         index         = notebook.append_page(scroll, tab_widget) | ||||||
|  |  | ||||||
|  |         self.fm_controller.set__wid_and_tid(wid, tab.get_id()) | ||||||
|  |         path_entry.set_text(tab.get_current_directory()) | ||||||
|  |         notebook.show_all() | ||||||
|  |         notebook.set_current_page(index) | ||||||
|  |  | ||||||
|  |         ctx = notebook.get_style_context() | ||||||
|  |         ctx.add_class("notebook-unselected-focus") | ||||||
|  |         notebook.set_tab_reorderable(scroll, True) | ||||||
|  |         self.load_store(tab, store) | ||||||
|  |         self.set_window_title() | ||||||
|  |         self.set_file_watcher(tab) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def close_tab(self, button, eve=None): | ||||||
|  |         notebook = button.get_parent().get_parent() | ||||||
|  |         wid      = int(notebook.get_name()[-1]) | ||||||
|  |         tid      = self.get_id_from_tab_box(button.get_parent()) | ||||||
|  |         scroll   = self.builder.get_object(f"{wid}|{tid}") | ||||||
|  |         page     = notebook.page_num(scroll) | ||||||
|  |         tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |         watcher  = tab.get_dir_watcher() | ||||||
|  |  | ||||||
|  |         watcher.cancel() | ||||||
|  |         self.get_fm_window(wid).delete_tab_by_id(tid) | ||||||
|  |         notebook.remove_page(page) | ||||||
|  |         self.fm_controller.save_state() | ||||||
|  |         self.set_window_title() | ||||||
|  |  | ||||||
|  |     def on_tab_reorder(self, child, page_num, new_index): | ||||||
|  |         wid, tid = page_num.get_name().split("|") | ||||||
|  |         window   = self.get_fm_window(wid) | ||||||
|  |         tab      = None | ||||||
|  |  | ||||||
|  |         for i, tab in enumerate(window.get_all_tabs()): | ||||||
|  |             if tab.get_id() == tid: | ||||||
|  |                 _tab    = window.get_tab_by_id(tid) | ||||||
|  |                 watcher = _tab.get_dir_watcher() | ||||||
|  |                 watcher.cancel() | ||||||
|  |                 window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i)) | ||||||
|  |  | ||||||
|  |         tab = window.get_tab_by_id(tid) | ||||||
|  |         self.set_file_watcher(tab) | ||||||
|  |         self.fm_controller.save_state() | ||||||
|  |  | ||||||
|  |     def on_tab_switch_update(self, notebook, content=None, index=None): | ||||||
|  |         self.selected_files.clear() | ||||||
|  |         wid, tid = content.get_children()[0].get_name().split("|") | ||||||
|  |         self.fm_controller.set__wid_and_tid(wid, tid) | ||||||
|  |         self.set_path_text(wid, tid) | ||||||
|  |         self.set_window_title() | ||||||
|  |  | ||||||
|  |     def get_id_from_tab_box(self, tab_box): | ||||||
|  |         return tab_box.get_children()[2].get_text() | ||||||
|  |  | ||||||
|  |     def get_tab_label(self, notebook, icon_grid): | ||||||
|  |         return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] | ||||||
|  |  | ||||||
|  |     def get_tab_close(self, notebook, icon_grid): | ||||||
|  |         return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1] | ||||||
|  |  | ||||||
|  |     def get_tab_icon_grid_from_notebook(self, notebook): | ||||||
|  |         return notebook.get_children()[1].get_children()[0] | ||||||
|  |  | ||||||
|  |     def refresh_tab(data=None): | ||||||
|  |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|  |         tab.load_directory() | ||||||
|  |         self.load_store(tab, store) | ||||||
|  |  | ||||||
|  |     def update_tab(self, tab_label, tab, store, wid, tid): | ||||||
|  |         self.load_store(tab, store) | ||||||
|  |         self.set_path_text(wid, tid) | ||||||
|  |  | ||||||
|  |         char_width = len(tab.get_end_of_path()) | ||||||
|  |         tab_label.set_width_chars(char_width) | ||||||
|  |         tab_label.set_label(tab.get_end_of_path()) | ||||||
|  |         self.set_window_title() | ||||||
|  |         self.set_file_watcher(tab) | ||||||
|  |         self.fm_controller.save_state() | ||||||
|  |  | ||||||
|  |     def do_action_from_bar_controls(self, widget, eve=None): | ||||||
|  |         action    = widget.get_name() | ||||||
|  |         wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
|  |         notebook  = self.builder.get_object(f"window_{wid}") | ||||||
|  |         store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") | ||||||
|  |         tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |  | ||||||
|  |         if action == "create_tab": | ||||||
|  |             dir = tab.get_current_directory() | ||||||
|  |             self.create_tab(wid, dir) | ||||||
|  |             self.fm_controller.save_state() | ||||||
|  |             return | ||||||
|  |         if action == "go_up": | ||||||
|  |             tab.pop_from_path() | ||||||
|  |         if action == "go_home": | ||||||
|  |             tab.set_to_home() | ||||||
|  |         if action == "refresh_tab": | ||||||
|  |             tab.load_directory() | ||||||
|  |         if action == "path_entry": | ||||||
|  |             focused_obj = self.window.get_focus() | ||||||
|  |             dir         = f"{tab.get_current_directory()}/" | ||||||
|  |             path        = widget.get_text() | ||||||
|  |  | ||||||
|  |             if isinstance(focused_obj, Gtk.Entry): | ||||||
|  |                 path_menu_buttons  = self.builder.get_object("path_menu_buttons") | ||||||
|  |                 query              = widget.get_text().replace(dir, "") | ||||||
|  |                 files              = tab.get_files() + tab.get_hidden() | ||||||
|  |  | ||||||
|  |                 self.clear_children(path_menu_buttons) | ||||||
|  |                 show_path_menu = False | ||||||
|  |                 for file, hash in files: | ||||||
|  |                     if os.path.isdir(f"{dir}{file}"): | ||||||
|  |                         if query.lower() in file.lower(): | ||||||
|  |                             button = Gtk.Button(label=file) | ||||||
|  |                             button.show() | ||||||
|  |                             button.connect("clicked", self.set_path_entry) | ||||||
|  |                             path_menu_buttons.add(button) | ||||||
|  |                             show_path_menu = True | ||||||
|  |  | ||||||
|  |                 if not show_path_menu: | ||||||
|  |                     self.path_menu.popdown() | ||||||
|  |                 else: | ||||||
|  |                     self.path_menu.popup() | ||||||
|  |                     widget.grab_focus_without_selecting() | ||||||
|  |                     widget.set_position(-1) | ||||||
|  |  | ||||||
|  |             if path.endswith(".") or path == dir: | ||||||
|  |                 return | ||||||
|  |  | ||||||
|  |             if not tab.set_path(path): | ||||||
|  |                 return | ||||||
|  |  | ||||||
|  |         self.update_tab(tab_label, tab, store, wid, tid) | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             widget.grab_focus_without_selecting() | ||||||
|  |             widget.set_position(-1) | ||||||
|  |         except Exception as e: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     def set_path_entry(self, button=None, eve=None): | ||||||
|  |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|  |         path       = f"{tab.get_current_directory()}/{button.get_label()}" | ||||||
|  |         path_entry = self.builder.get_object("path_entry") | ||||||
|  |         path_entry.set_text(path) | ||||||
|  |         path_entry.grab_focus_without_selecting() | ||||||
|  |         path_entry.set_position(-1) | ||||||
|  |         self.path_menu.popdown() | ||||||
|  |  | ||||||
|  |     def keyboard_close_tab(self): | ||||||
|  |         wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
|  |         notebook  = self.builder.get_object(f"window_{wid}") | ||||||
|  |         scroll    = self.builder.get_object(f"{wid}|{tid}") | ||||||
|  |         page      = notebook.page_num(scroll) | ||||||
|  |         tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |         watcher   = tab.get_dir_watcher() | ||||||
|  |         watcher.cancel() | ||||||
|  |  | ||||||
|  |         self.get_fm_window(wid).delete_tab_by_id(tid) | ||||||
|  |         notebook.remove_page(page) | ||||||
|  |         self.fm_controller.save_state() | ||||||
|  |         self.set_window_title() | ||||||
|  |  | ||||||
|  |     def show_hide_hidden_files(self): | ||||||
|  |         wid, tid = self.fm_controller.get_active_wid_and_tid() | ||||||
|  |         tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|  |         tab.set_hiding_hidden(not tab.is_hiding_hidden()) | ||||||
|  |         tab.load_directory() | ||||||
|  |         self.builder.get_object("refresh_tab").released() | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os | import os, time, threading | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| import gi | import gi | ||||||
| @@ -9,9 +9,15 @@ from gi.repository import Gtk, GObject, GLib, Gio | |||||||
| # Application imports | # Application imports | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WidgetFileActionMixin: | class WidgetFileActionMixin: | ||||||
|  |     """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"]: | ||||||
|             if abs(num) < 1024.0: |             if abs(num) < 1024.0: | ||||||
| @@ -34,28 +40,25 @@ class WidgetFileActionMixin: | |||||||
|         return size |         return size | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def set_file_watcher(self, view): |     def set_file_watcher(self, tab): | ||||||
|         if view.get_dir_watcher(): |         if tab.get_dir_watcher(): | ||||||
|             watcher = view.get_dir_watcher() |             watcher = tab.get_dir_watcher() | ||||||
|             watcher.cancel() |             watcher.cancel() | ||||||
|             if debug: |             if debug: | ||||||
|                 print(f"Watcher Is Cancelled:  {watcher.is_cancelled()}") |                 print(f"Watcher Is Cancelled:  {watcher.is_cancelled()}") | ||||||
| 
 | 
 | ||||||
|         cur_dir = view.get_current_directory() |         cur_dir = tab.get_current_directory() | ||||||
|         # Temp updating too much with current events we are checking for. |  | ||||||
|         # Seems to cause invalid iter errors in WidbetMixin > update_store |  | ||||||
|         if cur_dir == "/tmp": |  | ||||||
|             watcher = None |  | ||||||
|             return |  | ||||||
| 
 | 
 | ||||||
|         dir_watcher  = Gio.File.new_for_path(cur_dir) \ |         dir_watcher  = Gio.File.new_for_path(cur_dir) \ | ||||||
|                                 .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) |                                 .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) | ||||||
| 
 | 
 | ||||||
|         wid = view.get_wid() |         wid = tab.get_wid() | ||||||
|         tid = view.get_tab_id() |         tid = tab.get_id() | ||||||
|         dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) |         dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) | ||||||
|         view.set_dir_watcher(dir_watcher) |         tab.set_dir_watcher(dir_watcher) | ||||||
| 
 | 
 | ||||||
|  |     # NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab. | ||||||
|  |     #       Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency | ||||||
|     def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): |     def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None): | ||||||
|         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, | ||||||
| @@ -63,18 +66,47 @@ class WidgetFileActionMixin: | |||||||
|                 if debug: |                 if debug: | ||||||
|                     print(eve_type) |                     print(eve_type) | ||||||
| 
 | 
 | ||||||
|                 wid, tid  = data[0].split("|") |                 if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: | ||||||
|  |                     self.update_on_soft_lock_end(data[0]) | ||||||
|  |                 elif data[0] in self.soft_update_lock.keys(): | ||||||
|  |                     self.soft_update_lock[data[0]]["last_update_time"] = time.time() | ||||||
|  |                 else: | ||||||
|  |                     self.soft_lock_countdown(data[0]) | ||||||
|  | 
 | ||||||
|  |     @threaded | ||||||
|  |     def soft_lock_countdown(self, tab_widget): | ||||||
|  |         self.soft_update_lock[tab_widget] = { "last_update_time": time.time()} | ||||||
|  | 
 | ||||||
|  |         lock = True | ||||||
|  |         while lock: | ||||||
|  |             time.sleep(0.6) | ||||||
|  |             last_update_time = self.soft_update_lock[tab_widget]["last_update_time"] | ||||||
|  |             current_time     = time.time() | ||||||
|  |             if (current_time - last_update_time) > 0.6: | ||||||
|  |                 lock = False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         self.soft_update_lock.pop(tab_widget, None) | ||||||
|  |         GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def update_on_soft_lock_end(self, tab_widget): | ||||||
|  |         wid, tid  = tab_widget.split("|") | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |         notebook  = self.builder.get_object(f"window_{wid}") | ||||||
|                 view      = self.get_fm_window(wid).get_view_by_id(tid) |         tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|                 iconview  = self.builder.get_object(f"{wid}|{tid}|iconview") |         icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid") | ||||||
|                 store     = iconview.get_model() |         store     = icon_grid.get_model() | ||||||
|                 _store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |         _store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") | ||||||
| 
 | 
 | ||||||
|                 view.load_directory() |         tab.load_directory() | ||||||
|                 self.load_store(view, store) |         self.load_store(tab, store) | ||||||
|                 tab_label.set_label(view.get_end_of_path()) |  | ||||||
|                 self.set_bottom_labels(view) |  | ||||||
| 
 | 
 | ||||||
|  |         tab_widget_label.set_label(tab.get_end_of_path()) | ||||||
|  | 
 | ||||||
|  |         _wid, _tid, _tab, _icon_grid, _store = self.get_current_state() | ||||||
|  | 
 | ||||||
|  |         if [wid, tid] in [_wid, _tid]: | ||||||
|  |             self.set_bottom_labels(tab) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def popup_search_files(self, wid, keyname): |     def popup_search_files(self, wid, keyname): | ||||||
| @@ -86,43 +118,43 @@ class WidgetFileActionMixin: | |||||||
| 
 | 
 | ||||||
|     def do_file_search(self, widget, eve=None): |     def do_file_search(self, widget, eve=None): | ||||||
|         query = widget.get_text() |         query = widget.get_text() | ||||||
|         self.search_iconview.unselect_all() |         self.search_icon_grid.unselect_all() | ||||||
|         for i, file in enumerate(self.search_view.files): |         for i, file in enumerate(self.search_tab.get_files()): | ||||||
|             if query and query in file.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_iconview.select_path(path) |                 self.search_icon_grid.select_path(path) | ||||||
| 
 | 
 | ||||||
|         items = self.search_iconview.get_selected_items() |         items = self.search_icon_grid.get_selected_items() | ||||||
|         if len(items) == 1: |         if len(items) == 1: | ||||||
|             self.search_iconview.scroll_to_path(items[0], True, 0.5, 0.5) |             self.search_icon_grid.scroll_to_path(items[0], True, 0.5, 0.5) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def open_files(self): |     def open_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
| 
 | 
 | ||||||
|         for file in uris: |         for file in uris: | ||||||
|             view.open_file_locally(file) |             tab.open_file_locally(file) | ||||||
| 
 | 
 | ||||||
|     def open_with_files(self, appchooser_widget): |     def open_with_files(self, appchooser_widget): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         app_info  = appchooser_widget.get_app_info() |         app_info  = appchooser_widget.get_app_info() | ||||||
|         uris      = self.format_to_uris(store, wid, tid, self.selected_files) |         uris      = self.format_to_uris(store, wid, tid, self.selected_files) | ||||||
| 
 | 
 | ||||||
|         view.app_chooser_exec(app_info, uris) |         tab.app_chooser_exec(app_info, uris) | ||||||
| 
 | 
 | ||||||
|     def execute_files(self, in_terminal=False): |     def execute_files(self, in_terminal=False): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         paths       = self.format_to_uris(store, wid, tid, self.selected_files, True) |         paths       = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         current_dir = view.get_current_directory() |         current_dir = tab.get_current_directory() | ||||||
|         command     = None |         command     = None | ||||||
| 
 | 
 | ||||||
|         for path in paths: |         for path in paths: | ||||||
|             command = f"exec '{path}'" if not in_terminal else f"{view.terminal_app} -e '{path}'" |             command = f"exec '{path}'" if not in_terminal else f"{tab.terminal_app} -e '{path}'" | ||||||
|             view.execute(command, start_dir=view.get_current_directory(), use_os_system=False) |             tab.execute(command, start_dir=tab.get_current_directory(), use_os_system=False) | ||||||
| 
 | 
 | ||||||
|     def archive_files(self, archiver_dialogue): |     def archive_files(self, archiver_dialogue): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         paths = self.format_to_uris(store, wid, tid, self.selected_files, True) |         paths = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
| 
 | 
 | ||||||
|         save_target = archiver_dialogue.get_filename(); |         save_target = archiver_dialogue.get_filename(); | ||||||
| @@ -130,14 +162,14 @@ class WidgetFileActionMixin: | |||||||
|         pre_command = self.arc_command_buffer.get_text(sItr, eItr, False) |         pre_command = self.arc_command_buffer.get_text(sItr, eItr, False) | ||||||
|         pre_command = pre_command.replace("%o", save_target) |         pre_command = pre_command.replace("%o", save_target) | ||||||
|         pre_command = pre_command.replace("%N", ' '.join(paths)) |         pre_command = pre_command.replace("%N", ' '.join(paths)) | ||||||
|         command     = f"{view.terminal_app} -e '{pre_command}'" |         command     = f"{tab.terminal_app} -e '{pre_command}'" | ||||||
| 
 | 
 | ||||||
|         view.execute(command, start_dir=None, use_os_system=True) |         tab.execute(command, start_dir=None, use_os_system=True) | ||||||
| 
 | 
 | ||||||
|     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") | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris         = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris         = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
| 
 | 
 | ||||||
|         for uri in uris: |         for uri in uris: | ||||||
| @@ -154,7 +186,7 @@ class WidgetFileActionMixin: | |||||||
|                 break |                 break | ||||||
| 
 | 
 | ||||||
|             rname_to = rename_input.get_text().strip() |             rname_to = rename_input.get_text().strip() | ||||||
|             target   = f"{view.get_current_directory()}/{rname_to}" |             target   = f"{tab.get_current_directory()}/{rname_to}" | ||||||
|             self.handle_files([uri], "rename", target) |             self.handle_files([uri], "rename", target) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @@ -164,27 +196,27 @@ class WidgetFileActionMixin: | |||||||
|         self.selected_files.clear() |         self.selected_files.clear() | ||||||
| 
 | 
 | ||||||
|     def cut_files(self): |     def cut_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         self.to_cut_files = uris |         self.to_cut_files = uris | ||||||
| 
 | 
 | ||||||
|     def copy_files(self): |     def copy_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         self.to_copy_files = uris |         self.to_copy_files = uris | ||||||
| 
 | 
 | ||||||
|     def paste_files(self): |     def paste_files(self): | ||||||
|         wid, tid  = self.window_controller.get_active_data() |         wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
|         view      = self.get_fm_window(wid).get_view_by_id(tid) |         tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         target    = f"{view.get_current_directory()}" |         target    = f"{tab.get_current_directory()}" | ||||||
| 
 | 
 | ||||||
|         if len(self.to_copy_files) > 0: |         if self.to_copy_files: | ||||||
|             self.handle_files(self.to_copy_files, "copy", target) |             self.handle_files(self.to_copy_files, "copy", target) | ||||||
|         elif len(self.to_cut_files) > 0: |         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): |     def delete_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris     = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris     = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         response = None |         response = None | ||||||
| 
 | 
 | ||||||
| @@ -199,7 +231,7 @@ class WidgetFileActionMixin: | |||||||
|                 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: | ||||||
|                     view.delete_file( file.get_path() ) |                     tab.delete_file( file.get_path() ) | ||||||
|                 else: |                 else: | ||||||
|                     file.delete(cancellable=None) |                     file.delete(cancellable=None) | ||||||
|             else: |             else: | ||||||
| @@ -207,13 +239,13 @@ class WidgetFileActionMixin: | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def trash_files(self): |     def trash_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris      = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris      = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         for uri in uris: |         for uri in uris: | ||||||
|             self.trashman.trash(uri, False) |             self.trashman.trash(uri, False) | ||||||
| 
 | 
 | ||||||
|     def restore_trash_files(self): |     def restore_trash_files(self): | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |         wid, tid, tab, icon_grid, store = self.get_current_state() | ||||||
|         uris      = self.format_to_uris(store, wid, tid, self.selected_files, True) |         uris      = self.format_to_uris(store, wid, tid, self.selected_files, True) | ||||||
|         for uri in uris: |         for uri in uris: | ||||||
|             self.trashman.restore(filename=uri.split("/")[-1], verbose=False) |             self.trashman.restore(filename=uri.split("/")[-1], verbose=False) | ||||||
| @@ -227,9 +259,9 @@ class WidgetFileActionMixin: | |||||||
|         file_name   = fname_field.get_text().strip() |         file_name   = fname_field.get_text().strip() | ||||||
|         type        = self.builder.get_object("context_menu_type_toggle").get_state() |         type        = self.builder.get_object("context_menu_type_toggle").get_state() | ||||||
| 
 | 
 | ||||||
|         wid, tid    = self.window_controller.get_active_data() |         wid, tid    = self.fm_controller.get_active_wid_and_tid() | ||||||
|         view        = self.get_fm_window(wid).get_view_by_id(tid) |         tab         = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         target      = f"{view.get_current_directory()}" |         target      = f"{tab.get_current_directory()}" | ||||||
| 
 | 
 | ||||||
|         if file_name: |         if file_name: | ||||||
|             path = f"{target}/{file_name}" |             path = f"{target}/{file_name}" | ||||||
| @@ -294,9 +326,9 @@ class WidgetFileActionMixin: | |||||||
|                         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.window_controller.get_active_data() |                             wid, tid = self.fm_controller.get_active_wid_and_tid() | ||||||
|                             view      = self.get_fm_window(wid).get_view_by_id(tid) |                             tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|                             view.delete_file( _file.get_path() ) |                             tab.delete_file( _file.get_path() ) | ||||||
|                         else: |                         else: | ||||||
|                             _file.delete(cancellable=None) |                             _file.delete(cancellable=None) | ||||||
| 
 | 
 | ||||||
| @@ -321,16 +353,16 @@ class WidgetFileActionMixin: | |||||||
| 
 | 
 | ||||||
|                 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.window_controller.get_active_data() |                     wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
|                     view      = self.get_fm_window(wid).get_view_by_id(tid) |                     tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|                     fPath     = file.get_path() |                     fPath     = file.get_path() | ||||||
|                     tPath     = target.get_path() |                     tPath     = target.get_path() | ||||||
|                     state     = True |                     state     = True | ||||||
| 
 | 
 | ||||||
|                     if action == "copy": |                     if action == "copy": | ||||||
|                         view.copy_file(fPath, tPath) |                         tab.copy_file(fPath, tPath) | ||||||
|                     if action == "move" or action == "rename": |                     if action == "move" or action == "rename": | ||||||
|                         view.move_file(fPath, tPath) |                         tab.move_file(fPath, tPath) | ||||||
|                 else: |                 else: | ||||||
|                     if action == "copy": |                     if action == "copy": | ||||||
|                         file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) |                         file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) | ||||||
| @@ -20,29 +20,31 @@ def threaded(fn): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WidgetMixin: | class WidgetMixin: | ||||||
|     def load_store(self, view, store, save_state=False): |     """docstring for WidgetMixin""" | ||||||
|  | 
 | ||||||
|  |     def load_store(self, tab, store, save_state=False): | ||||||
|         store.clear() |         store.clear() | ||||||
|         dir   = view.get_current_directory() |         dir   = tab.get_current_directory() | ||||||
|         files = view.get_files() |         files = tab.get_files() | ||||||
| 
 | 
 | ||||||
|         for i, file in enumerate(files): |         for i, file in enumerate(files): | ||||||
|             store.append([None, file[0]]) |             store.append([None, file[0]]) | ||||||
|             self.create_icon(i, view, 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: | ||||||
|             self.window_controller.save_state() |             self.fm_controller.save_state() | ||||||
| 
 | 
 | ||||||
|     @threaded |     @threaded | ||||||
|     def create_icon(self, i, view, store, dir, file): |     def create_icon(self, i, tab, store, dir, file): | ||||||
|         icon  = view.create_icon(dir, file) |         icon  = tab.create_icon(dir, file) | ||||||
|         fpath = f"{dir}/{file}" |         fpath = f"{dir}/{file}" | ||||||
|         GLib.idle_add(self.update_store, (i, store, icon, view, fpath,)) |         GLib.idle_add(self.update_store, (i, store, icon, tab, fpath,)) | ||||||
| 
 | 
 | ||||||
|     # NOTE: Might need to keep an eye on this throwing invalid iters when too |     # NOTE: Might need to keep an eye on this throwing invalid iters when too | ||||||
|     #       many updates are happening to a folder. Example: /tmp |     #       many updates are happening to a folder. Example: /tmp | ||||||
|     def update_store(self, item): |     def update_store(self, item): | ||||||
|         i, store, icon, view, fpath = item |         i, store, icon, tab, fpath = item | ||||||
|         itr = None |         itr = None | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
| @@ -58,12 +60,12 @@ class WidgetMixin: | |||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
|         if not icon: |         if not icon: | ||||||
|             icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) |             icon = self.get_system_thumbnail(fpath, tab.SYS_ICON_WH[0]) | ||||||
|             if not icon: |             if not icon: | ||||||
|                 if fpath.endswith(".gif"): |                 if fpath.endswith(".gif"): | ||||||
|                     icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) |                     icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) | ||||||
|                 else: |                 else: | ||||||
|                     icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON) |                     icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) | ||||||
| 
 | 
 | ||||||
|         store.set_value(itr, 0, icon) |         store.set_value(itr, 0, icon) | ||||||
| 
 | 
 | ||||||
| @@ -88,31 +90,29 @@ class WidgetMixin: | |||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |     def create_tab_widget(self, tab): | ||||||
| 
 |         tab_widget = Gtk.ButtonBox() | ||||||
|     def create_tab_widget(self, view): |  | ||||||
|         tab   = Gtk.ButtonBox() |  | ||||||
|         label = Gtk.Label() |         label = Gtk.Label() | ||||||
|         tid   = Gtk.Label() |         tid   = Gtk.Label() | ||||||
|         close = Gtk.Button() |         close = Gtk.Button() | ||||||
|         icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE) |         icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE) | ||||||
| 
 | 
 | ||||||
|         label.set_label(f"{view.get_end_of_path()}") |         label.set_label(f"{tab.get_end_of_path()}") | ||||||
|         label.set_width_chars(len(view.get_end_of_path())) |         label.set_width_chars(len(tab.get_end_of_path())) | ||||||
|         label.set_xalign(0.0) |         label.set_xalign(0.0) | ||||||
|         tid.set_label(f"{view.id}") |         tid.set_label(f"{tab.get_id()}") | ||||||
| 
 | 
 | ||||||
|         close.add(icon) |         close.add(icon) | ||||||
|         tab.add(label) |         tab_widget.add(label) | ||||||
|         tab.add(close) |         tab_widget.add(close) | ||||||
|         tab.add(tid) |         tab_widget.add(tid) | ||||||
| 
 | 
 | ||||||
|         close.connect("released", self.close_tab) |         close.connect("released", self.close_tab) | ||||||
|         tab.show_all() |         tab_widget.show_all() | ||||||
|         tid.hide() |         tid.hide() | ||||||
|         return tab |         return tab_widget | ||||||
| 
 | 
 | ||||||
|     def create_grid_iconview_widget(self, view, wid): |     def create_icon_grid_widget(self, tab, wid): | ||||||
|         scroll = Gtk.ScrolledWindow() |         scroll = Gtk.ScrolledWindow() | ||||||
|         grid   = Gtk.IconView() |         grid   = Gtk.IconView() | ||||||
|         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) |         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) | ||||||
| @@ -133,8 +133,6 @@ class WidgetMixin: | |||||||
| 
 | 
 | ||||||
|         grid.connect("button_release_event", self.grid_icon_single_click) |         grid.connect("button_release_event", self.grid_icon_single_click) | ||||||
|         grid.connect("item-activated",       self.grid_icon_double_click) |         grid.connect("item-activated",       self.grid_icon_double_click) | ||||||
|         # grid.connect("toggle-cursor-item", self.grid_cursor_toggled) |  | ||||||
|         # grid.connect("notify", self.grid_cursor_toggled) |  | ||||||
|         grid.connect("selection-changed",    self.grid_set_selected_items) |         grid.connect("selection-changed",    self.grid_set_selected_items) | ||||||
|         grid.connect("drag-data-get",        self.grid_on_drag_set) |         grid.connect("drag-data-get",        self.grid_on_drag_set) | ||||||
|         grid.connect("drag-data-received",   self.grid_on_drag_data_received) |         grid.connect("drag-data-received",   self.grid_on_drag_data_received) | ||||||
| @@ -150,17 +148,16 @@ class WidgetMixin: | |||||||
| 
 | 
 | ||||||
|         grid.show_all() |         grid.show_all() | ||||||
|         scroll.add(grid) |         scroll.add(grid) | ||||||
|         grid.set_name(f"{wid}|{view.id}") |         grid.set_name(f"{wid}|{tab.get_id()}") | ||||||
|         scroll.set_name(f"{wid}|{view.id}") |         scroll.set_name(f"{wid}|{tab.get_id()}") | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}|iconview", grid) |         self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}", scroll) |         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) | ||||||
|         return scroll, store |         return scroll, store | ||||||
| 
 | 
 | ||||||
|     def create_grid_treeview_widget(self, view, wid): |     def create_icon_tree_widget(self, tab, wid): | ||||||
|         scroll = Gtk.ScrolledWindow() |         scroll = Gtk.ScrolledWindow() | ||||||
|         grid   = Gtk.TreeView() |         grid   = Gtk.TreeView() | ||||||
|         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) |         store  = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) | ||||||
|         # store  = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) |  | ||||||
|         column = Gtk.TreeViewColumn("Icons") |         column = Gtk.TreeViewColumn("Icons") | ||||||
|         icon   = Gtk.CellRendererPixbuf() |         icon   = Gtk.CellRendererPixbuf() | ||||||
|         name   = Gtk.CellRendererText() |         name   = Gtk.CellRendererText() | ||||||
| @@ -199,23 +196,23 @@ class WidgetMixin: | |||||||
| 
 | 
 | ||||||
|         grid.show_all() |         grid.show_all() | ||||||
|         scroll.add(grid) |         scroll.add(grid) | ||||||
|         grid.set_name(f"{wid}|{view.id}") |         grid.set_name(f"{wid}|{tab.get_id()}") | ||||||
|         scroll.set_name(f"{wid}|{view.id}") |         scroll.set_name(f"{wid}|{tab.get_id()}") | ||||||
|         grid.columns_autosize() |         grid.columns_autosize() | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}", scroll) |         self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) | ||||||
|         return scroll, store |         return scroll, store | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def get_store_and_label_from_notebook(self, notebook, _name): |     def get_store_and_label_from_notebook(self, notebook, _name): | ||||||
|         icon_view = None |         icon_grid = None | ||||||
|         tab_label = None |         tab_label = None | ||||||
|         store     = None |         store     = None | ||||||
| 
 | 
 | ||||||
|         for obj in notebook.get_children(): |         for obj in notebook.get_children(): | ||||||
|             icon_view = obj.get_children()[0] |             icon_grid = obj.get_children()[0] | ||||||
|             name      =  icon_view.get_name() |             name      = icon_grid.get_name() | ||||||
|             if name == _name: |             if name == _name: | ||||||
|                 store = icon_view.get_model() |                 store     = icon_grid.get_model() | ||||||
|                 tab_label = notebook.get_tab_label(obj).get_children()[0] |                 tab_label = notebook.get_tab_label(obj).get_children()[0] | ||||||
| 
 | 
 | ||||||
|         return store, tab_label |         return store, tab_label | ||||||
| @@ -9,58 +9,56 @@ gi.require_version('Gdk', '3.0') | |||||||
| from gi.repository import Gdk, Gio | from gi.repository import Gdk, Gio | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from . import TabMixin, WidgetMixin | from .tab_mixin import TabMixin | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class WindowMixin(TabMixin): | class WindowMixin(TabMixin): | ||||||
|     """docstring for WindowMixin""" |     """docstring for WindowMixin""" | ||||||
|     def generate_windows(self, data = None): | 
 | ||||||
|         if data: |     def generate_windows(self, session_json = None): | ||||||
|             for j, value in enumerate(data): |         if session_json: | ||||||
|  |             for j, value in enumerate(session_json): | ||||||
|                 i = j + 1 |                 i = j + 1 | ||||||
|                 isHidden = True if value[0]["window"]["isHidden"] == "True" else False |                 notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") | ||||||
|                 object   = self.builder.get_object(f"tggl_notebook_{i}") |                 is_hidden = True if value[0]["window"]["isHidden"] == "True" else False | ||||||
|                 views    = value[0]["window"]["views"] |                 tabs      = value[0]["window"]["tabs"] | ||||||
|                 self.window_controller.create_window() |                 self.fm_controller.create_window() | ||||||
|                 object.set_active(True) |                 notebook_tggl_button.set_active(True) | ||||||
| 
 | 
 | ||||||
|                 for view in views: |                 for tab in tabs: | ||||||
|                     self.create_new_view_notebook(None, i, view) |                     self.create_new_tab_notebook(None, i, tab) | ||||||
| 
 | 
 | ||||||
|                 if isHidden: |                 if is_hidden: | ||||||
|                     self.toggle_notebook_pane(object) |                     self.toggle_notebook_pane(notebook_tggl_button) | ||||||
| 
 | 
 | ||||||
|             try: |             try: | ||||||
|                 if not self.is_pane4_hidden: |                 if not self.is_pane4_hidden: | ||||||
|                     icon_view = self.window4.get_children()[1].get_children()[0] |                     icon_grid = self.window4.get_children()[1].get_children()[0] | ||||||
|                     icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |  | ||||||
|                 elif not self.is_pane3_hidden: |                 elif not self.is_pane3_hidden: | ||||||
|                     icon_view = self.window3.get_children()[1].get_children()[0] |                     icon_grid = self.window3.get_children()[1].get_children()[0] | ||||||
|                     icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |  | ||||||
|                 elif not self.is_pane2_hidden: |                 elif not self.is_pane2_hidden: | ||||||
|                     icon_view = self.window2.get_children()[1].get_children()[0] |                     icon_grid = self.window2.get_children()[1].get_children()[0] | ||||||
|                     icon_view.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) |  | ||||||
|                 elif not self.is_pane1_hidden: |                 elif not self.is_pane1_hidden: | ||||||
|                     icon_view = self.window1.get_children()[1].get_children()[0] |                     icon_grid = self.window1.get_children()[1].get_children()[0] | ||||||
|                     icon_view.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 Exception 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: | ||||||
|             for j in range(0, 4): |             for j in range(0, 4): | ||||||
|                 i = j + 1 |                 i = j + 1 | ||||||
|                 self.window_controller.create_window() |                 self.fm_controller.create_window() | ||||||
|                 self.create_new_view_notebook(None, i, None) |                 self.create_new_tab_notebook(None, i, None) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def get_fm_window(self, wid): |     def get_fm_window(self, wid): | ||||||
|         return self.window_controller.get_window_by_nickname(f"window_{wid}") |         return self.fm_controller.get_window_by_nickname(f"window_{wid}") | ||||||
| 
 | 
 | ||||||
|     def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False): |     def format_to_uris(self, store, wid, tid, treePaths, use_just_path=False): | ||||||
|         view = self.get_fm_window(wid).get_view_by_id(tid) |         tab  = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         dir  = view.get_current_directory() |         dir  = tab.get_current_directory() | ||||||
|         uris = [] |         uris = [] | ||||||
| 
 | 
 | ||||||
|         for path in treePaths: |         for path in treePaths: | ||||||
| @@ -78,10 +76,10 @@ class WindowMixin(TabMixin): | |||||||
|         return uris |         return uris | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def set_bottom_labels(self, view): |     def set_bottom_labels(self, tab): | ||||||
|         _wid, _tid, _view, iconview, store = self.get_current_state() |         _wid, _tid, _tab, icon_grid, store = self.get_current_state() | ||||||
|         selected_files       = iconview.get_selected_items() |         selected_files       = icon_grid.get_selected_items() | ||||||
|         current_directory    = view.get_current_directory() |         current_directory    = tab.get_current_directory() | ||||||
|         path_file            = Gio.File.new_for_path(current_directory) |         path_file            = Gio.File.new_for_path(current_directory) | ||||||
|         mount_file           = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) |         mount_file           = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) | ||||||
|         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")) ) | ||||||
| @@ -96,8 +94,8 @@ class WindowMixin(TabMixin): | |||||||
| 
 | 
 | ||||||
|         # 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}") | ||||||
|         self.bottom_path_label.set_label(view.get_current_directory()) |         self.bottom_path_label.set_label(tab.get_current_directory()) | ||||||
|         if len(selected_files) > 0: |         if selected_files: | ||||||
|             uris          = self.format_to_uris(store, _wid, _tid, selected_files, True) |             uris          = self.format_to_uris(store, _wid, _tid, selected_files, True) | ||||||
|             combined_size = 0 |             combined_size = 0 | ||||||
|             for uri in uris: |             for uri in uris: | ||||||
| @@ -113,29 +111,29 @@ class WindowMixin(TabMixin): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|             formatted_size = self.sizeof_fmt(combined_size) |             formatted_size = self.sizeof_fmt(combined_size) | ||||||
|             if view.hide_hidden: |             if tab.is_hiding_hidden(): | ||||||
|                 self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})") |                 self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_files_count()} ({formatted_size})") | ||||||
|             else: |             else: | ||||||
|                 self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})") |                 self.bottom_path_label.set_label(f" {len(uris)} / {tab.get_not_hidden_count()} ({formatted_size})") | ||||||
| 
 | 
 | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         # If nothing selected |         # If nothing selected | ||||||
|         if view.hide_hidden: |         if tab.get_hidden(): | ||||||
|             if view.get_hidden_count() > 0: |             if tab.get_hidden_count() > 0: | ||||||
|                 self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)") |                 self.bottom_file_count_label.set_label(f"{tab.get_not_hidden_count()} visible ({tab.get_hidden_count()} hidden)") | ||||||
|             else: |             else: | ||||||
|                 self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") |                 self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") | ||||||
|         else: |         else: | ||||||
|             self.bottom_file_count_label.set_label(f"{view.get_files_count()} items") |             self.bottom_file_count_label.set_label(f"{tab.get_files_count()} items") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def set_window_title(self): |     def set_window_title(self): | ||||||
|         wid, tid = self.window_controller.get_active_data() |         wid, tid = self.fm_controller.get_active_wid_and_tid() | ||||||
|         notebook = self.builder.get_object(f"window_{wid}") |         notebook = self.builder.get_object(f"window_{wid}") | ||||||
|         view     = self.get_fm_window(wid).get_view_by_id(tid) |         tab      = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         dir      = view.get_current_directory() |         dir      = tab.get_current_directory() | ||||||
| 
 | 
 | ||||||
|         for _notebook in self.notebooks: |         for _notebook in self.notebooks: | ||||||
|             ctx = _notebook.get_style_context() |             ctx = _notebook.get_style_context() | ||||||
| @@ -146,72 +144,74 @@ class WindowMixin(TabMixin): | |||||||
|         ctx.remove_class("notebook-unselected-focus") |         ctx.remove_class("notebook-unselected-focus") | ||||||
|         ctx.add_class("notebook-selected-focus") |         ctx.add_class("notebook-selected-focus") | ||||||
| 
 | 
 | ||||||
|         self.window.set_title("SolarFM ~ " + dir) |         self.window.set_title(f"SolarFM ~ {dir}") | ||||||
|         self.set_bottom_labels(view) |         self.set_bottom_labels(tab) | ||||||
| 
 | 
 | ||||||
|     def set_path_text(self, wid, tid): |     def set_path_text(self, wid, tid): | ||||||
|         path_entry = self.builder.get_object("path_entry") |         path_entry = self.builder.get_object("path_entry") | ||||||
|         view       = self.get_fm_window(wid).get_view_by_id(tid) |         tab        = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
|         path_entry.set_text(view.get_current_directory()) |         path_entry.set_text(tab.get_current_directory()) | ||||||
| 
 | 
 | ||||||
|     def grid_set_selected_items(self, iconview): |     def grid_set_selected_items(self, icons_grid): | ||||||
|         self.selected_files = iconview.get_selected_items() |         self.selected_files = icons_grid.get_selected_items() | ||||||
| 
 | 
 | ||||||
|     def grid_cursor_toggled(self, iconview): |     def grid_cursor_toggled(self, icons_grid): | ||||||
|         print("wat...") |         print("wat...") | ||||||
| 
 | 
 | ||||||
|     def grid_icon_single_click(self, iconview, eve): |     def grid_icon_single_click(self, icons_grid, eve): | ||||||
|         try: |         try: | ||||||
|             self.path_menu.popdown() |             self.path_menu.popdown() | ||||||
|             wid, tid = iconview.get_name().split("|") |             wid, tid = icons_grid.get_name().split("|") | ||||||
|             self.window_controller.set_active_data(wid, tid) |             self.fm_controller.set__wid_and_tid(wid, tid) | ||||||
|             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.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(iconview) |                     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 Exception as e: | ||||||
|             print(repr(e)) |             print(repr(e)) | ||||||
|             self.display_message(self.error, f"{repr(e)}") |             self.display_message(self.error_color, f"{repr(e)}") | ||||||
| 
 | 
 | ||||||
|     def grid_icon_double_click(self, iconview, item, data=None): |     def grid_icon_double_click(self, icons_grid, item, data=None): | ||||||
|         try: |         try: | ||||||
|             if self.ctrlDown and self.shiftDown: |             if self.ctrl_down and self.shift_down: | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|                 self.execute_files(in_terminal=True) |                 self.execute_files(in_terminal=True) | ||||||
|                 return |                 return | ||||||
|             elif self.ctrlDown: |             elif self.ctrl_down: | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|                 self.execute_files() |                 self.execute_files() | ||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|             wid, tid, view, _iconview, store = self.get_current_state() |             wid, tid, tab, _icons_grid, store = self.get_current_state() | ||||||
|             notebook   = self.builder.get_object(f"window_{wid}") |             notebook   = self.builder.get_object(f"window_{wid}") | ||||||
|             tab_label  = self.get_tab_label(notebook, iconview) |             tab_label  = self.get_tab_label(notebook, icons_grid) | ||||||
| 
 | 
 | ||||||
|             fileName   = store[item][1] |             fileName   = store[item][1] | ||||||
|             dir        = view.get_current_directory() |             dir        = tab.get_current_directory() | ||||||
|             file       = f"{dir}/{fileName}" |             file       = f"{dir}/{fileName}" | ||||||
| 
 | 
 | ||||||
|             if isdir(file): |             if isdir(file): | ||||||
|                 view.set_path(file) |                 tab.set_path(file) | ||||||
|                 self.update_view(tab_label, view, store, wid, tid) |                 self.update_tab(tab_label, tab, store, wid, tid) | ||||||
|             else: |             else: | ||||||
|                 self.open_files() |                 self.open_files() | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             self.display_message(self.error, f"{repr(e)}") |             self.display_message(self.error_color, f"{repr(e)}") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def grid_on_drag_set(self, iconview, drag_context, data, info, time): |     def grid_on_drag_set(self, icons_grid, drag_context, data, info, time): | ||||||
|         action    = iconview.get_name() |         action    = icons_grid.get_name() | ||||||
|         wid, tid  = action.split("|") |         wid, tid  = action.split("|") | ||||||
|         store     = iconview.get_model() |         store     = icons_grid.get_model() | ||||||
|         treePaths = iconview.get_selected_items() |         treePaths = icons_grid.get_selected_items() | ||||||
|         # NOTE: Need URIs as URI format for DnD to work. Will strip 'file://' |         # NOTE: Need URIs as URI format for DnD to work. Will strip 'file://' | ||||||
|         # further down call chain when doing internal fm stuff. |         # further down call chain when doing internal fm stuff. | ||||||
|         uris      = self.format_to_uris(store, wid, tid, treePaths) |         uris      = self.format_to_uris(store, wid, tid, treePaths) | ||||||
| @@ -220,30 +220,30 @@ class WindowMixin(TabMixin): | |||||||
|         data.set_uris(uris) |         data.set_uris(uris) | ||||||
|         data.set_text(uris_text, -1) |         data.set_text(uris_text, -1) | ||||||
| 
 | 
 | ||||||
|     def grid_on_drag_motion(self, iconview, drag_context, x, y, data): |     def grid_on_drag_motion(self, icons_grid, drag_context, x, y, data): | ||||||
|         current   = '|'.join(self.window_controller.get_active_data()) |         current   = '|'.join(self.fm_controller.get_active_wid_and_tid()) | ||||||
|         target    = iconview.get_name() |         target    = icons_grid.get_name() | ||||||
|         wid, tid  = target.split("|") |         wid, tid  = target.split("|") | ||||||
|         store     = iconview.get_model() |         store     = icons_grid.get_model() | ||||||
|         treePath  = iconview.get_drag_dest_item().path |         treePath  = icons_grid.get_drag_dest_item().path | ||||||
| 
 | 
 | ||||||
|         if treePath: |         if treePath: | ||||||
|             uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "") |             uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "") | ||||||
|             self.override_drop_dest = uri if isdir(uri) else None |             self.override_drop_dest = uri if isdir(uri) else None | ||||||
| 
 | 
 | ||||||
|         if target not in current: |         if target not in current: | ||||||
|             self.window_controller.set_active_data(wid, tid) |             self.fm_controller.set__wid_and_tid(wid, tid) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): |     def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): | ||||||
|         if info == 80: |         if info == 80: | ||||||
|             wid, tid  = self.window_controller.get_active_data() |             wid, tid  = self.fm_controller.get_active_wid_and_tid() | ||||||
|             notebook  = self.builder.get_object(f"window_{wid}") |             notebook  = self.builder.get_object(f"window_{wid}") | ||||||
|             store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |             store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") | ||||||
|             view      = self.get_fm_window(wid).get_view_by_id(tid) |             tab       = self.get_fm_window(wid).get_tab_by_id(tid) | ||||||
| 
 | 
 | ||||||
|             uris = data.get_uris() |             uris = data.get_uris() | ||||||
|             dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest |             dest = f"{tab.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest | ||||||
|             if len(uris) == 0: |             if len(uris) == 0: | ||||||
|                 uris = data.get_text().split("\n") |                 uris = data.get_text().split("\n") | ||||||
| 
 | 
 | ||||||
| @@ -252,5 +252,5 @@ class WindowMixin(TabMixin): | |||||||
|                 self.move_files(uris, dest) |                 self.move_files(uris, dest) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def create_new_view_notebook(self, widget=None, wid=None, path=None): |     def create_new_tab_notebook(self, widget=None, wid=None, path=None): | ||||||
|         self.create_tab(wid, path) |         self.create_tab(wid, path) | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | # 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 | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Signals module | ||||||
|  | """ | ||||||
| @@ -6,12 +6,14 @@ | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IPCSignalsMixin: | class IPCSignalsMixin: | ||||||
|  |     """ 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(self) | ||||||
|         print(message) |         print(message) | ||||||
| 
 | 
 | ||||||
|     def handle_file_from_ipc(self, path): |     def handle_file_from_ipc(self, path): | ||||||
|         wid, tid   = self.window_controller.get_active_data() |         wid, tid   = self.fm_controller.get_active_wid_and_tid() | ||||||
|         notebook   = self.builder.get_object(f"window_{wid}") |         notebook   = self.builder.get_object(f"window_{wid}") | ||||||
|         if notebook.is_visible(): |         if notebook.is_visible(): | ||||||
|             self.create_tab(wid, path) |             self.create_tab(wid, path) | ||||||
| @@ -0,0 +1,114 @@ | |||||||
|  | # Python imports | ||||||
|  | import re | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | from gi.repository import Gtk, Gdk | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class KeyboardSignalsMixin: | ||||||
|  |     """ KeyboardSignalsMixin keyboard hooks controller. """ | ||||||
|  |  | ||||||
|  |     def unset_keys_and_data(self, widget=None, eve=None): | ||||||
|  |         self.ctrl_down    = False | ||||||
|  |         self.shift_down   = False | ||||||
|  |         self.alt_down     = False | ||||||
|  |         self.is_searching = False | ||||||
|  |  | ||||||
|  |     def global_key_press_controller(self, eve, user_data): | ||||||
|  |         keyname = Gdk.keyval_name(user_data.keyval).lower() | ||||||
|  |         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: | ||||||
|  |             if "control" in keyname: | ||||||
|  |                 self.ctrl_down    = True | ||||||
|  |             if "shift" in keyname: | ||||||
|  |                 self.shift_down   = True | ||||||
|  |             if "alt" in keyname: | ||||||
|  |                 self.alt_down     = True | ||||||
|  |  | ||||||
|  |     # NOTE: Yes, this should actually be mapped to some key controller setting | ||||||
|  |     #       file or something. Sue me. | ||||||
|  |     def global_key_release_controller(self, eve, user_data): | ||||||
|  |         keyname = Gdk.keyval_name(user_data.keyval).lower() | ||||||
|  |         if debug: | ||||||
|  |             print(f"global_key_release_controller > key > {keyname}") | ||||||
|  |  | ||||||
|  |         if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: | ||||||
|  |             if "control" in keyname: | ||||||
|  |                 self.ctrl_down    = False | ||||||
|  |             if "shift" in keyname: | ||||||
|  |                 self.shift_down   = False | ||||||
|  |             if "alt" in keyname: | ||||||
|  |                 self.alt_down     = False | ||||||
|  |  | ||||||
|  |         if self.ctrl_down and self.shift_down and keyname == "t": | ||||||
|  |             self.unset_keys_and_data() | ||||||
|  |             self.trash_files() | ||||||
|  |  | ||||||
|  |         if self.ctrl_down: | ||||||
|  |             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() | ||||||
|  |             if keyname == "q": | ||||||
|  |                 self.tear_down() | ||||||
|  |             if keyname == "slash" or keyname == "home": | ||||||
|  |                 self.builder.get_object("go_home").released() | ||||||
|  |             if keyname == "r" or keyname == "f5": | ||||||
|  |                 self.builder.get_object("refresh_tab").released() | ||||||
|  |             if keyname == "up" or keyname == "u": | ||||||
|  |                 self.builder.get_object("go_up").released() | ||||||
|  |             if keyname == "l": | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|  |                 self.builder.get_object("path_entry").grab_focus() | ||||||
|  |             if keyname == "t": | ||||||
|  |                 self.builder.get_object("create_tab").released() | ||||||
|  |             if keyname == "o": | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|  |                 self.open_files() | ||||||
|  |             if keyname == "w": | ||||||
|  |                 self.keyboard_close_tab() | ||||||
|  |             if keyname == "h": | ||||||
|  |                 self.show_hide_hidden_files() | ||||||
|  |             if keyname == "e": | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|  |                 self.rename_files() | ||||||
|  |             if keyname == "c": | ||||||
|  |                 self.copy_files() | ||||||
|  |                 self.to_cut_files.clear() | ||||||
|  |             if keyname == "x": | ||||||
|  |                 self.to_copy_files.clear() | ||||||
|  |                 self.cut_files() | ||||||
|  |             if keyname == "v": | ||||||
|  |                 self.paste_files() | ||||||
|  |             if keyname == "n": | ||||||
|  |                 self.unset_keys_and_data() | ||||||
|  |                 self.show_new_file_menu() | ||||||
|  |  | ||||||
|  |         if keyname == "delete": | ||||||
|  |             self.unset_keys_and_data() | ||||||
|  |             self.delete_files() | ||||||
|  |         if keyname == "f2": | ||||||
|  |             self.unset_keys_and_data() | ||||||
|  |             self.rename_files() | ||||||
|  |         if keyname == "f4": | ||||||
|  |             self.unset_keys_and_data() | ||||||
|  |             self.open_terminal() | ||||||
|  |         if keyname in ["alt_l", "alt_r"]: | ||||||
|  |             top_main_menubar = self.builder.get_object("top_main_menubar") | ||||||
|  |             top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() | ||||||
|  |  | ||||||
|  |         if re.fullmatch(valid_keyvalue_pat, keyname): | ||||||
|  |             if not self.is_searching and not self.ctrl_down \ | ||||||
|  |                 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 | ||||||
|  |                         wid, tid, self.search_tab, self.search_icon_grid, store = self.get_current_state() | ||||||
|  |                         self.unset_keys_and_data() | ||||||
|  |                         self.popup_search_files(wid, keyname) | ||||||
|  |                         return | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import threading, socket, time | import os, threading, time | ||||||
| from multiprocessing.connection import Listener, Client | from multiprocessing.connection import Listener, Client | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| @@ -15,11 +15,32 @@ def threaded(fn): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class IPCServerMixin: | class IPCServer: | ||||||
|  |     """ Create a listener so that other SolarFM instances send requests back to existing instance. """ | ||||||
|  |     def __init__(self, conn_type="socket"): | ||||||
|  |         self.is_ipc_alive   = False | ||||||
|  |         self._conn_type     = conn_type | ||||||
|  |         self.ipc_authkey    = b'solarfm-ipc' | ||||||
|  |         self.ipc_timeout    = 15.0 | ||||||
|  | 
 | ||||||
|  |         if conn_type == "socket": | ||||||
|  |             self.ipc_address    = '/tmp/solarfm-ipc.sock' | ||||||
|  |         else: | ||||||
|  |             self.ipc_address    = '127.0.0.1' | ||||||
|  |             self.ipc_port       = 4848 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     @threaded |     @threaded | ||||||
|     def create_ipc_server(self): |     def create_ipc_server(self): | ||||||
|  |         if self._conn_type == "socket": | ||||||
|  |             if os.path.exists(self.ipc_address): | ||||||
|  |                 return | ||||||
|  | 
 | ||||||
|  |             listener = Listener(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) | ||||||
|  |         else: | ||||||
|             listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |             listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         self.is_ipc_alive = True |         self.is_ipc_alive = True | ||||||
|         while True: |         while True: | ||||||
|             conn       = listener.accept() |             conn       = listener.accept() | ||||||
| @@ -34,7 +55,7 @@ class IPCServerMixin: | |||||||
|                 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.push_gui_event([None, "handle_file_from_ipc", (file,)]) | ||||||
| 
 | 
 | ||||||
|                     conn.close() |                     conn.close() | ||||||
|                     break |                     break | ||||||
| @@ -57,7 +78,12 @@ class IPCServerMixin: | |||||||
| 
 | 
 | ||||||
|     def send_ipc_message(self, message="Empty Data..."): |     def send_ipc_message(self, message="Empty Data..."): | ||||||
|         try: |         try: | ||||||
|  |             if self._conn_type == "socket": | ||||||
|  |                 conn = Client(address=self.ipc_address, family="AF_UNIX", authkey=self.ipc_authkey) | ||||||
|  |             else: | ||||||
|                 conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |                 conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|             conn.send(message) |             conn.send(message) | ||||||
|             conn.send('close connection') |             conn.send('close connection') | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  |     Gtk Bound Plugins Module | ||||||
|  | """ | ||||||
							
								
								
									
										83
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/plugins/plugins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, sys, importlib, traceback | ||||||
|  | from os.path import join, isdir | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | from gi.repository import Gtk, Gio | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugin: | ||||||
|  |     name          = None | ||||||
|  |     module        = None | ||||||
|  |     reference     = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Plugins: | ||||||
|  |     """Plugins controller""" | ||||||
|  |  | ||||||
|  |     def __init__(self, settings): | ||||||
|  |         self._settings            = settings | ||||||
|  |         self._builder             = self._settings.get_builder() | ||||||
|  |         self._plugins_path        = self._settings.get_plugins_path() | ||||||
|  |         self._plugins_dir_watcher = None | ||||||
|  |         self._plugin_collection   = [] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def launch_plugins(self): | ||||||
|  |         self._set_plugins_watcher() | ||||||
|  |         self.load_plugins() | ||||||
|  |  | ||||||
|  |     def _set_plugins_watcher(self): | ||||||
|  |         self._plugins_dir_watcher  = Gio.File.new_for_path(self._plugins_path) \ | ||||||
|  |                                             .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) | ||||||
|  |         self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) | ||||||
|  |  | ||||||
|  |     def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None): | ||||||
|  |         if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, | ||||||
|  |                         Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, | ||||||
|  |                                                     Gio.FileMonitorEvent.MOVED_OUT]: | ||||||
|  |             self.reload_plugins(file) | ||||||
|  |  | ||||||
|  |     # @threaded | ||||||
|  |     def load_plugins(self, file=None): | ||||||
|  |         print(f"Loading plugins...") | ||||||
|  |         parent_path = os.getcwd() | ||||||
|  |  | ||||||
|  |         for file in os.listdir(self._plugins_path): | ||||||
|  |             try: | ||||||
|  |                 path = join(self._plugins_path, file) | ||||||
|  |                 if isdir(path): | ||||||
|  |                     os.chdir(path) | ||||||
|  |  | ||||||
|  |                     sys.path.insert(0, path) | ||||||
|  |                     spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py")) | ||||||
|  |                     app  = importlib.util.module_from_spec(spec) | ||||||
|  |                     spec.loader.exec_module(app) | ||||||
|  |  | ||||||
|  |                     plugin_reference = app.Plugin(self._builder, event_system) | ||||||
|  |                     plugin           = Plugin() | ||||||
|  |                     plugin.name      = plugin_reference.get_plugin_name() | ||||||
|  |                     plugin.module    = path | ||||||
|  |                     plugin.reference = plugin_reference | ||||||
|  |  | ||||||
|  |                     self._plugin_collection.append(plugin) | ||||||
|  |             except Exception as e: | ||||||
|  |                 print("Malformed plugin! Not loading!") | ||||||
|  |                 traceback.print_exc() | ||||||
|  |  | ||||||
|  |         os.chdir(parent_path) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def reload_plugins(self, file=None): | ||||||
|  |         print(f"Reloading plugins... stub.") | ||||||
|  |  | ||||||
|  |     def send_message_to_plugin(self, type, data): | ||||||
|  |         print("Trying to send message to plugin...") | ||||||
|  |         for plugin in self._plugin_collection: | ||||||
|  |             if type in plugin.name: | ||||||
|  |                 print('Found plugin; posting message...') | ||||||
|  |                 plugin.reference.set_message(data) | ||||||
| @@ -1 +1,3 @@ | |||||||
| from .windows import WindowController | """ | ||||||
|  | Root of ShellFM | ||||||
|  | """ | ||||||
|   | |||||||
| @@ -1,66 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| from random import randint |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .view import View |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Window: |  | ||||||
|     def __init__(self): |  | ||||||
|         self.id_length = 10 |  | ||||||
|         self.id        = "" |  | ||||||
|         self.name      = "" |  | ||||||
|         self.nickname  = "" |  | ||||||
|         self.isHidden  = False |  | ||||||
|         self.views     = [] |  | ||||||
|  |  | ||||||
|         self.generate_id() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def random_with_N_digits(self, n): |  | ||||||
|         range_start = 10**(n-1) |  | ||||||
|         range_end = (10**n)-1 |  | ||||||
|         return randint(range_start, range_end) |  | ||||||
|  |  | ||||||
|     def generate_id(self): |  | ||||||
|         self.id = str(self.random_with_N_digits(self.id_length)) |  | ||||||
|  |  | ||||||
|     def get_window_id(self): |  | ||||||
|         return self.id |  | ||||||
|  |  | ||||||
|     def create_view(self): |  | ||||||
|         view = View() |  | ||||||
|         self.views.append(view) |  | ||||||
|         return view |  | ||||||
|  |  | ||||||
|     def pop_view(self): |  | ||||||
|         self.views.pop() |  | ||||||
|  |  | ||||||
|     def delete_view_by_id(self, vid): |  | ||||||
|         for view in self.views: |  | ||||||
|             if view.id == vid: |  | ||||||
|                 self.views.remove(view) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_view_by_id(self, vid): |  | ||||||
|         for view in self.views: |  | ||||||
|             if view.id == vid: |  | ||||||
|                 return view |  | ||||||
|  |  | ||||||
|     def get_view_by_index(self, index): |  | ||||||
|         return self.views[index] |  | ||||||
|  |  | ||||||
|     def get_views_count(self): |  | ||||||
|         return len(self.views) |  | ||||||
|  |  | ||||||
|     def get_all_views(self): |  | ||||||
|         return self.views |  | ||||||
|  |  | ||||||
|     def list_files_from_views(self): |  | ||||||
|         for view in self.views: |  | ||||||
|             print(view.files) |  | ||||||
| @@ -1,181 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import threading, subprocess, time, json |  | ||||||
| from os import path |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from . import Window |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WindowController: |  | ||||||
|     def __init__(self): |  | ||||||
|         USER_HOME              = path.expanduser('~') |  | ||||||
|         CONFIG_PATH            = USER_HOME   + "/.config/solarfm" |  | ||||||
|         self.session_file      = CONFIG_PATH + "/session.json" |  | ||||||
|  |  | ||||||
|         self._event_sleep_time = 1 |  | ||||||
|         self.active_window_id  = "" |  | ||||||
|         self.active_tab_id     = "" |  | ||||||
|         self.windows           = [] |  | ||||||
|  |  | ||||||
|         if not trace_debug: |  | ||||||
|             self.fm_event_observer() |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def fm_event_observer(self): |  | ||||||
|         while True: |  | ||||||
|             time.sleep(event_sleep_time) |  | ||||||
|             event = event_system.consume_fm_event() |  | ||||||
|             if event: |  | ||||||
|                 print(event) |  | ||||||
|  |  | ||||||
|     def set_active_data(self, wid, tid): |  | ||||||
|         self.active_window_id = str(wid) |  | ||||||
|         self.active_tab_id    = str(tid) |  | ||||||
|  |  | ||||||
|     def get_active_data(self): |  | ||||||
|         return self.active_window_id, self.active_tab_id |  | ||||||
|  |  | ||||||
|     def create_window(self): |  | ||||||
|         window          = Window() |  | ||||||
|         window.name     = "window_" + window.id |  | ||||||
|         window.nickname = "window_" + str(len(self.windows) + 1) |  | ||||||
|  |  | ||||||
|         self.windows.append(window) |  | ||||||
|         return window |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def add_view_for_window(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 return window.create_view() |  | ||||||
|  |  | ||||||
|     def add_view_for_window_by_name(self, name): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.name == name: |  | ||||||
|                 return window.create_view() |  | ||||||
|  |  | ||||||
|     def add_view_for_window_by_nickname(self, nickname): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.nickname == nickname: |  | ||||||
|                 return window.create_view() |  | ||||||
|  |  | ||||||
|     def pop_window(self): |  | ||||||
|         self.windows.pop() |  | ||||||
|  |  | ||||||
|     def delete_window_by_id(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 self.windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def delete_window_by_name(self, name): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.name == name: |  | ||||||
|                 self.windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def delete_window_by_nickname(self, nickname): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.nickname == nickname: |  | ||||||
|                 self.windows.remove(window) |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def get_window_by_id(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by ID {win_id} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_name(self, name): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.name == name: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by Name {name} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_nickname(self, nickname): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.nickname == nickname: |  | ||||||
|                 return window |  | ||||||
|  |  | ||||||
|         raise(f"No Window by Nickname {nickname} found!") |  | ||||||
|  |  | ||||||
|     def get_window_by_index(self, index): |  | ||||||
|         return self.windows[index] |  | ||||||
|  |  | ||||||
|     def get_all_windows(self): |  | ||||||
|         return self.windows |  | ||||||
|  |  | ||||||
|     def set_window_nickname(self, win_id = None, nickname = ""): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 window.nickname = nickname |  | ||||||
|  |  | ||||||
|     def list_windows(self): |  | ||||||
|         print("\n[  ----  Windows  ----  ]\n") |  | ||||||
|         for window in self.windows: |  | ||||||
|             print(f"\nID: {window.id}") |  | ||||||
|             print(f"Name: {window.name}") |  | ||||||
|             print(f"Nickname: {window.nickname}") |  | ||||||
|             print(f"Is Hidden: {window.isHidden}") |  | ||||||
|             print(f"View Count: {window.get_views_count()}") |  | ||||||
|         print("\n-------------------------\n") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def list_files_from_views_of_window(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 window.list_files_from_views() |  | ||||||
|                 break |  | ||||||
|  |  | ||||||
|     def get_views_count(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 return window.get_views_count() |  | ||||||
|  |  | ||||||
|     def get_views_from_window(self, win_id): |  | ||||||
|         for window in self.windows: |  | ||||||
|             if window.id == win_id: |  | ||||||
|                 return window.get_all_views() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def save_state(self): |  | ||||||
|         windows = [] |  | ||||||
|         for window in self.windows: |  | ||||||
|             views = [] |  | ||||||
|             for view in window.views: |  | ||||||
|                 views.append(view.get_current_directory()) |  | ||||||
|  |  | ||||||
|             windows.append( |  | ||||||
|                 [ |  | ||||||
|                     { |  | ||||||
|                         'window':{ |  | ||||||
|                             "ID": window.id, |  | ||||||
|                             "Name": window.name, |  | ||||||
|                             "Nickname": window.nickname, |  | ||||||
|                             "isHidden": f"{window.isHidden}", |  | ||||||
|                             'views': views |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 ] |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         with open(self.session_file, 'w') as outfile: |  | ||||||
|             json.dump(windows, outfile, separators=(',', ':'), indent=4) |  | ||||||
|  |  | ||||||
|     def load_state(self): |  | ||||||
|         if path.isfile(self.session_file): |  | ||||||
|             with open(self.session_file) as infile: |  | ||||||
|                 return json.load(infile) |  | ||||||
| @@ -1,2 +1,3 @@ | |||||||
| from .Window import Window | """ | ||||||
| from .WindowController import WindowController | Window module | ||||||
|  | """ | ||||||
|   | |||||||
| @@ -0,0 +1,185 @@ | |||||||
|  | # Python imports | ||||||
|  | import threading, json | ||||||
|  | from os import path | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .window import Window | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def threaded(fn): | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WindowController: | ||||||
|  |     def __init__(self): | ||||||
|  |         USER_HOME               = path.expanduser('~') | ||||||
|  |         CONFIG_PATH             = USER_HOME   + "/.config/solarfm" | ||||||
|  |         self._session_file      = CONFIG_PATH + "/session.json" | ||||||
|  |  | ||||||
|  |         self._event_sleep_time  = 1 | ||||||
|  |         self._active_window_id  = "" | ||||||
|  |         self._active_tab_id     = "" | ||||||
|  |         self._windows           = [] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def set__wid_and_tid(self, wid, tid): | ||||||
|  |         self._active_window_id = str(wid) | ||||||
|  |         self._active_tab_id    = str(tid) | ||||||
|  |  | ||||||
|  |     def get_active_wid_and_tid(self): | ||||||
|  |         return self._active_window_id, self._active_tab_id | ||||||
|  |  | ||||||
|  |     def create_window(self): | ||||||
|  |         window = Window() | ||||||
|  |         window.set_nickname(f"window_{str(len(self._windows) + 1)}") | ||||||
|  |         self._windows.append(window) | ||||||
|  |         return window | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def add_tab_for_window(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 return window.create_tab() | ||||||
|  |  | ||||||
|  |     def add_tab_for_window_by_name(self, name): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_name() == name: | ||||||
|  |                 return window.create_tab() | ||||||
|  |  | ||||||
|  |     def add_tab_for_window_by_nickname(self, nickname): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_nickname() == nickname: | ||||||
|  |                 return window.create_tab() | ||||||
|  |  | ||||||
|  |     def pop_window(self): | ||||||
|  |         self._windows.pop() | ||||||
|  |  | ||||||
|  |     def delete_window_by_id(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 self._windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def delete_window_by_name(self, name): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_name() == name: | ||||||
|  |                 self._windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def delete_window_by_nickname(self, nickname): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_nickname() == nickname: | ||||||
|  |                 self._windows.remove(window) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def get_window_by_id(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by ID {win_id} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_name(self, name): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_name() == name: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by Name {name} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_nickname(self, nickname): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_nickname() == nickname: | ||||||
|  |                 return window | ||||||
|  |  | ||||||
|  |         raise(f"No Window by Nickname {nickname} found!") | ||||||
|  |  | ||||||
|  |     def get_window_by_index(self, index): | ||||||
|  |         return self._windows[index] | ||||||
|  |  | ||||||
|  |     def get_all_windows(self): | ||||||
|  |         return self._windows | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def set_window_nickname(self, win_id = None, nickname = ""): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 window.set_nickname(nickname) | ||||||
|  |  | ||||||
|  |     def list_windows(self): | ||||||
|  |         print("\n[  ----  Windows  ----  ]\n") | ||||||
|  |         for window in self._windows: | ||||||
|  |             print(f"\nID: {window.get_id()}") | ||||||
|  |             print(f"Name: {window.get_name()}") | ||||||
|  |             print(f"Nickname: {window.get_nickname()}") | ||||||
|  |             print(f"Is Hidden: {window.is_hidden()}") | ||||||
|  |             print(f"Tab Count: {window.get_tabs_count()}") | ||||||
|  |         print("\n-------------------------\n") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def list_files_from_tabs_of_window(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 window.list_files_from_tabs() | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |     def get_tabs_count(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 return window.get_tabs_count() | ||||||
|  |  | ||||||
|  |     def get_tabs_from_window(self, win_id): | ||||||
|  |         for window in self._windows: | ||||||
|  |             if window.get_id() == win_id: | ||||||
|  |                 return window.get_all_tabs() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def unload_tabs_and_windows(self): | ||||||
|  |         for window in self._windows: | ||||||
|  |             window.get_all_tabs().clear() | ||||||
|  |  | ||||||
|  |         self._windows.clear() | ||||||
|  |  | ||||||
|  |     def save_state(self, session_file = None): | ||||||
|  |         if not session_file: | ||||||
|  |             session_file = self._session_file | ||||||
|  |  | ||||||
|  |         if len(self._windows) > 0: | ||||||
|  |             windows = [] | ||||||
|  |             for window in self._windows: | ||||||
|  |                 tabs = [] | ||||||
|  |                 for tab in window.get_all_tabs(): | ||||||
|  |                     tabs.append(tab.get_current_directory()) | ||||||
|  |  | ||||||
|  |                 windows.append( | ||||||
|  |                     [ | ||||||
|  |                         { | ||||||
|  |                             'window':{ | ||||||
|  |                                 "ID": window.get_id(), | ||||||
|  |                                 "Name": window.get_name(), | ||||||
|  |                                 "Nickname": window.get_nickname(), | ||||||
|  |                                 "isHidden": f"{window.is_hidden()}", | ||||||
|  |                                 'tabs': tabs | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     ] | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|  |             with open(session_file, 'w') as outfile: | ||||||
|  |                 json.dump(windows, outfile, separators=(',', ':'), indent=4) | ||||||
|  |         else: | ||||||
|  |             raise Exception("Window data corrupted! Can not save session!") | ||||||
|  |  | ||||||
|  |     def load_state(self, session_file = None): | ||||||
|  |         if not session_file: | ||||||
|  |             session_file = self._session_file | ||||||
|  |  | ||||||
|  |         if path.isfile(session_file): | ||||||
|  |             with open(session_file) as infile: | ||||||
|  |                 return json.load(infile) | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Tabs module | ||||||
|  | """ | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Icons module | ||||||
|  | """ | ||||||
| @@ -3,10 +3,13 @@ import os, subprocess, threading, hashlib | |||||||
| from os.path import isfile | from os.path import isfile | ||||||
| 
 | 
 | ||||||
| # Gtk imports | # Gtk imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('GdkPixbuf', '2.0') | ||||||
| from gi.repository import GdkPixbuf | from gi.repository import GdkPixbuf | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .mixins import * | from .mixins.desktopiconmixin import DesktopIconMixin | ||||||
|  | from .mixins.videoiconmixin import VideoIconMixin | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def threaded(fn): | def threaded(fn): | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Icons mixins module | ||||||
|  | """ | ||||||
| @@ -3,9 +3,6 @@ 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 |  | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .xdg.DesktopEntry import DesktopEntry | from .xdg.DesktopEntry import DesktopEntry | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import hashlib | import hashlib, re | ||||||
| import os |  | ||||||
| from os import listdir | from os import listdir | ||||||
| from os.path import isdir, isfile, join | from os.path import isdir, isfile, join | ||||||
| 
 | 
 | ||||||
| @@ -11,64 +10,43 @@ from random import randint | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Application imports | # Application imports | ||||||
| from .utils import Settings, Launcher, FileHandler | from .utils.settings import Settings | ||||||
| from .icons import Icon | from .utils.launcher import Launcher | ||||||
| from . import Path | from .utils.filehandler import FileHandler | ||||||
|  | 
 | ||||||
|  | from .icons.icon import Icon | ||||||
|  | from .path import Path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class View(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   = 10 | ||||||
| 
 | 
 | ||||||
|         self.id          = "" |         self._id          = "" | ||||||
|         self.wid         = None |         self._wid         = None | ||||||
|         self.dir_watcher = None |         self._dir_watcher = None | ||||||
|         self.hide_hidden = self.HIDE_HIDDEN_FILES |         self._hide_hidden = self.HIDE_HIDDEN_FILES | ||||||
|         self.files       = [] |         self._files       = [] | ||||||
|         self.dirs        = [] |         self._dirs        = [] | ||||||
|         self.vids        = [] |         self._vids        = [] | ||||||
|         self.images      = [] |         self._images      = [] | ||||||
|         self.desktop     = [] |         self._desktop     = [] | ||||||
|         self.ungrouped   = [] |         self._ungrouped   = [] | ||||||
|         self.hidden      = [] |         self._hidden      = [] | ||||||
| 
 | 
 | ||||||
|         self.generate_id() |         self._generate_id() | ||||||
|         self.set_to_home() |         self.set_to_home() | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     def random_with_N_digits(self, n): |  | ||||||
|         range_start = 10**(n-1) |  | ||||||
|         range_end = (10**n)-1 |  | ||||||
|         return randint(range_start, range_end) |  | ||||||
| 
 |  | ||||||
|     def generate_id(self): |  | ||||||
|         self.id = str(self.random_with_N_digits(self.id_length)) |  | ||||||
| 
 |  | ||||||
|     def get_tab_id(self): |  | ||||||
|         return self.id |  | ||||||
| 
 |  | ||||||
|     def set_wid(self, _wid): |  | ||||||
|         self.wid = _wid |  | ||||||
| 
 |  | ||||||
|     def get_wid(self): |  | ||||||
|         return self.wid |  | ||||||
| 
 |  | ||||||
|     def set_dir_watcher(self, watcher): |  | ||||||
|         self.dir_watcher = watcher |  | ||||||
| 
 |  | ||||||
|     def get_dir_watcher(self): |  | ||||||
|         return self.dir_watcher |  | ||||||
| 
 |  | ||||||
|     def load_directory(self): |     def load_directory(self): | ||||||
|         path            = self.get_path() |         path            = self.get_path() | ||||||
|         self.dirs      = [] |         self._dirs      = [] | ||||||
|         self.vids      = [] |         self._vids      = [] | ||||||
|         self.images    = [] |         self._images    = [] | ||||||
|         self.desktop   = [] |         self._desktop   = [] | ||||||
|         self.ungrouped = [] |         self._ungrouped = [] | ||||||
|         self.hidden    = [] |         self._hidden    = [] | ||||||
|         self.files     = [] |         self._files     = [] | ||||||
| 
 | 
 | ||||||
|         if not isdir(path): |         if not isdir(path): | ||||||
|             self.set_to_home() |             self.set_to_home() | ||||||
| @@ -76,40 +54,31 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
| 
 | 
 | ||||||
|         for f in listdir(path): |         for f in listdir(path): | ||||||
|             file = join(path, f) |             file = join(path, f) | ||||||
|             if self.hide_hidden: |             if self._hide_hidden: | ||||||
|                 if f.startswith('.'): |                 if f.startswith('.'): | ||||||
|                     self.hidden.append(f) |                     self._hidden.append(f) | ||||||
|                     continue |                     continue | ||||||
| 
 | 
 | ||||||
|             if isfile(file): |             if isfile(file): | ||||||
|                 lowerName = file.lower() |                 lowerName = file.lower() | ||||||
|                 if lowerName.endswith(self.fvideos): |                 if lowerName.endswith(self.fvideos): | ||||||
|                     self.vids.append(f) |                     self._vids.append(f) | ||||||
|                 elif lowerName.endswith(self.fimages): |                 elif lowerName.endswith(self.fimages): | ||||||
|                     self.images.append(f) |                     self._images.append(f) | ||||||
|                 elif lowerName.endswith((".desktop",)): |                 elif lowerName.endswith((".desktop",)): | ||||||
|                     self.desktop.append(f) |                     self._desktop.append(f) | ||||||
|                 else: |                 else: | ||||||
|                     self.ungrouped.append(f) |                     self._ungrouped.append(f) | ||||||
|             else: |             else: | ||||||
|                 self.dirs.append(f) |                 self._dirs.append(f) | ||||||
| 
 | 
 | ||||||
|         self.dirs.sort() |         self._dirs.sort(key=self._natural_keys) | ||||||
|         self.vids.sort() |         self._vids.sort(key=self._natural_keys) | ||||||
|         self.images.sort() |         self._images.sort(key=self._natural_keys) | ||||||
|         self.desktop.sort() |         self._desktop.sort(key=self._natural_keys) | ||||||
|         self.ungrouped.sort() |         self._ungrouped.sort(key=self._natural_keys) | ||||||
| 
 | 
 | ||||||
|         self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped |         self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped | ||||||
| 
 |  | ||||||
|     def hash_text(self, text): |  | ||||||
|         return hashlib.sha256(str.encode(text)).hexdigest()[:18] |  | ||||||
| 
 |  | ||||||
|     def hash_set(self, arry): |  | ||||||
|         data = [] |  | ||||||
|         for arr in arry: |  | ||||||
|             data.append([arr, self.hash_text(arr)]) |  | ||||||
|         return data |  | ||||||
| 
 | 
 | ||||||
|     def is_folder_locked(self, hash): |     def is_folder_locked(self, hash): | ||||||
|         if self.lock_folder: |         if self.lock_folder: | ||||||
| @@ -129,18 +98,18 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     def get_not_hidden_count(self): |     def get_not_hidden_count(self): | ||||||
|         return len(self.files)    + \ |         return len(self._files)    + \ | ||||||
|                 len(self.dirs)    + \ |                 len(self._dirs)    + \ | ||||||
|                 len(self.vids)    + \ |                 len(self._vids)    + \ | ||||||
|                 len(self.images)  + \ |                 len(self._images)  + \ | ||||||
|                 len(self.desktop) + \ |                 len(self._desktop) + \ | ||||||
|                 len(self.ungrouped) |                 len(self._ungrouped) | ||||||
| 
 | 
 | ||||||
|     def get_hidden_count(self): |     def get_hidden_count(self): | ||||||
|         return len(self.hidden) |         return len(self._hidden) | ||||||
| 
 | 
 | ||||||
|     def get_files_count(self): |     def get_files_count(self): | ||||||
|         return len(self.files) |         return len(self._files) | ||||||
| 
 | 
 | ||||||
|     def get_path_part_from_hash(self, hash): |     def get_path_part_from_hash(self, hash): | ||||||
|         files = self.get_files() |         files = self.get_files() | ||||||
| @@ -154,13 +123,13 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|         return file |         return file | ||||||
| 
 | 
 | ||||||
|     def get_files_formatted(self): |     def get_files_formatted(self): | ||||||
|         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(), | ||||||
|         images    = self.hash_set(self.images), |         images    = self._hash_set(self._images), | ||||||
|         desktops  = self.hash_set(self.desktop), |         desktops  = self._hash_set(self._desktop), | ||||||
|         ungrouped = self.hash_set(self.ungrouped) |         ungrouped = self._hash_set(self._ungrouped) | ||||||
|         hidden    = self.hash_set(self.hidden) |         hidden    = self._hash_set(self._hidden) | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             'path_head': self.get_path(), |             'path_head': self.get_path(), | ||||||
| @@ -178,7 +147,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|     def get_pixbuf_icon_str_combo(self): |     def get_pixbuf_icon_str_combo(self): | ||||||
|         data = [] |         data = [] | ||||||
|         dir  = self.get_current_directory() |         dir  = self.get_current_directory() | ||||||
|         for file in self.files: |         for file in self._files: | ||||||
|             icon = self.create_icon(dir, file).get_pixbuf() |             icon = self.create_icon(dir, file).get_pixbuf() | ||||||
|             data.append([icon, file]) |             data.append([icon, file]) | ||||||
| 
 | 
 | ||||||
| @@ -188,7 +157,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|     def get_gtk_icon_str_combo(self): |     def get_gtk_icon_str_combo(self): | ||||||
|         data = [] |         data = [] | ||||||
|         dir  = self.get_current_directory() |         dir  = self.get_current_directory() | ||||||
|         for file in self.files: |         for file in self._files: | ||||||
|             icon = self.create_icon(dir, file) |             icon = self.create_icon(dir, file) | ||||||
|             data.append([icon, file[0]]) |             data.append([icon, file[0]]) | ||||||
| 
 | 
 | ||||||
| @@ -207,23 +176,71 @@ class View(Settings, FileHandler, Launcher, Icon, Path): | |||||||
|         size  = len(parts) |         size  = len(parts) | ||||||
|         return parts[size - 1] |         return parts[size - 1] | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     def set_hiding_hidden(self, state): | ||||||
|  |         self._hide_hidden = state | ||||||
|  | 
 | ||||||
|  |     def is_hiding_hidden(self): | ||||||
|  |         return self._hide_hidden | ||||||
|  | 
 | ||||||
|     def get_dot_dots(self): |     def get_dot_dots(self): | ||||||
|         return self.hash_set(['.', '..']) |         return self._hash_set(['.', '..']) | ||||||
| 
 | 
 | ||||||
|     def get_files(self): |     def get_files(self): | ||||||
|         return self.hash_set(self.files) |         return self._hash_set(self._files) | ||||||
| 
 | 
 | ||||||
|     def get_dirs(self): |     def get_dirs(self): | ||||||
|         return self.hash_set(self.dirs) |         return self._hash_set(self._dirs) | ||||||
| 
 | 
 | ||||||
|     def get_videos(self): |     def get_videos(self): | ||||||
|         return self.hash_set(self.vids) |         return self._hash_set(self._vids) | ||||||
| 
 | 
 | ||||||
|     def get_images(self): |     def get_images(self): | ||||||
|         return self.hash_set(self.images) |         return self._hash_set(self._images) | ||||||
| 
 | 
 | ||||||
|     def get_desktops(self): |     def get_desktops(self): | ||||||
|         return self.hash_set(self.desktop) |         return self._hash_set(self._desktop) | ||||||
| 
 | 
 | ||||||
|     def get_ungrouped(self): |     def get_ungrouped(self): | ||||||
|         return self.hash_set(self.ungrouped) |         return self._hash_set(self._ungrouped) | ||||||
|  | 
 | ||||||
|  |     def get_hidden(self): | ||||||
|  |         return self._hash_set(self._hidden) | ||||||
|  | 
 | ||||||
|  |     def get_id(self): | ||||||
|  |         return self._id | ||||||
|  | 
 | ||||||
|  |     def set_wid(self, _wid): | ||||||
|  |         self._wid = _wid | ||||||
|  | 
 | ||||||
|  |     def get_wid(self): | ||||||
|  |         return self._wid | ||||||
|  | 
 | ||||||
|  |     def set_dir_watcher(self, watcher): | ||||||
|  |         self._dir_watcher = watcher | ||||||
|  | 
 | ||||||
|  |     def get_dir_watcher(self): | ||||||
|  |         return self._dir_watcher | ||||||
|  | 
 | ||||||
|  |     def _atoi(self, text): | ||||||
|  |         return int(text) if text.isdigit() else text | ||||||
|  | 
 | ||||||
|  |     def _natural_keys(self, text): | ||||||
|  |         return [ self._atoi(c) for c in re.split('(\d+)',text) ] | ||||||
|  | 
 | ||||||
|  |     def _hash_text(self, text): | ||||||
|  |         return hashlib.sha256(str.encode(text)).hexdigest()[:18] | ||||||
|  | 
 | ||||||
|  |     def _hash_set(self, arry): | ||||||
|  |         data = [] | ||||||
|  |         for arr in arry: | ||||||
|  |             data.append([arr, self._hash_text(arr)]) | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  |     def _random_with_N_digits(self, n): | ||||||
|  |         range_start = 10**(n-1) | ||||||
|  |         range_end = (10**n)-1 | ||||||
|  |         return randint(range_start, range_end) | ||||||
|  | 
 | ||||||
|  |     def _generate_id(self): | ||||||
|  |         self._id = str(self._random_with_N_digits(self._id_length)) | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Utils module | ||||||
|  | """ | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| # Python imports | # Python imports | ||||||
| import os, shutil, subprocess, threading | import os, shutil | ||||||
| 
 | 
 | ||||||
| # Lib imports | # Lib imports | ||||||
| 
 | 
 | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| from .utils import * |  | ||||||
| from .icons import * |  | ||||||
|  |  | ||||||
| from .Path  import Path |  | ||||||
| from .View  import View |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| from .mixins import DesktopIconMixin |  | ||||||
| from .mixins import VideoIconMixin |  | ||||||
|  |  | ||||||
| from .Icon   import Icon |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| from . import xdg |  | ||||||
|  |  | ||||||
| from .VideoIconMixin   import VideoIconMixin |  | ||||||
| from .DesktopIconMixin import DesktopIconMixin |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from .Settings import Settings |  | ||||||
| from .Launcher import Launcher |  | ||||||
| from .FileHandler import FileHandler |  | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | # Python imports | ||||||
|  | from random import randint | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .tabs.tab import Tab | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Window: | ||||||
|  |     def __init__(self): | ||||||
|  |         self._id_length = 10 | ||||||
|  |         self._id        = "" | ||||||
|  |         self._name      = "" | ||||||
|  |         self._nickname  = "" | ||||||
|  |         self._isHidden  = False | ||||||
|  |         self._tabs      = [] | ||||||
|  |  | ||||||
|  |         self._generate_id() | ||||||
|  |         self._set_name() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def create_tab(self): | ||||||
|  |         tab = Tab() | ||||||
|  |         self._tabs.append(tab) | ||||||
|  |         return tab | ||||||
|  |  | ||||||
|  |     def pop_tab(self): | ||||||
|  |         self._tabs.pop() | ||||||
|  |  | ||||||
|  |     def delete_tab_by_id(self, vid): | ||||||
|  |         for tab in self._tabs: | ||||||
|  |             if tab.get_id() == vid: | ||||||
|  |                 self._tabs.remove(tab) | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def get_tab_by_id(self, vid): | ||||||
|  |         for tab in self._tabs: | ||||||
|  |             if tab.get_id() == vid: | ||||||
|  |                 return tab | ||||||
|  |  | ||||||
|  |     def get_tab_by_index(self, index): | ||||||
|  |         return self._tabs[index] | ||||||
|  |  | ||||||
|  |     def get_tabs_count(self): | ||||||
|  |         return len(self._tabs) | ||||||
|  |  | ||||||
|  |     def get_all_tabs(self): | ||||||
|  |         return self._tabs | ||||||
|  |  | ||||||
|  |     def get_id(self): | ||||||
|  |         return self._id | ||||||
|  |  | ||||||
|  |     def get_name(self): | ||||||
|  |         return self._name | ||||||
|  |  | ||||||
|  |     def get_nickname(self): | ||||||
|  |         return self._nickname | ||||||
|  |  | ||||||
|  |     def is_hidden(self): | ||||||
|  |         return self._isHidden | ||||||
|  |  | ||||||
|  |     def list_files_from_tabs(self): | ||||||
|  |         for tab in self._tabs: | ||||||
|  |             print(tab.get_files()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def set_nickname(self, nickname): | ||||||
|  |         self._nickname = f"{nickname}" | ||||||
|  |  | ||||||
|  |     def set_is_hidden(self, state): | ||||||
|  |         self._isHidden = f"{state}" | ||||||
|  |  | ||||||
|  |     def _set_name(self): | ||||||
|  |         self._name = "window_" + self.get_id() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _random_with_N_digits(self, n): | ||||||
|  |         range_start = 10**(n-1) | ||||||
|  |         range_end = (10**n)-1 | ||||||
|  |         return randint(range_start, range_end) | ||||||
|  |  | ||||||
|  |     def _generate_id(self): | ||||||
|  |         self._id = str(self._random_with_N_digits(self._id_length)) | ||||||
| @@ -1,165 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import sys, traceback, threading, inspect, os, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .mixins.ui import * |  | ||||||
| from .mixins import ShowHideMixin, KeyboardSignalsMixin |  | ||||||
| from . import Controller_Data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \ |  | ||||||
|                                         KeyboardSignalsMixin, Controller_Data): |  | ||||||
|     def __init__(self, args, unknownargs, _settings): |  | ||||||
|         # sys.excepthook = self.custom_except_hook |  | ||||||
|         self.setup_controller_data(_settings) |  | ||||||
|         self.window.show() |  | ||||||
|         self.generate_windows(self.state) |  | ||||||
|         self.plugins.launch_plugins() |  | ||||||
|  |  | ||||||
|         if not trace_debug: |  | ||||||
|             self.gui_event_observer() |  | ||||||
|  |  | ||||||
|             if unknownargs: |  | ||||||
|                 for arg in unknownargs: |  | ||||||
|                     if os.path.isdir(arg): |  | ||||||
|                         message = f"FILE|{arg}" |  | ||||||
|                         event_system.send_ipc_message(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 tear_down(self, widget=None, eve=None): |  | ||||||
|         event_system.send_ipc_message("close server") |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         time.sleep(event_sleep_time) |  | ||||||
|         Gtk.main_quit() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def gui_event_observer(self): |  | ||||||
|         while True: |  | ||||||
|             time.sleep(event_sleep_time) |  | ||||||
|             event = event_system.consume_gui_event() |  | ||||||
|             if event: |  | ||||||
|                 try: |  | ||||||
|                     type, target, data = event |  | ||||||
|                     method = getattr(self.__class__, type) |  | ||||||
|                     GLib.idle_add(method, (self, data,)) |  | ||||||
|                 except Exception as e: |  | ||||||
|                     print(repr(e)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def custom_except_hook(self, exctype, value, _traceback): |  | ||||||
|         trace     = ''.join(traceback.format_tb(_traceback)) |  | ||||||
|         data      = f"Exectype:  {exctype}  <-->  Value:  {value}\n\n{trace}\n\n\n\n" |  | ||||||
|         start_itr = self.message_buffer.get_start_iter() |  | ||||||
|         self.message_buffer.place_cursor(start_itr) |  | ||||||
|         self.display_message(self.error, data) |  | ||||||
|  |  | ||||||
|     def display_message(self, type, text, seconds=None): |  | ||||||
|         self.message_buffer.insert_at_cursor(text) |  | ||||||
|         self.message_widget.popup() |  | ||||||
|         if seconds: |  | ||||||
|             self.hide_message_timeout(seconds) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def hide_message_timeout(self, seconds=3): |  | ||||||
|         time.sleep(seconds) |  | ||||||
|         GLib.idle_add(self.message_widget.popdown) |  | ||||||
|  |  | ||||||
|     def save_debug_alerts(self, widget=None, eve=None): |  | ||||||
|         start_itr, end_itr   = self.message_buffer.get_bounds() |  | ||||||
|         save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ |  | ||||||
|                                                         action  = Gtk.FileChooserAction.SAVE, \ |  | ||||||
|                                                         buttons = (Gtk.STOCK_CANCEL, \ |  | ||||||
|                                                                     Gtk.ResponseType.CANCEL, \ |  | ||||||
|                                                                     Gtk.STOCK_SAVE, \ |  | ||||||
|                                                                     Gtk.ResponseType.OK)) |  | ||||||
|  |  | ||||||
|         text = self.message_buffer.get_text(start_itr, end_itr, False) |  | ||||||
|         resp = save_location_prompt.run() |  | ||||||
|         if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|         elif resp == Gtk.ResponseType.OK: |  | ||||||
|             target = save_location_prompt.get_filename(); |  | ||||||
|             with open(target, "w") as f: |  | ||||||
|                 f.write(text) |  | ||||||
|  |  | ||||||
|         save_location_prompt.destroy() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_arc_buffer_text(self, widget=None, eve=None): |  | ||||||
|         id = widget.get_active_id() |  | ||||||
|         self.arc_command_buffer.set_text(self.arc_commands[int(id)]) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def clear_children(self, widget): |  | ||||||
|         for child in widget.get_children(): |  | ||||||
|             widget.remove(child) |  | ||||||
|  |  | ||||||
|     def get_current_state(self): |  | ||||||
|         wid, tid     = self.window_controller.get_active_data() |  | ||||||
|         view         = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         iconview     = self.builder.get_object(f"{wid}|{tid}|iconview") |  | ||||||
|         store        = iconview.get_model() |  | ||||||
|         return wid, tid, view, iconview, store |  | ||||||
|  |  | ||||||
|     def do_action_from_menu_controls(self, widget, eventbutton): |  | ||||||
|         action        = widget.get_name() |  | ||||||
|         self.ctrlDown = True |  | ||||||
|         self.hide_context_menu() |  | ||||||
|         self.hide_new_file_menu() |  | ||||||
|         self.hide_edit_file_menu() |  | ||||||
|  |  | ||||||
|         if action == "open": |  | ||||||
|             self.open_files() |  | ||||||
|         if action == "open_with": |  | ||||||
|             self.show_appchooser_menu() |  | ||||||
|         if action == "execute": |  | ||||||
|             self.execute_files() |  | ||||||
|         if action == "execute_in_terminal": |  | ||||||
|             self.execute_files(in_terminal=True) |  | ||||||
|         if action == "rename": |  | ||||||
|             self.rename_files() |  | ||||||
|         if action == "cut": |  | ||||||
|             self.to_copy_files.clear() |  | ||||||
|             self.cut_files() |  | ||||||
|         if action == "copy": |  | ||||||
|             self.to_cut_files.clear() |  | ||||||
|             self.copy_files() |  | ||||||
|         if action == "paste": |  | ||||||
|             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.builder.get_object("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": |  | ||||||
|             self.create_files() |  | ||||||
|             self.hide_new_file_menu() |  | ||||||
|  |  | ||||||
|         self.ctrlDown = False |  | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import signal |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| from gi.repository import GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from shellfm import WindowController |  | ||||||
| from trasher.xdgtrash import XDGTrash |  | ||||||
| from . import Plugins |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller_Data: |  | ||||||
|     def has_method(self, o, name): |  | ||||||
|         return callable(getattr(o, name, None)) |  | ||||||
|  |  | ||||||
|     def setup_controller_data(self, _settings): |  | ||||||
|         self.trashman           = XDGTrash() |  | ||||||
|         self.window_controller  = WindowController() |  | ||||||
|         self.plugins            = Plugins(_settings) |  | ||||||
|         self.state              = self.window_controller.load_state() |  | ||||||
|         self.trashman.regenerate() |  | ||||||
|  |  | ||||||
|         self.settings           = _settings |  | ||||||
|         self.builder            = self.settings.get_builder() |  | ||||||
|         self.logger             = self.settings.get_logger() |  | ||||||
|  |  | ||||||
|         self.window             = self.settings.get_main_window() |  | ||||||
|         self.window1            = self.builder.get_object("window_1") |  | ||||||
|         self.window2            = self.builder.get_object("window_2") |  | ||||||
|         self.window3            = self.builder.get_object("window_3") |  | ||||||
|         self.window4            = self.builder.get_object("window_4") |  | ||||||
|         self.message_widget     = self.builder.get_object("message_widget") |  | ||||||
|         self.message_view       = self.builder.get_object("message_view") |  | ||||||
|         self.message_buffer     = self.builder.get_object("message_buffer") |  | ||||||
|         self.arc_command_buffer = self.builder.get_object("arc_command_buffer") |  | ||||||
|  |  | ||||||
|         self.warning_alert      = self.builder.get_object("warning_alert") |  | ||||||
|         self.edit_file_menu     = self.builder.get_object("edit_file_menu") |  | ||||||
|         self.file_exists_dialog = self.builder.get_object("file_exists_dialog") |  | ||||||
|         self.exists_file_label  = self.builder.get_object("exists_file_label") |  | ||||||
|         self.exists_file_field  = self.builder.get_object("exists_file_field") |  | ||||||
|         self.path_menu          = self.builder.get_object("path_menu") |  | ||||||
|         self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn") |  | ||||||
|  |  | ||||||
|         self.bottom_size_label       = self.builder.get_object("bottom_size_label") |  | ||||||
|         self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label") |  | ||||||
|         self.bottom_path_label       = self.builder.get_object("bottom_path_label") |  | ||||||
|  |  | ||||||
|         self.trash_files_path        = GLib.get_user_data_dir() + "/Trash/files" |  | ||||||
|         self.trash_info_path         = GLib.get_user_data_dir() + "/Trash/info" |  | ||||||
|  |  | ||||||
|         # 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.selected_files    = [] |  | ||||||
|         self.to_copy_files     = [] |  | ||||||
|         self.to_cut_files      = [] |  | ||||||
|  |  | ||||||
|         self.single_click_open = False |  | ||||||
|         self.is_pane1_hidden   = False |  | ||||||
|         self.is_pane2_hidden   = False |  | ||||||
|         self.is_pane3_hidden   = False |  | ||||||
|         self.is_pane4_hidden   = False |  | ||||||
|  |  | ||||||
|         self.override_drop_dest = None |  | ||||||
|         self.is_searching       = False |  | ||||||
|         self.search_iconview    = None |  | ||||||
|         self.search_view        = None |  | ||||||
|  |  | ||||||
|         self.skip_edit         = False |  | ||||||
|         self.cancel_edit       = False |  | ||||||
|         self.ctrlDown          = False |  | ||||||
|         self.shiftDown         = False |  | ||||||
|         self.altDown           = False |  | ||||||
|  |  | ||||||
|         self.success           = "#88cc27" |  | ||||||
|         self.warning           = "#ffa800" |  | ||||||
|         self.error             = "#ff0000" |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         self.window.connect("delete-event", self.tear_down) |  | ||||||
|         GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) |  | ||||||
| @@ -1,64 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import threading, socket, time |  | ||||||
| from multiprocessing.connection import Listener, Client |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IPCServerMixin: |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def create_ipc_server(self): |  | ||||||
|         listener          = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |  | ||||||
|         self.is_ipc_alive = True |  | ||||||
|         while True: |  | ||||||
|             conn       = listener.accept() |  | ||||||
|             start_time = time.time() |  | ||||||
|  |  | ||||||
|             print(f"New Connection: {listener.last_accepted}") |  | ||||||
|             while True: |  | ||||||
|                 msg = conn.recv() |  | ||||||
|                 if debug: |  | ||||||
|                     print(msg) |  | ||||||
|  |  | ||||||
|                 if "FILE|" in msg: |  | ||||||
|                     file = msg.split("FILE|")[1].strip() |  | ||||||
|                     if file: |  | ||||||
|                         event_system.push_gui_event(["create_tab_from_ipc", None, file]) |  | ||||||
|  |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                 if msg == 'close connection': |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|                 if msg == 'close server': |  | ||||||
|                     conn.close() |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|                 # NOTE: Not perfect but insures we don't lockup the connection for too long. |  | ||||||
|                 end_time = time.time() |  | ||||||
|                 if (end - start) > self.ipc_timeout: |  | ||||||
|                     conn.close() |  | ||||||
|  |  | ||||||
|         listener.close() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def send_ipc_message(self, message="Empty Data..."): |  | ||||||
|         try: |  | ||||||
|             conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) |  | ||||||
|             conn.send(message) |  | ||||||
|             conn.send('close connection') |  | ||||||
|         except Exception as e: |  | ||||||
|             print(repr(e)) |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import importlib |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gio |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Plugins: |  | ||||||
|     """docstring for Plugins""" |  | ||||||
|     def __init__(self, settings): |  | ||||||
|         self._settings            = settings |  | ||||||
|         self._plugins_path        = self._settings.get_plugins_path() |  | ||||||
|         self._plugins_dir_watcher = None |  | ||||||
|         self._socket              = Gtk.Socket().new() |  | ||||||
|  |  | ||||||
|     def launch_plugins(self): |  | ||||||
|         self._set_plugins_watcher() |  | ||||||
|         self.load_plugins() |  | ||||||
|  |  | ||||||
|     def _set_plugins_watcher(self): |  | ||||||
|         self._plugins_dir_watcher  = Gio.File.new_for_path(self._plugins_path) \ |  | ||||||
|                                             .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) |  | ||||||
|         self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) |  | ||||||
|  |  | ||||||
|     def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None): |  | ||||||
|         if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, |  | ||||||
|                         Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, |  | ||||||
|                                                     Gio.FileMonitorEvent.MOVED_OUT]: |  | ||||||
|             self.load_plugins(file) |  | ||||||
|  |  | ||||||
|     def load_plugins(self, file=None): |  | ||||||
|         print(f"(Re)loading plugins...") |  | ||||||
|         print(locals()) |  | ||||||
|  |  | ||||||
|         # importlib.reload(stl_utils) |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| """ |  | ||||||
|     Gtk Bound Signal Module |  | ||||||
| """ |  | ||||||
| from .mixins import * |  | ||||||
| from .IPCServerMixin import IPCServerMixin |  | ||||||
| from .Plugins import Plugins |  | ||||||
| from .Controller_Data import Controller_Data |  | ||||||
| from .Controller import Controller |  | ||||||
| @@ -1,120 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class KeyboardSignalsMixin: |  | ||||||
|     def unset_keys_and_data(self, widget=None, eve=None): |  | ||||||
|         self.ctrlDown     = False |  | ||||||
|         self.shiftDown    = False |  | ||||||
|         self.altDown      = False |  | ||||||
|         self.is_searching = False |  | ||||||
|  |  | ||||||
|     def global_key_press_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if "control" in keyname or "alt" in keyname or "shift" in keyname: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrlDown    = True |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shiftDown   = True |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.altDown     = True |  | ||||||
|  |  | ||||||
|     # NOTE: Yes, this should actually be mapped to some key controller setting |  | ||||||
|     #       file or something. Sue me. |  | ||||||
|     def global_key_release_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if debug: |  | ||||||
|             print(f"global_key_release_controller > key > {keyname}") |  | ||||||
|  |  | ||||||
|         if "control" in keyname or "alt" in keyname or "shift" in keyname: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrlDown    = False |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shiftDown   = False |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.altDown     = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if self.ctrlDown and self.shiftDown and keyname == "t": |  | ||||||
|             self.trash_files() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if re.fullmatch(valid_keyvalue_pat, keyname): |  | ||||||
|             if not self.is_searching and not self.ctrlDown \ |  | ||||||
|                 and not self.shiftDown and not self.altDown: |  | ||||||
|                     focused_obj = self.window.get_focus() |  | ||||||
|                     if isinstance(focused_obj, Gtk.IconView): |  | ||||||
|                         self.is_searching = True |  | ||||||
|                         wid, tid, self.search_view, self.search_iconview, store = self.get_current_state() |  | ||||||
|                         self.popup_search_files(wid, keyname) |  | ||||||
|                         return |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if (self.ctrlDown and keyname in ["1", "kp_1"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_1").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["2", "kp_2"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_2").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["3", "kp_3"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_3").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["4", "kp_4"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_4").released() |  | ||||||
|  |  | ||||||
|         if self.ctrlDown and keyname == "q": |  | ||||||
|             self.tear_down() |  | ||||||
|         if (self.ctrlDown and keyname == "slash") or keyname == "home": |  | ||||||
|             self.builder.get_object("go_home").released() |  | ||||||
|         if (self.ctrlDown and keyname == "r") or keyname == "f5": |  | ||||||
|             self.builder.get_object("refresh_view").released() |  | ||||||
|         if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): |  | ||||||
|             self.builder.get_object("go_up").released() |  | ||||||
|         if self.ctrlDown and keyname == "l": |  | ||||||
|             self.builder.get_object("path_entry").grab_focus() |  | ||||||
|         if self.ctrlDown and keyname == "t": |  | ||||||
|             self.builder.get_object("create_tab").released() |  | ||||||
|         if self.ctrlDown and keyname == "o": |  | ||||||
|             self.open_files() |  | ||||||
|         if self.ctrlDown and keyname == "w": |  | ||||||
|             self.keyboard_close_tab() |  | ||||||
|         if self.ctrlDown and keyname == "h": |  | ||||||
|             self.show_hide_hidden_files() |  | ||||||
|         if (self.ctrlDown and keyname == "e"): |  | ||||||
|             self.edit_files() |  | ||||||
|         if self.ctrlDown and keyname == "c": |  | ||||||
|             self.to_cut_files.clear() |  | ||||||
|             self.copy_files() |  | ||||||
|         if self.ctrlDown and keyname == "x": |  | ||||||
|             self.to_copy_files.clear() |  | ||||||
|             self.cut_files() |  | ||||||
|         if self.ctrlDown and keyname == "v": |  | ||||||
|             self.paste_files() |  | ||||||
|         if self.ctrlDown and keyname == "n": |  | ||||||
|             self.show_new_file_menu() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if keyname in ["alt_l", "alt_r"]: |  | ||||||
|             top_main_menubar = self.builder.get_object("top_main_menubar") |  | ||||||
|             if top_main_menubar.is_visible(): |  | ||||||
|                 top_main_menubar.hide() |  | ||||||
|             else: |  | ||||||
|                 top_main_menubar.show() |  | ||||||
|         if keyname == "delete": |  | ||||||
|             self.delete_files() |  | ||||||
|         if keyname == "f2": |  | ||||||
|             self.rename_files() |  | ||||||
|         if keyname == "f4": |  | ||||||
|             wid, tid = self.window_controller.get_active_data() |  | ||||||
|             view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|             dir      = view.get_current_directory() |  | ||||||
|             view.execute(f"{view.terminal_app}", dir) |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| from .KeyboardSignalsMixin import KeyboardSignalsMixin |  | ||||||
| from .ShowHideMixin import ShowHideMixin |  | ||||||
| @@ -1,223 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from . import WidgetMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TabMixin(WidgetMixin): |  | ||||||
|     """docstring for TabMixin""" |  | ||||||
|  |  | ||||||
|     def create_tab_from_ipc(data): |  | ||||||
|         self, path = data |  | ||||||
|         wid, tid   = self.window_controller.get_active_data() |  | ||||||
|         notebook   = self.builder.get_object(f"window_{wid}") |  | ||||||
|         if notebook.is_visible(): |  | ||||||
|             self.create_tab(wid, path) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         if not self.is_pane4_hidden: |  | ||||||
|             self.create_tab(4, path) |  | ||||||
|         elif not self.is_pane3_hidden: |  | ||||||
|             self.create_tab(3, path) |  | ||||||
|         elif not self.is_pane2_hidden: |  | ||||||
|             self.create_tab(2, path) |  | ||||||
|         elif not self.is_pane1_hidden: |  | ||||||
|             self.create_tab(1, path) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_tab(self, wid, path=None): |  | ||||||
|         notebook    = self.builder.get_object(f"window_{wid}") |  | ||||||
|         path_entry  = self.builder.get_object(f"path_entry") |  | ||||||
|         view        = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}") |  | ||||||
|         view.logger = self.logger |  | ||||||
|  |  | ||||||
|         view.set_wid(wid) |  | ||||||
|         if path: view.set_path(path) |  | ||||||
|  |  | ||||||
|         tab           = self.create_tab_widget(view) |  | ||||||
|         scroll, store = self.create_grid_iconview_widget(view, wid) |  | ||||||
|         # scroll, store = self.create_grid_treeview_widget(view, wid) |  | ||||||
|         index         = notebook.append_page(scroll, tab) |  | ||||||
|  |  | ||||||
|         self.window_controller.set_active_data(wid, view.get_tab_id()) |  | ||||||
|         path_entry.set_text(view.get_current_directory()) |  | ||||||
|         notebook.show_all() |  | ||||||
|         notebook.set_current_page(index) |  | ||||||
|  |  | ||||||
|         ctx = notebook.get_style_context() |  | ||||||
|         ctx.add_class("notebook-unselected-focus") |  | ||||||
|         notebook.set_tab_reorderable(scroll, True) |  | ||||||
|         self.load_store(view, store) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def close_tab(self, button, eve=None): |  | ||||||
|         notebook = button.get_parent().get_parent() |  | ||||||
|         tid      = self.get_tab_id_from_tab_box(button.get_parent()) |  | ||||||
|         wid      = int(notebook.get_name()[-1]) |  | ||||||
|         scroll   = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page     = notebook.page_num(scroll) |  | ||||||
|         view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         watcher  = view.get_dir_watcher() |  | ||||||
|  |  | ||||||
|         watcher.cancel() |  | ||||||
|         self.get_fm_window(wid).delete_view_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def on_tab_reorder(self, child, page_num, new_index): |  | ||||||
|         wid, tid = page_num.get_name().split("|") |  | ||||||
|         window   = self.get_fm_window(wid) |  | ||||||
|         view     = None |  | ||||||
|  |  | ||||||
|         for i, view in enumerate(window.views): |  | ||||||
|             if view.id == tid: |  | ||||||
|                 _view   = window.get_view_by_id(tid) |  | ||||||
|                 watcher = _view.get_dir_watcher() |  | ||||||
|                 watcher.cancel() |  | ||||||
|                 window.views.insert(new_index, window.views.pop(i)) |  | ||||||
|  |  | ||||||
|         view = window.get_view_by_id(tid) |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|  |  | ||||||
|     def on_tab_switch_update(self, notebook, content=None, index=None): |  | ||||||
|         self.selected_files.clear() |  | ||||||
|         wid, tid = content.get_children()[0].get_name().split("|") |  | ||||||
|         self.window_controller.set_active_data(wid, tid) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def get_tab_id_from_tab_box(self, tab_box): |  | ||||||
|         tid = tab_box.get_children()[2] |  | ||||||
|         return tid.get_text() |  | ||||||
|  |  | ||||||
|     def get_tab_label(self, notebook, iconview): |  | ||||||
|         return notebook.get_tab_label(iconview.get_parent()).get_children()[0] |  | ||||||
|  |  | ||||||
|     def get_tab_close(self, notebook, iconview): |  | ||||||
|         return notebook.get_tab_label(iconview.get_parent()).get_children()[1] |  | ||||||
|  |  | ||||||
|     def get_tab_iconview_from_notebook(self, notebook): |  | ||||||
|         return notebook.get_children()[1].get_children()[0] |  | ||||||
|  |  | ||||||
|     def refresh_tab(data=None): |  | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |  | ||||||
|         view.load_directory() |  | ||||||
|         self.load_store(view, store) |  | ||||||
|  |  | ||||||
|     def update_view(self, tab_label, view, store, wid, tid): |  | ||||||
|         self.load_store(view, store) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|  |  | ||||||
|         char_width = len(view.get_end_of_path()) |  | ||||||
|         tab_label.set_width_chars(char_width) |  | ||||||
|         tab_label.set_label(view.get_end_of_path()) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|  |  | ||||||
|     def do_action_from_bar_controls(self, widget, eve=None): |  | ||||||
|         action    = widget.get_name() |  | ||||||
|         wid, tid  = self.window_controller.get_active_data() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |  | ||||||
|         view      = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|  |  | ||||||
|         if action == "go_up": |  | ||||||
|             view.pop_from_path() |  | ||||||
|         if action == "go_home": |  | ||||||
|             view.set_to_home() |  | ||||||
|         if action == "refresh_view": |  | ||||||
|             view.load_directory() |  | ||||||
|         if action == "create_tab": |  | ||||||
|             dir = view.get_current_directory() |  | ||||||
|             self.create_tab(wid, dir) |  | ||||||
|             self.window_controller.save_state() |  | ||||||
|             return |  | ||||||
|         if action == "path_entry": |  | ||||||
|             focused_obj = self.window.get_focus() |  | ||||||
|             dir         = f"{view.get_current_directory()}/" |  | ||||||
|             path        = widget.get_text() |  | ||||||
|  |  | ||||||
|             if isinstance(focused_obj, Gtk.Entry): |  | ||||||
|                 button_box  = self.path_menu.get_children()[0].get_children()[0].get_children()[0] |  | ||||||
|                 query       = widget.get_text().replace(dir, "") |  | ||||||
|                 files       = view.files + view.hidden |  | ||||||
|  |  | ||||||
|                 self.clear_children(button_box) |  | ||||||
|                 show_path_menu = False |  | ||||||
|                 for file in files: |  | ||||||
|                     if os.path.isdir(f"{dir}{file}"): |  | ||||||
|                         if query.lower() in file.lower(): |  | ||||||
|                             button = Gtk.Button(label=file) |  | ||||||
|                             button.show() |  | ||||||
|                             button.connect("clicked", self.set_path_entry) |  | ||||||
|                             button_box.add(button) |  | ||||||
|                             show_path_menu = True |  | ||||||
|  |  | ||||||
|                 if not show_path_menu: |  | ||||||
|                     self.path_menu.popdown() |  | ||||||
|                 else: |  | ||||||
|                     self.path_menu.popup() |  | ||||||
|                     widget.grab_focus_without_selecting() |  | ||||||
|                     widget.set_position(-1) |  | ||||||
|  |  | ||||||
|             if path.endswith(".") or path == dir: |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|             traversed = view.set_path(path) |  | ||||||
|             if not traversed: |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|         self.update_view(tab_label, view, store, wid, tid) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             widget.grab_focus_without_selecting() |  | ||||||
|             widget.set_position(-1) |  | ||||||
|         except Exception as e: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def set_path_entry(self, button=None, eve=None): |  | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |  | ||||||
|         path       = f"{view.get_current_directory()}/{button.get_label()}" |  | ||||||
|         path_entry = self.builder.get_object("path_entry") |  | ||||||
|         path_entry.set_text(path) |  | ||||||
|         path_entry.grab_focus_without_selecting() |  | ||||||
|         path_entry.set_position(-1) |  | ||||||
|         self.path_menu.popdown() |  | ||||||
|  |  | ||||||
|     def keyboard_close_tab(self): |  | ||||||
|         wid, tid  = self.window_controller.get_active_data() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         scroll    = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page      = notebook.page_num(scroll) |  | ||||||
|         view      = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         watcher   = view.get_dir_watcher() |  | ||||||
|         watcher.cancel() |  | ||||||
|  |  | ||||||
|         self.get_fm_window(wid).delete_view_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     # File control events |  | ||||||
|     def show_hide_hidden_files(self): |  | ||||||
|         wid, tid = self.window_controller.get_active_data() |  | ||||||
|         view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         view.hide_hidden = not view.hide_hidden |  | ||||||
|         view.load_directory() |  | ||||||
|         self.builder.get_object("refresh_view").released() |  | ||||||
| @@ -1,221 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os, threading, subprocess, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
|  |  | ||||||
| gi.require_version("Gtk", "3.0") |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class WidgetMixin: |  | ||||||
|     def load_store(self, view, store, save_state=False): |  | ||||||
|         store.clear() |  | ||||||
|         dir   = view.get_current_directory() |  | ||||||
|         files = view.get_files() |  | ||||||
|  |  | ||||||
|         for i, file in enumerate(files): |  | ||||||
|             store.append([None, file[0]]) |  | ||||||
|             self.create_icon(i, view, store, dir, file[0]) |  | ||||||
|  |  | ||||||
|         # NOTE: Not likely called often from here but it could be useful |  | ||||||
|         if save_state: |  | ||||||
|             self.window_controller.save_state() |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def create_icon(self, i, view, store, dir, file): |  | ||||||
|         icon  = view.create_icon(dir, file) |  | ||||||
|         fpath = f"{dir}/{file}" |  | ||||||
|         GLib.idle_add(self.update_store, (i, store, icon, view, fpath,)) |  | ||||||
|  |  | ||||||
|     # NOTE: Might need to keep an eye on this throwing invalid iters when too |  | ||||||
|     #       many updates are happening to a folder. Example: /tmp |  | ||||||
|     def update_store(self, item): |  | ||||||
|         i, store, icon, view, fpath = item |  | ||||||
|         itr = None |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             itr = store.get_iter(i) |  | ||||||
|         except Exception as e: |  | ||||||
|             try: |  | ||||||
|                 time.sleep(0.2) |  | ||||||
|                 itr = store.get_iter(i) |  | ||||||
|             except Exception as e: |  | ||||||
|                 print(":Invalid Itr detected: (Potential race condition...)") |  | ||||||
|                 print(f"Index Requested:  {i}") |  | ||||||
|                 print(f"Store Size:  {len(store)}") |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|         if not icon: |  | ||||||
|             icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) |  | ||||||
|             if not icon: |  | ||||||
|                 if fpath.endswith(".gif"): |  | ||||||
|                     icon = GdkPixbuf.PixbufAnimation.get_static_image(fpath) |  | ||||||
|                 else: |  | ||||||
|                     icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON) |  | ||||||
|  |  | ||||||
|         store.set_value(itr, 0, icon) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_system_thumbnail(self, filename, size): |  | ||||||
|         try: |  | ||||||
|             if os.path.exists(filename): |  | ||||||
|                 gioFile   = Gio.File.new_for_path(filename) |  | ||||||
|                 info      = gioFile.query_info('standard::icon' , 0, Gio.Cancellable()) |  | ||||||
|                 icon      = info.get_icon().get_names()[0] |  | ||||||
|                 iconTheme = Gtk.IconTheme.get_default() |  | ||||||
|                 iconData  = iconTheme.lookup_icon(icon , size , 0) |  | ||||||
|                 if iconData: |  | ||||||
|                     iconPath  = iconData.get_filename() |  | ||||||
|                     return GdkPixbuf.Pixbuf.new_from_file(iconPath) |  | ||||||
|                 else: |  | ||||||
|                     return None |  | ||||||
|             else: |  | ||||||
|                 return None |  | ||||||
|         except Exception as e: |  | ||||||
|             print("System icon generation issue:") |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_tab_widget(self, view): |  | ||||||
|         tab   = Gtk.ButtonBox() |  | ||||||
|         label = Gtk.Label() |  | ||||||
|         tid   = Gtk.Label() |  | ||||||
|         close = Gtk.Button() |  | ||||||
|         icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE) |  | ||||||
|  |  | ||||||
|         label.set_label(f"{view.get_end_of_path()}") |  | ||||||
|         label.set_width_chars(len(view.get_end_of_path())) |  | ||||||
|         label.set_xalign(0.0) |  | ||||||
|         tid.set_label(f"{view.id}") |  | ||||||
|  |  | ||||||
|         close.add(icon) |  | ||||||
|         tab.add(label) |  | ||||||
|         tab.add(close) |  | ||||||
|         tab.add(tid) |  | ||||||
|  |  | ||||||
|         close.connect("released", self.close_tab) |  | ||||||
|         tab.show_all() |  | ||||||
|         tid.hide() |  | ||||||
|         return tab |  | ||||||
|  |  | ||||||
|     def create_grid_iconview_widget(self, view, wid): |  | ||||||
|         scroll = Gtk.ScrolledWindow() |  | ||||||
|         grid   = Gtk.IconView() |  | ||||||
|         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) |  | ||||||
|  |  | ||||||
|         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("toggle-cursor-item", self.grid_cursor_toggled) |  | ||||||
|         # grid.connect("notify", self.grid_cursor_toggled) |  | ||||||
|         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) |  | ||||||
|         grid.set_name(f"{wid}|{view.id}") |  | ||||||
|         scroll.set_name(f"{wid}|{view.id}") |  | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}|iconview", grid) |  | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}", scroll) |  | ||||||
|         return scroll, store |  | ||||||
|  |  | ||||||
|     def create_grid_treeview_widget(self, view, wid): |  | ||||||
|         scroll = Gtk.ScrolledWindow() |  | ||||||
|         grid   = Gtk.TreeView() |  | ||||||
|         store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str) |  | ||||||
|         # store  = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) |  | ||||||
|         column = Gtk.TreeViewColumn("Icons") |  | ||||||
|         icon   = Gtk.CellRendererPixbuf() |  | ||||||
|         name   = Gtk.CellRendererText() |  | ||||||
|         selec  = grid.get_selection() |  | ||||||
|  |  | ||||||
|         grid.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) |  | ||||||
|  |  | ||||||
|         grid.append_column(column) |  | ||||||
|         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) |  | ||||||
|         grid.connect("row-activated", self.grid_icon_double_click) |  | ||||||
|         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) |  | ||||||
|         grid.set_name(f"{wid}|{view.id}") |  | ||||||
|         scroll.set_name(f"{wid}|{view.id}") |  | ||||||
|         grid.columns_autosize() |  | ||||||
|         self.builder.expose_object(f"{wid}|{view.id}", scroll) |  | ||||||
|         return scroll, store |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def get_store_and_label_from_notebook(self, notebook, _name): |  | ||||||
|         icon_view = None |  | ||||||
|         tab_label = None |  | ||||||
|         store     = None |  | ||||||
|  |  | ||||||
|         for obj in notebook.get_children(): |  | ||||||
|             icon_view = obj.get_children()[0] |  | ||||||
|             name      =  icon_view.get_name() |  | ||||||
|             if name == _name: |  | ||||||
|                 store = icon_view.get_model() |  | ||||||
|                 tab_label = notebook.get_tab_label(obj).get_children()[0] |  | ||||||
|  |  | ||||||
|         return store, tab_label |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| from .PaneMixin    import PaneMixin |  | ||||||
| from .WidgetMixin  import WidgetMixin |  | ||||||
| from .TabMixin     import TabMixin |  | ||||||
| from .WindowMixin import WindowMixin |  | ||||||
| from .WidgetFileActionMixin import WidgetFileActionMixin |  | ||||||
							
								
								
									
										3
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/__init__.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | """ | ||||||
|  | Trasher module | ||||||
|  | """ | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										2
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/trash.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -21,7 +21,7 @@ class Trash(object): | |||||||
|             if os.path.isfile(item): |             if os.path.isfile(item): | ||||||
|                 size = size + os.path.getsize(item) |                 size = size + os.path.getsize(item) | ||||||
|             elif os.path.isdir(item): |             elif os.path.isdir(item): | ||||||
|                 size = size + size_dir(item) |                 size = size + self.size_dir(item) | ||||||
|  |  | ||||||
|         return size |         return size | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/xdgtrash.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/trasher/xdgtrash.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,95 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
| from os import path |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
| import gi, cairo |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
|  |  | ||||||
| from gi.repository import Gtk as gtk |  | ||||||
| from gi.repository import Gdk as gdk |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from . import Logger |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Settings: |  | ||||||
|     def __init__(self): |  | ||||||
|         self.builder       = gtk.Builder() |  | ||||||
|  |  | ||||||
|         self.SCRIPT_PTH    = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         self.USER_HOME     = path.expanduser('~') |  | ||||||
|         self.CONFIG_PATH   = f"{self.USER_HOME}/.config/{app_name.lower()}" |  | ||||||
|         self.PLUGINS_PATH  = f"{self.CONFIG_PATH}/plugins" |  | ||||||
|         self.USR_SOLARFM   = f"/usr/share/{app_name.lower()}" |  | ||||||
|  |  | ||||||
|         self.cssFile       = f"{self.CONFIG_PATH}/stylesheet.css" |  | ||||||
|         self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade" |  | ||||||
|         self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" |  | ||||||
|         self.window_icon   = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png" |  | ||||||
|         self.main_window   = None |  | ||||||
|  |  | ||||||
|         if not os.path.exists(self.CONFIG_PATH): |  | ||||||
|             os.mkdir(self.CONFIG_PATH) |  | ||||||
|         if not os.path.exists(self.PLUGINS_PATH): |  | ||||||
|             os.mkdir(self.PLUGINS_PATH) |  | ||||||
|  |  | ||||||
|         if not os.path.exists(self.windows_glade): |  | ||||||
|             self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade" |  | ||||||
|         if not os.path.exists(self.cssFile): |  | ||||||
|             self.cssFile       = f"{self.USR_SOLARFM}/stylesheet.css" |  | ||||||
|         if not os.path.exists(self.window_icon): |  | ||||||
|             self.window_icon   = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png" |  | ||||||
|         if not os.path.exists(self.DEFAULT_ICONS): |  | ||||||
|             self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons" |  | ||||||
|  |  | ||||||
|         self.logger = Logger(self.CONFIG_PATH).get_logger() |  | ||||||
|         self.builder.add_from_file(self.windows_glade) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def create_window(self): |  | ||||||
|         # Get window and connect signals |  | ||||||
|         self.main_window = self.builder.get_object("Main_Window") |  | ||||||
|         self._set_window_data() |  | ||||||
|  |  | ||||||
|     def _set_window_data(self): |  | ||||||
|         self.main_window.set_icon_from_file(self.window_icon) |  | ||||||
|         screen = self.main_window.get_screen() |  | ||||||
|         visual = screen.get_rgba_visual() |  | ||||||
|  |  | ||||||
|         if visual != None and screen.is_composited(): |  | ||||||
|             self.main_window.set_visual(visual) |  | ||||||
|             self.main_window.set_app_paintable(True) |  | ||||||
|             self.main_window.connect("draw", self._area_draw) |  | ||||||
|  |  | ||||||
|         # bind css file |  | ||||||
|         cssProvider  = gtk.CssProvider() |  | ||||||
|         cssProvider.load_from_path(self.cssFile) |  | ||||||
|         screen       = gdk.Screen.get_default() |  | ||||||
|         styleContext = gtk.StyleContext() |  | ||||||
|         styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) |  | ||||||
|  |  | ||||||
|     def _area_draw(self, widget, cr): |  | ||||||
|         cr.set_source_rgba(0, 0, 0, 0.54) |  | ||||||
|         cr.set_operator(cairo.OPERATOR_SOURCE) |  | ||||||
|         cr.paint() |  | ||||||
|         cr.set_operator(cairo.OPERATOR_OVER) |  | ||||||
|  |  | ||||||
|     def get_monitor_data(self): |  | ||||||
|         screen = self.builder.get_object("Main_Window").get_screen() |  | ||||||
|         monitors = [] |  | ||||||
|         for m in range(screen.get_n_monitors()): |  | ||||||
|             monitors.append(screen.get_monitor_geometry(m)) |  | ||||||
|  |  | ||||||
|         for monitor in monitors: |  | ||||||
|             print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) |  | ||||||
|  |  | ||||||
|         return monitors |  | ||||||
|  |  | ||||||
|     def get_builder(self):       return self.builder |  | ||||||
|     def get_logger(self):        return self.logger |  | ||||||
|     def get_main_window(self):   return self.main_window |  | ||||||
|     def get_plugins_path(self):  return self.PLUGINS_PATH |  | ||||||
| @@ -1,6 +1,3 @@ | |||||||
| """ | """ | ||||||
|     Utils module |     Utils module | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from .Logger import Logger |  | ||||||
| from .Settings import Settings |  | ||||||
|   | |||||||
							
								
								
									
										102
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/debs/solarfm-0-0-1-x64/opt/SolarFM/utils/settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | # Python imports | ||||||
|  | import os | ||||||
|  | from os import path | ||||||
|  |  | ||||||
|  | # Gtk imports | ||||||
|  | import gi, cairo | ||||||
|  | gi.require_version('Gtk', '3.0') | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  |  | ||||||
|  | from gi.repository import Gtk | ||||||
|  | from gi.repository import Gdk | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from .logger import Logger | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Settings: | ||||||
|  |     def __init__(self): | ||||||
|  |         self._SCRIPT_PTH    = os.path.dirname(os.path.realpath(__file__)) | ||||||
|  |         self._USER_HOME     = path.expanduser('~') | ||||||
|  |         self._CONFIG_PATH   = f"{self._USER_HOME}/.config/{app_name.lower()}" | ||||||
|  |         self._PLUGINS_PATH  = f"{self._CONFIG_PATH}/plugins" | ||||||
|  |         self._USR_SOLARFM   = f"/usr/share/{app_name.lower()}" | ||||||
|  |  | ||||||
|  |         self._CSS_FILE      = f"{self._CONFIG_PATH}/stylesheet.css" | ||||||
|  |         self._WINDOWS_GLADE = f"{self._CONFIG_PATH}/Main_Window.glade" | ||||||
|  |         self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" | ||||||
|  |         self._WINDOW_ICON   = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" | ||||||
|  |  | ||||||
|  |         if not os.path.exists(self._CONFIG_PATH): | ||||||
|  |             os.mkdir(self._CONFIG_PATH) | ||||||
|  |         if not os.path.exists(self._PLUGINS_PATH): | ||||||
|  |             os.mkdir(self._PLUGINS_PATH) | ||||||
|  |  | ||||||
|  |         if not os.path.exists(self._WINDOWS_GLADE): | ||||||
|  |             self._WINDOWS_GLADE = f"{self._USR_SOLARFM}/Main_Window.glade" | ||||||
|  |         if not os.path.exists(self._CSS_FILE): | ||||||
|  |             self._CSS_FILE      = f"{self._USR_SOLARFM}/stylesheet.css" | ||||||
|  |         if not os.path.exists(self._WINDOW_ICON): | ||||||
|  |             self._WINDOW_ICON   = f"{self._USR_SOLARFM}/icons/{app_name.lower()}.png" | ||||||
|  |         if not os.path.exists(self._DEFAULT_ICONS): | ||||||
|  |             self._DEFAULT_ICONS = f"{self._USR_SOLARFM}/icons" | ||||||
|  |  | ||||||
|  |         self._success_color = "#88cc27" | ||||||
|  |         self._warning_color = "#ffa800" | ||||||
|  |         self._error_color   = "#ff0000" | ||||||
|  |  | ||||||
|  |         self.main_window    = None | ||||||
|  |         self.logger         = Logger(self._CONFIG_PATH).get_logger() | ||||||
|  |         self.builder        = Gtk.Builder() | ||||||
|  |         self.builder.add_from_file(self._WINDOWS_GLADE) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def create_window(self): | ||||||
|  |         # Get window and connect signals | ||||||
|  |         self.main_window = self.builder.get_object("Main_Window") | ||||||
|  |         self._set_window_data() | ||||||
|  |  | ||||||
|  |     def _set_window_data(self): | ||||||
|  |         self.main_window.set_icon_from_file(self._WINDOW_ICON) | ||||||
|  |         screen = self.main_window.get_screen() | ||||||
|  |         visual = screen.get_rgba_visual() | ||||||
|  |  | ||||||
|  |         if visual != None and screen.is_composited(): | ||||||
|  |             self.main_window.set_visual(visual) | ||||||
|  |             self.main_window.set_app_paintable(True) | ||||||
|  |             self.main_window.connect("draw", self._area_draw) | ||||||
|  |  | ||||||
|  |         # bind css file | ||||||
|  |         cssProvider  = Gtk.CssProvider() | ||||||
|  |         cssProvider.load_from_path(self._CSS_FILE) | ||||||
|  |         screen       = Gdk.Screen.get_default() | ||||||
|  |         styleContext = Gtk.StyleContext() | ||||||
|  |         styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER) | ||||||
|  |  | ||||||
|  |     def _area_draw(self, widget, cr): | ||||||
|  |         cr.set_source_rgba(0, 0, 0, 0.54) | ||||||
|  |         cr.set_operator(cairo.OPERATOR_SOURCE) | ||||||
|  |         cr.paint() | ||||||
|  |         cr.set_operator(cairo.OPERATOR_OVER) | ||||||
|  |  | ||||||
|  |     def get_monitor_data(self): | ||||||
|  |         screen = self.builder.get_object("Main_Window").get_screen() | ||||||
|  |         monitors = [] | ||||||
|  |         for m in range(screen.get_n_monitors()): | ||||||
|  |             monitors.append(screen.get_monitor_geometry(m)) | ||||||
|  |  | ||||||
|  |         for monitor in monitors: | ||||||
|  |             print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) | ||||||
|  |  | ||||||
|  |         return monitors | ||||||
|  |  | ||||||
|  |     def get_builder(self):       return self.builder | ||||||
|  |     def get_logger(self):        return self.logger | ||||||
|  |     def get_main_window(self):   return self.main_window | ||||||
|  |     def get_plugins_path(self):  return self._PLUGINS_PATH | ||||||
|  |  | ||||||
|  |     def get_success_color(self):  return self._success_color | ||||||
|  |     def get_warning_color(self):  return self._warning_color | ||||||
|  |     def get_error_color(self):    return self._error_color | ||||||
							
								
								
									
										18
									
								
								src/versions/solarfm-0.0.1/SolarFM/debugger.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								src/versions/solarfm-0.0.1/SolarFM/debugger.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #!/bin/bash | ||||||
|  |  | ||||||
|  | # . CONFIG.sh | ||||||
|  |  | ||||||
|  | # set -o xtrace       ## To debug scripts | ||||||
|  | # set -o errexit      ## To exit on error | ||||||
|  | # set -o errunset     ## To exit if a variable is referenced but not set | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function main() { | ||||||
|  |     SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )" | ||||||
|  |     cd "${SCRIPTPATH}" | ||||||
|  |     echo "Working Dir: " $(pwd) | ||||||
|  |  | ||||||
|  |     source '/home/abaddon/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/activate' | ||||||
|  |     python -m pudb $(pwd)/solarfm/__main__.py; bash | ||||||
|  | } | ||||||
|  | main "$@"; | ||||||
| @@ -4,73 +4,71 @@ import builtins | |||||||
| # Lib imports | # Lib imports | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from controller import IPCServerMixin | from utils.ipc_server import IPCServer | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Builtins(IPCServerMixin): | class EventSystem(IPCServer): | ||||||
|     """Docstring for __builtins__ extender""" |     """ Inheret IPCServerMixin. Create an pub/sub systems. """ | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         # NOTE: The format used is list of [type, target, data] Where: |         super(EventSystem, self).__init__() | ||||||
|         #             type is useful context for control flow, |  | ||||||
|         #             target is the method to call, |         # NOTE: The format used is list of ['who', target, (data,)] Where: | ||||||
|         #             data is the method parameters to give |         #             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 |         #       Where data may be any kind of data | ||||||
|         self._gui_events    = [] |         self._gui_events    = [] | ||||||
|         self._fm_events     = [] |         self._module_events = [] | ||||||
|         self.is_ipc_alive   = False |  | ||||||
|         self.ipc_authkey    = b'solarfm-ipc' |  | ||||||
|         self.ipc_address    = '127.0.0.1' |  | ||||||
|         self.ipc_port       = 4848 |  | ||||||
|         self.ipc_timeout    = 15.0 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Makeshift fake "events" type system FIFO |  | ||||||
|     def _pop_gui_event(self): |     # Makeshift "events" system FIFO | ||||||
|  |     def _pop_gui_event(self) -> None: | ||||||
|         if len(self._gui_events) > 0: |         if len(self._gui_events) > 0: | ||||||
|             return self._gui_events.pop(0) |             return self._gui_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     def _pop_fm_event(self): |     def _pop_module_event(self) -> None: | ||||||
|         if len(self._fm_events) > 0: |         if len(self._module_events) > 0: | ||||||
|             return self._fm_events.pop(0) |             return self._module_events.pop(0) | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
|     def push_gui_event(self, event): |     def push_gui_event(self, event: list) -> None: | ||||||
|         if len(event) == 3: |         if len(event) == 3: | ||||||
|             self._gui_events.append(event) |             self._gui_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, data]") |         raise Exception("Invald event format! Please do:  ['sender_id': str, method_target: method, (data,): any]") | ||||||
|  |  | ||||||
|     def push_fm_event(self, event): |     def push_module_event(self, event: list) -> None: | ||||||
|         if len(event) == 3: |         if len(event) == 3: | ||||||
|             self._fm_events.append(event) |             self._module_events.append(event) | ||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         raise Exception("Invald event format! Please do:  [type, target, data]") |         raise Exception("Invald event format! Please do:  ['target_id': str, method_target: method, (data,): any]") | ||||||
|  |  | ||||||
|     def read_gui_event(self): |     def read_gui_event(self) -> list: | ||||||
|         return self._gui_events[0] |         return self._gui_events[0] if self._gui_events else None | ||||||
|  |  | ||||||
|     def read_fm_event(self): |     def read_module_event(self) -> list: | ||||||
|         return self._fm_events[0] |         return self._module_events[0] if self._module_events else None | ||||||
|  |  | ||||||
|     def consume_gui_event(self): |     def consume_gui_event(self) -> list: | ||||||
|         return self._pop_gui_event() |         return self._pop_gui_event() | ||||||
|  |  | ||||||
|     def consume_fm_event(self): |     def consume_module_event(self) -> list: | ||||||
|         return self._pop_fm_event() |         return self._pop_module_event() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # 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.event_system      = Builtins() | builtins.event_system      = EventSystem() | ||||||
| builtins.event_sleep_time  = 0.2 | builtins.event_sleep_time  = 0.05 | ||||||
| builtins.debug             = False |  | ||||||
| builtins.trace_debug       = False | builtins.trace_debug       = False | ||||||
|  | builtins.debug             = False | ||||||
|   | |||||||
| @@ -1,54 +1,3 @@ | |||||||
| # Python imports | """ | ||||||
| import os, inspect, time |     Base module | ||||||
|  | """ | ||||||
| # Lib imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from utils import Settings |  | ||||||
| from controller import Controller |  | ||||||
| from __builtins__ import Builtins |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Main(Builtins): |  | ||||||
|     def __init__(self, args, unknownargs): |  | ||||||
|         if not debug: |  | ||||||
|             event_system.create_ipc_server() |  | ||||||
|  |  | ||||||
|         time.sleep(0.2) |  | ||||||
|         if not trace_debug: |  | ||||||
|             if not event_system.is_ipc_alive: |  | ||||||
|                 if unknownargs: |  | ||||||
|                     for arg in unknownargs: |  | ||||||
|                         if os.path.isdir(arg): |  | ||||||
|                             message = f"FILE|{arg}" |  | ||||||
|                             event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 if args.new_tab and os.path.isdir(args.new_tab): |  | ||||||
|                     message = f"FILE|{args.new_tab}" |  | ||||||
|                     event_system.send_ipc_message(message) |  | ||||||
|  |  | ||||||
|                 raise Exception("IPC Server Exists: Will send path(s) to it and close...") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         settings = Settings() |  | ||||||
|         settings.create_window() |  | ||||||
|  |  | ||||||
|         controller = Controller(args, unknownargs, settings) |  | ||||||
|         if not controller: |  | ||||||
|             raise Exception("Controller exited and doesn't exist...") |  | ||||||
|  |  | ||||||
|         # Gets the methods from the classes and sets to handler. |  | ||||||
|         # Then, builder connects to any signals it needs. |  | ||||||
|         classes  = [controller] |  | ||||||
|         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)) |  | ||||||
|  |  | ||||||
|         settings.builder.connect_signals(handlers) |  | ||||||
|   | |||||||
| @@ -15,10 +15,12 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
| from __init__ import Main | from app import Application | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|  |     """ Set process title, get arguments, and create GTK main thread. """ | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         # import web_pdb |         # import web_pdb | ||||||
|         # web_pdb.set_trace() |         # web_pdb.set_trace() | ||||||
| @@ -33,7 +35,7 @@ if __name__ == "__main__": | |||||||
|         # Read arguments (If any...) |         # Read arguments (If any...) | ||||||
|         args, unknownargs = parser.parse_known_args() |         args, unknownargs = parser.parse_known_args() | ||||||
|  |  | ||||||
|         Main(args, unknownargs) |         Application(args, unknownargs) | ||||||
|         Gtk.main() |         Gtk.main() | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         traceback.print_exc() |         traceback.print_exc() | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | # Python imports | ||||||
|  | import os, inspect, time | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  | from utils.settings import Settings | ||||||
|  | from core.controller import Controller | ||||||
|  | from __builtins__ import EventSystem | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Application(EventSystem): | ||||||
|  |     """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ | ||||||
|  |  | ||||||
|  |     def __init__(self, args, unknownargs): | ||||||
|  |         if not trace_debug: | ||||||
|  |             event_system.create_ipc_listener() | ||||||
|  |             time.sleep(0.05) | ||||||
|  |  | ||||||
|  |             if not event_system.is_ipc_alive: | ||||||
|  |                 if unknownargs: | ||||||
|  |                     for arg in unknownargs: | ||||||
|  |                         if os.path.isdir(arg): | ||||||
|  |                             message = f"FILE|{arg}" | ||||||
|  |                             event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |                 if args.new_tab and os.path.isdir(args.new_tab): | ||||||
|  |                     message = f"FILE|{args.new_tab}" | ||||||
|  |                     event_system.send_ipc_message(message) | ||||||
|  |  | ||||||
|  |                 raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         settings = Settings() | ||||||
|  |         settings.create_window() | ||||||
|  |  | ||||||
|  |         controller = Controller(args, unknownargs, settings) | ||||||
|  |         if not controller: | ||||||
|  |             raise Exception("Controller exited and doesn't exist...") | ||||||
|  |  | ||||||
|  |         # Gets the methods from the classes and sets to handler. | ||||||
|  |         # Then, builder connects to any signals it needs. | ||||||
|  |         classes  = [controller] | ||||||
|  |         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)) | ||||||
|  |  | ||||||
|  |         settings.get_builder().connect_signals(handlers) | ||||||
| @@ -1,162 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import sys, traceback, threading, inspect, os, time |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| from gi.repository import Gtk, GLib |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from .mixins import UIMixin |  | ||||||
| from .signals import IPCSignalsMixin, KeyboardSignalsMixin |  | ||||||
| from . import Controller_Data |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def threaded(fn): |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data): |  | ||||||
|     def __init__(self, args, unknownargs, _settings): |  | ||||||
|         sys.excepthook = self.custom_except_hook |  | ||||||
|         self.setup_controller_data(_settings) |  | ||||||
|         self.window.show() |  | ||||||
|         self.generate_windows(self.state) |  | ||||||
|         self.plugins.launch_plugins() |  | ||||||
|  |  | ||||||
|         if not trace_debug: |  | ||||||
|             self.gui_event_observer() |  | ||||||
|  |  | ||||||
|             if unknownargs: |  | ||||||
|                 for arg in unknownargs: |  | ||||||
|                     if os.path.isdir(arg): |  | ||||||
|                         message = f"FILE|{arg}" |  | ||||||
|                         event_system.send_ipc_message(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 tear_down(self, widget=None, eve=None): |  | ||||||
|         event_system.send_ipc_message("close server") |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         time.sleep(event_sleep_time) |  | ||||||
|         Gtk.main_quit() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def gui_event_observer(self): |  | ||||||
|         while True: |  | ||||||
|             time.sleep(event_sleep_time) |  | ||||||
|             event = event_system.consume_gui_event() |  | ||||||
|             if event: |  | ||||||
|                 try: |  | ||||||
|                     type, target, data = event |  | ||||||
|                     method = getattr(self.__class__, target) |  | ||||||
|                     GLib.idle_add(method, *(self, *data,)) |  | ||||||
|                 except Exception as e: |  | ||||||
|                     print(repr(e)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def custom_except_hook(self, exctype, value, _traceback): |  | ||||||
|         trace     = ''.join(traceback.format_tb(_traceback)) |  | ||||||
|         data      = f"Exectype:  {exctype}  <-->  Value:  {value}\n\n{trace}\n\n\n\n" |  | ||||||
|         start_itr = self.message_buffer.get_start_iter() |  | ||||||
|         self.message_buffer.place_cursor(start_itr) |  | ||||||
|         self.display_message(self.error, data) |  | ||||||
|  |  | ||||||
|     def display_message(self, type, text, seconds=None): |  | ||||||
|         self.message_buffer.insert_at_cursor(text) |  | ||||||
|         self.message_widget.popup() |  | ||||||
|         if seconds: |  | ||||||
|             self.hide_message_timeout(seconds) |  | ||||||
|  |  | ||||||
|     @threaded |  | ||||||
|     def hide_message_timeout(self, seconds=3): |  | ||||||
|         time.sleep(seconds) |  | ||||||
|         GLib.idle_add(self.message_widget.popdown) |  | ||||||
|  |  | ||||||
|     def save_debug_alerts(self, widget=None, eve=None): |  | ||||||
|         start_itr, end_itr   = self.message_buffer.get_bounds() |  | ||||||
|         save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \ |  | ||||||
|                                                         action  = Gtk.FileChooserAction.SAVE, \ |  | ||||||
|                                                         buttons = (Gtk.STOCK_CANCEL, \ |  | ||||||
|                                                                     Gtk.ResponseType.CANCEL, \ |  | ||||||
|                                                                     Gtk.STOCK_SAVE, \ |  | ||||||
|                                                                     Gtk.ResponseType.OK)) |  | ||||||
|  |  | ||||||
|         text = self.message_buffer.get_text(start_itr, end_itr, False) |  | ||||||
|         resp = save_location_prompt.run() |  | ||||||
|         if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT): |  | ||||||
|             pass |  | ||||||
|         elif resp == Gtk.ResponseType.OK: |  | ||||||
|             target = save_location_prompt.get_filename(); |  | ||||||
|             with open(target, "w") as f: |  | ||||||
|                 f.write(text) |  | ||||||
|  |  | ||||||
|         save_location_prompt.destroy() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def set_arc_buffer_text(self, widget=None, eve=None): |  | ||||||
|         id = widget.get_active_id() |  | ||||||
|         self.arc_command_buffer.set_text(self.arc_commands[int(id)]) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def clear_children(self, widget): |  | ||||||
|         for child in widget.get_children(): |  | ||||||
|             widget.remove(child) |  | ||||||
|  |  | ||||||
|     def get_current_state(self): |  | ||||||
|         wid, tid     = self.window_controller.get_active_data() |  | ||||||
|         view         = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         iconview     = self.builder.get_object(f"{wid}|{tid}|iconview") |  | ||||||
|         store        = iconview.get_model() |  | ||||||
|         return wid, tid, view, iconview, store |  | ||||||
|  |  | ||||||
|     def do_action_from_menu_controls(self, widget, eventbutton): |  | ||||||
|         action        = widget.get_name() |  | ||||||
|         self.ctrlDown = True |  | ||||||
|         self.hide_context_menu() |  | ||||||
|         self.hide_new_file_menu() |  | ||||||
|         self.hide_edit_file_menu() |  | ||||||
|  |  | ||||||
|         if action == "open": |  | ||||||
|             self.open_files() |  | ||||||
|         if action == "open_with": |  | ||||||
|             self.show_appchooser_menu() |  | ||||||
|         if action == "execute": |  | ||||||
|             self.execute_files() |  | ||||||
|         if action == "execute_in_terminal": |  | ||||||
|             self.execute_files(in_terminal=True) |  | ||||||
|         if action == "rename": |  | ||||||
|             self.rename_files() |  | ||||||
|         if action == "cut": |  | ||||||
|             self.to_copy_files.clear() |  | ||||||
|             self.cut_files() |  | ||||||
|         if action == "copy": |  | ||||||
|             self.to_cut_files.clear() |  | ||||||
|             self.copy_files() |  | ||||||
|         if action == "paste": |  | ||||||
|             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.builder.get_object("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": |  | ||||||
|             self.create_files() |  | ||||||
|  |  | ||||||
|         self.ctrlDown = False |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| """ |  | ||||||
|     Gtk Bound Signal Module |  | ||||||
| """ |  | ||||||
| from .mixins import * |  | ||||||
| from .IPCServerMixin import IPCServerMixin |  | ||||||
| from .Controller_Data import Controller_Data |  | ||||||
| from .Controller import Controller |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| # Python imports |  | ||||||
|  |  | ||||||
| # Gtk imports |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from . import ShowHideMixin |  | ||||||
| from .ui import * |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): |  | ||||||
|     pass |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| from .ShowHideMixin import ShowHideMixin |  | ||||||
| from .UIMixin import UIMixin |  | ||||||
| @@ -1,205 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import os |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
| from . import WidgetMixin |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TabMixin(WidgetMixin): |  | ||||||
|     """docstring for TabMixin""" |  | ||||||
|  |  | ||||||
|     def create_tab(self, wid, path=None): |  | ||||||
|         notebook    = self.builder.get_object(f"window_{wid}") |  | ||||||
|         path_entry  = self.builder.get_object(f"path_entry") |  | ||||||
|         view        = self.window_controller.add_view_for_window_by_nickname(f"window_{wid}") |  | ||||||
|         view.logger = self.logger |  | ||||||
|  |  | ||||||
|         view.set_wid(wid) |  | ||||||
|         if path: view.set_path(path) |  | ||||||
|  |  | ||||||
|         tab           = self.create_tab_widget(view) |  | ||||||
|         scroll, store = self.create_grid_iconview_widget(view, wid) |  | ||||||
|         # scroll, store = self.create_grid_treeview_widget(view, wid) |  | ||||||
|         index         = notebook.append_page(scroll, tab) |  | ||||||
|  |  | ||||||
|         self.window_controller.set_active_data(wid, view.get_tab_id()) |  | ||||||
|         path_entry.set_text(view.get_current_directory()) |  | ||||||
|         notebook.show_all() |  | ||||||
|         notebook.set_current_page(index) |  | ||||||
|  |  | ||||||
|         ctx = notebook.get_style_context() |  | ||||||
|         ctx.add_class("notebook-unselected-focus") |  | ||||||
|         notebook.set_tab_reorderable(scroll, True) |  | ||||||
|         self.load_store(view, store) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def close_tab(self, button, eve=None): |  | ||||||
|         notebook = button.get_parent().get_parent() |  | ||||||
|         tid      = self.get_tab_id_from_tab_box(button.get_parent()) |  | ||||||
|         wid      = int(notebook.get_name()[-1]) |  | ||||||
|         scroll   = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page     = notebook.page_num(scroll) |  | ||||||
|         view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         watcher  = view.get_dir_watcher() |  | ||||||
|  |  | ||||||
|         watcher.cancel() |  | ||||||
|         self.get_fm_window(wid).delete_view_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def on_tab_reorder(self, child, page_num, new_index): |  | ||||||
|         wid, tid = page_num.get_name().split("|") |  | ||||||
|         window   = self.get_fm_window(wid) |  | ||||||
|         view     = None |  | ||||||
|  |  | ||||||
|         for i, view in enumerate(window.views): |  | ||||||
|             if view.id == tid: |  | ||||||
|                 _view   = window.get_view_by_id(tid) |  | ||||||
|                 watcher = _view.get_dir_watcher() |  | ||||||
|                 watcher.cancel() |  | ||||||
|                 window.views.insert(new_index, window.views.pop(i)) |  | ||||||
|  |  | ||||||
|         view = window.get_view_by_id(tid) |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|  |  | ||||||
|     def on_tab_switch_update(self, notebook, content=None, index=None): |  | ||||||
|         self.selected_files.clear() |  | ||||||
|         wid, tid = content.get_children()[0].get_name().split("|") |  | ||||||
|         self.window_controller.set_active_data(wid, tid) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     def get_tab_id_from_tab_box(self, tab_box): |  | ||||||
|         tid = tab_box.get_children()[2] |  | ||||||
|         return tid.get_text() |  | ||||||
|  |  | ||||||
|     def get_tab_label(self, notebook, iconview): |  | ||||||
|         return notebook.get_tab_label(iconview.get_parent()).get_children()[0] |  | ||||||
|  |  | ||||||
|     def get_tab_close(self, notebook, iconview): |  | ||||||
|         return notebook.get_tab_label(iconview.get_parent()).get_children()[1] |  | ||||||
|  |  | ||||||
|     def get_tab_iconview_from_notebook(self, notebook): |  | ||||||
|         return notebook.get_children()[1].get_children()[0] |  | ||||||
|  |  | ||||||
|     def refresh_tab(data=None): |  | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |  | ||||||
|         view.load_directory() |  | ||||||
|         self.load_store(view, store) |  | ||||||
|  |  | ||||||
|     def update_view(self, tab_label, view, store, wid, tid): |  | ||||||
|         self.load_store(view, store) |  | ||||||
|         self.set_path_text(wid, tid) |  | ||||||
|  |  | ||||||
|         char_width = len(view.get_end_of_path()) |  | ||||||
|         tab_label.set_width_chars(char_width) |  | ||||||
|         tab_label.set_label(view.get_end_of_path()) |  | ||||||
|         self.set_window_title() |  | ||||||
|         self.set_file_watcher(view) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|  |  | ||||||
|     def do_action_from_bar_controls(self, widget, eve=None): |  | ||||||
|         action    = widget.get_name() |  | ||||||
|         wid, tid  = self.window_controller.get_active_data() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") |  | ||||||
|         view      = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|  |  | ||||||
|         if action == "go_up": |  | ||||||
|             view.pop_from_path() |  | ||||||
|         if action == "go_home": |  | ||||||
|             view.set_to_home() |  | ||||||
|         if action == "refresh_view": |  | ||||||
|             view.load_directory() |  | ||||||
|         if action == "create_tab": |  | ||||||
|             dir = view.get_current_directory() |  | ||||||
|             self.create_tab(wid, dir) |  | ||||||
|             self.window_controller.save_state() |  | ||||||
|             return |  | ||||||
|         if action == "path_entry": |  | ||||||
|             focused_obj = self.window.get_focus() |  | ||||||
|             dir         = f"{view.get_current_directory()}/" |  | ||||||
|             path        = widget.get_text() |  | ||||||
|  |  | ||||||
|             if isinstance(focused_obj, Gtk.Entry): |  | ||||||
|                 button_box  = self.path_menu.get_children()[0].get_children()[0].get_children()[0] |  | ||||||
|                 query       = widget.get_text().replace(dir, "") |  | ||||||
|                 files       = view.files + view.hidden |  | ||||||
|  |  | ||||||
|                 self.clear_children(button_box) |  | ||||||
|                 show_path_menu = False |  | ||||||
|                 for file in files: |  | ||||||
|                     if os.path.isdir(f"{dir}{file}"): |  | ||||||
|                         if query.lower() in file.lower(): |  | ||||||
|                             button = Gtk.Button(label=file) |  | ||||||
|                             button.show() |  | ||||||
|                             button.connect("clicked", self.set_path_entry) |  | ||||||
|                             button_box.add(button) |  | ||||||
|                             show_path_menu = True |  | ||||||
|  |  | ||||||
|                 if not show_path_menu: |  | ||||||
|                     self.path_menu.popdown() |  | ||||||
|                 else: |  | ||||||
|                     self.path_menu.popup() |  | ||||||
|                     widget.grab_focus_without_selecting() |  | ||||||
|                     widget.set_position(-1) |  | ||||||
|  |  | ||||||
|             if path.endswith(".") or path == dir: |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|             traversed = view.set_path(path) |  | ||||||
|             if not traversed: |  | ||||||
|                 return |  | ||||||
|  |  | ||||||
|         self.update_view(tab_label, view, store, wid, tid) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             widget.grab_focus_without_selecting() |  | ||||||
|             widget.set_position(-1) |  | ||||||
|         except Exception as e: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def set_path_entry(self, button=None, eve=None): |  | ||||||
|         wid, tid, view, iconview, store = self.get_current_state() |  | ||||||
|         path       = f"{view.get_current_directory()}/{button.get_label()}" |  | ||||||
|         path_entry = self.builder.get_object("path_entry") |  | ||||||
|         path_entry.set_text(path) |  | ||||||
|         path_entry.grab_focus_without_selecting() |  | ||||||
|         path_entry.set_position(-1) |  | ||||||
|         self.path_menu.popdown() |  | ||||||
|  |  | ||||||
|     def keyboard_close_tab(self): |  | ||||||
|         wid, tid  = self.window_controller.get_active_data() |  | ||||||
|         notebook  = self.builder.get_object(f"window_{wid}") |  | ||||||
|         scroll    = self.builder.get_object(f"{wid}|{tid}") |  | ||||||
|         page      = notebook.page_num(scroll) |  | ||||||
|         view      = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         watcher   = view.get_dir_watcher() |  | ||||||
|         watcher.cancel() |  | ||||||
|  |  | ||||||
|         self.get_fm_window(wid).delete_view_by_id(tid) |  | ||||||
|         notebook.remove_page(page) |  | ||||||
|         self.window_controller.save_state() |  | ||||||
|         self.set_window_title() |  | ||||||
|  |  | ||||||
|     # File control events |  | ||||||
|     def show_hide_hidden_files(self): |  | ||||||
|         wid, tid = self.window_controller.get_active_data() |  | ||||||
|         view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|         view.hide_hidden = not view.hide_hidden |  | ||||||
|         view.load_directory() |  | ||||||
|         self.builder.get_object("refresh_view").released() |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| from .PaneMixin    import PaneMixin |  | ||||||
| from .WidgetMixin  import WidgetMixin |  | ||||||
| from .TabMixin     import TabMixin |  | ||||||
| from .WindowMixin import WindowMixin |  | ||||||
| from .WidgetFileActionMixin import WidgetFileActionMixin |  | ||||||
| @@ -1,120 +0,0 @@ | |||||||
| # Python imports |  | ||||||
| import re |  | ||||||
|  |  | ||||||
| # Lib imports |  | ||||||
| import gi |  | ||||||
| gi.require_version('Gtk', '3.0') |  | ||||||
| gi.require_version('Gdk', '3.0') |  | ||||||
| from gi.repository import Gtk, Gdk |  | ||||||
|  |  | ||||||
| # Application imports |  | ||||||
|  |  | ||||||
|  |  | ||||||
| valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class KeyboardSignalsMixin: |  | ||||||
|     def unset_keys_and_data(self, widget=None, eve=None): |  | ||||||
|         self.ctrlDown     = False |  | ||||||
|         self.shiftDown    = False |  | ||||||
|         self.altDown      = False |  | ||||||
|         self.is_searching = False |  | ||||||
|  |  | ||||||
|     def global_key_press_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if "control" in keyname or "alt" in keyname or "shift" in keyname: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrlDown    = True |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shiftDown   = True |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.altDown     = True |  | ||||||
|  |  | ||||||
|     # NOTE: Yes, this should actually be mapped to some key controller setting |  | ||||||
|     #       file or something. Sue me. |  | ||||||
|     def global_key_release_controller(self, eve, user_data): |  | ||||||
|         keyname = Gdk.keyval_name(user_data.keyval).lower() |  | ||||||
|         if debug: |  | ||||||
|             print(f"global_key_release_controller > key > {keyname}") |  | ||||||
|  |  | ||||||
|         if "control" in keyname or "alt" in keyname or "shift" in keyname: |  | ||||||
|             if "control" in keyname: |  | ||||||
|                 self.ctrlDown    = False |  | ||||||
|             if "shift" in keyname: |  | ||||||
|                 self.shiftDown   = False |  | ||||||
|             if "alt" in keyname: |  | ||||||
|                 self.altDown     = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if self.ctrlDown and self.shiftDown and keyname == "t": |  | ||||||
|             self.trash_files() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if re.fullmatch(valid_keyvalue_pat, keyname): |  | ||||||
|             if not self.is_searching and not self.ctrlDown \ |  | ||||||
|                 and not self.shiftDown and not self.altDown: |  | ||||||
|                     focused_obj = self.window.get_focus() |  | ||||||
|                     if isinstance(focused_obj, Gtk.IconView): |  | ||||||
|                         self.is_searching = True |  | ||||||
|                         wid, tid, self.search_view, self.search_iconview, store = self.get_current_state() |  | ||||||
|                         self.popup_search_files(wid, keyname) |  | ||||||
|                         return |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if (self.ctrlDown and keyname in ["1", "kp_1"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_1").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["2", "kp_2"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_2").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["3", "kp_3"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_3").released() |  | ||||||
|         if (self.ctrlDown and keyname in ["4", "kp_4"]): |  | ||||||
|             self.builder.get_object("tggl_notebook_4").released() |  | ||||||
|  |  | ||||||
|         if self.ctrlDown and keyname == "q": |  | ||||||
|             self.tear_down() |  | ||||||
|         if (self.ctrlDown and keyname == "slash") or keyname == "home": |  | ||||||
|             self.builder.get_object("go_home").released() |  | ||||||
|         if (self.ctrlDown and keyname == "r") or keyname == "f5": |  | ||||||
|             self.builder.get_object("refresh_view").released() |  | ||||||
|         if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"): |  | ||||||
|             self.builder.get_object("go_up").released() |  | ||||||
|         if self.ctrlDown and keyname == "l": |  | ||||||
|             self.builder.get_object("path_entry").grab_focus() |  | ||||||
|         if self.ctrlDown and keyname == "t": |  | ||||||
|             self.builder.get_object("create_tab").released() |  | ||||||
|         if self.ctrlDown and keyname == "o": |  | ||||||
|             self.open_files() |  | ||||||
|         if self.ctrlDown and keyname == "w": |  | ||||||
|             self.keyboard_close_tab() |  | ||||||
|         if self.ctrlDown and keyname == "h": |  | ||||||
|             self.show_hide_hidden_files() |  | ||||||
|         if (self.ctrlDown and keyname == "e"): |  | ||||||
|             self.edit_files() |  | ||||||
|         if self.ctrlDown and keyname == "c": |  | ||||||
|             self.to_cut_files.clear() |  | ||||||
|             self.copy_files() |  | ||||||
|         if self.ctrlDown and keyname == "x": |  | ||||||
|             self.to_copy_files.clear() |  | ||||||
|             self.cut_files() |  | ||||||
|         if self.ctrlDown and keyname == "v": |  | ||||||
|             self.paste_files() |  | ||||||
|         if self.ctrlDown and keyname == "n": |  | ||||||
|             self.show_new_file_menu() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         if keyname in ["alt_l", "alt_r"]: |  | ||||||
|             top_main_menubar = self.builder.get_object("top_main_menubar") |  | ||||||
|             if top_main_menubar.is_visible(): |  | ||||||
|                 top_main_menubar.hide() |  | ||||||
|             else: |  | ||||||
|                 top_main_menubar.show() |  | ||||||
|         if keyname == "delete": |  | ||||||
|             self.delete_files() |  | ||||||
|         if keyname == "f2": |  | ||||||
|             self.rename_files() |  | ||||||
|         if keyname == "f4": |  | ||||||
|             wid, tid = self.window_controller.get_active_data() |  | ||||||
|             view     = self.get_fm_window(wid).get_view_by_id(tid) |  | ||||||
|             dir      = view.get_current_directory() |  | ||||||
|             view.execute(f"{view.terminal_app}", dir) |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| from .KeyboardSignalsMixin import KeyboardSignalsMixin |  | ||||||
| from .IPCSignalsMixin import IPCSignalsMixin |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user