Merge Stable Changesto Master #9
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					docs/
 | 
				
			||||||
.idea/
 | 
					.idea/
 | 
				
			||||||
*.zip
 | 
					*.zip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -140,4 +141,3 @@ dmypy.json
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Cython debug symbols
 | 
					# Cython debug symbols
 | 
				
			||||||
cython_debug/
 | 
					cython_debug/
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ requests: {}  = {
 | 
				
			|||||||
    'pass_fm_events': "true",                             # If empty or not present will be ignored.
 | 
					    'pass_fm_events': "true",                             # If empty or not present will be ignored.
 | 
				
			||||||
    "pass_ui_objects": [""],                              # Request reference to a UI component. Will be passed back as array to plugin.
 | 
					    "pass_ui_objects": [""],                              # Request reference to a UI component. Will be passed back as array to plugin.
 | 
				
			||||||
    'bind_keys': [f"{name}||send_message:<Control>f"],
 | 
					    '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.
 | 
					                  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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@@ -40,25 +40,3 @@ UI Targets:
 | 
				
			|||||||
<li>context_menu</li>
 | 
					<li>context_menu</li>
 | 
				
			||||||
<li>other</li>
 | 
					<li>other</li>
 | 
				
			||||||
</ul>
 | 
					</ul>
 | 
				
			||||||
 | 
					 | 
				
			||||||
### Methods
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
# Must define and return a widget if "ui_target" is defined.
 | 
					 | 
				
			||||||
def get_ui_element(self):
 | 
					 | 
				
			||||||
    button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
    button.connect("button-release-event", self._do_download)
 | 
					 | 
				
			||||||
    return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Must define in plugin if "pass_fm_events" is set to "true" string.
 | 
					 | 
				
			||||||
def set_fm_event_system(self, fm_event_system):
 | 
					 | 
				
			||||||
    self._fm_event_system = fm_event_system
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Must define regardless if needed. Can just pass if plugin does stuff in its __init__
 | 
					 | 
				
			||||||
def run(self):
 | 
					 | 
				
			||||||
    self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Must define in plugin if "pass_ui_objects" is set and an array of valid glade UI IDs.
 | 
					 | 
				
			||||||
def set_ui_object_collection(self, ui_objects):
 | 
					 | 
				
			||||||
    self._ui_objects = ui_objects
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								plugins/archiver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										3
									
								
								plugins/archiver/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Package
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										164
									
								
								plugins/archiver/archiver.glade
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<!-- Generated with glade 3.38.2 -->
 | 
				
			||||||
 | 
					<interface>
 | 
				
			||||||
 | 
					  <requires lib="gtk+" version="3.16"/>
 | 
				
			||||||
 | 
					  <object class="GtkTextBuffer" id="arc_command_buffer">
 | 
				
			||||||
 | 
					    <property name="text" translatable="yes">$(which 7za || echo 7zr) a %o %N</property>
 | 
				
			||||||
 | 
					  </object>
 | 
				
			||||||
 | 
					  <object class="GtkFileChooserDialog" id="archiver_dialogue">
 | 
				
			||||||
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					    <property name="modal">True</property>
 | 
				
			||||||
 | 
					    <property name="window-position">center</property>
 | 
				
			||||||
 | 
					    <property name="type-hint">dialog</property>
 | 
				
			||||||
 | 
					    <property name="gravity">center</property>
 | 
				
			||||||
 | 
					    <property name="do-overwrite-confirmation">True</property>
 | 
				
			||||||
 | 
					    <property name="select-multiple">True</property>
 | 
				
			||||||
 | 
					    <child internal-child="vbox">
 | 
				
			||||||
 | 
					      <object class="GtkBox">
 | 
				
			||||||
 | 
					        <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					        <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					        <property name="spacing">2</property>
 | 
				
			||||||
 | 
					        <child internal-child="action_area">
 | 
				
			||||||
 | 
					          <object class="GtkButtonBox">
 | 
				
			||||||
 | 
					            <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					            <property name="layout-style">end</property>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkButton" id="button21">
 | 
				
			||||||
 | 
					                <property name="label">gtk-cancel</property>
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                <property name="use-stock">True</property>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">0</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkButton" id="button22">
 | 
				
			||||||
 | 
					                <property name="label">gtk-ok</property>
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                <property name="use-stock">True</property>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">1</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					          </object>
 | 
				
			||||||
 | 
					          <packing>
 | 
				
			||||||
 | 
					            <property name="expand">False</property>
 | 
				
			||||||
 | 
					            <property name="fill">False</property>
 | 
				
			||||||
 | 
					            <property name="position">0</property>
 | 
				
			||||||
 | 
					          </packing>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					        <child>
 | 
				
			||||||
 | 
					          <object class="GtkBox">
 | 
				
			||||||
 | 
					            <property name="visible">True</property>
 | 
				
			||||||
 | 
					            <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					            <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkBox">
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					                <property name="homogeneous">True</property>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkLabel">
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					                    <property name="label" translatable="yes">Compress Commands:</property>
 | 
				
			||||||
 | 
					                    <property name="xalign">0.20000000298023224</property>
 | 
				
			||||||
 | 
					                    <attributes>
 | 
				
			||||||
 | 
					                      <attribute name="gravity" value="west"/>
 | 
				
			||||||
 | 
					                    </attributes>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                  <packing>
 | 
				
			||||||
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
 | 
					                    <property name="fill">True</property>
 | 
				
			||||||
 | 
					                    <property name="position">0</property>
 | 
				
			||||||
 | 
					                  </packing>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <placeholder/>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkLabel">
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					                    <property name="label" translatable="yes">Archive Format:</property>
 | 
				
			||||||
 | 
					                    <property name="xalign">1</property>
 | 
				
			||||||
 | 
					                    <attributes>
 | 
				
			||||||
 | 
					                      <attribute name="gravity" value="east"/>
 | 
				
			||||||
 | 
					                    </attributes>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                  <packing>
 | 
				
			||||||
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
 | 
					                    <property name="fill">True</property>
 | 
				
			||||||
 | 
					                    <property name="position">2</property>
 | 
				
			||||||
 | 
					                  </packing>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkComboBoxText">
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					                    <property name="active">0</property>
 | 
				
			||||||
 | 
					                    <property name="active-id">0</property>
 | 
				
			||||||
 | 
					                    <items>
 | 
				
			||||||
 | 
					                      <item id="0" translatable="yes">7Zip (*.7z)</item>
 | 
				
			||||||
 | 
					                      <item id="1" translatable="yes">Zip (*.zip *.ZIP)</item>
 | 
				
			||||||
 | 
					                      <item id="2" translatable="yes">RAR (*.rar *.RAR)</item>
 | 
				
			||||||
 | 
					                      <item id="3" translatable="yes">Tar (*.tar)</item>
 | 
				
			||||||
 | 
					                      <item id="4" translatable="yes">Tar bzip2 (*.tar.bz2)</item>
 | 
				
			||||||
 | 
					                      <item id="5" translatable="yes">Tar Gzip (*.tar.gz *.tgz)</item>
 | 
				
			||||||
 | 
					                      <item id="6" translatable="yes">Tar xz (*.tar.xz *.txz)</item>
 | 
				
			||||||
 | 
					                      <item id="7" translatable="yes">Gzip (*.gz)</item>
 | 
				
			||||||
 | 
					                      <item id="8" translatable="yes">XZ (*.xz)</item>
 | 
				
			||||||
 | 
					                    </items>
 | 
				
			||||||
 | 
					                    <signal name="changed" handler="set_arc_buffer_text" swapped="no"/>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                  <packing>
 | 
				
			||||||
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
 | 
					                    <property name="fill">True</property>
 | 
				
			||||||
 | 
					                    <property name="position">3</property>
 | 
				
			||||||
 | 
					                  </packing>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">False</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">0</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkTextView" id="arc_command">
 | 
				
			||||||
 | 
					                <property name="height-request">72</property>
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                <property name="buffer">arc_command_buffer</property>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">1</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					          </object>
 | 
				
			||||||
 | 
					          <packing>
 | 
				
			||||||
 | 
					            <property name="expand">False</property>
 | 
				
			||||||
 | 
					            <property name="fill">True</property>
 | 
				
			||||||
 | 
					            <property name="position">2</property>
 | 
				
			||||||
 | 
					          </packing>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					      </object>
 | 
				
			||||||
 | 
					    </child>
 | 
				
			||||||
 | 
					    <action-widgets>
 | 
				
			||||||
 | 
					      <action-widget response="-6">button21</action-widget>
 | 
				
			||||||
 | 
					      <action-widget response="-5">button22</action-widget>
 | 
				
			||||||
 | 
					    </action-widgets>
 | 
				
			||||||
 | 
					  </object>
 | 
				
			||||||
 | 
					</interface>
 | 
				
			||||||
							
								
								
									
										12
									
								
								plugins/archiver/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "manifest": {
 | 
				
			||||||
 | 
					        "name": "Archiver",
 | 
				
			||||||
 | 
					        "author": "ITDominator",
 | 
				
			||||||
 | 
					        "version": "0.0.1",
 | 
				
			||||||
 | 
					        "support": "",
 | 
				
			||||||
 | 
					        "requests": {
 | 
				
			||||||
 | 
					            "ui_target": "context_menu_plugins",
 | 
				
			||||||
 | 
					            "pass_fm_events": "true"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								plugins/archiver/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
 | 
					def threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL die with parent's destruction.
 | 
				
			||||||
 | 
					def daemon_threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Plugin(PluginBase):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.path               = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
 | 
					        self._GLADE_FILE        = f"{self.path}/archiver.glade"
 | 
				
			||||||
 | 
					        self.name = "Archiver"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
 | 
					                                #       where self.name should not be needed for message comms
 | 
				
			||||||
 | 
					        self._archiver_dialogue  = None
 | 
				
			||||||
 | 
					        self._arc_command_buffer = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # In compress commands:
 | 
				
			||||||
 | 
					        #    %n: First selected filename/dir to archive
 | 
				
			||||||
 | 
					        #    %N: All selected filenames/dirs to archive, or (with %O) a single filename
 | 
				
			||||||
 | 
					        #    %o: Resulting single archive file
 | 
				
			||||||
 | 
					        #    %O: Resulting archive per source file/directory (use changes %N meaning)
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #  In extract commands:
 | 
				
			||||||
 | 
					        #    %x: Archive file to extract
 | 
				
			||||||
 | 
					        #    %g: Unique extraction target filename with optional subfolder
 | 
				
			||||||
 | 
					        #    %G: Unique extraction target filename, never with subfolder
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #  In list commands:
 | 
				
			||||||
 | 
					        #      %x: Archive to list
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #  Plus standard bash variables are accepted.
 | 
				
			||||||
 | 
					        self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N',
 | 
				
			||||||
 | 
					                                                                'zip -r %o %N',
 | 
				
			||||||
 | 
					                                                                'rar a -r %o %N',
 | 
				
			||||||
 | 
					                                                                'tar -cvf %o %N',
 | 
				
			||||||
 | 
					                                                                'tar -cvjf %o %N',
 | 
				
			||||||
 | 
					                                                                'tar -cvzf %o %N',
 | 
				
			||||||
 | 
					                                                                'tar -cvJf %o %N',
 | 
				
			||||||
 | 
					                                                                'gzip -c %N > %O',
 | 
				
			||||||
 | 
					                                                                'xz -cz %N > %O'
 | 
				
			||||||
 | 
					                                        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        self._builder           = Gtk.Builder()
 | 
				
			||||||
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        classes  = [self]
 | 
				
			||||||
 | 
					        handlers = {}
 | 
				
			||||||
 | 
					        for c in classes:
 | 
				
			||||||
 | 
					            methods = None
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                methods = inspect.getmembers(c, predicate=inspect.ismethod)
 | 
				
			||||||
 | 
					                handlers.update(methods)
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                print(repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._builder.connect_signals(handlers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._archiver_dialogue  = self._builder.get_object("archiver_dialogue")
 | 
				
			||||||
 | 
					        self._arc_command_buffer = self._builder.get_object("arc_command_buffer")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					        item.set_image( Gtk.Image(stock=Gtk.STOCK_FLOPPY) )
 | 
				
			||||||
 | 
					        item.connect("activate", self.show_archiver_dialogue)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def show_archiver_dialogue(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					        state = self._fm_state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE)
 | 
				
			||||||
 | 
					        self._archiver_dialogue.set_current_folder(state.tab.get_current_directory())
 | 
				
			||||||
 | 
					        self._archiver_dialogue.set_current_name("arc.7z")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = self._archiver_dialogue.run()
 | 
				
			||||||
 | 
					        if response == Gtk.ResponseType.OK:
 | 
				
			||||||
 | 
					            save_target = self._archiver_dialogue.get_filename()
 | 
				
			||||||
 | 
					            self.archive_files(save_target, state)
 | 
				
			||||||
 | 
					        if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._archiver_dialogue.hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def archive_files(self, save_target, state):
 | 
				
			||||||
 | 
					        paths       = [shlex.quote(p) for p in state.selected_files]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sItr, eItr  = self._arc_command_buffer.get_bounds()
 | 
				
			||||||
 | 
					        pre_command = self._arc_command_buffer.get_text(sItr, eItr, False)
 | 
				
			||||||
 | 
					        pre_command = pre_command.replace("%o", shlex.quote(save_target))
 | 
				
			||||||
 | 
					        pre_command = pre_command.replace("%N", ' '.join(paths))
 | 
				
			||||||
 | 
					        command     = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}"
 | 
				
			||||||
 | 
					        current_dir = state.tab.get_current_directory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.tab.execute(shlex.split(command), start_dir=shlex.quote(current_dir))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_arc_buffer_text(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        sid = widget.get_active_id()
 | 
				
			||||||
 | 
					        self._arc_command_buffer.set_text(self.arc_commands[int(sid)])
 | 
				
			||||||
							
								
								
									
										3
									
								
								plugins/disk_usage/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										3
									
								
								plugins/disk_usage/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Package
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										129
									
								
								plugins/disk_usage/du_usage.glade
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<!-- Generated with glade 3.40.0 -->
 | 
				
			||||||
 | 
					<interface>
 | 
				
			||||||
 | 
					  <requires lib="gtk+" version="3.24"/>
 | 
				
			||||||
 | 
					  <object class="GtkListStore" id="du_store">
 | 
				
			||||||
 | 
					    <columns>
 | 
				
			||||||
 | 
					      <!-- column-name Size -->
 | 
				
			||||||
 | 
					      <column type="gchararray"/>
 | 
				
			||||||
 | 
					      <!-- column-name Dir -->
 | 
				
			||||||
 | 
					      <column type="gchararray"/>
 | 
				
			||||||
 | 
					    </columns>
 | 
				
			||||||
 | 
					  </object>
 | 
				
			||||||
 | 
					  <object class="GtkDialog" id="du_dialog">
 | 
				
			||||||
 | 
					    <property name="width-request">420</property>
 | 
				
			||||||
 | 
					    <property name="height-request">450</property>
 | 
				
			||||||
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					    <property name="modal">True</property>
 | 
				
			||||||
 | 
					    <property name="window-position">center</property>
 | 
				
			||||||
 | 
					    <property name="destroy-with-parent">True</property>
 | 
				
			||||||
 | 
					    <property name="type-hint">dialog</property>
 | 
				
			||||||
 | 
					    <property name="skip-taskbar-hint">True</property>
 | 
				
			||||||
 | 
					    <property name="skip-pager-hint">True</property>
 | 
				
			||||||
 | 
					    <property name="decorated">False</property>
 | 
				
			||||||
 | 
					    <property name="deletable">False</property>
 | 
				
			||||||
 | 
					    <property name="gravity">center</property>
 | 
				
			||||||
 | 
					    <child internal-child="vbox">
 | 
				
			||||||
 | 
					      <object class="GtkBox">
 | 
				
			||||||
 | 
					        <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					        <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					        <property name="spacing">2</property>
 | 
				
			||||||
 | 
					        <child internal-child="action_area">
 | 
				
			||||||
 | 
					          <object class="GtkButtonBox">
 | 
				
			||||||
 | 
					            <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					            <property name="layout-style">end</property>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkButton">
 | 
				
			||||||
 | 
					                <property name="label">gtk-close</property>
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                <property name="use-stock">True</property>
 | 
				
			||||||
 | 
					                <signal name="released" handler="_hide_du_menu" swapped="no"/>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">2</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					          </object>
 | 
				
			||||||
 | 
					          <packing>
 | 
				
			||||||
 | 
					            <property name="expand">False</property>
 | 
				
			||||||
 | 
					            <property name="fill">False</property>
 | 
				
			||||||
 | 
					            <property name="position">0</property>
 | 
				
			||||||
 | 
					          </packing>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					        <child>
 | 
				
			||||||
 | 
					          <object class="GtkBox">
 | 
				
			||||||
 | 
					            <property name="visible">True</property>
 | 
				
			||||||
 | 
					            <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					            <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkLabel" id="current_dir_lbl">
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					                <property name="margin-start">5</property>
 | 
				
			||||||
 | 
					                <property name="margin-end">5</property>
 | 
				
			||||||
 | 
					                <property name="margin-top">5</property>
 | 
				
			||||||
 | 
					                <property name="margin-bottom">5</property>
 | 
				
			||||||
 | 
					                <property name="label" translatable="yes">Current Directory:</property>
 | 
				
			||||||
 | 
					                <property name="justify">center</property>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">False</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">0</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkScrolledWindow">
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                <property name="shadow-type">in</property>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkTreeView">
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                    <property name="model">du_store</property>
 | 
				
			||||||
 | 
					                    <property name="headers-clickable">False</property>
 | 
				
			||||||
 | 
					                    <child internal-child="selection">
 | 
				
			||||||
 | 
					                      <object class="GtkTreeSelection"/>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child>
 | 
				
			||||||
 | 
					                      <object class="GtkTreeViewColumn">
 | 
				
			||||||
 | 
					                        <property name="title" translatable="yes">Disk Usage</property>
 | 
				
			||||||
 | 
					                        <child>
 | 
				
			||||||
 | 
					                          <object class="GtkCellRendererText"/>
 | 
				
			||||||
 | 
					                          <attributes>
 | 
				
			||||||
 | 
					                            <attribute name="text">0</attribute>
 | 
				
			||||||
 | 
					                          </attributes>
 | 
				
			||||||
 | 
					                        </child>
 | 
				
			||||||
 | 
					                        <child>
 | 
				
			||||||
 | 
					                          <object class="GtkCellRendererText"/>
 | 
				
			||||||
 | 
					                          <attributes>
 | 
				
			||||||
 | 
					                            <attribute name="text">1</attribute>
 | 
				
			||||||
 | 
					                          </attributes>
 | 
				
			||||||
 | 
					                        </child>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">1</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					          </object>
 | 
				
			||||||
 | 
					          <packing>
 | 
				
			||||||
 | 
					            <property name="expand">True</property>
 | 
				
			||||||
 | 
					            <property name="fill">True</property>
 | 
				
			||||||
 | 
					            <property name="position">1</property>
 | 
				
			||||||
 | 
					          </packing>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					      </object>
 | 
				
			||||||
 | 
					    </child>
 | 
				
			||||||
 | 
					  </object>
 | 
				
			||||||
 | 
					</interface>
 | 
				
			||||||
							
								
								
									
										12
									
								
								plugins/disk_usage/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "manifest": {
 | 
				
			||||||
 | 
					        "name": "Disk Usage",
 | 
				
			||||||
 | 
					        "author": "ITDominator",
 | 
				
			||||||
 | 
					        "version": "0.0.1",
 | 
				
			||||||
 | 
					        "support": "",
 | 
				
			||||||
 | 
					        "requests": {
 | 
				
			||||||
 | 
					            "ui_target": "context_menu_plugins",
 | 
				
			||||||
 | 
					            "pass_fm_events": "true"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										94
									
								
								plugins/disk_usage/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Plugin(PluginBase):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.name        = "Disk Usage"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
 | 
					                                         #       where self.name should not be needed for message comms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.path        = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
 | 
					        self._GLADE_FILE = f"{self.path}/du_usage.glade"
 | 
				
			||||||
 | 
					        self._du_dialog  = None
 | 
				
			||||||
 | 
					        self._du_store   = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        self._builder    = Gtk.Builder()
 | 
				
			||||||
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        classes  = [self]
 | 
				
			||||||
 | 
					        handlers = {}
 | 
				
			||||||
 | 
					        for c in classes:
 | 
				
			||||||
 | 
					            methods = None
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                methods = inspect.getmembers(c, predicate=inspect.ismethod)
 | 
				
			||||||
 | 
					                handlers.update(methods)
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                print(repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._builder.connect_signals(handlers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._du_dialog = self._builder.get_object("du_dialog")
 | 
				
			||||||
 | 
					        self._du_store  = self._builder.get_object("du_store")
 | 
				
			||||||
 | 
					        self._current_dir_lbl  = self._builder.get_object("current_dir_lbl")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._event_system.subscribe("show_du_menu", self._show_du_menu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        item = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					        item.set_image( Gtk.Image(stock=Gtk.STOCK_HARDDISK) )
 | 
				
			||||||
 | 
					        item.connect("activate", self._show_du_menu)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_state(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_current_dir_lbl(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _show_du_menu(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._fm_state = None
 | 
				
			||||||
 | 
					        self._get_state()
 | 
				
			||||||
 | 
					        self._set_current_dir_lbl()
 | 
				
			||||||
 | 
					        self.load_du_data()
 | 
				
			||||||
 | 
					        self._du_dialog.run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def load_du_data(self):
 | 
				
			||||||
 | 
					        self._du_store.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        path     = self._fm_state.tab.get_current_directory()
 | 
				
			||||||
 | 
					        # NOTE: -h = human readable, -d = depth asigned to 1
 | 
				
			||||||
 | 
					        command  = ["du", "-h", "-d", "1", path]
 | 
				
			||||||
 | 
					        proc     = subprocess.Popen(command, stdout=subprocess.PIPE)
 | 
				
			||||||
 | 
					        raw_data = proc.communicate()[0]
 | 
				
			||||||
 | 
					        data     = raw_data.decode("utf-8").strip()  # NOTE: Will return data AFTER completion (if any)
 | 
				
			||||||
 | 
					        parts    = data.split("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Last entry is curret dir. Move to top of list and pop off...
 | 
				
			||||||
 | 
					        size, file = parts[-1].split("\t")
 | 
				
			||||||
 | 
					        self._du_store.append([size, file.split("/")[-1]])
 | 
				
			||||||
 | 
					        parts.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for part in parts:
 | 
				
			||||||
 | 
					            size, file = part.split("\t")
 | 
				
			||||||
 | 
					            self._du_store.append([size, file.split("/")[-1]])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _hide_du_menu(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._du_dialog.hide()
 | 
				
			||||||
@@ -1,15 +1,17 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<!-- Generated with glade 3.38.2 -->
 | 
					<!-- Generated with glade 3.40.0 -->
 | 
				
			||||||
<interface>
 | 
					<interface>
 | 
				
			||||||
  <requires lib="gtk+" version="3.24"/>
 | 
					  <requires lib="gtk+" version="3.24"/>
 | 
				
			||||||
  <object class="GtkListStore" id="favorites_store">
 | 
					  <object class="GtkListStore" id="favorites_store">
 | 
				
			||||||
    <columns>
 | 
					    <columns>
 | 
				
			||||||
      <!-- column-name Favorites -->
 | 
					      <!-- column-name Favorites -->
 | 
				
			||||||
      <column type="gchararray"/>
 | 
					      <column type="gchararray"/>
 | 
				
			||||||
 | 
					      <!-- column-name Path -->
 | 
				
			||||||
 | 
					      <column type="gchararray"/>
 | 
				
			||||||
    </columns>
 | 
					    </columns>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkDialog" id="favorites_dialog">
 | 
					  <object class="GtkDialog" id="favorites_dialog">
 | 
				
			||||||
    <property name="width-request">320</property>
 | 
					    <property name="width-request">420</property>
 | 
				
			||||||
    <property name="height-request">450</property>
 | 
					    <property name="height-request">450</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="modal">True</property>
 | 
					    <property name="modal">True</property>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
            "ui_target": "main_menu_bttn_box_bar",
 | 
					            "ui_target": "main_menu_bttn_box_bar",
 | 
				
			||||||
            "pass_fm_events": "true",
 | 
					            "pass_fm_events": "true",
 | 
				
			||||||
            "pass_ui_objects": ["path_entry"],
 | 
					            "pass_ui_objects": ["path_entry"],
 | 
				
			||||||
            "bind_keys": []
 | 
					            "bind_keys": ["Favorites||show_favorites_menu:<Control>f"]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, subprocess, time, inspect, json
 | 
					import os
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
@@ -10,19 +12,6 @@ from gi.repository import Gtk
 | 
				
			|||||||
from plugins.plugin_base import PluginBase
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NOTE: Threads WILL NOT die with parent's destruction.
 | 
					 | 
				
			||||||
def threaded(fn):
 | 
					 | 
				
			||||||
    def wrapper(*args, **kwargs):
 | 
					 | 
				
			||||||
        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
					 | 
				
			||||||
    return wrapper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# NOTE: Threads WILL die with parent's destruction.
 | 
					 | 
				
			||||||
def daemon_threaded(fn):
 | 
					 | 
				
			||||||
    def wrapper(*args, **kwargs):
 | 
					 | 
				
			||||||
        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
					 | 
				
			||||||
    return wrapper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Plugin(PluginBase):
 | 
					class Plugin(PluginBase):
 | 
				
			||||||
@@ -30,7 +19,7 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.name               = "Favorites"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
					        self.name               = "Favorites"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
                                                      #       where self.name should not be needed for message comms
 | 
					                                                #       where self.name should not be needed for message comms
 | 
				
			||||||
        self.path               = os.path.dirname(os.path.realpath(__file__))
 | 
					        self.path               = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
        self._GLADE_FILE        = f"{self.path}/favorites.glade"
 | 
					        self._GLADE_FILE        = f"{self.path}/favorites.glade"
 | 
				
			||||||
        self._FAVORITES_FILE    = f"{self.path}/favorites.json"
 | 
					        self._FAVORITES_FILE    = f"{self.path}/favorites.json"
 | 
				
			||||||
@@ -38,18 +27,10 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._favorites_dialog  = None
 | 
					        self._favorites_dialog  = None
 | 
				
			||||||
        self._favorites_store   = None
 | 
					        self._favorites_store   = None
 | 
				
			||||||
        self._favorites         = None
 | 
					        self._favorites         = None
 | 
				
			||||||
        self._state             = None
 | 
					 | 
				
			||||||
        self._selected          = None
 | 
					        self._selected          = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
        button.connect("button-release-event", self._show_favorites_menu)
 | 
					 | 
				
			||||||
        return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._builder          = Gtk.Builder()
 | 
					        self._builder          = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,35 +54,43 @@ class Plugin(PluginBase):
 | 
				
			|||||||
            with open(self._FAVORITES_FILE) as f:
 | 
					            with open(self._FAVORITES_FILE) as f:
 | 
				
			||||||
                self._favorites = json.load(f)
 | 
					                self._favorites = json.load(f)
 | 
				
			||||||
                for favorite in self._favorites:
 | 
					                for favorite in self._favorites:
 | 
				
			||||||
                    self._favorites_store.append([favorite])
 | 
					                    display, path = favorite
 | 
				
			||||||
 | 
					                    self._favorites_store.append([display, path])
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            with open(self._FAVORITES_FILE, 'a') as f:
 | 
					            with open(self._FAVORITES_FILE, 'a') as f:
 | 
				
			||||||
                f.write('[]')
 | 
					                f.write('[]')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._event_system.subscribe("show_favorites_menu", self._show_favorites_menu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        button = Gtk.Button(label=self.name)
 | 
				
			||||||
 | 
					        button.connect("button-release-event", self._show_favorites_menu)
 | 
				
			||||||
 | 
					        return button
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					 | 
				
			||||||
    def _get_state(self, widget=None, eve=None):
 | 
					    def _get_state(self, widget=None, eve=None):
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._state         = self._event_message
 | 
					 | 
				
			||||||
        self._event_message = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @threaded
 | 
					 | 
				
			||||||
    def _set_current_dir_lbl(self, widget=None, eve=None):
 | 
					    def _set_current_dir_lbl(self, widget=None, eve=None):
 | 
				
			||||||
        self.wait_for_state()
 | 
					        self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}")
 | 
				
			||||||
        self._current_dir_lbl.set_label(f"Current Directory:\n{self._state.tab.get_current_directory()}")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _add_to_favorite(self, state):
 | 
					    def _add_to_favorite(self, state):
 | 
				
			||||||
        current_directory = self._state.tab.get_current_directory()
 | 
					        path    = self._fm_state.tab.get_current_directory()
 | 
				
			||||||
        self._favorites_store.append([current_directory])
 | 
					        parts   = path.split("/")
 | 
				
			||||||
        self._favorites.append(current_directory)
 | 
					        display = '/'.join(parts[-3:]) if len(parts) > 3 else path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._favorites_store.append([display, path])
 | 
				
			||||||
 | 
					        self._favorites.append([display, path])
 | 
				
			||||||
        self._save_favorites()
 | 
					        self._save_favorites()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _remove_from_favorite(self, state):
 | 
					    def _remove_from_favorite(self, state):
 | 
				
			||||||
        path = self._favorites_store.get_value(self._selected, 0)
 | 
					        path = self._favorites_store.get_value(self._selected, 1)
 | 
				
			||||||
        self._favorites_store.remove(self._selected)
 | 
					        self._favorites_store.remove(self._selected)
 | 
				
			||||||
        self._favorites.remove(path)
 | 
					
 | 
				
			||||||
 | 
					        for i, f in enumerate(self._favorites):
 | 
				
			||||||
 | 
					            if f[1] == path:
 | 
				
			||||||
 | 
					                self._favorites.remove( self._favorites[i] )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._save_favorites()
 | 
					        self._save_favorites()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _save_favorites(self):
 | 
					    def _save_favorites(self):
 | 
				
			||||||
@@ -109,13 +98,12 @@ class Plugin(PluginBase):
 | 
				
			|||||||
            json.dump(self._favorites, outfile, separators=(',', ':'), indent=4)
 | 
					            json.dump(self._favorites, outfile, separators=(',', ':'), indent=4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _set_selected_path(self, widget=None, eve=None):
 | 
					    def _set_selected_path(self, widget=None, eve=None):
 | 
				
			||||||
        path = self._favorites_store.get_value(self._selected, 0)
 | 
					        path = self._favorites_store.get_value(self._selected, 1)
 | 
				
			||||||
        self._ui_objects[0].set_text(path)
 | 
					        self._ui_objects[0].set_text(path)
 | 
				
			||||||
 | 
					        self._set_current_dir_lbl()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _show_favorites_menu(self, widget=None, eve=None):
 | 
					    def _show_favorites_menu(self, widget=None, eve=None):
 | 
				
			||||||
        self._state = None
 | 
					        self._fm_state = None
 | 
				
			||||||
        self._get_state()
 | 
					        self._get_state()
 | 
				
			||||||
        self._set_current_dir_lbl()
 | 
					        self._set_current_dir_lbl()
 | 
				
			||||||
        self._favorites_dialog.run()
 | 
					        self._favorites_dialog.run()
 | 
				
			||||||
@@ -125,9 +113,5 @@ class Plugin(PluginBase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def _set_selected(self, user_data):
 | 
					    def _set_selected(self, user_data):
 | 
				
			||||||
        selected = user_data.get_selected()[1]
 | 
					        selected = user_data.get_selected()[1]
 | 
				
			||||||
        if selected:
 | 
					        if selected and not self._selected == selected:
 | 
				
			||||||
            self._selected = selected
 | 
					            self._selected = selected
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def wait_for_state(self):
 | 
					 | 
				
			||||||
        while not self._state:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,18 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, subprocess, time, pwd, grp
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import pwd
 | 
				
			||||||
 | 
					import grp
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, GLib, Gio
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from plugins.plugin_base import PluginBase
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
@@ -83,14 +90,7 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
        button.connect("button-release-event", self._show_properties_page)
 | 
					 | 
				
			||||||
        return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._builder           = Gtk.Builder()
 | 
					        self._builder           = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -105,14 +105,19 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._file_owner    = self._builder.get_object("file_owner")
 | 
					        self._file_owner    = self._builder.get_object("file_owner")
 | 
				
			||||||
        self._file_group    = self._builder.get_object("file_group")
 | 
					        self._file_group    = self._builder.get_object("file_group")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        item = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					        item.set_image( Gtk.Image(stock=Gtk.STOCK_PROPERTIES) )
 | 
				
			||||||
 | 
					        item.connect("activate", self._show_properties_page)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					    @threaded
 | 
				
			||||||
    def _show_properties_page(self, widget=None, eve=None):
 | 
					    def _show_properties_page(self, widget=None, eve=None):
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					        event_system.emit("get_current_state")
 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state               = self._event_message
 | 
					        state               = self._fm_state
 | 
				
			||||||
        self._event_message = None
 | 
					        self._event_message = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GLib.idle_add(self._process_changes, (state))
 | 
					        GLib.idle_add(self._process_changes, (state))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
        "version": "0.0.1",
 | 
					        "version": "0.0.1",
 | 
				
			||||||
        "support": "",
 | 
					        "support": "",
 | 
				
			||||||
        "requests": {
 | 
					        "requests": {
 | 
				
			||||||
            "ui_target": "context_menu",
 | 
					            "ui_target": "context_menu_plugins",
 | 
				
			||||||
            "pass_fm_events": "true"
 | 
					            "pass_fm_events": "true"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,18 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, subprocess, inspect, requests, shutil
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
gi.require_version('GdkPixbuf', '2.0')
 | 
					gi.require_version('GdkPixbuf', '2.0')
 | 
				
			||||||
from gi.repository import Gtk, GLib, GdkPixbuf
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import GdkPixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from plugins.plugin_base import PluginBase
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
@@ -39,22 +46,13 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._dialog                = None
 | 
					        self._dialog                = None
 | 
				
			||||||
        self._thumbnail_preview_img = None
 | 
					        self._thumbnail_preview_img = None
 | 
				
			||||||
        self._tmdb                  = scraper.get_tmdb_scraper()
 | 
					        self._tmdb                  = scraper.get_tmdb_scraper()
 | 
				
			||||||
        self._state                 = None
 | 
					 | 
				
			||||||
        self._overview              = None
 | 
					        self._overview              = None
 | 
				
			||||||
        self._file_name             = None
 | 
					        self._file_name             = None
 | 
				
			||||||
        self._file_location         = None
 | 
					        self._file_location         = None
 | 
				
			||||||
        self._trailer_link          = None
 | 
					        self._trailer_link          = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
        button.connect("button-release-event", self._show_info_page)
 | 
					 | 
				
			||||||
        return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._builder           = Gtk.Builder()
 | 
					        self._builder           = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,21 +76,27 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._file_hash             = self._builder.get_object("file_hash")
 | 
					        self._file_hash             = self._builder.get_object("file_hash")
 | 
				
			||||||
        self._trailer_link          = self._builder.get_object("trailer_link")
 | 
					        self._trailer_link          = self._builder.get_object("trailer_link")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        item = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					        item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) )
 | 
				
			||||||
 | 
					        item.connect("activate", self._show_info_page)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					    @threaded
 | 
				
			||||||
    def _show_info_page(self, widget=None, eve=None):
 | 
					    def _show_info_page(self, widget=None, eve=None):
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state               = self._event_message
 | 
					        state               = self._fm_state
 | 
				
			||||||
        self._event_message = None
 | 
					        self._event_message = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GLib.idle_add(self._process_changes, (state))
 | 
					        GLib.idle_add(self._process_changes, (state))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _process_changes(self, state):
 | 
					    def _process_changes(self, state):
 | 
				
			||||||
        self._state = None
 | 
					        self._fm_state = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if len(state.selected_files) == 1:
 | 
					        if len(state.selected_files) == 1:
 | 
				
			||||||
            self._state = state
 | 
					            self._fm_state = state
 | 
				
			||||||
            self._set_ui_data()
 | 
					            self._set_ui_data()
 | 
				
			||||||
            response   = self._thumbnailer_dialog.run()
 | 
					            response   = self._thumbnailer_dialog.run()
 | 
				
			||||||
            if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
 | 
					            if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
 | 
				
			||||||
@@ -111,10 +115,11 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ...
 | 
					        print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_video_data(self):
 | 
					    def get_video_data(self):
 | 
				
			||||||
        uri            = self._state.selected_files[0]
 | 
					        uri            = self._fm_state.selected_files[0]
 | 
				
			||||||
        path           = self._state.tab.get_current_directory()
 | 
					        path           = self._fm_state.tab.get_current_directory()
 | 
				
			||||||
        parts          = uri.split("/")
 | 
					        parts          = uri.split("/")
 | 
				
			||||||
        _title         = parts[ len(parts) - 1 ]
 | 
					        _title         = parts[ len(parts) - 1 ]
 | 
				
			||||||
 | 
					        trailer        = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            title          = _title.split("(")[0].strip()
 | 
					            title          = _title.split("(")[0].strip()
 | 
				
			||||||
@@ -140,7 +145,6 @@ class Plugin(PluginBase):
 | 
				
			|||||||
                    raise Exception("No key found. Defering to none...")
 | 
					                    raise Exception("No key found. Defering to none...")
 | 
				
			||||||
            except Exception as e:
 | 
					            except Exception as e:
 | 
				
			||||||
                print("No trailer found...")
 | 
					                print("No trailer found...")
 | 
				
			||||||
                trailer = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            print(repr(e))
 | 
					            print(repr(e))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								plugins/movie_tv_info/tmdbscraper/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
@@ -7,7 +7,7 @@
 | 
				
			|||||||
        "requests": {
 | 
					        "requests": {
 | 
				
			||||||
            "ui_target": "context_menu",
 | 
					            "ui_target": "context_menu",
 | 
				
			||||||
            "pass_fm_events": "true",
 | 
					            "pass_fm_events": "true",
 | 
				
			||||||
            "bind_keys": ["Search||_show_grep_list_page:<Control>f"]
 | 
					            "bind_keys": ["Search||show_search_page:<Control>s"]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								plugins/searcher/mixins/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Mixins Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										79
									
								
								plugins/searcher/mixins/file_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from ..widgets.file_preview_widget import FilePreviewWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
 | 
					def threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL die with parent's destruction.
 | 
				
			||||||
 | 
					def daemon_threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FileSearchMixin:
 | 
				
			||||||
 | 
					    def _run_find_file_query(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._handle_find_file_query(query=widget)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: Merge this logic with nearly the exact same thing in grep_search_mixin
 | 
				
			||||||
 | 
					    @daemon_threaded
 | 
				
			||||||
 | 
					    def _handle_find_file_query(self, widget=None, eve=None, query=None):
 | 
				
			||||||
 | 
					        # NOTE: Freeze IPC consumption
 | 
				
			||||||
 | 
					        self.pause_fifo_update = True
 | 
				
			||||||
 | 
					        self.search_query      = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Kill the former process
 | 
				
			||||||
 | 
					        if self._list_proc:
 | 
				
			||||||
 | 
					            if self._list_proc.poll() == None:
 | 
				
			||||||
 | 
					                self._list_proc.terminate()
 | 
				
			||||||
 | 
					                while self._list_proc.poll() == None:
 | 
				
			||||||
 | 
					                    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._list_proc = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Clear children from ui and make sure ui thread redraws
 | 
				
			||||||
 | 
					        GLib.idle_add(self.reset_file_list_box)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: If query create new process and do all new loop.
 | 
				
			||||||
 | 
					        if query:
 | 
				
			||||||
 | 
					            self.pause_fifo_update = False
 | 
				
			||||||
 | 
					            GLib.idle_add(self._exec_find_file_query, query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _exec_find_file_query(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        query = widget.get_text()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not query in ("", None):
 | 
				
			||||||
 | 
					            self.search_query = query
 | 
				
			||||||
 | 
					            target_dir = shlex.quote( self._fm_state.tab.get_current_directory() )
 | 
				
			||||||
 | 
					            command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"]
 | 
				
			||||||
 | 
					            self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_file_ui(self, data):
 | 
				
			||||||
 | 
					        Gtk.main_iteration()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not data in ("", None):
 | 
				
			||||||
 | 
					            jdata  = json.loads( data )
 | 
				
			||||||
 | 
					            target = jdata[0]
 | 
				
			||||||
 | 
					            file   = jdata[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            widget = FilePreviewWidget(target, file)
 | 
				
			||||||
 | 
					            self._file_list.add(widget)
 | 
				
			||||||
							
								
								
									
										83
									
								
								plugins/searcher/mixins/grep_search_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import ctypes
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					libgcc_s = ctypes.CDLL('libgcc_s.so.1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from ..widgets.grep_preview_widget import GrepPreviewWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
 | 
					def threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL die with parent's destruction.
 | 
				
			||||||
 | 
					def daemon_threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GrepSearchMixin:
 | 
				
			||||||
 | 
					    def _run_grep_query(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._handle_grep_query(query=widget)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: Merge this logic with nearly the exact same thing in file_search_mixin
 | 
				
			||||||
 | 
					    @daemon_threaded
 | 
				
			||||||
 | 
					    def _handle_grep_query(self, widget=None, eve=None, query=None):
 | 
				
			||||||
 | 
					        # NOTE: Freeze IPC consumption
 | 
				
			||||||
 | 
					        self.pause_fifo_update = True
 | 
				
			||||||
 | 
					        self.grep_query        = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Kill the former process
 | 
				
			||||||
 | 
					        if self._grep_proc:
 | 
				
			||||||
 | 
					            if self._grep_proc.poll() == None:
 | 
				
			||||||
 | 
					                self._grep_proc.terminate()
 | 
				
			||||||
 | 
					                while self._grep_proc.poll() == None:
 | 
				
			||||||
 | 
					                    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._grep_proc = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Clear children from ui and make sure ui thread redraws
 | 
				
			||||||
 | 
					        GLib.idle_add(self.reset_grep_box)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: If query create new process and do all new loop.
 | 
				
			||||||
 | 
					        if query:
 | 
				
			||||||
 | 
					            self.pause_fifo_update = False
 | 
				
			||||||
 | 
					            GLib.idle_add(self._exec_grep_query, query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _exec_grep_query(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        query = widget.get_text()
 | 
				
			||||||
 | 
					        if not query.strip() in ("", None):
 | 
				
			||||||
 | 
					            self.grep_query = query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            target_dir = shlex.quote( self._fm_state.tab.get_current_directory() )
 | 
				
			||||||
 | 
					            command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"]
 | 
				
			||||||
 | 
					            self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_grep_ui(self, data):
 | 
				
			||||||
 | 
					        Gtk.main_iteration()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not data in ("", None):
 | 
				
			||||||
 | 
					            jdata = json.loads( data )
 | 
				
			||||||
 | 
					            jkeys = jdata.keys()
 | 
				
			||||||
 | 
					            for key in jkeys:
 | 
				
			||||||
 | 
					                sub_keys    = jdata[key].keys()
 | 
				
			||||||
 | 
					                grep_result = jdata[key]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                widget = GrepPreviewWidget(key, sub_keys, grep_result, self.grep_query)
 | 
				
			||||||
 | 
					                self._grep_list.add(widget)
 | 
				
			||||||
@@ -1,14 +1,21 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, multiprocessing, threading, subprocess, inspect, time, json
 | 
					import os
 | 
				
			||||||
from multiprocessing import Manager, Process
 | 
					import threading
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, GLib, GObject
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from plugins.plugin_base import PluginBase
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
 | 
					from .mixins.file_search_mixin import FileSearchMixin
 | 
				
			||||||
 | 
					from .mixins.grep_search_mixin import GrepSearchMixin
 | 
				
			||||||
 | 
					from .utils.ipc_server import IPCServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NOTE: Threads WILL NOT die with parent's destruction.
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
@@ -26,56 +33,7 @@ def daemon_threaded(fn):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FilePreviewWidget(Gtk.LinkButton):
 | 
					class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase):
 | 
				
			||||||
    def __init__(self, path, file):
 | 
					 | 
				
			||||||
        super(FilePreviewWidget, self).__init__()
 | 
					 | 
				
			||||||
        self.set_label(file)
 | 
					 | 
				
			||||||
        self.set_uri(f"file://{path}")
 | 
					 | 
				
			||||||
        self.show_all()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GrepPreviewWidget(Gtk.Box):
 | 
					 | 
				
			||||||
    def __init__(self, path, sub_keys, data):
 | 
					 | 
				
			||||||
        super(GrepPreviewWidget, self).__init__()
 | 
					 | 
				
			||||||
        self.set_orientation(Gtk.Orientation.VERTICAL)
 | 
					 | 
				
			||||||
        self.line_color = "#e0cc64"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _label = '/'.join( path.split("/")[-3:] )
 | 
					 | 
				
			||||||
        title  = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.add(title)
 | 
					 | 
				
			||||||
        for key in sub_keys:
 | 
					 | 
				
			||||||
            line_num     = key
 | 
					 | 
				
			||||||
            text         = data[key]
 | 
					 | 
				
			||||||
            box          = Gtk.Box()
 | 
					 | 
				
			||||||
            number_label = Gtk.Label()
 | 
					 | 
				
			||||||
            text_view    = Gtk.Label(label=text[:-1])
 | 
					 | 
				
			||||||
            label_text   = f"<span foreground='{self.line_color}'>{line_num}</span>"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            number_label.set_markup(label_text)
 | 
					 | 
				
			||||||
            number_label.set_margin_left(15)
 | 
					 | 
				
			||||||
            number_label.set_margin_right(5)
 | 
					 | 
				
			||||||
            number_label.set_margin_top(5)
 | 
					 | 
				
			||||||
            number_label.set_margin_bottom(5)
 | 
					 | 
				
			||||||
            text_view.set_margin_top(5)
 | 
					 | 
				
			||||||
            text_view.set_margin_bottom(5)
 | 
					 | 
				
			||||||
            text_view.set_line_wrap(True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            box.add(number_label)
 | 
					 | 
				
			||||||
            box.add(text_view)
 | 
					 | 
				
			||||||
            self.add(box)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.show_all()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
manager  = Manager()
 | 
					 | 
				
			||||||
grep_result_set  = manager.dict()
 | 
					 | 
				
			||||||
file_result_set  = manager.list()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Plugin(PluginBase):
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -86,20 +44,19 @@ class Plugin(PluginBase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self._search_dialog    = None
 | 
					        self._search_dialog    = None
 | 
				
			||||||
        self._active_path      = None
 | 
					        self._active_path      = None
 | 
				
			||||||
 | 
					        self.file_list_parent  = None
 | 
				
			||||||
 | 
					        self.grep_list_parent  = None
 | 
				
			||||||
        self._file_list        = None
 | 
					        self._file_list        = None
 | 
				
			||||||
        self._grep_list        = None
 | 
					        self._grep_list        = None
 | 
				
			||||||
        self._grep_proc        = None
 | 
					        self._grep_proc        = None
 | 
				
			||||||
        self._list_proc        = None
 | 
					        self._list_proc        = None
 | 
				
			||||||
 | 
					        self.pause_fifo_update = False
 | 
				
			||||||
 | 
					        self.update_list_ui_buffer = ()
 | 
				
			||||||
 | 
					        self.grep_query        = ""
 | 
				
			||||||
 | 
					        self.search_query      = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
        button.connect("button-release-event", self._show_grep_list_page)
 | 
					 | 
				
			||||||
        return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._builder          = Gtk.Builder()
 | 
					        self._builder          = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -116,116 +73,65 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._builder.connect_signals(handlers)
 | 
					        self._builder.connect_signals(handlers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._search_dialog = self._builder.get_object("search_dialog")
 | 
					        self._search_dialog = self._builder.get_object("search_dialog")
 | 
				
			||||||
        self._grep_list     = self._builder.get_object("grep_list")
 | 
					        self.fsearch        = self._builder.get_object("fsearch")
 | 
				
			||||||
        self._file_list     = self._builder.get_object("file_list")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GObject.signal_new("update-file-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
 | 
					        self.grep_list_parent = self._builder.get_object("grep_list_parent")
 | 
				
			||||||
        self._search_dialog.connect("update-file-ui-signal", self._load_file_ui)
 | 
					        self.file_list_parent = self._builder.get_object("file_list_parent")
 | 
				
			||||||
        GObject.signal_new("update-grep-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,))
 | 
					
 | 
				
			||||||
        self._search_dialog.connect("update-grep-ui-signal", self._load_grep_ui)
 | 
					        self._event_system.subscribe("update-file-ui", self._load_file_ui)
 | 
				
			||||||
 | 
					        self._event_system.subscribe("update-grep-ui", self._load_grep_ui)
 | 
				
			||||||
 | 
					        self._event_system.subscribe("show_search_page", self._show_page)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @daemon_threaded
 | 
					        self.create_ipc_listener()
 | 
				
			||||||
    def _show_grep_list_page(self, widget=None, eve=None):
 | 
					 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state               = self._event_message
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        item = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					        item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) )
 | 
				
			||||||
 | 
					        item.connect("activate", self._show_page)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _show_page(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state               = self._fm_state
 | 
				
			||||||
        self._event_message = None
 | 
					        self._event_message = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GLib.idle_add(self._process_queries, (state))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _process_queries(self, state):
 | 
					 | 
				
			||||||
        self._active_path   = state.tab.get_current_directory()
 | 
					        self._active_path   = state.tab.get_current_directory()
 | 
				
			||||||
        response            = self._search_dialog.run()
 | 
					        response            = self._search_dialog.run()
 | 
				
			||||||
        self._search_dialog.hide()
 | 
					        self._search_dialog.hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: Merge the below methods into some unified logic
 | 
				
			||||||
    def _run_find_file_query(self, widget=None, eve=None):
 | 
					    def reset_grep_box(self) -> None:
 | 
				
			||||||
        if self._list_proc:
 | 
					 | 
				
			||||||
            self._list_proc.terminate()
 | 
					 | 
				
			||||||
            self._list_proc = None
 | 
					 | 
				
			||||||
            time.sleep(.2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        del file_result_set[:]
 | 
					 | 
				
			||||||
        self.clear_children(self._file_list)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        query = widget.get_text()
 | 
					 | 
				
			||||||
        if query:
 | 
					 | 
				
			||||||
            self._list_proc = multiprocessing.Process(self._do_list_search(self._active_path, query))
 | 
					 | 
				
			||||||
            self._list_proc.start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _do_list_search(self, path, query):
 | 
					 | 
				
			||||||
        self._file_traverse_path(path, query)
 | 
					 | 
				
			||||||
        for target, file in file_result_set:
 | 
					 | 
				
			||||||
            widget = FilePreviewWidget(target, file)
 | 
					 | 
				
			||||||
            self._search_dialog.emit("update-file-ui-signal", (widget))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _load_file_ui(self, parent=None, widget=None):
 | 
					 | 
				
			||||||
        self._file_list.add(widget)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _file_traverse_path(self, path, query):
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            for file in os.listdir(path):
 | 
					            child = self.grep_list_parent.get_children()[0]
 | 
				
			||||||
                target = os.path.join(path, file)
 | 
					            self._grep_list = None
 | 
				
			||||||
                if os.path.isdir(target):
 | 
					            self.grep_list_parent.remove(child)
 | 
				
			||||||
                    self._file_traverse_path(target, query)
 | 
					        except Exception:
 | 
				
			||||||
                else:
 | 
					            ...
 | 
				
			||||||
                    if query.lower() in file.lower():
 | 
					 | 
				
			||||||
                        file_result_set.append([target, file])
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            if debug:
 | 
					 | 
				
			||||||
                print("Couldn't traverse to path. Might be permissions related...")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._grep_list = Gtk.Box()
 | 
				
			||||||
 | 
					        self._grep_list.set_orientation(Gtk.Orientation.VERTICAL)
 | 
				
			||||||
 | 
					        self.grep_list_parent.add(self._grep_list)
 | 
				
			||||||
 | 
					        self.grep_list_parent.show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _run_grep_query(self, widget=None, eve=None):
 | 
					        time.sleep(0.05)
 | 
				
			||||||
        if self._grep_proc:
 | 
					        Gtk.main_iteration()
 | 
				
			||||||
            self._grep_proc.terminate()
 | 
					 | 
				
			||||||
            self._grep_proc = None
 | 
					 | 
				
			||||||
            time.sleep(.2)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        grep_result_set.clear()
 | 
					    def reset_file_list_box(self) -> None:
 | 
				
			||||||
        self.clear_children(self._grep_list)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        query = widget.get_text()
 | 
					 | 
				
			||||||
        if query:
 | 
					 | 
				
			||||||
            self._grep_proc = multiprocessing.Process(self._do_grep_search(self._active_path, query))
 | 
					 | 
				
			||||||
            self._grep_proc.start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _do_grep_search(self, path, query):
 | 
					 | 
				
			||||||
        self._grep_traverse_path(path, query)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        keys = grep_result_set.keys()
 | 
					 | 
				
			||||||
        for key in keys:
 | 
					 | 
				
			||||||
            sub_keys = grep_result_set[key].keys()
 | 
					 | 
				
			||||||
            widget   = GrepPreviewWidget(key, sub_keys, grep_result_set[key])
 | 
					 | 
				
			||||||
            self._search_dialog.emit("update-grep-ui-signal", (widget))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _load_grep_ui(self, parent=None, widget=None):
 | 
					 | 
				
			||||||
        self._grep_list.add(widget)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _grep_traverse_path(self, path, query):
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            for file in os.listdir(path):
 | 
					            child = self.file_list_parent.get_children()[0]
 | 
				
			||||||
                target = os.path.join(path, file)
 | 
					            self._file_list = None
 | 
				
			||||||
                if os.path.isdir(target):
 | 
					            self.file_list_parent.remove(child)
 | 
				
			||||||
                    self._grep_traverse_path(target, query)
 | 
					        except Exception:
 | 
				
			||||||
                else:
 | 
					            ...
 | 
				
			||||||
                    self._search_for_string(target, query)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            if debug:
 | 
					 | 
				
			||||||
                print("Couldn't traverse to path. Might be permissions related...")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _search_for_string(self, file, query):
 | 
					        self._file_list = Gtk.Box()
 | 
				
			||||||
        try:
 | 
					        self._file_list.set_orientation(Gtk.Orientation.VERTICAL)
 | 
				
			||||||
            with open(file, 'r') as fp:
 | 
					        self.file_list_parent.add(self._file_list)
 | 
				
			||||||
                for i, line in enumerate(fp):
 | 
					        self.file_list_parent.show_all()
 | 
				
			||||||
                    if query in line:
 | 
					
 | 
				
			||||||
                        if f"{file}" in grep_result_set.keys():
 | 
					        time.sleep(0.05)
 | 
				
			||||||
                            grep_result_set[f"{file}"][f"{i+1}"] = line
 | 
					        Gtk.main_iteration()
 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            grep_result_set[f"{file}"] = {}
 | 
					 | 
				
			||||||
                            grep_result_set[f"{file}"] = {f"{i+1}": line}
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            if debug:
 | 
					 | 
				
			||||||
                print("Couldn't read file. Might be binary or other cause...")
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,15 +74,41 @@
 | 
				
			|||||||
                <property name="can-focus">False</property>
 | 
					                <property name="can-focus">False</property>
 | 
				
			||||||
                <property name="orientation">vertical</property>
 | 
					                <property name="orientation">vertical</property>
 | 
				
			||||||
                <child>
 | 
					                <child>
 | 
				
			||||||
                  <object class="GtkSearchEntry">
 | 
					                  <object class="GtkBox">
 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
                    <property name="can-focus">True</property>
 | 
					                    <property name="can-focus">False</property>
 | 
				
			||||||
                    <property name="tooltip-text" translatable="yes">Query...</property>
 | 
					                    <child>
 | 
				
			||||||
                    <property name="primary-icon-name">edit-find-symbolic</property>
 | 
					                      <object class="GtkSearchEntry" id="fsearch">
 | 
				
			||||||
                    <property name="primary-icon-activatable">False</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                    <property name="primary-icon-sensitive">False</property>
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
                    <property name="placeholder-text" translatable="yes">Search for file...</property>
 | 
					                        <property name="tooltip-text" translatable="yes">Query...</property>
 | 
				
			||||||
                    <signal name="search-changed" handler="_run_find_file_query" swapped="no"/>
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Search for file...</property>
 | 
				
			||||||
 | 
					                        <signal name="search-changed" handler="_run_find_file_query" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="expand">True</property>
 | 
				
			||||||
 | 
					                        <property name="fill">True</property>
 | 
				
			||||||
 | 
					                        <property name="position">0</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child>
 | 
				
			||||||
 | 
					                      <object class="GtkButton">
 | 
				
			||||||
 | 
					                        <property name="label">gtk-stop</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                        <property name="use-stock">True</property>
 | 
				
			||||||
 | 
					                        <signal name="released" handler="_handle_find_file_query" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="expand">False</property>
 | 
				
			||||||
 | 
					                        <property name="fill">True</property>
 | 
				
			||||||
 | 
					                        <property name="position">1</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
@@ -102,7 +128,7 @@
 | 
				
			|||||||
                        <property name="visible">True</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                        <property name="can-focus">False</property>
 | 
					                        <property name="can-focus">False</property>
 | 
				
			||||||
                        <child>
 | 
					                        <child>
 | 
				
			||||||
                          <object class="GtkBox" id="file_list">
 | 
					                          <object class="GtkBox" id="file_list_parent">
 | 
				
			||||||
                            <property name="visible">True</property>
 | 
					                            <property name="visible">True</property>
 | 
				
			||||||
                            <property name="can-focus">False</property>
 | 
					                            <property name="can-focus">False</property>
 | 
				
			||||||
                            <property name="orientation">vertical</property>
 | 
					                            <property name="orientation">vertical</property>
 | 
				
			||||||
@@ -140,15 +166,41 @@
 | 
				
			|||||||
                <property name="can-focus">False</property>
 | 
					                <property name="can-focus">False</property>
 | 
				
			||||||
                <property name="orientation">vertical</property>
 | 
					                <property name="orientation">vertical</property>
 | 
				
			||||||
                <child>
 | 
					                <child>
 | 
				
			||||||
                  <object class="GtkSearchEntry">
 | 
					                  <object class="GtkBox">
 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
                    <property name="can-focus">True</property>
 | 
					                    <property name="can-focus">False</property>
 | 
				
			||||||
                    <property name="tooltip-text" translatable="yes">Query...</property>
 | 
					                    <child>
 | 
				
			||||||
                    <property name="primary-icon-name">edit-find-symbolic</property>
 | 
					                      <object class="GtkSearchEntry">
 | 
				
			||||||
                    <property name="primary-icon-activatable">False</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                    <property name="primary-icon-sensitive">False</property>
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
                    <property name="placeholder-text" translatable="yes">Query string in file...</property>
 | 
					                        <property name="tooltip-text" translatable="yes">Query...</property>
 | 
				
			||||||
                    <signal name="search-changed" handler="_run_grep_query" swapped="no"/>
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Query string in file...</property>
 | 
				
			||||||
 | 
					                        <signal name="search-changed" handler="_run_grep_query" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="expand">True</property>
 | 
				
			||||||
 | 
					                        <property name="fill">True</property>
 | 
				
			||||||
 | 
					                        <property name="position">0</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child>
 | 
				
			||||||
 | 
					                      <object class="GtkButton">
 | 
				
			||||||
 | 
					                        <property name="label">gtk-stop</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                        <property name="use-stock">True</property>
 | 
				
			||||||
 | 
					                        <signal name="released" handler="_handle_grep_query" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="expand">False</property>
 | 
				
			||||||
 | 
					                        <property name="fill">True</property>
 | 
				
			||||||
 | 
					                        <property name="position">1</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
@@ -168,12 +220,10 @@
 | 
				
			|||||||
                        <property name="visible">True</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                        <property name="can-focus">False</property>
 | 
					                        <property name="can-focus">False</property>
 | 
				
			||||||
                        <child>
 | 
					                        <child>
 | 
				
			||||||
                          <object class="GtkBox" id="grep_list">
 | 
					                          <object class="GtkBox" id="grep_list_parent">
 | 
				
			||||||
                            <property name="visible">True</property>
 | 
					                            <property name="visible">True</property>
 | 
				
			||||||
                            <property name="can-focus">False</property>
 | 
					                            <property name="can-focus">False</property>
 | 
				
			||||||
                            <property name="orientation">vertical</property>
 | 
					                            <property name="orientation">vertical</property>
 | 
				
			||||||
                            <property name="spacing">5</property>
 | 
					 | 
				
			||||||
                            <property name="baseline-position">top</property>
 | 
					 | 
				
			||||||
                            <child>
 | 
					                            <child>
 | 
				
			||||||
                              <placeholder/>
 | 
					                              <placeholder/>
 | 
				
			||||||
                            </child>
 | 
					                            </child>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										90
									
								
								plugins/searcher/utils/ipc_server.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import os, threading, pickle
 | 
				
			||||||
 | 
					from multiprocessing.connection import Listener, Client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IPCServer:
 | 
				
			||||||
 | 
					    """ Create a listener so that other SolarFM instances send requests back to existing instance. """
 | 
				
			||||||
 | 
					    def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
 | 
				
			||||||
 | 
					        self.is_ipc_alive     = False
 | 
				
			||||||
 | 
					        self._ipc_port        = 4848
 | 
				
			||||||
 | 
					        self._ipc_address     = ipc_address
 | 
				
			||||||
 | 
					        self._conn_type       = conn_type
 | 
				
			||||||
 | 
					        self._ipc_authkey     = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8')
 | 
				
			||||||
 | 
					        self._ipc_timeout     = 15.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if conn_type == "socket":
 | 
				
			||||||
 | 
					            self._ipc_address = f'/tmp/solarfm-search_grep-ipc.sock'
 | 
				
			||||||
 | 
					        elif conn_type == "full_network":
 | 
				
			||||||
 | 
					            self._ipc_address = '0.0.0.0'
 | 
				
			||||||
 | 
					        elif conn_type == "full_network_unsecured":
 | 
				
			||||||
 | 
					            self._ipc_authkey = None
 | 
				
			||||||
 | 
					            self._ipc_address = '0.0.0.0'
 | 
				
			||||||
 | 
					        elif conn_type == "local_network_unsecured":
 | 
				
			||||||
 | 
					            self._ipc_authkey = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @daemon_threaded
 | 
				
			||||||
 | 
					    def create_ipc_listener(self) -> None:
 | 
				
			||||||
 | 
					        if self._conn_type == "socket":
 | 
				
			||||||
 | 
					            if os.path.exists(self._ipc_address):
 | 
				
			||||||
 | 
					                os.unlink(self._ipc_address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
 | 
				
			||||||
 | 
					        elif "unsecured" not in self._conn_type:
 | 
				
			||||||
 | 
					            listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            listener = Listener((self._ipc_address, self._ipc_port))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.is_ipc_alive = True
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            conn = listener.accept()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not self.pause_fifo_update:
 | 
				
			||||||
 | 
					                self.handle_message(conn)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        listener.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_message(self, conn) -> None:
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            msg  = conn.recv()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if "SEARCH|" in msg:
 | 
				
			||||||
 | 
					                file = msg.split("SEARCH|")[1].strip()
 | 
				
			||||||
 | 
					                if file:
 | 
				
			||||||
 | 
					                    GLib.idle_add(self._load_file_ui, file, priority=GLib.PRIORITY_LOW)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if "GREP|" in msg:
 | 
				
			||||||
 | 
					                data = msg.split("GREP|")[1].strip()
 | 
				
			||||||
 | 
					                if data:
 | 
				
			||||||
 | 
					                    GLib.idle_add(self._load_grep_ui, data, priority=GLib.PRIORITY_LOW)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.close()
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_ipc_message(self, message: str = "Empty Data...") -> None:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if self._conn_type == "socket":
 | 
				
			||||||
 | 
					                conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
 | 
				
			||||||
 | 
					            elif "unsecured" not in self._conn_type:
 | 
				
			||||||
 | 
					                conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                conn = Client((self._ipc_address, self._ipc_port))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conn.send(message)
 | 
				
			||||||
 | 
					            conn.close()
 | 
				
			||||||
 | 
					        except ConnectionRefusedError as e:
 | 
				
			||||||
 | 
					            print("Connection refused...")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            print(repr(e))
 | 
				
			||||||
							
								
								
									
										152
									
								
								plugins/searcher/utils/search.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import pickle
 | 
				
			||||||
 | 
					from setproctitle import setproctitle
 | 
				
			||||||
 | 
					from multiprocessing.connection import Client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_ipc_address = f'/tmp/solarfm-search_grep-ipc.sock'
 | 
				
			||||||
 | 
					_ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \
 | 
				
			||||||
 | 
					            (".txt", ".text", ".sh", ".cfg", ".conf", ".log")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
 | 
					def threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL die with parent's destruction.
 | 
				
			||||||
 | 
					def daemon_threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def send_ipc_message(message) -> None:
 | 
				
			||||||
 | 
					    conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey)
 | 
				
			||||||
 | 
					    conn.send(message)
 | 
				
			||||||
 | 
					    conn.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE: Kinda important as this prevents overloading the UI thread
 | 
				
			||||||
 | 
					    time.sleep(0.05)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def file_search(path, query):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        for _path, _dir, _files in os.walk(path, topdown = True):
 | 
				
			||||||
 | 
					             for file in _files:
 | 
				
			||||||
 | 
					                 if query in file.lower():
 | 
				
			||||||
 | 
					                     target = os.path.join(_path, file)
 | 
				
			||||||
 | 
					                     data = f"SEARCH|{json.dumps([target, file])}"
 | 
				
			||||||
 | 
					                     send_ipc_message(data)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Couldn't traverse to path. Might be permissions related...")
 | 
				
			||||||
 | 
					        traceback.print_exc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _search_for_string(file, query):
 | 
				
			||||||
 | 
					    b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8')
 | 
				
			||||||
 | 
					    grep_result_set = {}
 | 
				
			||||||
 | 
					    padding = 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(file, 'rb') as fp:
 | 
				
			||||||
 | 
					        # NOTE: I know there's an issue if there's a very large file with content
 | 
				
			||||||
 | 
					        #       all on one line will lower and dupe it. And, yes, it will only
 | 
				
			||||||
 | 
					        #       return one instance from the file.
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            for i, raw in enumerate(fp):
 | 
				
			||||||
 | 
					                line   = None
 | 
				
			||||||
 | 
					                llower = raw.lower()
 | 
				
			||||||
 | 
					                if not query in llower:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if len(raw) > 72:
 | 
				
			||||||
 | 
					                    start  = 0
 | 
				
			||||||
 | 
					                    end    = len(raw) - 1
 | 
				
			||||||
 | 
					                    index  = llower.index(query)
 | 
				
			||||||
 | 
					                    sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index
 | 
				
			||||||
 | 
					                    eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index
 | 
				
			||||||
 | 
					                    line   = raw[sindex:eindex]
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    line = raw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                b64_line = base64.urlsafe_b64encode(line).decode('utf-8')
 | 
				
			||||||
 | 
					                if f"{b64_file}" in grep_result_set.keys():
 | 
				
			||||||
 | 
					                    grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    grep_result_set[f"{b64_file}"] = {}
 | 
				
			||||||
 | 
					                    grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = f"GREP|{json.dumps(grep_result_set)}"
 | 
				
			||||||
 | 
					            send_ipc_message(data)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@daemon_threaded
 | 
				
			||||||
 | 
					def _search_for_string_threaded(file, query):
 | 
				
			||||||
 | 
					    _search_for_string(file, query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def grep_search(path, query):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        for file in os.listdir(path):
 | 
				
			||||||
 | 
					            target = os.path.join(path, file)
 | 
				
			||||||
 | 
					            if os.path.isdir(target):
 | 
				
			||||||
 | 
					                grep_search(target, query)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if target.lower().endswith(filter):
 | 
				
			||||||
 | 
					                    size = os.path.getsize(target)
 | 
				
			||||||
 | 
					                    if not size > 5000:
 | 
				
			||||||
 | 
					                        _search_for_string(target, query)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        _search_for_string_threaded(target, query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Couldn't traverse to path. Might be permissions related...")
 | 
				
			||||||
 | 
					        traceback.print_exc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def search(args):
 | 
				
			||||||
 | 
					    if args.type == "file_search":
 | 
				
			||||||
 | 
					        file_search(args.dir, args.query.lower())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if args.type == "grep_search":
 | 
				
			||||||
 | 
					        grep_search(args.dir, args.query.lower().encode("utf-8"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        setproctitle('SolarFM: File Search - Grepy')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser = argparse.ArgumentParser()
 | 
				
			||||||
 | 
					        # Add long and short arguments
 | 
				
			||||||
 | 
					        parser.add_argument("--type", "-t", default=None, help="Type of search to do.")
 | 
				
			||||||
 | 
					        parser.add_argument("--dir", "-d", default=None, help="Directory root for search type.")
 | 
				
			||||||
 | 
					        parser.add_argument("--query", "-q", default=None, help="Query search is working against.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Read arguments (If any...)
 | 
				
			||||||
 | 
					        args = parser.parse_args()
 | 
				
			||||||
 | 
					        search(args)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        traceback.print_exc()
 | 
				
			||||||
							
								
								
									
										3
									
								
								plugins/searcher/widgets/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Widgets Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										16
									
								
								plugins/searcher/widgets/file_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Gtk imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FilePreviewWidget(Gtk.LinkButton):
 | 
				
			||||||
 | 
					    def __init__(self, path, file):
 | 
				
			||||||
 | 
					        super(FilePreviewWidget, self).__init__()
 | 
				
			||||||
 | 
					        self.set_label(file)
 | 
				
			||||||
 | 
					        self.set_uri(f"file://{path}")
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
							
								
								
									
										60
									
								
								plugins/searcher/widgets/grep_preview_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GrepPreviewWidget(Gtk.Box):
 | 
				
			||||||
 | 
					    def __init__(self, _path, sub_keys, _data, _grep_query):
 | 
				
			||||||
 | 
					        super(GrepPreviewWidget, self).__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.set_orientation(Gtk.Orientation.VERTICAL)
 | 
				
			||||||
 | 
					        line_color      = "#e0cc64"
 | 
				
			||||||
 | 
					        highlight_color = "#FBF719"
 | 
				
			||||||
 | 
					        grep_query      = _grep_query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        path  = self.decode_str(_path)
 | 
				
			||||||
 | 
					        lbl   = '/'.join( path.split("/")[-3:] )
 | 
				
			||||||
 | 
					        title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=lbl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text_view = Gtk.TextView()
 | 
				
			||||||
 | 
					        buffer    = text_view.get_buffer()
 | 
				
			||||||
 | 
					        text_view.set_editable(False)
 | 
				
			||||||
 | 
					        text_view.set_monospace(True)
 | 
				
			||||||
 | 
					        text_view.set_wrap_mode(Gtk.WrapMode.NONE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i, key in enumerate(sub_keys):
 | 
				
			||||||
 | 
					            line_num = self.make_utf8_line_num(line_color, key)
 | 
				
			||||||
 | 
					            itr      = buffer.get_end_iter()
 | 
				
			||||||
 | 
					            buffer.insert_markup(itr, line_num, len(line_num))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            decoded  = f"\t{self.decode_str(_data[key])}"
 | 
				
			||||||
 | 
					            self.make_utf8_line_highlight(buffer, itr, i, highlight_color, decoded, grep_query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add(title)
 | 
				
			||||||
 | 
					        self.add(text_view)
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def decode_str(self, target):
 | 
				
			||||||
 | 
					        return base64.urlsafe_b64decode(target.encode('utf-8')).decode('utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_utf8_line_num(self, color, target):
 | 
				
			||||||
 | 
					        return bytes(f"\n<span foreground='{color}'>{target}</span>", "utf-8").decode("utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_utf8_line_highlight(self, buffer, itr, i, color, target, query):
 | 
				
			||||||
 | 
					        parts = re.split(r"(" + query + ")(?i)", target.replace("\n", ""))
 | 
				
			||||||
 | 
					        for part in parts:
 | 
				
			||||||
 | 
					            itr  = buffer.get_end_iter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not query.lower() == part.lower() and not query.lower() in part.lower():
 | 
				
			||||||
 | 
					                buffer.insert(itr, part, length=len(part))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new_s = f"<span foreground='#000000' background='{color}'>{part}</span>"
 | 
				
			||||||
 | 
					                _part = bytes(new_s, "utf-8").decode("utf-8")
 | 
				
			||||||
 | 
					                buffer.insert_markup(itr, _part, len(_part))
 | 
				
			||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, subprocess, time
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import ime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
@@ -33,14 +36,14 @@ class Plugin(PluginBase):
 | 
				
			|||||||
                                                    #       where self.name should not be needed for message comms
 | 
					                                                    #       where self.name should not be needed for message comms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ui_element(self):
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					        button = Gtk.Button(label=self.name)
 | 
				
			||||||
        button.connect("button-release-event", self.send_message)
 | 
					        button.connect("button-release-event", self.send_message)
 | 
				
			||||||
        return button
 | 
					        return button
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def send_message(self, widget=None, eve=None):
 | 
					    def send_message(self, widget=None, eve=None):
 | 
				
			||||||
        message = "Hello, World!"
 | 
					        message = "Hello, World!"
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)])
 | 
					        event_system.emit("display_message", ("warning", message, None))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								plugins/trasher/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										3
									
								
								plugins/trasher/__main__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Pligin Package
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										16
									
								
								plugins/trasher/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "manifest": {
 | 
				
			||||||
 | 
					        "name": "Trasher",
 | 
				
			||||||
 | 
					        "author": "ITDominator",
 | 
				
			||||||
 | 
					        "version": "0.0.1",
 | 
				
			||||||
 | 
					        "support": "",
 | 
				
			||||||
 | 
					        "requests": {
 | 
				
			||||||
 | 
					            "ui_target": "context_menu",
 | 
				
			||||||
 | 
					            "pass_fm_events": "true",
 | 
				
			||||||
 | 
					            "bind_keys": [
 | 
				
			||||||
 | 
					                            "Trasher||delete_files:Delete",
 | 
				
			||||||
 | 
					                            "Trasher||trash_files:<Control>d"
 | 
				
			||||||
 | 
					                        ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										143
									
								
								plugins/trasher/plugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
 | 
					from .xdgtrash import XDGTrash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL NOT die with parent's destruction.
 | 
				
			||||||
 | 
					def threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NOTE: Threads WILL die with parent's destruction.
 | 
				
			||||||
 | 
					def daemon_threaded(fn):
 | 
				
			||||||
 | 
					    def wrapper(*args, **kwargs):
 | 
				
			||||||
 | 
					        threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Plugin(PluginBase):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.path               = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
 | 
					        self._GLADE_FILE        = f"{self.path}/trasher.glade"
 | 
				
			||||||
 | 
					        self.name               = "Trasher"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
 | 
					                                             #       where self.name should not be needed for message comms
 | 
				
			||||||
 | 
					        self.trashman           = XDGTrash()
 | 
				
			||||||
 | 
					        self.trash_files_path   = f"{GLib.get_user_data_dir()}/Trash/files"
 | 
				
			||||||
 | 
					        self.trash_info_path    = f"{GLib.get_user_data_dir()}/Trash/info"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.trashman.regenerate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons)
 | 
				
			||||||
 | 
					        self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons)
 | 
				
			||||||
 | 
					        self._event_system.subscribe("delete_files", self.delete_files)
 | 
				
			||||||
 | 
					        self._event_system.subscribe("trash_files", self.trash_files)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        trash_a = Gtk.MenuItem("Trash Actions")
 | 
				
			||||||
 | 
					        trash_menu = Gtk.Menu()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.restore = Gtk.MenuItem("Restore From Trash")
 | 
				
			||||||
 | 
					        self.restore.connect("activate", self.restore_trash_files)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.empty = Gtk.MenuItem("Empty Trash")
 | 
				
			||||||
 | 
					        self.empty.connect("activate", self.empty_trash)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trash = Gtk.ImageMenuItem("Trash")
 | 
				
			||||||
 | 
					        trash.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) )
 | 
				
			||||||
 | 
					        trash.connect("activate", self.trash_files)
 | 
				
			||||||
 | 
					        trash.set_always_show_image(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        go_to = Gtk.ImageMenuItem("Go To Trash")
 | 
				
			||||||
 | 
					        go_to.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) )
 | 
				
			||||||
 | 
					        go_to.connect("activate", self.go_to_trash)
 | 
				
			||||||
 | 
					        go_to.set_always_show_image(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delete = Gtk.ImageMenuItem("Delete")
 | 
				
			||||||
 | 
					        delete.set_image( Gtk.Image(stock=Gtk.STOCK_DELETE) )
 | 
				
			||||||
 | 
					        delete.connect("activate", self.delete_files)
 | 
				
			||||||
 | 
					        delete.set_always_show_image(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trash_a.set_submenu(trash_menu)
 | 
				
			||||||
 | 
					        trash_a.show_all()
 | 
				
			||||||
 | 
					        self._appen_menu_items(trash_menu, [self.restore, self.empty, trash, go_to, delete])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return trash_a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _appen_menu_items(self, menu, items):
 | 
				
			||||||
 | 
					        for item in items:
 | 
				
			||||||
 | 
					            menu.append(item)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _show_trash_buttons(self):
 | 
				
			||||||
 | 
					        self.restore.show()
 | 
				
			||||||
 | 
					        self.empty.show()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _hide_trash_buttons(self):
 | 
				
			||||||
 | 
					        self.restore.hide()
 | 
				
			||||||
 | 
					        self.empty.hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_files(self, widget = None, eve = None):
 | 
				
			||||||
 | 
					        self._event_system.emit("do_hide_context_menu")
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					        state    = self._fm_state
 | 
				
			||||||
 | 
					        uris     = state.selected_files
 | 
				
			||||||
 | 
					        response = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        state.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
 | 
				
			||||||
 | 
					        for uri in uris:
 | 
				
			||||||
 | 
					            file = Gio.File.new_for_path(uri)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not response:
 | 
				
			||||||
 | 
					                response = state.warning_alert.run()
 | 
				
			||||||
 | 
					                state.warning_alert.hide()
 | 
				
			||||||
 | 
					            if response == Gtk.ResponseType.YES:
 | 
				
			||||||
 | 
					                type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if type == Gio.FileType.DIRECTORY:
 | 
				
			||||||
 | 
					                    state.tab.delete_file( file.get_path() )
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    file.delete(cancellable=None)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def trash_files(self, widget = None, eve = None, verbocity = False):
 | 
				
			||||||
 | 
					        self._event_system.emit("do_hide_context_menu")
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					        state = self._fm_state
 | 
				
			||||||
 | 
					        for uri in state.selected_files:
 | 
				
			||||||
 | 
					            self.trashman.trash(uri, verbocity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def restore_trash_files(self, widget = None, eve = None, verbocity = False):
 | 
				
			||||||
 | 
					        self._event_system.emit("do_hide_context_menu")
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					        state = self._fm_state
 | 
				
			||||||
 | 
					        for uri in state.selected_files:
 | 
				
			||||||
 | 
					            self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def empty_trash(self, widget = None, eve = None, verbocity = False):
 | 
				
			||||||
 | 
					        self._event_system.emit("do_hide_context_menu")
 | 
				
			||||||
 | 
					        self.trashman.empty(verbose = verbocity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def go_to_trash(self, widget = None, eve = None, verbocity = False):
 | 
				
			||||||
 | 
					        self._event_system.emit("do_hide_context_menu")
 | 
				
			||||||
 | 
					        self._event_system.emit("go_to_path", self.trash_files_path)
 | 
				
			||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
        "version": "0.0.1",
 | 
					        "version": "0.0.1",
 | 
				
			||||||
        "support": "",
 | 
					        "support": "",
 | 
				
			||||||
        "requests": {
 | 
					        "requests": {
 | 
				
			||||||
            "ui_target": "context_menu",
 | 
					            "ui_target": "context_menu_plugins",
 | 
				
			||||||
            "pass_fm_events": "true"
 | 
					            "pass_fm_events": "true"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,20 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, subprocess, time, inspect, hashlib
 | 
					import os
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Gtk imports
 | 
					# Gtk imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
gi.require_version('GdkPixbuf', '2.0')
 | 
					gi.require_version('GdkPixbuf', '2.0')
 | 
				
			||||||
from gi.repository import Gtk, GLib, Gio, GdkPixbuf
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					from gi.repository import GdkPixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from plugins.plugin_base import PluginBase
 | 
					from plugins.plugin_base import PluginBase
 | 
				
			||||||
@@ -42,17 +50,9 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._file_name             = None
 | 
					        self._file_name             = None
 | 
				
			||||||
        self._file_location         = None
 | 
					        self._file_location         = None
 | 
				
			||||||
        self._file_hash             = None
 | 
					        self._file_hash             = None
 | 
				
			||||||
        self._state                 = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					 | 
				
			||||||
        button.connect("button-release-event", self._show_thumbnailer_page)
 | 
					 | 
				
			||||||
        return button
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self._builder           = Gtk.Builder()
 | 
					        self._builder           = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -75,23 +75,32 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img")
 | 
					        self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img")
 | 
				
			||||||
        self._file_hash             = self._builder.get_object("file_hash")
 | 
					        self._file_hash             = self._builder.get_object("file_hash")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
 | 
					        pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True)
 | 
				
			||||||
 | 
					        icon   = Gtk.Image.new_from_pixbuf(pixbuf)
 | 
				
			||||||
 | 
					        item   = Gtk.ImageMenuItem(self.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        item.set_image( icon )
 | 
				
			||||||
 | 
					        item.connect("activate", self._show_thumbnailer_page)
 | 
				
			||||||
 | 
					        item.set_always_show_image(True)
 | 
				
			||||||
 | 
					        return item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					    @threaded
 | 
				
			||||||
    def _show_thumbnailer_page(self, widget=None, eve=None):
 | 
					    def _show_thumbnailer_page(self, widget=None, eve=None):
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state               = self._event_message
 | 
					        state               = self._fm_state
 | 
				
			||||||
        self._event_message = None
 | 
					        self._event_message = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        GLib.idle_add(self._process_changes, (state))
 | 
					        GLib.idle_add(self._process_changes, (state))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _process_changes(self, state):
 | 
					    def _process_changes(self, state):
 | 
				
			||||||
        self._state = None
 | 
					        self._fm_state = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if len(state.selected_files) == 1:
 | 
					        if len(state.selected_files) == 1:
 | 
				
			||||||
            if state.selected_files[0].lower().endswith(state.tab.fvideos):
 | 
					            if state.selected_files[0].lower().endswith(state.tab.fvideos):
 | 
				
			||||||
                self._state = state
 | 
					                self._fm_state = state
 | 
				
			||||||
                self._set_ui_data()
 | 
					                self._set_ui_data()
 | 
				
			||||||
                response   = self._thumbnailer_dialog.run()
 | 
					                response   = self._thumbnailer_dialog.run()
 | 
				
			||||||
                if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
 | 
					                if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
 | 
				
			||||||
@@ -103,32 +112,32 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        file          = self._file_name.get_text()
 | 
					        file          = self._file_name.get_text()
 | 
				
			||||||
        dir           = self._file_location.get_text()
 | 
					        dir           = self._file_location.get_text()
 | 
				
			||||||
        file_hash     = self._file_hash.get_text()
 | 
					        file_hash     = self._file_hash.get_text()
 | 
				
			||||||
        hash_img_pth  = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
					        hash_img_pth  = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ...
 | 
					            os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self._state.tab.create_thumbnail(dir, file, f"{scrub_percent}%")
 | 
					            self._fm_state.tab.create_thumbnail(dir, file, f"{scrub_percent}%")
 | 
				
			||||||
            preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
 | 
					            preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
 | 
				
			||||||
            self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
 | 
					            self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            img_pixbuf = self._state.tab.create_scaled_image(hash_img_pth)
 | 
					            img_pixbuf = self._fm_state.tab.create_scaled_image(hash_img_pth)
 | 
				
			||||||
            tree_pth   = self._state.icon_grid.get_selected_items()[0]
 | 
					            tree_pth   = self._fm_state.icon_grid.get_selected_items()[0]
 | 
				
			||||||
            itr        = self._state.store.get_iter(tree_pth)
 | 
					            itr        = self._fm_state.store.get_iter(tree_pth)
 | 
				
			||||||
            pixbuff    = self._state.store.get(itr, 0)[0]
 | 
					            pixbuff    = self._fm_state.store.get(itr, 0)[0]
 | 
				
			||||||
            self._state.store.set(itr, 0, img_pixbuf)
 | 
					            self._fm_state.store.set(itr, 0, img_pixbuf)
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            print(repr(e))
 | 
					            print(repr(e))
 | 
				
			||||||
            print("Couldn't regenerate thumbnail!")
 | 
					            print("Couldn't regenerate thumbnail!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _set_ui_data(self):
 | 
					    def _set_ui_data(self):
 | 
				
			||||||
        uri            = self._state.selected_files[0]
 | 
					        uri            = self._fm_state.selected_files[0]
 | 
				
			||||||
        path           = self._state.tab.get_current_directory()
 | 
					        path           = self._fm_state.tab.get_current_directory()
 | 
				
			||||||
        parts          = uri.split("/")
 | 
					        parts          = uri.split("/")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        file_hash      = hashlib.sha256(str.encode(uri)).hexdigest()
 | 
					        file_hash      = hashlib.sha256(str.encode(uri)).hexdigest()
 | 
				
			||||||
        hash_img_pth   = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
					        hash_img_pth   = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
				
			||||||
        preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
 | 
					        preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
 | 
					        self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,21 +33,21 @@ class Plugin(PluginBase):
 | 
				
			|||||||
        self.name              = "Youtube Download"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
					        self.name              = "Youtube Download"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
                                                     #       where self.name should not be needed for message comms
 | 
					                                                     #       where self.name should not be needed for message comms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
    def get_ui_element(self):
 | 
					 | 
				
			||||||
        button = Gtk.Button(label=self.name)
 | 
					        button = Gtk.Button(label=self.name)
 | 
				
			||||||
        button.connect("button-release-event", self._do_download)
 | 
					        button.connect("button-release-event", self._do_download)
 | 
				
			||||||
        return button
 | 
					        return button
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        self._module_event_observer()
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _do_download(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self._event_system.emit("get_current_state")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dir = self._fm_state.tab.get_current_directory()
 | 
				
			||||||
 | 
					        self._download(dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					    @threaded
 | 
				
			||||||
    def _do_download(self, widget=None, eve=None):
 | 
					    def _download(self, dir):
 | 
				
			||||||
        self._event_system.push_gui_event([self.name, "get_current_state", ()])
 | 
					        subprocess.Popen([f'{self.path}/download.sh', dir])
 | 
				
			||||||
        self.wait_for_fm_message()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        state = self._event_message
 | 
					 | 
				
			||||||
        subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()])
 | 
					 | 
				
			||||||
        self._event_message = None
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,8 @@ import builtins, threading
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from utils.event_system import EventSystem
 | 
					from utils.event_system import EventSystem
 | 
				
			||||||
 | 
					from utils.endpoint_registry import EndpointRegistry
 | 
				
			||||||
 | 
					from utils.settings import Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,32 +24,14 @@ def daemon_threaded_wrapper(fn):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class EndpointRegistry():
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self._endpoints = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def register(self, rule, **options):
 | 
					 | 
				
			||||||
        def decorator(f):
 | 
					 | 
				
			||||||
            self._endpoints[rule] = f
 | 
					 | 
				
			||||||
            return f
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return decorator
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_endpoints(self):
 | 
					 | 
				
			||||||
        return self._endpoints
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# NOTE: Just reminding myself we can add to builtins two different ways...
 | 
					# NOTE: Just reminding myself we can add to builtins two different ways...
 | 
				
			||||||
# __builtins__.update({"event_system": Builtins()})
 | 
					# __builtins__.update({"event_system": Builtins()})
 | 
				
			||||||
builtins.app_name          = "SolarFM"
 | 
					builtins.app_name          = "SolarFM"
 | 
				
			||||||
 | 
					builtins.settings          = Settings()
 | 
				
			||||||
 | 
					builtins.logger            = settings.get_logger()
 | 
				
			||||||
builtins.event_system      = EventSystem()
 | 
					builtins.event_system      = EventSystem()
 | 
				
			||||||
builtins.endpoint_registry = EndpointRegistry()
 | 
					builtins.endpoint_registry = EndpointRegistry()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
builtins.threaded          = threaded_wrapper
 | 
					builtins.threaded          = threaded_wrapper
 | 
				
			||||||
builtins.daemon_threaded   = daemon_threaded_wrapper
 | 
					builtins.daemon_threaded   = daemon_threaded_wrapper
 | 
				
			||||||
builtins.event_sleep_time  = 0.05
 | 
					builtins.event_sleep_time  = 0.05
 | 
				
			||||||
builtins.trace_debug       = False
 | 
					 | 
				
			||||||
builtins.debug             = False
 | 
					 | 
				
			||||||
builtins.app_settings      = None
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
#!/usr/bin/python3
 | 
					#!/usr/bin/python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import argparse, faulthandler, traceback
 | 
					import argparse
 | 
				
			||||||
 | 
					import faulthandler
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
from setproctitle import setproctitle
 | 
					from setproctitle import setproctitle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import tracemalloc
 | 
					import tracemalloc
 | 
				
			||||||
@@ -15,25 +16,43 @@ gi.require_version('Gtk', '3.0')
 | 
				
			|||||||
from gi.repository import Gtk
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					from __builtins__ import *
 | 
				
			||||||
from app import Application
 | 
					from app import Application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    """ Set process title, get arguments, and create GTK main thread. """
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run():
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        setproctitle('SolarFM')
 | 
					        setproctitle(f"{app_name}")
 | 
				
			||||||
        faulthandler.enable()  # For better debug info
 | 
					        faulthandler.enable()  # For better debug info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser = argparse.ArgumentParser()
 | 
					        parser = argparse.ArgumentParser()
 | 
				
			||||||
        # Add long and short arguments
 | 
					        # Add long and short arguments
 | 
				
			||||||
 | 
					        parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
 | 
				
			||||||
 | 
					        parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
 | 
				
			||||||
 | 
					        parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
 | 
					        parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
 | 
				
			||||||
        parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
 | 
					        parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Read arguments (If any...)
 | 
					        # Read arguments (If any...)
 | 
				
			||||||
        args, unknownargs = parser.parse_known_args()
 | 
					        args, unknownargs = parser.parse_known_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if args.debug == "true":
 | 
				
			||||||
 | 
					            settings.set_debug(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if args.trace_debug == "true":
 | 
				
			||||||
 | 
					            settings.set_trace_debug(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        settings.do_dirty_start_check()
 | 
				
			||||||
        Application(args, unknownargs)
 | 
					        Application(args, unknownargs)
 | 
				
			||||||
        Gtk.main()
 | 
					        Gtk.main()
 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        traceback.print_exc()
 | 
					        traceback.print_exc()
 | 
				
			||||||
        quit()
 | 
					        quit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    """ Set process title, get arguments, and create GTK main thread. """
 | 
				
			||||||
 | 
					    run()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,52 +1,51 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, inspect, time
 | 
					import os
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from __builtins__ import *
 | 
					
 | 
				
			||||||
from utils.ipc_server import IPCServer
 | 
					from utils.ipc_server import IPCServer
 | 
				
			||||||
from utils.settings import Settings
 | 
					 | 
				
			||||||
from core.controller import Controller
 | 
					from core.controller import Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class App_Launch_Exception(Exception):
 | 
					class AppLaunchException(Exception):
 | 
				
			||||||
    ...
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Controller_Start_Exceptio(Exception):
 | 
					class ControllerStartException(Exception):
 | 
				
			||||||
    ...
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Application(IPCServer):
 | 
					class Application(IPCServer):
 | 
				
			||||||
    """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """
 | 
					    """ Inherit from IPCServer; create Controller classe; bind any signal(s) to Builder. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, args, unknownargs):
 | 
					    def __init__(self, args, unknownargs):
 | 
				
			||||||
        super(Application, self).__init__()
 | 
					        super(Application, self).__init__()
 | 
				
			||||||
 | 
					        self.args, self.unknownargs = args, unknownargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not trace_debug:
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
            self.create_ipc_listener()
 | 
					            try:
 | 
				
			||||||
            time.sleep(0.05)
 | 
					                self.create_ipc_listener()
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not self.is_ipc_alive:
 | 
					            if not self.is_ipc_alive:
 | 
				
			||||||
                if unknownargs:
 | 
					                for arg in unknownargs + [args.new_tab,]:
 | 
				
			||||||
                    for arg in unknownargs:
 | 
					                    if os.path.isdir(arg):
 | 
				
			||||||
                        if os.path.isdir(arg):
 | 
					                        message = f"FILE|{arg}"
 | 
				
			||||||
                            message = f"FILE|{arg}"
 | 
					                        self.send_ipc_message(message)
 | 
				
			||||||
                            self.send_ipc_message(message)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if args.new_tab and os.path.isdir(args.new_tab):
 | 
					                raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...")
 | 
				
			||||||
                    message = f"FILE|{args.new_tab}"
 | 
					 | 
				
			||||||
                    self.send_ipc_message(message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                raise App_Launch_Exception(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings = Settings()
 | 
					 | 
				
			||||||
        settings.create_window()
 | 
					        settings.create_window()
 | 
				
			||||||
 | 
					        self._load_controller_and_builder()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        controller = Controller(args, unknownargs, settings)
 | 
					    def _load_controller_and_builder(self):
 | 
				
			||||||
 | 
					        controller = Controller(self.args, self.unknownargs)
 | 
				
			||||||
        if not controller:
 | 
					        if not controller:
 | 
				
			||||||
            raise Controller_Start_Exceptio("Controller exited and doesn't exist...")
 | 
					            raise ControllerStartException("Controller exited and doesn't exist...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Gets the methods from the classes and sets to handler.
 | 
					        # Gets the methods from the classes and sets to handler.
 | 
				
			||||||
        # Then, builder connects to any signals it needs.
 | 
					        # Then, builder connects to any signals it needs.
 | 
				
			||||||
@@ -57,7 +56,7 @@ class Application(IPCServer):
 | 
				
			|||||||
            try:
 | 
					            try:
 | 
				
			||||||
                methods = inspect.getmembers(c, predicate=inspect.ismethod)
 | 
					                methods = inspect.getmembers(c, predicate=inspect.ismethod)
 | 
				
			||||||
                handlers.update(methods)
 | 
					                handlers.update(methods)
 | 
				
			||||||
            except Exception as e:
 | 
					            except AppLaunchException as e:
 | 
				
			||||||
                print(repr(e))
 | 
					                print(repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings.get_builder().connect_signals(handlers)
 | 
					        settings.get_builder().connect_signals(handlers)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,80 +7,58 @@ gi.require_version('Gtk', '3.0')
 | 
				
			|||||||
from gi.repository import Gtk, GLib
 | 
					from gi.repository import Gtk, GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .mixins.exception_hook_mixin import ExceptionHookMixin
 | 
					 | 
				
			||||||
from .mixins.ui_mixin import UIMixin
 | 
					 | 
				
			||||||
from .signals.ipc_signals_mixin import IPCSignalsMixin
 | 
					 | 
				
			||||||
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
 | 
					 | 
				
			||||||
from .controller_data import Controller_Data
 | 
					from .controller_data import Controller_Data
 | 
				
			||||||
 | 
					from .mixins.signals_mixins import SignalsMixins
 | 
				
			||||||
 | 
					from .ui import UI
 | 
				
			||||||
 | 
					from widgets.context_menu_widget import ContextMenuWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data):
 | 
					class Controller(UI, SignalsMixins, Controller_Data):
 | 
				
			||||||
    """ Controller coordinates the mixins and is somewhat the root hub of it all. """
 | 
					    """ Controller coordinates the mixins and is somewhat the root hub of it all. """
 | 
				
			||||||
    def __init__(self, args, unknownargs, _settings):
 | 
					    def __init__(self, args, unknownargs):
 | 
				
			||||||
        self.setup_controller_data(_settings)
 | 
					        self._subscribe_to_events()
 | 
				
			||||||
        self.window.show()
 | 
					        self.setup_controller_data()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.generate_windows(self.fm_controller_data)
 | 
					        self.generate_windows(self.fm_controller_data)
 | 
				
			||||||
        self.plugins.launch_plugins()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if debug:
 | 
					        ContextMenuWidget().build_context_menu()
 | 
				
			||||||
            self.window.set_interactive_debugging(True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not trace_debug:
 | 
					        if args.no_plugins == "false":
 | 
				
			||||||
            self.gui_event_observer()
 | 
					            self.plugins.launch_plugins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if unknownargs:
 | 
					        for arg in unknownargs + [args.new_tab,]:
 | 
				
			||||||
                for arg in unknownargs:
 | 
					            if os.path.isdir(arg):
 | 
				
			||||||
                    if os.path.isdir(arg):
 | 
					                message = f"FILE|{arg}"
 | 
				
			||||||
                        message = f"FILE|{arg}"
 | 
					                event_system.emit("post_file_to_ipc", message)
 | 
				
			||||||
                        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 _subscribe_to_events(self):
 | 
				
			||||||
 | 
					        event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
 | 
				
			||||||
 | 
					        event_system.subscribe("get_current_state", self.get_current_state)
 | 
				
			||||||
 | 
					        event_system.subscribe("display_message", self.display_message)
 | 
				
			||||||
 | 
					        event_system.subscribe("go_to_path", self.go_to_path)
 | 
				
			||||||
 | 
					        event_system.subscribe("do_hide_context_menu", self.do_hide_context_menu)
 | 
				
			||||||
 | 
					        event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tear_down(self, widget=None, eve=None):
 | 
					    def tear_down(self, widget=None, eve=None):
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        settings.clear_pid()
 | 
				
			||||||
        time.sleep(event_sleep_time)
 | 
					        time.sleep(event_sleep_time)
 | 
				
			||||||
        Gtk.main_quit()
 | 
					        Gtk.main_quit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @daemon_threaded
 | 
					 | 
				
			||||||
    def gui_event_observer(self):
 | 
					 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            time.sleep(event_sleep_time)
 | 
					 | 
				
			||||||
            event = event_system.consume_gui_event()
 | 
					 | 
				
			||||||
            if event:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    sender_id, method_target, parameters = event
 | 
					 | 
				
			||||||
                    if sender_id:
 | 
					 | 
				
			||||||
                        method = getattr(self.__class__, "handle_gui_event_and_return_message")
 | 
					 | 
				
			||||||
                        GLib.idle_add(method, *(self, sender_id, method_target, parameters))
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        method = getattr(self.__class__, method_target)
 | 
					 | 
				
			||||||
                        GLib.idle_add(method, *(self, *parameters,))
 | 
					 | 
				
			||||||
                except Exception as e:
 | 
					 | 
				
			||||||
                    print(repr(e))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle_gui_event_and_return_message(self, sender, method_target, parameters):
 | 
					 | 
				
			||||||
        method = getattr(self.__class__, f"{method_target}")
 | 
					 | 
				
			||||||
        data   = method(*(self, *parameters))
 | 
					 | 
				
			||||||
        event_system.push_module_event([sender, None, data])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle_plugin_key_event(self, sender, method_target, parameters=()):
 | 
					 | 
				
			||||||
        event_system.push_module_event([sender, method_target, parameters])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def save_load_session(self, action="save_session"):
 | 
					    def save_load_session(self, action="save_session"):
 | 
				
			||||||
        wid, tid          = self.fm_controller.get_active_wid_and_tid()
 | 
					        wid, tid          = self.fm_controller.get_active_wid_and_tid()
 | 
				
			||||||
        tab               = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
					        tab               = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
				
			||||||
        save_load_dialog  = self.builder.get_object("save_load_dialog")
 | 
					        save_load_dialog  = self.builder.get_object("save_load_dialog")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if action == "save_session":
 | 
					        if action == "save_session":
 | 
				
			||||||
            self.fm_controller.save_state()
 | 
					            if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					                self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        elif action == "save_session_as":
 | 
					        elif action == "save_session_as":
 | 
				
			||||||
            save_load_dialog.set_action(Gtk.FileChooserAction.SAVE)
 | 
					            save_load_dialog.set_action(Gtk.FileChooserAction.SAVE)
 | 
				
			||||||
@@ -101,13 +79,13 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
 | 
				
			|||||||
                session_json = self.fm_controller.get_state_from_file(path)
 | 
					                session_json = self.fm_controller.get_state_from_file(path)
 | 
				
			||||||
                self.load_session(session_json)
 | 
					                self.load_session(session_json)
 | 
				
			||||||
        if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
 | 
					        if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
 | 
				
			||||||
            pass
 | 
					            ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        save_load_dialog.hide()
 | 
					        save_load_dialog.hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_session(self, session_json):
 | 
					    def load_session(self, session_json):
 | 
				
			||||||
        if debug:
 | 
					        if settings.is_debug():
 | 
				
			||||||
            self.logger.debug(f"Session Data: {session_json}")
 | 
					            logger.debug(f"Session Data: {session_json}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.ctrl_down  = False
 | 
					        self.ctrl_down  = False
 | 
				
			||||||
        self.shift_down = False
 | 
					        self.shift_down = False
 | 
				
			||||||
@@ -120,8 +98,12 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
 | 
				
			|||||||
        gc.collect()
 | 
					        gc.collect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def do_action_from_menu_controls(self, widget, event_button):
 | 
					    def do_action_from_menu_controls(self, widget, eve = None):
 | 
				
			||||||
        action = widget.get_name()
 | 
					        if not isinstance(widget, str):
 | 
				
			||||||
 | 
					            action = widget.get_name()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            action = widget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.hide_context_menu()
 | 
					        self.hide_context_menu()
 | 
				
			||||||
        self.hide_new_file_menu()
 | 
					        self.hide_new_file_menu()
 | 
				
			||||||
        self.hide_edit_file_menu()
 | 
					        self.hide_edit_file_menu()
 | 
				
			||||||
@@ -142,18 +124,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
 | 
				
			|||||||
            self.copy_files()
 | 
					            self.copy_files()
 | 
				
			||||||
        if action == "paste":
 | 
					        if action == "paste":
 | 
				
			||||||
            self.paste_files()
 | 
					            self.paste_files()
 | 
				
			||||||
        if action == "archive":
 | 
					 | 
				
			||||||
            self.show_archiver_dialogue()
 | 
					 | 
				
			||||||
        if action == "delete":
 | 
					 | 
				
			||||||
            self.delete_files()
 | 
					 | 
				
			||||||
        if action == "trash":
 | 
					 | 
				
			||||||
            self.trash_files()
 | 
					 | 
				
			||||||
        if action == "go_to_trash":
 | 
					 | 
				
			||||||
            self.path_entry.set_text(self.trash_files_path)
 | 
					 | 
				
			||||||
        if action == "restore_from_trash":
 | 
					 | 
				
			||||||
            self.restore_trash_files()
 | 
					 | 
				
			||||||
        if action == "empty_trash":
 | 
					 | 
				
			||||||
            self.empty_trash()
 | 
					 | 
				
			||||||
        if action == "create":
 | 
					        if action == "create":
 | 
				
			||||||
            self.create_files()
 | 
					            self.create_files()
 | 
				
			||||||
        if action in ["save_session", "save_session_as", "load_session"]:
 | 
					        if action in ["save_session", "save_session_as", "load_session"]:
 | 
				
			||||||
@@ -162,7 +132,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @endpoint_registry.register(rule="go_home")
 | 
					    @endpoint_registry.register(rule="go_home")
 | 
				
			||||||
    def go_home(self, widget=None, eve=None):
 | 
					    def go_home(self, widget=None, eve=None):
 | 
				
			||||||
        self.builder.get_object("go_home").released()
 | 
					        self.builder.get_object("go_home").released()
 | 
				
			||||||
@@ -189,3 +158,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
 | 
				
			|||||||
        wid, tid = self.fm_controller.get_active_wid_and_tid()
 | 
					        wid, tid = self.fm_controller.get_active_wid_and_tid()
 | 
				
			||||||
        tab      = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
					        tab      = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
				
			||||||
        tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory())
 | 
					        tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def go_to_path(self, path):
 | 
				
			||||||
 | 
					        self.path_entry.set_text(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_hide_context_menu(self):
 | 
				
			||||||
 | 
					        self.hide_context_menu()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,12 +4,13 @@ from dataclasses import dataclass
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
from gi.repository import GLib
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from trasher.xdgtrash import XDGTrash
 | 
					 | 
				
			||||||
from shellfm.windows.controller import WindowController
 | 
					from shellfm.windows.controller import WindowController
 | 
				
			||||||
from plugins.plugins import Plugins
 | 
					from plugins.plugins_controller import PluginsController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass(slots=True)
 | 
					@dataclass(slots=True)
 | 
				
			||||||
@@ -22,25 +23,22 @@ class State:
 | 
				
			|||||||
    selected_files: [] = None
 | 
					    selected_files: [] = None
 | 
				
			||||||
    to_copy_files:  [] = None
 | 
					    to_copy_files:  [] = None
 | 
				
			||||||
    to_cut_files:   [] = None
 | 
					    to_cut_files:   [] = None
 | 
				
			||||||
 | 
					    warning_alert: type = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Controller_Data:
 | 
					class Controller_Data:
 | 
				
			||||||
    """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """
 | 
					    """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """
 | 
				
			||||||
    __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4"
 | 
					    __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setup_controller_data(self, _settings: type) -> None:
 | 
					    def setup_controller_data(self) -> None:
 | 
				
			||||||
        self.settings            = _settings
 | 
					        self.builder             = settings.get_builder()
 | 
				
			||||||
        self.builder             = self.settings.get_builder()
 | 
					        self.keybindings         = settings.get_keybindings()
 | 
				
			||||||
        self.logger              = self.settings.get_logger()
 | 
					 | 
				
			||||||
        self.keybindings         = self.settings.get_keybindings()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.trashman            = XDGTrash()
 | 
					 | 
				
			||||||
        self.fm_controller       = WindowController()
 | 
					        self.fm_controller       = WindowController()
 | 
				
			||||||
        self.plugins             = Plugins(_settings)
 | 
					        self.plugins             = PluginsController()
 | 
				
			||||||
        self.fm_controller_data  = self.fm_controller.get_state_from_file()
 | 
					        self.fm_controller_data  = self.fm_controller.get_state_from_file()
 | 
				
			||||||
        self.trashman.regenerate()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.window             = self.settings.get_main_window()
 | 
					        self.window             = settings.get_main_window()
 | 
				
			||||||
        self.window1            = self.builder.get_object("window_1")
 | 
					        self.window1            = self.builder.get_object("window_1")
 | 
				
			||||||
        self.window2            = self.builder.get_object("window_2")
 | 
					        self.window2            = self.builder.get_object("window_2")
 | 
				
			||||||
        self.window3            = self.builder.get_object("window_3")
 | 
					        self.window3            = self.builder.get_object("window_3")
 | 
				
			||||||
@@ -66,39 +64,14 @@ class Controller_Data:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.trash_files_path        = f"{GLib.get_user_data_dir()}/Trash/files"
 | 
					        self.trash_files_path        = f"{GLib.get_user_data_dir()}/Trash/files"
 | 
				
			||||||
        self.trash_info_path         = f"{GLib.get_user_data_dir()}/Trash/info"
 | 
					        self.trash_info_path         = f"{GLib.get_user_data_dir()}/Trash/info"
 | 
				
			||||||
        self.icon_theme              = self.settings.get_icon_theme()
 | 
					        self.icon_theme              = settings.get_icon_theme()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # In compress commands:
 | 
					 | 
				
			||||||
        #    %n: First selected filename/dir to archive
 | 
					 | 
				
			||||||
        #    %N: All selected filenames/dirs to archive, or (with %O) a single filename
 | 
					 | 
				
			||||||
        #    %o: Resulting single archive file
 | 
					 | 
				
			||||||
        #    %O: Resulting archive per source file/directory (use changes %N meaning)
 | 
					 | 
				
			||||||
        #
 | 
					 | 
				
			||||||
        #  In extract commands:
 | 
					 | 
				
			||||||
        #    %x: Archive file to extract
 | 
					 | 
				
			||||||
        #    %g: Unique extraction target filename with optional subfolder
 | 
					 | 
				
			||||||
        #    %G: Unique extraction target filename, never with subfolder
 | 
					 | 
				
			||||||
        #
 | 
					 | 
				
			||||||
        #  In list commands:
 | 
					 | 
				
			||||||
        #      %x: Archive to list
 | 
					 | 
				
			||||||
        #
 | 
					 | 
				
			||||||
        #  Plus standard bash variables are accepted.
 | 
					 | 
				
			||||||
        self.arc_commands            = [ '$(which 7za || echo 7zr) a %o %N',
 | 
					 | 
				
			||||||
                                                                'zip -r %o %N',
 | 
					 | 
				
			||||||
                                                                'rar a -r %o %N',
 | 
					 | 
				
			||||||
                                                                'tar -cvf %o %N',
 | 
					 | 
				
			||||||
                                                                'tar -cvjf %o %N',
 | 
					 | 
				
			||||||
                                                                'tar -cvzf %o %N',
 | 
					 | 
				
			||||||
                                                                'tar -cvJf %o %N',
 | 
					 | 
				
			||||||
                                                                'gzip -c %N > %O',
 | 
					 | 
				
			||||||
                                                                'xz -cz %N > %O'
 | 
					 | 
				
			||||||
                                        ]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.notebooks          = [self.window1, self.window2, self.window3, self.window4]
 | 
					        self.notebooks          = [self.window1, self.window2, self.window3, self.window4]
 | 
				
			||||||
        self.selected_files     = []
 | 
					        self.selected_files     = []
 | 
				
			||||||
        self.to_copy_files      = []
 | 
					        self.to_copy_files      = []
 | 
				
			||||||
        self.to_cut_files       = []
 | 
					        self.to_cut_files       = []
 | 
				
			||||||
        self.soft_update_lock   = {}
 | 
					        self.soft_update_lock   = {}
 | 
				
			||||||
 | 
					        self.dnd_left_primed    = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.single_click_open  = False
 | 
					        self.single_click_open  = False
 | 
				
			||||||
        self.is_pane1_hidden    = False
 | 
					        self.is_pane1_hidden    = False
 | 
				
			||||||
@@ -107,9 +80,6 @@ class Controller_Data:
 | 
				
			|||||||
        self.is_pane4_hidden    = False
 | 
					        self.is_pane4_hidden    = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.override_drop_dest = None
 | 
					        self.override_drop_dest = None
 | 
				
			||||||
        self.is_searching       = False
 | 
					 | 
				
			||||||
        self.search_icon_grid   = None
 | 
					 | 
				
			||||||
        self.search_tab         = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.cancel_creation    = False
 | 
					        self.cancel_creation    = False
 | 
				
			||||||
        self.skip_edit          = False
 | 
					        self.skip_edit          = False
 | 
				
			||||||
@@ -118,14 +88,18 @@ class Controller_Data:
 | 
				
			|||||||
        self.shift_down         = False
 | 
					        self.shift_down         = False
 | 
				
			||||||
        self.alt_down           = False
 | 
					        self.alt_down           = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.success_color      = self.settings.get_success_color()
 | 
					        self.success_color      = settings.get_success_color()
 | 
				
			||||||
        self.warning_color      = self.settings.get_warning_color()
 | 
					        self.warning_color      = settings.get_warning_color()
 | 
				
			||||||
        self.error_color        = self.settings.get_error_color()
 | 
					        self.error_color        = settings.get_error_color()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # sys.excepthook = self.custom_except_hook
 | 
					        # sys.excepthook = self.custom_except_hook
 | 
				
			||||||
        self.window.connect("delete-event", self.tear_down)
 | 
					        self.window.connect("delete-event", self.tear_down)
 | 
				
			||||||
        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
 | 
					        GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.window.show()
 | 
				
			||||||
 | 
					        if settings.is_debug():
 | 
				
			||||||
 | 
					            self.window.set_interactive_debugging(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_current_state(self) -> State:
 | 
					    def get_current_state(self) -> State:
 | 
				
			||||||
        '''
 | 
					        '''
 | 
				
			||||||
@@ -142,7 +116,7 @@ class Controller_Data:
 | 
				
			|||||||
        state.tab            = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
 | 
					        state.tab            = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
 | 
				
			||||||
        state.icon_grid      = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid")
 | 
					        state.icon_grid      = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid")
 | 
				
			||||||
        state.store          = state.icon_grid.get_model()
 | 
					        state.store          = state.icon_grid.get_model()
 | 
				
			||||||
 | 
					        state.warning_alert  = self.warning_alert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        selected_files       = state.icon_grid.get_selected_items()
 | 
					        selected_files       = state.icon_grid.get_selected_items()
 | 
				
			||||||
        if selected_files:
 | 
					        if selected_files:
 | 
				
			||||||
@@ -154,6 +128,7 @@ class Controller_Data:
 | 
				
			|||||||
        # if self.to_cut_files:
 | 
					        # if self.to_cut_files:
 | 
				
			||||||
        #     state.to_cut_files   = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True)
 | 
					        #     state.to_cut_files   = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        event_system.emit("update_state_info_plugins", state)
 | 
				
			||||||
        return state
 | 
					        return state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,3 @@
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
Mixins module
 | 
					    Mixins module
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,12 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import traceback, time
 | 
					import traceback
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, GLib
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,8 +53,3 @@ class ExceptionHookMixin:
 | 
				
			|||||||
                f.write(text)
 | 
					                f.write(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        save_location_prompt.destroy()
 | 
					        save_location_prompt.destroy()
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def set_arc_buffer_text(self, widget=None, eve=None):
 | 
					 | 
				
			||||||
        sid = widget.get_active_id()
 | 
					 | 
				
			||||||
        self.arc_command_buffer.set_text(self.arc_commands[int(sid)])
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,8 @@
 | 
				
			|||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
gi.require_version('Gdk', '3.0')
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, Gdk
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,26 +56,6 @@ class ShowHideMixin:
 | 
				
			|||||||
        self.builder.get_object("about_page").hide()
 | 
					        self.builder.get_object("about_page").hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_archiver_dialogue(self, widget=None, eve=None):
 | 
					 | 
				
			||||||
        wid, tid          = self.fm_controller.get_active_wid_and_tid()
 | 
					 | 
				
			||||||
        tab               = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
					 | 
				
			||||||
        archiver_dialogue = self.builder.get_object("archiver_dialogue")
 | 
					 | 
				
			||||||
        archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE)
 | 
					 | 
				
			||||||
        archiver_dialogue.set_current_folder(tab.get_current_directory())
 | 
					 | 
				
			||||||
        archiver_dialogue.set_current_name("arc.7z")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        response = archiver_dialogue.run()
 | 
					 | 
				
			||||||
        if response == Gtk.ResponseType.OK:
 | 
					 | 
				
			||||||
            self.archive_files(archiver_dialogue)
 | 
					 | 
				
			||||||
        if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        archiver_dialogue.hide()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def hide_archiver_dialogue(self, widget=None, eve=None):
 | 
					 | 
				
			||||||
        self.builder.get_object("archiver_dialogue").hide()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def show_appchooser_menu(self, widget=None, eve=None):
 | 
					    def show_appchooser_menu(self, widget=None, eve=None):
 | 
				
			||||||
        appchooser_menu   = self.builder.get_object("appchooser_menu")
 | 
					        appchooser_menu   = self.builder.get_object("appchooser_menu")
 | 
				
			||||||
        appchooser_widget = self.builder.get_object("appchooser_widget")
 | 
					        appchooser_widget = self.builder.get_object("appchooser_widget")
 | 
				
			||||||
@@ -103,10 +84,10 @@ class ShowHideMixin:
 | 
				
			|||||||
        self.builder.get_object("plugin_controls").hide()
 | 
					        self.builder.get_object("plugin_controls").hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_context_menu(self, widget=None, eve=None):
 | 
					    def show_context_menu(self, widget=None, eve=None):
 | 
				
			||||||
        self.builder.get_object("context_menu_popup").run()
 | 
					        self.builder.get_object("context_menu").popup_at_pointer(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def hide_context_menu(self, widget=None, eve=None):
 | 
					    def hide_context_menu(self, widget=None, eve=None):
 | 
				
			||||||
        self.builder.get_object("context_menu_popup").hide()
 | 
					        self.builder.get_object("context_menu").popdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_new_file_menu(self, widget=None, eve=None):
 | 
					    def show_new_file_menu(self, widget=None, eve=None):
 | 
				
			||||||
        if widget:
 | 
					        if widget:
 | 
				
			||||||
@@ -135,6 +116,9 @@ class ShowHideMixin:
 | 
				
			|||||||
        if response == Gtk.ResponseType.CANCEL:
 | 
					        if response == Gtk.ResponseType.CANCEL:
 | 
				
			||||||
            self.cancel_edit = True
 | 
					            self.cancel_edit = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def show_io_popup(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self.builder.get_object("io_popup").popup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def hide_edit_file_menu(self, widget=None, eve=None):
 | 
					    def hide_edit_file_menu(self, widget=None, eve=None):
 | 
				
			||||||
        self.builder.get_object("edit_file_menu").hide()
 | 
					        self.builder.get_object("edit_file_menu").hide()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Signals module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
@@ -1,18 +1,24 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, time, shlex
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import shlex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, GObject, GLib, Gio
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GObject
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					from widgets.io_widget import IOWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FileActionSignalsMixin:
 | 
				
			||||||
class WidgetFileActionMixin:
 | 
					    """docstring for FileActionSignalsMixin"""
 | 
				
			||||||
    """docstring for WidgetFileActionMixin"""
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def sizeof_fmt(self, num, suffix="B"):
 | 
					    def sizeof_fmt(self, num, suffix="B"):
 | 
				
			||||||
        for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
 | 
					        for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
 | 
				
			||||||
@@ -40,8 +46,8 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
        if tab.get_dir_watcher():
 | 
					        if tab.get_dir_watcher():
 | 
				
			||||||
            watcher = tab.get_dir_watcher()
 | 
					            watcher = tab.get_dir_watcher()
 | 
				
			||||||
            watcher.cancel()
 | 
					            watcher.cancel()
 | 
				
			||||||
            if debug:
 | 
					            if settings.is_debug():
 | 
				
			||||||
                self.logger.debug(f"Watcher Is Cancelled:  {watcher.is_cancelled()}")
 | 
					                logger.debug(f"Watcher Is Cancelled:  {watcher.is_cancelled()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cur_dir = tab.get_current_directory()
 | 
					        cur_dir = tab.get_current_directory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,8 +65,8 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
        if eve_type in  [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
 | 
					        if eve_type in  [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
 | 
				
			||||||
                        Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
 | 
					                        Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
 | 
				
			||||||
                        Gio.FileMonitorEvent.MOVED_OUT]:
 | 
					                        Gio.FileMonitorEvent.MOVED_OUT]:
 | 
				
			||||||
            if debug:
 | 
					            if settings.is_debug():
 | 
				
			||||||
                self.logger.debug(eve_type)
 | 
					                logger.debug(eve_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
 | 
					            if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
 | 
				
			||||||
                self.update_on_soft_lock_end(data[0])
 | 
					                self.update_on_soft_lock_end(data[0])
 | 
				
			||||||
@@ -102,24 +108,27 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
            self.set_bottom_labels(tab)
 | 
					            self.set_bottom_labels(tab)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def popup_search_files(self, wid, keyname):
 | 
					 | 
				
			||||||
        entry = self.builder.get_object(f"win{wid}_search_field")
 | 
					 | 
				
			||||||
        self.builder.get_object(f"win{wid}_search").popup()
 | 
					 | 
				
			||||||
        entry.set_text(keyname)
 | 
					 | 
				
			||||||
        entry.grab_focus_without_selecting()
 | 
					 | 
				
			||||||
        entry.set_position(-1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def do_file_search(self, widget, eve=None):
 | 
					    def do_file_search(self, widget, eve=None):
 | 
				
			||||||
        query = widget.get_text().lower()
 | 
					        if not self.ctrl_down and not self.shift_down and not self.alt_down:
 | 
				
			||||||
        self.search_icon_grid.unselect_all()
 | 
					            target    = widget.get_name()
 | 
				
			||||||
        for i, file in enumerate(self.search_tab.get_files()):
 | 
					            notebook  = self.builder.get_object(target)
 | 
				
			||||||
            if query and query in file[0].lower():
 | 
					            page      = notebook.get_current_page()
 | 
				
			||||||
                path = Gtk.TreePath().new_from_indices([i])
 | 
					            nth_page  = notebook.get_nth_page(page)
 | 
				
			||||||
                self.search_icon_grid.select_path(path)
 | 
					            icon_grid = nth_page.get_children()[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        items = self.search_icon_grid.get_selected_items()
 | 
					            wid, tid  = icon_grid.get_name().split("|")
 | 
				
			||||||
        if len(items) > 0:
 | 
					            tab       = self.get_fm_window(wid).get_tab_by_id(tid)
 | 
				
			||||||
            self.search_icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5)
 | 
					            query     = widget.get_text().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            icon_grid.unselect_all()
 | 
				
			||||||
 | 
					            for i, file in enumerate(tab.get_files()):
 | 
				
			||||||
 | 
					                if query and query in file[0].lower():
 | 
				
			||||||
 | 
					                    path = Gtk.TreePath().new_from_indices([i])
 | 
				
			||||||
 | 
					                    icon_grid.select_path(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            items = icon_grid.get_selected_items()
 | 
				
			||||||
 | 
					            if len(items) == 1:
 | 
				
			||||||
 | 
					                icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def open_files(self):
 | 
					    def open_files(self):
 | 
				
			||||||
@@ -143,19 +152,6 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
            command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}"
 | 
					            command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}"
 | 
				
			||||||
            state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory())
 | 
					            state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def archive_files(self, archiver_dialogue):
 | 
					 | 
				
			||||||
        state       = self.get_current_state()
 | 
					 | 
				
			||||||
        paths       = [shlex.quote(p) for p in self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        save_target = archiver_dialogue.get_filename();
 | 
					 | 
				
			||||||
        sItr, eItr  = self.arc_command_buffer.get_bounds()
 | 
					 | 
				
			||||||
        pre_command = self.arc_command_buffer.get_text(sItr, eItr, False)
 | 
					 | 
				
			||||||
        pre_command = pre_command.replace("%o", shlex.quote(save_target))
 | 
					 | 
				
			||||||
        pre_command = pre_command.replace("%N", ' '.join(paths))
 | 
					 | 
				
			||||||
        command     = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        state.tab.execute(shlex.split(command), start_dir=shlex.quote(state.tab.get_current_directory()))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def rename_files(self):
 | 
					    def rename_files(self):
 | 
				
			||||||
        rename_label = self.builder.get_object("file_to_rename_label")
 | 
					        rename_label = self.builder.get_object("file_to_rename_label")
 | 
				
			||||||
        rename_input = self.builder.get_object("new_rename_fname")
 | 
					        rename_input = self.builder.get_object("new_rename_fname")
 | 
				
			||||||
@@ -208,51 +204,12 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
        elif self.to_cut_files:
 | 
					        elif self.to_cut_files:
 | 
				
			||||||
            self.handle_files(self.to_cut_files, "move", target)
 | 
					            self.handle_files(self.to_cut_files, "move", target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_files(self):
 | 
					 | 
				
			||||||
        state    = self.get_current_state()
 | 
					 | 
				
			||||||
        uris     = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
 | 
					 | 
				
			||||||
        response = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
 | 
					 | 
				
			||||||
        for uri in uris:
 | 
					 | 
				
			||||||
            file = Gio.File.new_for_path(uri)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if not response:
 | 
					 | 
				
			||||||
                response = self.warning_alert.run()
 | 
					 | 
				
			||||||
                self.warning_alert.hide()
 | 
					 | 
				
			||||||
            if response == Gtk.ResponseType.YES:
 | 
					 | 
				
			||||||
                type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if type == Gio.FileType.DIRECTORY:
 | 
					 | 
				
			||||||
                    state.tab.delete_file( file.get_path() )
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    file.delete(cancellable=None)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def trash_files(self):
 | 
					 | 
				
			||||||
        state = self.get_current_state()
 | 
					 | 
				
			||||||
        uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
 | 
					 | 
				
			||||||
        for uri in uris:
 | 
					 | 
				
			||||||
            self.trashman.trash(uri, False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def restore_trash_files(self):
 | 
					 | 
				
			||||||
        state = self.get_current_state()
 | 
					 | 
				
			||||||
        uris  = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
 | 
					 | 
				
			||||||
        for uri in uris:
 | 
					 | 
				
			||||||
            self.trashman.restore(filename=uri.split("/")[-1], verbose=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def empty_trash(self):
 | 
					 | 
				
			||||||
        self.trashman.empty(verbose=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_files(self):
 | 
					    def create_files(self):
 | 
				
			||||||
        fname_field = self.builder.get_object("new_fname_field")
 | 
					        fname_field = self.builder.get_object("new_fname_field")
 | 
				
			||||||
        self.show_new_file_menu(fname_field)
 | 
					        self.show_new_file_menu(fname_field)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.cancel_creation:
 | 
					        if self.cancel_creation:
 | 
				
			||||||
            self.cancel_creation    = False
 | 
					            self.cancel_creation = False
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        file_name   = fname_field.get_text().strip()
 | 
					        file_name   = fname_field.get_text().strip()
 | 
				
			||||||
@@ -355,7 +312,6 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
                    file.make_directory(cancellable=None)
 | 
					                    file.make_directory(cancellable=None)
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
                type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
 | 
					                type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
 | 
				
			||||||
                if type == Gio.FileType.DIRECTORY:
 | 
					                if type == Gio.FileType.DIRECTORY:
 | 
				
			||||||
                    wid, tid  = self.fm_controller.get_active_wid_and_tid()
 | 
					                    wid, tid  = self.fm_controller.get_active_wid_and_tid()
 | 
				
			||||||
@@ -369,10 +325,27 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
                    if action == "move" or action == "rename":
 | 
					                    if action == "move" or action == "rename":
 | 
				
			||||||
                        tab.move_file(fPath, tPath)
 | 
					                        tab.move_file(fPath, tPath)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
 | 
					                    io_widget = IOWidget(action, file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if action == "copy":
 | 
					                    if action == "copy":
 | 
				
			||||||
                        file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
 | 
					                        file.copy_async(destination=target,
 | 
				
			||||||
 | 
					                                        flags=Gio.FileCopyFlags.BACKUP,
 | 
				
			||||||
 | 
					                                        io_priority=98,
 | 
				
			||||||
 | 
					                                        cancellable=io_widget.cancle_eve,
 | 
				
			||||||
 | 
					                                        progress_callback=io_widget.update_progress,
 | 
				
			||||||
 | 
					                                        callback=io_widget.finish_callback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        self.builder.get_object("io_list").add(io_widget)
 | 
				
			||||||
                    if action == "move" or action == "rename":
 | 
					                    if action == "move" or action == "rename":
 | 
				
			||||||
                        file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
 | 
					                        file.move_async(destination=target,
 | 
				
			||||||
 | 
					                                        flags=Gio.FileCopyFlags.BACKUP,
 | 
				
			||||||
 | 
					                                        io_priority=98,
 | 
				
			||||||
 | 
					                                        cancellable=io_widget.cancle_eve,
 | 
				
			||||||
 | 
					                                        progress_callback=None,
 | 
				
			||||||
 | 
					                                        # NOTE: progress_callback here causes seg fault when set
 | 
				
			||||||
 | 
					                                        callback=io_widget.finish_callback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        self.builder.get_object("io_list").add(io_widget)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            except GObject.GError as e:
 | 
					            except GObject.GError as e:
 | 
				
			||||||
                raise OSError(e)
 | 
					                raise OSError(e)
 | 
				
			||||||
@@ -428,11 +401,11 @@ class WidgetFileActionMixin:
 | 
				
			|||||||
        target    = Gio.File.new_for_path(full_path)
 | 
					        target    = Gio.File.new_for_path(full_path)
 | 
				
			||||||
        start     = "-copy"
 | 
					        start     = "-copy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if debug:
 | 
					        if settings.is_debug():
 | 
				
			||||||
            self.logger.debug(f"Path:  {full_path}")
 | 
					            logger.debug(f"Path:  {full_path}")
 | 
				
			||||||
            self.logger.debug(f"Base Path:  {base_path}")
 | 
					            logger.debug(f"Base Path:  {base_path}")
 | 
				
			||||||
            self.logger.debug(f'Name:  {file_name}')
 | 
					            logger.debug(f'Name:  {file_name}')
 | 
				
			||||||
            self.logger.debug(f"Extension:  {extension}")
 | 
					            logger.debug(f"Extension:  {extension}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        i = 2
 | 
					        i = 2
 | 
				
			||||||
        while target.query_exists():
 | 
					        while target.query_exists():
 | 
				
			||||||
@@ -5,11 +5,12 @@
 | 
				
			|||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IPCSignalsMixin:
 | 
					class IPCSignalsMixin:
 | 
				
			||||||
    """ IPCSignalsMixin handle messages from another starting solarfm process. """
 | 
					    """ IPCSignalsMixin handle messages from another starting solarfm process. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def print_to_console(self, message=None):
 | 
					    def print_to_console(self, message=None):
 | 
				
			||||||
        print(self)
 | 
					 | 
				
			||||||
        print(message)
 | 
					        print(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_file_from_ipc(self, path):
 | 
					    def handle_file_from_ipc(self, path):
 | 
				
			||||||
@@ -5,7 +5,8 @@ import re
 | 
				
			|||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
gi.require_version('Gdk', '3.0')
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, Gdk
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -13,6 +14,8 @@ from gi.repository import Gtk, Gdk
 | 
				
			|||||||
valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
 | 
					valid_keyvalue_pat    = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class KeyboardSignalsMixin:
 | 
					class KeyboardSignalsMixin:
 | 
				
			||||||
    """ KeyboardSignalsMixin keyboard hooks controller. """
 | 
					    """ KeyboardSignalsMixin keyboard hooks controller. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,7 +24,6 @@ class KeyboardSignalsMixin:
 | 
				
			|||||||
        self.ctrl_down    = False
 | 
					        self.ctrl_down    = False
 | 
				
			||||||
        self.shift_down   = False
 | 
					        self.shift_down   = False
 | 
				
			||||||
        self.alt_down     = False
 | 
					        self.alt_down     = False
 | 
				
			||||||
        self.is_searching = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_global_key_press_controller(self, eve, user_data):
 | 
					    def on_global_key_press_controller(self, eve, user_data):
 | 
				
			||||||
        keyname = Gdk.keyval_name(user_data.keyval).lower()
 | 
					        keyname = Gdk.keyval_name(user_data.keyval).lower()
 | 
				
			||||||
@@ -52,30 +54,18 @@ class KeyboardSignalsMixin:
 | 
				
			|||||||
                return True
 | 
					                return True
 | 
				
			||||||
            except Exception:
 | 
					            except Exception:
 | 
				
			||||||
                # Must be plugins scope or we forgot to add method to file manager scope
 | 
					                # Must be plugins scope or we forgot to add method to file manager scope
 | 
				
			||||||
                sender, method_target = mapping.split("||")
 | 
					                sender, eve_type = mapping.split("||")
 | 
				
			||||||
                self.handle_plugin_key_event(sender, method_target)
 | 
					                self.handle_plugin_key_event(sender, eve_type)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if debug:
 | 
					            if settings.is_debug():
 | 
				
			||||||
                print(f"on_global_key_release_controller > key > {keyname}")
 | 
					                print(f"on_global_key_release_controller > key > {keyname}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if self.ctrl_down:
 | 
					            if self.ctrl_down:
 | 
				
			||||||
                if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
 | 
					                if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
 | 
				
			||||||
                    self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
 | 
					                    self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if re.fullmatch(valid_keyvalue_pat, keyname):
 | 
					    def handle_plugin_key_event(self, sender, eve_type):
 | 
				
			||||||
                if not self.is_searching and not self.ctrl_down \
 | 
					        event_system.emit(eve_type)
 | 
				
			||||||
                    and not self.shift_down and not self.alt_down:
 | 
					 | 
				
			||||||
                        focused_obj = self.window.get_focus()
 | 
					 | 
				
			||||||
                        if isinstance(focused_obj, Gtk.IconView):
 | 
					 | 
				
			||||||
                            self.is_searching     = True
 | 
					 | 
				
			||||||
                            state                 = self.get_current_state()
 | 
					 | 
				
			||||||
                            self.search_tab       = state.tab
 | 
					 | 
				
			||||||
                            self.search_icon_grid = state.icon_grid
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            self.unset_keys_and_data()
 | 
					 | 
				
			||||||
                            self.popup_search_files(state.wid, keyname)
 | 
					 | 
				
			||||||
                            return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def keyboard_close_tab(self):
 | 
					    def keyboard_close_tab(self):
 | 
				
			||||||
        wid, tid  = self.fm_controller.get_active_wid_and_tid()
 | 
					        wid, tid  = self.fm_controller.get_active_wid_and_tid()
 | 
				
			||||||
@@ -88,5 +78,6 @@ class KeyboardSignalsMixin:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.get_fm_window(wid).delete_tab_by_id(tid)
 | 
					        self.get_fm_window(wid).delete_tab_by_id(tid)
 | 
				
			||||||
        notebook.remove_page(page)
 | 
					        notebook.remove_page(page)
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not trace_debug:
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
        self.set_window_title()
 | 
					        self.set_window_title()
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					from .exception_hook_mixin import ExceptionHookMixin
 | 
				
			||||||
 | 
					from .signals.file_action_signals_mixin import FileActionSignalsMixin
 | 
				
			||||||
 | 
					from .signals.ipc_signals_mixin import IPCSignalsMixin
 | 
				
			||||||
 | 
					from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin):
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
@@ -1,3 +1,3 @@
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
UI module
 | 
					    UI module
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,48 +6,22 @@ import gi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
gi.require_version("Gtk", "3.0")
 | 
					gi.require_version("Gtk", "3.0")
 | 
				
			||||||
gi.require_version('Gdk', '3.0')
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					from gi.repository import GdkPixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					from widgets.tab_header_widget import TabHeaderWidget
 | 
				
			||||||
 | 
					from widgets.icon_grid_widget import IconGridWidget
 | 
				
			||||||
 | 
					from widgets.icon_tree_widget import IconTreeWidget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid...
 | 
					 | 
				
			||||||
# Can possibly use this to dynamicly load icons instead...
 | 
					 | 
				
			||||||
class Icon(Gtk.HBox):
 | 
					 | 
				
			||||||
    def __init__(self, tab, dir, file):
 | 
					 | 
				
			||||||
        super(Icon, self).__init__()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.load_icon(tab, dir, file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @threaded
 | 
					 | 
				
			||||||
    def load_icon(self, tab, dir, file):
 | 
					 | 
				
			||||||
        icon = tab.create_icon(dir, file)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not icon:
 | 
					 | 
				
			||||||
            path = f"{dir}/{file}"
 | 
					 | 
				
			||||||
            icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not icon:
 | 
					 | 
				
			||||||
            icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.add(Gtk.Image.new_from_pixbuf(icon))
 | 
					 | 
				
			||||||
        self.show_all()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_system_thumbnail(self, file, size):
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            gio_file  = Gio.File.new_for_path(file)
 | 
					 | 
				
			||||||
            info      = gio_file.query_info('standard::icon' , 0, None)
 | 
					 | 
				
			||||||
            icon      = info.get_icon().get_names()[0]
 | 
					 | 
				
			||||||
            icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename()
 | 
					 | 
				
			||||||
            return GdkPixbuf.Pixbuf.new_from_file(icon_path)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GridMixin:
 | 
					class GridMixin:
 | 
				
			||||||
    """docstring for WidgetMixin"""
 | 
					    """docstring for GridMixin"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_store(self, tab, store, save_state=False):
 | 
					    def load_store(self, tab, store, save_state=False):
 | 
				
			||||||
        store.clear()
 | 
					        store.clear()
 | 
				
			||||||
@@ -61,7 +35,7 @@ class GridMixin:
 | 
				
			|||||||
            self.create_icon(i, tab, store, dir, file[0])
 | 
					            self.create_icon(i, tab, store, dir, file[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # NOTE: Not likely called often from here but it could be useful
 | 
					        # NOTE: Not likely called often from here but it could be useful
 | 
				
			||||||
        if save_state:
 | 
					        if save_state and not trace_debug:
 | 
				
			||||||
            self.fm_controller.save_state()
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @threaded
 | 
					    @threaded
 | 
				
			||||||
@@ -86,133 +60,59 @@ class GridMixin:
 | 
				
			|||||||
            info      = gio_file.query_info('standard::icon' , 0, None)
 | 
					            info      = gio_file.query_info('standard::icon' , 0, None)
 | 
				
			||||||
            icon      = info.get_icon().get_names()[0]
 | 
					            icon      = info.get_icon().get_names()[0]
 | 
				
			||||||
            icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename()
 | 
					            icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GdkPixbuf.Pixbuf.new_from_file(icon_path)
 | 
					            return GdkPixbuf.Pixbuf.new_from_file(icon_path)
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception:
 | 
				
			||||||
            return None
 | 
					            ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_tab_widget(self, tab):
 | 
					    def create_tab_widget(self, tab):
 | 
				
			||||||
        tab_widget = Gtk.ButtonBox()
 | 
					        return TabHeaderWidget(tab, self.close_tab)
 | 
				
			||||||
        label = Gtk.Label()
 | 
					 | 
				
			||||||
        tid   = Gtk.Label()
 | 
					 | 
				
			||||||
        close = Gtk.Button()
 | 
					 | 
				
			||||||
        icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        label.set_label(f"{tab.get_end_of_path()}")
 | 
					 | 
				
			||||||
        label.set_width_chars(len(tab.get_end_of_path()))
 | 
					 | 
				
			||||||
        label.set_xalign(0.0)
 | 
					 | 
				
			||||||
        tid.set_label(f"{tab.get_id()}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        close.add(icon)
 | 
					 | 
				
			||||||
        tab_widget.add(label)
 | 
					 | 
				
			||||||
        tab_widget.add(close)
 | 
					 | 
				
			||||||
        tab_widget.add(tid)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        close.connect("released", self.close_tab)
 | 
					 | 
				
			||||||
        tab_widget.show_all()
 | 
					 | 
				
			||||||
        tid.hide()
 | 
					 | 
				
			||||||
        return tab_widget
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_scroll_and_store(self, tab, wid, use_tree_view=False):
 | 
					    def create_scroll_and_store(self, tab, wid, use_tree_view=False):
 | 
				
			||||||
 | 
					        scroll = Gtk.ScrolledWindow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not use_tree_view:
 | 
					        if not use_tree_view:
 | 
				
			||||||
            scroll, store = self.create_icon_grid_widget(tab, wid)
 | 
					            grid = self.create_icon_grid_widget()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # TODO: Fix global logic to make the below work too
 | 
					            # TODO: Fix global logic to make the below work too
 | 
				
			||||||
            scroll, store = self.create_icon_tree_widget(tab, wid)
 | 
					            grid = self.create_icon_tree_widget()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return scroll, store
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def create_icon_grid_widget(self, tab, wid):
 | 
					 | 
				
			||||||
        scroll = Gtk.ScrolledWindow()
 | 
					 | 
				
			||||||
        grid   = Gtk.IconView()
 | 
					 | 
				
			||||||
        store  = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        grid.set_model(store)
 | 
					 | 
				
			||||||
        grid.set_pixbuf_column(0)
 | 
					 | 
				
			||||||
        grid.set_text_column(1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        grid.set_item_orientation(1)
 | 
					 | 
				
			||||||
        grid.set_selection_mode(3)
 | 
					 | 
				
			||||||
        grid.set_item_width(96)
 | 
					 | 
				
			||||||
        grid.set_item_padding(8)
 | 
					 | 
				
			||||||
        grid.set_margin(12)
 | 
					 | 
				
			||||||
        grid.set_row_spacing(18)
 | 
					 | 
				
			||||||
        grid.set_columns(-1)
 | 
					 | 
				
			||||||
        grid.set_spacing(12)
 | 
					 | 
				
			||||||
        grid.set_column_spacing(18)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        grid.connect("button_release_event", self.grid_icon_single_click)
 | 
					 | 
				
			||||||
        grid.connect("item-activated",       self.grid_icon_double_click)
 | 
					 | 
				
			||||||
        grid.connect("selection-changed",    self.grid_set_selected_items)
 | 
					 | 
				
			||||||
        grid.connect("drag-data-get",        self.grid_on_drag_set)
 | 
					 | 
				
			||||||
        grid.connect("drag-data-received",   self.grid_on_drag_data_received)
 | 
					 | 
				
			||||||
        grid.connect("drag-motion",          self.grid_on_drag_motion)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        URI_TARGET_TYPE  = 80
 | 
					 | 
				
			||||||
        uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
 | 
					 | 
				
			||||||
        targets          = [ uri_target ]
 | 
					 | 
				
			||||||
        action           = Gdk.DragAction.COPY
 | 
					 | 
				
			||||||
        grid.enable_model_drag_dest(targets, action)
 | 
					 | 
				
			||||||
        grid.enable_model_drag_source(0, targets, action)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        grid.show_all()
 | 
					 | 
				
			||||||
        scroll.add(grid)
 | 
					        scroll.add(grid)
 | 
				
			||||||
        grid.set_name(f"{wid}|{tab.get_id()}")
 | 
					 | 
				
			||||||
        scroll.set_name(f"{wid}|{tab.get_id()}")
 | 
					        scroll.set_name(f"{wid}|{tab.get_id()}")
 | 
				
			||||||
 | 
					        grid.set_name(f"{wid}|{tab.get_id()}")
 | 
				
			||||||
        self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid)
 | 
					        self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid)
 | 
				
			||||||
        self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
 | 
					        self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
 | 
				
			||||||
        return scroll, store
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_icon_tree_widget(self, tab, wid):
 | 
					        return scroll, grid.get_store()
 | 
				
			||||||
        scroll = Gtk.ScrolledWindow()
 | 
					 | 
				
			||||||
        grid   = Gtk.TreeView()
 | 
					 | 
				
			||||||
        store  = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
 | 
					 | 
				
			||||||
        column = Gtk.TreeViewColumn("Icons")
 | 
					 | 
				
			||||||
        icon   = Gtk.CellRendererPixbuf()
 | 
					 | 
				
			||||||
        name   = Gtk.CellRendererText()
 | 
					 | 
				
			||||||
        selec  = grid.get_selection()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        grid.set_model(store)
 | 
					    def create_icon_grid_widget(self):
 | 
				
			||||||
        selec.set_mode(3)
 | 
					        grid = IconGridWidget()
 | 
				
			||||||
        column.pack_start(icon, False)
 | 
					        grid._setup_additional_signals(
 | 
				
			||||||
        column.pack_start(name, True)
 | 
					            self.grid_icon_single_click,
 | 
				
			||||||
        column.add_attribute(icon, "pixbuf", 0)
 | 
					            self.grid_icon_double_click,
 | 
				
			||||||
        column.add_attribute(name, "text", 1)
 | 
					            self.grid_set_selected_items,
 | 
				
			||||||
        column.set_expand(False)
 | 
					            self.grid_on_drag_set,
 | 
				
			||||||
        column.set_sizing(2)
 | 
					            self.grid_on_drag_data_received,
 | 
				
			||||||
        column.set_min_width(120)
 | 
					            self.grid_on_drag_motion
 | 
				
			||||||
        column.set_max_width(74)
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        grid.append_column(column)
 | 
					        return grid
 | 
				
			||||||
        grid.set_search_column(1)
 | 
					 | 
				
			||||||
        grid.set_rubber_banding(True)
 | 
					 | 
				
			||||||
        grid.set_headers_visible(False)
 | 
					 | 
				
			||||||
        grid.set_enable_tree_lines(False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        grid.connect("button_release_event", self.grid_icon_single_click)
 | 
					    def create_icon_tree_widget(self):
 | 
				
			||||||
        grid.connect("row-activated",        self.grid_icon_double_click)
 | 
					        grid = IconTreeWidget()
 | 
				
			||||||
        grid.connect("drag-data-get",        self.grid_on_drag_set)
 | 
					        grid._setup_additional_signals(
 | 
				
			||||||
        grid.connect("drag-data-received",   self.grid_on_drag_data_received)
 | 
					            self.grid_icon_single_click,
 | 
				
			||||||
        grid.connect("drag-motion",          self.grid_on_drag_motion)
 | 
					            self.grid_icon_double_click,
 | 
				
			||||||
 | 
					            self.grid_on_drag_set,
 | 
				
			||||||
 | 
					            self.grid_on_drag_data_received,
 | 
				
			||||||
 | 
					            self.grid_on_drag_motion
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        URI_TARGET_TYPE  = 80
 | 
					 | 
				
			||||||
        uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
 | 
					 | 
				
			||||||
        targets          = [ uri_target ]
 | 
					 | 
				
			||||||
        action           = Gdk.DragAction.COPY
 | 
					 | 
				
			||||||
        grid.enable_model_drag_dest(targets, action)
 | 
					 | 
				
			||||||
        grid.enable_model_drag_source(0, targets, action)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        grid.show_all()
 | 
					 | 
				
			||||||
        scroll.add(grid)
 | 
					 | 
				
			||||||
        grid.set_name(f"{wid}|{tab.get_id()}")
 | 
					 | 
				
			||||||
        scroll.set_name(f"{wid}|{tab.get_id()}")
 | 
					 | 
				
			||||||
        self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid)
 | 
					 | 
				
			||||||
        self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
 | 
					 | 
				
			||||||
        grid.columns_autosize()
 | 
					        grid.columns_autosize()
 | 
				
			||||||
 | 
					        return grid
 | 
				
			||||||
        self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll)
 | 
					 | 
				
			||||||
        return scroll, store
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_store_and_label_from_notebook(self, notebook, _name):
 | 
					    def get_store_and_label_from_notebook(self, notebook, _name):
 | 
				
			||||||
        icon_grid = None
 | 
					        icon_grid = None
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,4 +56,5 @@ class PaneMixin:
 | 
				
			|||||||
    def _save_state(self, state, pane_index):
 | 
					    def _save_state(self, state, pane_index):
 | 
				
			||||||
        window = self.fm_controller.get_window_by_index(pane_index - 1)
 | 
					        window = self.fm_controller.get_window_by_index(pane_index - 1)
 | 
				
			||||||
        window.set_is_hidden(state)
 | 
					        window.set_is_hidden(state)
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,7 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
        notebook    = self.builder.get_object(f"window_{wid}")
 | 
					        notebook    = self.builder.get_object(f"window_{wid}")
 | 
				
			||||||
        path_entry  = self.builder.get_object(f"path_entry")
 | 
					        path_entry  = self.builder.get_object(f"path_entry")
 | 
				
			||||||
        tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}")
 | 
					        tab         = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}")
 | 
				
			||||||
        tab.logger  = self.logger
 | 
					        tab.logger  = logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tab.set_wid(wid)
 | 
					        tab.set_wid(wid)
 | 
				
			||||||
        if not path:
 | 
					        if not path:
 | 
				
			||||||
@@ -35,6 +35,7 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
        tab_widget    = self.create_tab_widget(tab)
 | 
					        tab_widget    = self.create_tab_widget(tab)
 | 
				
			||||||
        scroll, store = self.create_scroll_and_store(tab, wid)
 | 
					        scroll, store = self.create_scroll_and_store(tab, wid)
 | 
				
			||||||
        index         = notebook.append_page(scroll, tab_widget)
 | 
					        index         = notebook.append_page(scroll, tab_widget)
 | 
				
			||||||
 | 
					        notebook.set_tab_detachable(scroll, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.fm_controller.set_wid_and_tid(wid, tab.get_id())
 | 
					        self.fm_controller.set_wid_and_tid(wid, tab.get_id())
 | 
				
			||||||
        path_entry.set_text(tab.get_current_directory())
 | 
					        path_entry.set_text(tab.get_current_directory())
 | 
				
			||||||
@@ -63,9 +64,14 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
        watcher.cancel()
 | 
					        watcher.cancel()
 | 
				
			||||||
        self.get_fm_window(wid).delete_tab_by_id(tid)
 | 
					        self.get_fm_window(wid).delete_tab_by_id(tid)
 | 
				
			||||||
        notebook.remove_page(page)
 | 
					        notebook.remove_page(page)
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
        self.set_window_title()
 | 
					        self.set_window_title()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # NOTE: Not actually getting called even tho set in the glade file...
 | 
				
			||||||
 | 
					    def on_tab_dnded(self, notebook, page, x, y):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_tab_reorder(self, child, page_num, new_index):
 | 
					    def on_tab_reorder(self, child, page_num, new_index):
 | 
				
			||||||
        wid, tid = page_num.get_name().split("|")
 | 
					        wid, tid = page_num.get_name().split("|")
 | 
				
			||||||
        window   = self.get_fm_window(wid)
 | 
					        window   = self.get_fm_window(wid)
 | 
				
			||||||
@@ -80,7 +86,8 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        tab = window.get_tab_by_id(tid)
 | 
					        tab = window.get_tab_by_id(tid)
 | 
				
			||||||
        self.set_file_watcher(tab)
 | 
					        self.set_file_watcher(tab)
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_tab_switch_update(self, notebook, content=None, index=None):
 | 
					    def on_tab_switch_update(self, notebook, content=None, index=None):
 | 
				
			||||||
        self.selected_files.clear()
 | 
					        self.selected_files.clear()
 | 
				
			||||||
@@ -115,7 +122,8 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
        tab_label.set_label(tab.get_end_of_path())
 | 
					        tab_label.set_label(tab.get_end_of_path())
 | 
				
			||||||
        self.set_window_title()
 | 
					        self.set_window_title()
 | 
				
			||||||
        self.set_file_watcher(tab)
 | 
					        self.set_file_watcher(tab)
 | 
				
			||||||
        self.fm_controller.save_state()
 | 
					        if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					            self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def do_action_from_bar_controls(self, widget, eve=None):
 | 
					    def do_action_from_bar_controls(self, widget, eve=None):
 | 
				
			||||||
        action    = widget.get_name()
 | 
					        action    = widget.get_name()
 | 
				
			||||||
@@ -127,7 +135,9 @@ class TabMixin(GridMixin):
 | 
				
			|||||||
        if action == "create_tab":
 | 
					        if action == "create_tab":
 | 
				
			||||||
            dir = tab.get_current_directory()
 | 
					            dir = tab.get_current_directory()
 | 
				
			||||||
            self.create_tab(wid, None, dir)
 | 
					            self.create_tab(wid, None, dir)
 | 
				
			||||||
            self.fm_controller.save_state()
 | 
					            if not settings.is_trace_debug():
 | 
				
			||||||
 | 
					                self.fm_controller.save_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        if action == "go_up":
 | 
					        if action == "go_up":
 | 
				
			||||||
            tab.pop_from_path()
 | 
					            tab.pop_from_path()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,12 +6,17 @@ from os.path import isdir
 | 
				
			|||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gdk', '3.0')
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
from gi.repository import Gdk, Gio
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .tab_mixin import TabMixin
 | 
					from .tab_mixin import TabMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WindowException(Exception):
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WindowMixin(TabMixin):
 | 
					class WindowMixin(TabMixin):
 | 
				
			||||||
    """docstring for WindowMixin"""
 | 
					    """docstring for WindowMixin"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,8 +25,8 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
            for j, value in enumerate(session_json):
 | 
					            for j, value in enumerate(session_json):
 | 
				
			||||||
                i = j + 1
 | 
					                i = j + 1
 | 
				
			||||||
                notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}")
 | 
					                notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}")
 | 
				
			||||||
                is_hidden = True if value[0]["window"]["isHidden"] == "True" else False
 | 
					                is_hidden = True if value["window"]["isHidden"] == "True" else False
 | 
				
			||||||
                tabs      = value[0]["window"]["tabs"]
 | 
					                tabs      = value["window"]["tabs"]
 | 
				
			||||||
                self.fm_controller.create_window()
 | 
					                self.fm_controller.create_window()
 | 
				
			||||||
                notebook_tggl_button.set_active(True)
 | 
					                notebook_tggl_button.set_active(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,7 +51,7 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
 | 
					                icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
 | 
				
			||||||
                icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
 | 
					                icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE))
 | 
				
			||||||
            except Exception as e:
 | 
					            except WindowException as e:
 | 
				
			||||||
                print("\n:  The saved session might be missing window data!  :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
 | 
					                print("\n:  The saved session might be missing window data!  :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
 | 
				
			||||||
                print(repr(e))
 | 
					                print(repr(e))
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
@@ -88,12 +93,11 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
        formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
 | 
					        formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
 | 
				
			||||||
        formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
 | 
					        formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE: Hides empty trash and other desired buttons based on context.
 | 
				
			||||||
        if self.trash_files_path == current_directory:
 | 
					        if self.trash_files_path == current_directory:
 | 
				
			||||||
            self.builder.get_object("restore_from_trash").show()
 | 
					            event_system.emit("show_trash_buttons")
 | 
				
			||||||
            self.builder.get_object("empty_trash").show()
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.builder.get_object("restore_from_trash").hide()
 | 
					            event_system.emit("hide_trash_buttons")
 | 
				
			||||||
            self.builder.get_object("empty_trash").hide()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If something selected
 | 
					        # If something selected
 | 
				
			||||||
        self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
 | 
					        self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}")
 | 
				
			||||||
@@ -108,8 +112,8 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
                                                        cancellable=None)
 | 
					                                                        cancellable=None)
 | 
				
			||||||
                    file_size = file_info.get_size()
 | 
					                    file_size = file_info.get_size()
 | 
				
			||||||
                    combined_size += file_size
 | 
					                    combined_size += file_size
 | 
				
			||||||
                except Exception as e:
 | 
					                except WindowException as e:
 | 
				
			||||||
                    if debug:
 | 
					                    if settings.is_debug():
 | 
				
			||||||
                        print(repr(e))
 | 
					                        print(repr(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -156,10 +160,31 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
        path_entry.set_text(tab.get_current_directory())
 | 
					        path_entry.set_text(tab.get_current_directory())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def grid_set_selected_items(self, icons_grid):
 | 
					    def grid_set_selected_items(self, icons_grid):
 | 
				
			||||||
        self.selected_files = icons_grid.get_selected_items()
 | 
					        items = icons_grid.get_selected_items()
 | 
				
			||||||
 | 
					        size  = len(items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def grid_cursor_toggled(self, icons_grid):
 | 
					        if size == 1:
 | 
				
			||||||
        print("wat...")
 | 
					            # NOTE: If already in selection, likely dnd else not so wont readd
 | 
				
			||||||
 | 
					            if items[0] in self.selected_files:
 | 
				
			||||||
 | 
					                self.dnd_left_primed += 1
 | 
				
			||||||
 | 
					                # NOTE: If in selection but trying to just select an already selected item.
 | 
				
			||||||
 | 
					                if self.dnd_left_primed > 1:
 | 
				
			||||||
 | 
					                    self.dnd_left_primed = 0
 | 
				
			||||||
 | 
					                    self.selected_files.clear()
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # NOTE: Likely trying dnd, just readd to selection the former set.
 | 
				
			||||||
 | 
					                #       Prevents losing highlighting of grid selected.
 | 
				
			||||||
 | 
					                for path in self.selected_files:
 | 
				
			||||||
 | 
					                    icons_grid.select_path(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if size > 0:
 | 
				
			||||||
 | 
					            self.selected_files = icons_grid.get_selected_items()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.dnd_left_primed = 0
 | 
				
			||||||
 | 
					            self.selected_files.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def grid_icon_single_click(self, icons_grid, eve):
 | 
					    def grid_icon_single_click(self, icons_grid, eve):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -169,14 +194,16 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
            self.set_path_text(wid, tid)
 | 
					            self.set_path_text(wid, tid)
 | 
				
			||||||
            self.set_window_title()
 | 
					            self.set_window_title()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1:   # l-click
 | 
					            if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1:   # l-click
 | 
				
			||||||
 | 
					                if self.ctrl_down:
 | 
				
			||||||
 | 
					                    self.dnd_left_primed = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if self.single_click_open: # FIXME: need to find a way to pass the model index
 | 
					                if self.single_click_open: # FIXME: need to find a way to pass the model index
 | 
				
			||||||
                    self.grid_icon_double_click(icons_grid)
 | 
					                    self.grid_icon_double_click(icons_grid)
 | 
				
			||||||
            elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
 | 
					            elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
 | 
				
			||||||
                self.show_context_menu()
 | 
					                self.show_context_menu()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except Exception as e:
 | 
					        except WindowException as e:
 | 
				
			||||||
            print(repr(e))
 | 
					            print(repr(e))
 | 
				
			||||||
            self.display_message(self.error_color, f"{repr(e)}")
 | 
					            self.display_message(self.error_color, f"{repr(e)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -205,7 +232,7 @@ class WindowMixin(TabMixin):
 | 
				
			|||||||
                self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid)
 | 
					                self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.open_files()
 | 
					                self.open_files()
 | 
				
			||||||
        except Exception as e:
 | 
					        except WindowException as e:
 | 
				
			||||||
            traceback.print_exc()
 | 
					            traceback.print_exc()
 | 
				
			||||||
            self.display_message(self.error_color, f"{repr(e)}")
 | 
					            self.display_message(self.error_color, f"{repr(e)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
# Python imports
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Gtk imports
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Application imports
 | 
					 | 
				
			||||||
from .show_hide_mixin import ShowHideMixin
 | 
					 | 
				
			||||||
from .ui.widget_file_action_mixin import WidgetFileActionMixin
 | 
					 | 
				
			||||||
from .ui.pane_mixin import PaneMixin
 | 
					 | 
				
			||||||
from .ui.window_mixin import WindowMixin
 | 
					 | 
				
			||||||
from .show_hide_mixin import ShowHideMixin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin):
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
"""
 | 
					 | 
				
			||||||
Signals module
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Gtk imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					from .mixins.show_hide_mixin import ShowHideMixin
 | 
				
			||||||
 | 
					from .mixins.ui.pane_mixin import PaneMixin
 | 
				
			||||||
 | 
					from .mixins.ui.window_mixin import WindowMixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UI(PaneMixin, WindowMixin, ShowHideMixin):
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, json
 | 
					import os
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
from os.path import join
 | 
					from os.path import join
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
@@ -56,7 +57,8 @@ class ManifestProcessor:
 | 
				
			|||||||
            if requests["ui_target"] in  [
 | 
					            if requests["ui_target"] in  [
 | 
				
			||||||
                                            "none", "other", "main_Window", "main_menu_bar",
 | 
					                                            "none", "other", "main_Window", "main_menu_bar",
 | 
				
			||||||
                                            "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
 | 
					                                            "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
 | 
				
			||||||
                                            "context_menu", "window_1", "window_2", "window_3", "window_4"
 | 
					                                            "context_menu", "context_menu_plugins", "window_1",
 | 
				
			||||||
 | 
					                                            "window_2", "window_3", "window_4"
 | 
				
			||||||
                                        ]:
 | 
					                                        ]:
 | 
				
			||||||
                if requests["ui_target"] == "other":
 | 
					                if requests["ui_target"] == "other":
 | 
				
			||||||
                    if "ui_target_id" in keys:
 | 
					                    if "ui_target_id" in keys:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,16 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, time
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PluginBaseException(Exception):
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PluginBase:
 | 
					class PluginBase:
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.name               = "Example Plugin"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
					        self.name               = "Example Plugin"  # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
 | 
				
			||||||
@@ -13,43 +18,48 @@ class PluginBase:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self._builder           = None
 | 
					        self._builder           = None
 | 
				
			||||||
        self._ui_objects        = None
 | 
					        self._ui_objects        = None
 | 
				
			||||||
 | 
					        self._fm_state          = None
 | 
				
			||||||
        self._event_system      = None
 | 
					        self._event_system      = None
 | 
				
			||||||
        self._event_sleep_time  = .5
 | 
					
 | 
				
			||||||
        self._event_message     = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_fm_event_system(self, fm_event_system):
 | 
					    def set_fm_event_system(self, fm_event_system):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					            Requests Key:  'pass_fm_events': "true"
 | 
				
			||||||
 | 
					            Must define in plugin if "pass_fm_events" is set to "true" string.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self._event_system = fm_event_system
 | 
					        self._event_system = fm_event_system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_ui_object_collection(self, ui_objects):
 | 
					    def set_ui_object_collection(self, ui_objects):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					            Requests Key:  "pass_ui_objects": [""]
 | 
				
			||||||
 | 
					            Request reference to a UI component. Will be passed back as array to plugin.
 | 
				
			||||||
 | 
					            Must define in plugin if set and an array of valid glade UI IDs is given.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self._ui_objects = ui_objects
 | 
					        self._ui_objects = ui_objects
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def wait_for_fm_message(self):
 | 
					 | 
				
			||||||
        while not self._event_message:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def clear_children(self, widget: type) -> None:
 | 
					    def clear_children(self, widget: type) -> None:
 | 
				
			||||||
        ''' Clear children of a gtk widget. '''
 | 
					        """ Clear children of a gtk widget. """
 | 
				
			||||||
        for child in widget.get_children():
 | 
					        for child in widget.get_children():
 | 
				
			||||||
            widget.remove(child)
 | 
					            widget.remove(child)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @daemon_threaded
 | 
					    def subscribe_to_events(self):
 | 
				
			||||||
    def _module_event_observer(self):
 | 
					        self._event_system.subscribe("update_state_info_plugins", self._update_fm_state_info)
 | 
				
			||||||
        while True:
 | 
					 | 
				
			||||||
            time.sleep(self._event_sleep_time)
 | 
					 | 
				
			||||||
            event = self._event_system.read_module_event()
 | 
					 | 
				
			||||||
            if event:
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    if event[0] == self.name:
 | 
					 | 
				
			||||||
                        target_id, method_target, data = self._event_system.consume_module_event()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if not method_target:
 | 
					    def _update_fm_state_info(self, state):
 | 
				
			||||||
                            self._event_message = data
 | 
					        self._fm_state = state
 | 
				
			||||||
                        else:
 | 
					
 | 
				
			||||||
                            method = getattr(self.__class__, f"{method_target}")
 | 
					    def generate_reference_ui_element(self):
 | 
				
			||||||
                            if data:
 | 
					        """
 | 
				
			||||||
                                data = method(*(self, *data))
 | 
					            Requests Key:  'ui_target': "plugin_control_list",
 | 
				
			||||||
                            else:
 | 
					            Must define regardless if needed and can 'pass' if plugin doesn't use it.
 | 
				
			||||||
                                method(*(self,))
 | 
					            Must return a widget if "ui_target" is set.
 | 
				
			||||||
                except Exception as e:
 | 
					        """
 | 
				
			||||||
                    print(repr(e))
 | 
					        raise PluginBaseException("Method hasn't been overriden...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					            Must define regardless if needed and can 'pass' if plugin doesn't need it.
 | 
				
			||||||
 | 
					            Is intended to be used to setup internal signals or custom Gtk Builders/UI logic.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise PluginBaseException("Method hasn't been overriden...")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,20 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, sys, importlib, traceback
 | 
					import os
 | 
				
			||||||
from os.path import join, isdir
 | 
					import sys
 | 
				
			||||||
 | 
					import importlib
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					from os.path import join
 | 
				
			||||||
 | 
					from os.path import isdir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
from gi.repository import Gtk, Gio
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .manifest import PluginInfo, ManifestProcessor
 | 
					from .manifest import PluginInfo
 | 
				
			||||||
 | 
					from .manifest import ManifestProcessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,17 +23,16 @@ class InvalidPluginException(Exception):
 | 
				
			|||||||
    ...
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Plugins:
 | 
					class PluginsController:
 | 
				
			||||||
    """Plugins controller"""
 | 
					    """PluginsController controller"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, settings: type):
 | 
					    def __init__(self):
 | 
				
			||||||
        path                      = os.path.dirname(os.path.realpath(__file__))
 | 
					        path                      = os.path.dirname(os.path.realpath(__file__))
 | 
				
			||||||
        sys.path.insert(0, path)  # NOTE: I think I'm not using this correctly...
 | 
					        sys.path.insert(0, path)  # NOTE: I think I'm not using this correctly...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._settings            = settings
 | 
					        self._builder             = settings.get_builder()
 | 
				
			||||||
        self._builder             = self._settings.get_builder()
 | 
					        self._plugins_path        = settings.get_plugins_path()
 | 
				
			||||||
        self._plugins_path        = self._settings.get_plugins_path()
 | 
					        self._keybindings         = settings.get_keybindings()
 | 
				
			||||||
        self._keybindings         = self._settings.get_keybindings()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._plugins_dir_watcher = None
 | 
					        self._plugins_dir_watcher = None
 | 
				
			||||||
        self._plugin_collection   = []
 | 
					        self._plugin_collection   = []
 | 
				
			||||||
@@ -72,20 +77,30 @@ class Plugins:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def load_plugin_module(self, path, folder, target):
 | 
					    def load_plugin_module(self, path, folder, target):
 | 
				
			||||||
        os.chdir(path)
 | 
					        os.chdir(path)
 | 
				
			||||||
        spec   = importlib.util.spec_from_file_location(folder, target, submodule_search_locations=path)
 | 
					
 | 
				
			||||||
 | 
					        locations = []
 | 
				
			||||||
 | 
					        self.collect_search_locations(path, locations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        spec   = importlib.util.spec_from_file_location(folder, target, submodule_search_locations = locations)
 | 
				
			||||||
        module = importlib.util.module_from_spec(spec)
 | 
					        module = importlib.util.module_from_spec(spec)
 | 
				
			||||||
        sys.modules[folder] = module
 | 
					        sys.modules[folder] = module
 | 
				
			||||||
        spec.loader.exec_module(module)
 | 
					        spec.loader.exec_module(module)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return module
 | 
					        return module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def collect_search_locations(self, path, locations):
 | 
				
			||||||
 | 
					        locations.append(path)
 | 
				
			||||||
 | 
					        for file in os.listdir(path):
 | 
				
			||||||
 | 
					            _path = os.path.join(path, file)
 | 
				
			||||||
 | 
					            if os.path.isdir(_path):
 | 
				
			||||||
 | 
					                self.collect_search_locations(_path, locations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
 | 
					    def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
 | 
				
			||||||
        plugin.reference = module.Plugin()
 | 
					        plugin.reference = module.Plugin()
 | 
				
			||||||
        keys             = loading_data.keys()
 | 
					        keys             = loading_data.keys()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "ui_target" in keys:
 | 
					        if "ui_target" in keys:
 | 
				
			||||||
            loading_data["ui_target"].add( plugin.reference.get_ui_element() )
 | 
					            loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() )
 | 
				
			||||||
            loading_data["ui_target"].show_all()
 | 
					            loading_data["ui_target"].show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "pass_ui_objects" in keys:
 | 
					        if "pass_ui_objects" in keys:
 | 
				
			||||||
@@ -93,6 +108,7 @@ class Plugins:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if "pass_fm_events" in keys:
 | 
					        if "pass_fm_events" in keys:
 | 
				
			||||||
            plugin.reference.set_fm_event_system(event_system)
 | 
					            plugin.reference.set_fm_event_system(event_system)
 | 
				
			||||||
 | 
					            plugin.reference.subscribe_to_events()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "bind_keys" in keys:
 | 
					        if "bind_keys" in keys:
 | 
				
			||||||
            self._keybindings.append_bindings( loading_data["bind_keys"] )
 | 
					            self._keybindings.append_bindings( loading_data["bind_keys"] )
 | 
				
			||||||
@@ -16,100 +16,100 @@ def threaded(fn):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class WindowController:
 | 
					class WindowController:
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        USER_HOME               = path.expanduser('~')
 | 
					        USER_HOME: str              = path.expanduser('~')
 | 
				
			||||||
        CONFIG_PATH             = USER_HOME   + "/.config/solarfm"
 | 
					        CONFIG_PATH: str            = f"{USER_HOME}/.config/solarfm"
 | 
				
			||||||
        self._session_file      = CONFIG_PATH + "/session.json"
 | 
					        self._session_file: srr     = f"{CONFIG_PATH}/session.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._event_sleep_time  = 1
 | 
					        self._event_sleep_time: int = 1
 | 
				
			||||||
        self._active_window_id  = ""
 | 
					        self._active_window_id: str = ""
 | 
				
			||||||
        self._active_tab_id     = ""
 | 
					        self._active_tab_id: str    = ""
 | 
				
			||||||
        self._windows           = []
 | 
					        self._windows: list         = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_wid_and_tid(self, wid, tid):
 | 
					    def set_wid_and_tid(self, wid: int, tid: int) -> None:
 | 
				
			||||||
        self._active_window_id = str(wid)
 | 
					        self._active_window_id = str(wid)
 | 
				
			||||||
        self._active_tab_id    = str(tid)
 | 
					        self._active_tab_id    = str(tid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_active_wid_and_tid(self):
 | 
					    def get_active_wid_and_tid(self) -> list:
 | 
				
			||||||
        return self._active_window_id, self._active_tab_id
 | 
					        return self._active_window_id, self._active_tab_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_window(self):
 | 
					    def create_window(self) -> Window:
 | 
				
			||||||
        window = Window()
 | 
					        window = Window()
 | 
				
			||||||
        window.set_nickname(f"window_{str(len(self._windows) + 1)}")
 | 
					        window.set_nickname(f"window_{str(len(self._windows) + 1)}")
 | 
				
			||||||
        self._windows.append(window)
 | 
					        self._windows.append(window)
 | 
				
			||||||
        return window
 | 
					        return window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_tab_for_window(self, win_id):
 | 
					    def add_tab_for_window(self, win_id: str) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                return window.create_tab()
 | 
					                return window.create_tab()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_tab_for_window_by_name(self, name):
 | 
					    def add_tab_for_window_by_name(self, name: str) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_name() == name:
 | 
					            if window.get_name() == name:
 | 
				
			||||||
                return window.create_tab()
 | 
					                return window.create_tab()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def add_tab_for_window_by_nickname(self, nickname):
 | 
					    def add_tab_for_window_by_nickname(self, nickname: str) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_nickname() == nickname:
 | 
					            if window.get_nickname() == nickname:
 | 
				
			||||||
                return window.create_tab()
 | 
					                return window.create_tab()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pop_window(self):
 | 
					    def pop_window(self) -> None:
 | 
				
			||||||
        self._windows.pop()
 | 
					        self._windows.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_window_by_id(self, win_id):
 | 
					    def delete_window_by_id(self, win_id: str) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                self._windows.remove(window)
 | 
					                self._windows.remove(window)
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_window_by_name(self, name):
 | 
					    def delete_window_by_name(self, name: str) -> str:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_name() == name:
 | 
					            if window.get_name() == name:
 | 
				
			||||||
                self._windows.remove(window)
 | 
					                self._windows.remove(window)
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_window_by_nickname(self, nickname):
 | 
					    def delete_window_by_nickname(self, nickname: str) -> str:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_nickname() == nickname:
 | 
					            if window.get_nickname() == nickname:
 | 
				
			||||||
                self._windows.remove(window)
 | 
					                self._windows.remove(window)
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_window_by_id(self, win_id):
 | 
					    def get_window_by_id(self, win_id: str) -> Window:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                return window
 | 
					                return window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise(f"No Window by ID {win_id} found!")
 | 
					        raise(f"No Window by ID {win_id} found!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_window_by_name(self, name):
 | 
					    def get_window_by_name(self, name: str) -> Window:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_name() == name:
 | 
					            if window.get_name() == name:
 | 
				
			||||||
                return window
 | 
					                return window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise(f"No Window by Name {name} found!")
 | 
					        raise(f"No Window by Name {name} found!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_window_by_nickname(self, nickname):
 | 
					    def get_window_by_nickname(self, nickname: str) -> Window:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_nickname() == nickname:
 | 
					            if window.get_nickname() == nickname:
 | 
				
			||||||
                return window
 | 
					                return window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise(f"No Window by Nickname {nickname} found!")
 | 
					        raise(f"No Window by Nickname {nickname} found!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_window_by_index(self, index):
 | 
					    def get_window_by_index(self, index: int) -> Window:
 | 
				
			||||||
        return self._windows[index]
 | 
					        return self._windows[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_all_windows(self):
 | 
					    def get_all_windows(self) -> list:
 | 
				
			||||||
        return self._windows
 | 
					        return self._windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_window_nickname(self, win_id = None, nickname = ""):
 | 
					    def set_window_nickname(self, win_id: str = None, nickname: str = "") -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                window.set_nickname(nickname)
 | 
					                window.set_nickname(nickname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list_windows(self):
 | 
					    def list_windows(self) -> None:
 | 
				
			||||||
        print("\n[  ----  Windows  ----  ]\n")
 | 
					        print("\n[  ----  Windows  ----  ]\n")
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            print(f"\nID: {window.get_id()}")
 | 
					            print(f"\nID: {window.get_id()}")
 | 
				
			||||||
@@ -121,18 +121,18 @@ class WindowController:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list_files_from_tabs_of_window(self, win_id):
 | 
					    def list_files_from_tabs_of_window(self, win_id: str) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                window.list_files_from_tabs()
 | 
					                window.list_files_from_tabs()
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_tabs_count(self, win_id):
 | 
					    def get_tabs_count(self, win_id: str) -> int:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                return window.get_tabs_count()
 | 
					                return window.get_tabs_count()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_tabs_from_window(self, win_id):
 | 
					    def get_tabs_from_window(self, win_id: str) -> list:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            if window.get_id() == win_id:
 | 
					            if window.get_id() == win_id:
 | 
				
			||||||
                return window.get_all_tabs()
 | 
					                return window.get_all_tabs()
 | 
				
			||||||
@@ -140,13 +140,13 @@ class WindowController:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def unload_tabs_and_windows(self):
 | 
					    def unload_tabs_and_windows(self) -> None:
 | 
				
			||||||
        for window in self._windows:
 | 
					        for window in self._windows:
 | 
				
			||||||
            window.get_all_tabs().clear()
 | 
					            window.get_all_tabs().clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._windows.clear()
 | 
					        self._windows.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save_state(self, session_file = None):
 | 
					    def save_state(self, session_file: str = None) -> None:
 | 
				
			||||||
        if not session_file:
 | 
					        if not session_file:
 | 
				
			||||||
            session_file = self._session_file
 | 
					            session_file = self._session_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -158,17 +158,15 @@ class WindowController:
 | 
				
			|||||||
                    tabs.append(tab.get_current_directory())
 | 
					                    tabs.append(tab.get_current_directory())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                windows.append(
 | 
					                windows.append(
 | 
				
			||||||
                    [
 | 
					                    {
 | 
				
			||||||
                        {
 | 
					                        'window':{
 | 
				
			||||||
                            'window':{
 | 
					                            "ID": window.get_id(),
 | 
				
			||||||
                                "ID": window.get_id(),
 | 
					                            "Name": window.get_name(),
 | 
				
			||||||
                                "Name": window.get_name(),
 | 
					                            "Nickname": window.get_nickname(),
 | 
				
			||||||
                                "Nickname": window.get_nickname(),
 | 
					                            "isHidden": f"{window.is_hidden()}",
 | 
				
			||||||
                                "isHidden": f"{window.is_hidden()}",
 | 
					                            'tabs': tabs
 | 
				
			||||||
                                'tabs': tabs
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    ]
 | 
					                    }
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            with open(session_file, 'w') as outfile:
 | 
					            with open(session_file, 'w') as outfile:
 | 
				
			||||||
@@ -176,7 +174,7 @@ class WindowController:
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            raise Exception("Window data corrupted! Can not save session!")
 | 
					            raise Exception("Window data corrupted! Can not save session!")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_state_from_file(self, session_file = None):
 | 
					    def get_state_from_file(self, session_file: str = None) -> dict:
 | 
				
			||||||
        if not session_file:
 | 
					        if not session_file:
 | 
				
			||||||
            session_file = self._session_file
 | 
					            session_file = self._session_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,17 @@
 | 
				
			|||||||
import os, subprocess, threading, hashlib
 | 
					import os, subprocess, threading, hashlib
 | 
				
			||||||
from os.path import isfile
 | 
					from os.path import isfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Gtk imports
 | 
					# Lib imports
 | 
				
			||||||
import gi
 | 
					import gi
 | 
				
			||||||
gi.require_version('GdkPixbuf', '2.0')
 | 
					gi.require_version('GdkPixbuf', '2.0')
 | 
				
			||||||
from gi.repository import GdkPixbuf
 | 
					from gi.repository import GdkPixbuf, GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from PIL import Image as PImage
 | 
				
			||||||
 | 
					except Exception as e:
 | 
				
			||||||
 | 
					    PImage = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .mixins.desktopiconmixin import DesktopIconMixin
 | 
					from .mixins.desktopiconmixin import DesktopIconMixin
 | 
				
			||||||
@@ -35,11 +42,14 @@ class Icon(DesktopIconMixin, VideoIconMixin):
 | 
				
			|||||||
                thumbnl = self.parse_desktop_files(full_path)
 | 
					                thumbnl = self.parse_desktop_files(full_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return thumbnl
 | 
					            return thumbnl
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception:
 | 
				
			||||||
            return None
 | 
					            ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_thumbnail(self, dir, file, scrub_percent = "65%"):
 | 
					    def create_thumbnail(self, dir, file, scrub_percent = "65%"):
 | 
				
			||||||
        full_path = f"{dir}/{file}"
 | 
					        full_path = f"{dir}/{file}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest()
 | 
					            file_hash    = hashlib.sha256(str.encode(full_path)).hexdigest()
 | 
				
			||||||
            hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
					            hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
 | 
				
			||||||
@@ -54,24 +64,41 @@ class Icon(DesktopIconMixin, VideoIconMixin):
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            print("Thumbnail generation issue:")
 | 
					            print("Thumbnail generation issue:")
 | 
				
			||||||
            print( repr(e) )
 | 
					            print( repr(e) )
 | 
				
			||||||
            return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
 | 
					
 | 
				
			||||||
 | 
					        return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_scaled_image(self, path, wxh = None):
 | 
					    def create_scaled_image(self, path, wxh = None):
 | 
				
			||||||
        if not wxh:
 | 
					        if not wxh:
 | 
				
			||||||
            wxh = self.video_icon_wh
 | 
					            wxh = self.video_icon_wh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        if path:
 | 
				
			||||||
            if path.lower().endswith(".gif"):
 | 
					            try:
 | 
				
			||||||
                return  GdkPixbuf.PixbufAnimation.new_from_file(path) \
 | 
					                if path.lower().endswith(".gif"):
 | 
				
			||||||
                                                    .get_static_image() \
 | 
					                    return  GdkPixbuf.PixbufAnimation.new_from_file(path) \
 | 
				
			||||||
                                                    .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
 | 
					                                                        .get_static_image() \
 | 
				
			||||||
            else:
 | 
					                                                        .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
 | 
				
			||||||
 | 
					                elif path.lower().endswith(".webp") and PImage:
 | 
				
			||||||
 | 
					                    return self.image2pixbuf(path, wxh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
 | 
					                return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
 | 
				
			||||||
        except Exception as e:
 | 
					            except Exception as e:
 | 
				
			||||||
            print("Image Scaling Issue:")
 | 
					                print("Image Scaling Issue:")
 | 
				
			||||||
            print( repr(e) )
 | 
					                print( repr(e) )
 | 
				
			||||||
            return None
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def image2pixbuf(self, path, wxh):
 | 
				
			||||||
 | 
					        """Convert Pillow image to GdkPixbuf"""
 | 
				
			||||||
 | 
					        im   = PImage.open(path)
 | 
				
			||||||
 | 
					        data = im.tobytes()
 | 
				
			||||||
 | 
					        data = GLib.Bytes.new(data)
 | 
				
			||||||
 | 
					        w, h = im.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
 | 
				
			||||||
 | 
					                                                            False, 8, w, h, w * 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_from_file(self, path):
 | 
					    def create_from_file(self, path):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -79,7 +106,8 @@ class Icon(DesktopIconMixin, VideoIconMixin):
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            print("Image from file Issue:")
 | 
					            print("Image from file Issue:")
 | 
				
			||||||
            print( repr(e) )
 | 
					            print( repr(e) )
 | 
				
			||||||
            return None
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def return_generic_icon(self):
 | 
					    def return_generic_icon(self):
 | 
				
			||||||
        return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
 | 
					        return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,11 @@ import os, subprocess, hashlib
 | 
				
			|||||||
from os.path import isfile
 | 
					from os.path import isfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Gtk imports
 | 
					# Gtk imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .xdg.DesktopEntry import DesktopEntry
 | 
					from .xdg.DesktopEntry import DesktopEntry
 | 
				
			||||||
@@ -36,8 +41,13 @@ class DesktopIconMixin:
 | 
				
			|||||||
            elif os.path.exists(icon):
 | 
					            elif os.path.exists(icon):
 | 
				
			||||||
                return self.create_scaled_image(icon, self.sys_icon_wh)
 | 
					                return self.create_scaled_image(icon, self.sys_icon_wh)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                alt_icon_path = ""
 | 
					                gio_icon = Gio.Icon.new_for_string(icon)
 | 
				
			||||||
 | 
					                gicon    = Gtk.Image.new_from_gicon(gio_icon, 32)
 | 
				
			||||||
 | 
					                pixbuf   = gicon.get_pixbuf()
 | 
				
			||||||
 | 
					                if pixbuf:
 | 
				
			||||||
 | 
					                    return pixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                alt_icon_path = ""
 | 
				
			||||||
                for dir in self.ICON_DIRS:
 | 
					                for dir in self.ICON_DIRS:
 | 
				
			||||||
                    alt_icon_path = self.traverse_icons_folder(dir, icon)
 | 
					                    alt_icon_path = self.traverse_icons_folder(dir, icon)
 | 
				
			||||||
                    if alt_icon_path != "":
 | 
					                    if alt_icon_path != "":
 | 
				
			||||||
@@ -50,13 +60,8 @@ class DesktopIconMixin:
 | 
				
			|||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def traverse_icons_folder(self, path, icon):
 | 
					    def traverse_icons_folder(self, path, icon):
 | 
				
			||||||
        alt_icon_path = ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (dirpath, dirnames, filenames) in os.walk(path):
 | 
					        for (dirpath, dirnames, filenames) in os.walk(path):
 | 
				
			||||||
            for file in filenames:
 | 
					            for file in filenames:
 | 
				
			||||||
                appNM = "application-x-" + icon
 | 
					                appNM = "application-x-" + icon
 | 
				
			||||||
                if icon in file or appNM in file:
 | 
					                if icon in file or appNM in file:
 | 
				
			||||||
                    alt_icon_path = dirpath + "/" + file
 | 
					                    return f"{dirpath}/{file}"
 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return alt_icon_path
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -322,10 +322,8 @@ def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg",
 | 
				
			|||||||
                    icon_cache[tmp] = [time.time(), icon]
 | 
					                    icon_cache[tmp] = [time.time(), icon]
 | 
				
			||||||
                    return icon
 | 
					                    return icon
 | 
				
			||||||
            except UnicodeDecodeError as e:
 | 
					            except UnicodeDecodeError as e:
 | 
				
			||||||
                if debug:
 | 
					                ...
 | 
				
			||||||
                    raise e
 | 
					
 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    pass
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # we haven't found anything? "hicolor" is our fallback
 | 
					    # we haven't found anything? "hicolor" is our fallback
 | 
				
			||||||
    if theme != "hicolor":
 | 
					    if theme != "hicolor":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,20 +7,20 @@ import os
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Path:
 | 
					class Path:
 | 
				
			||||||
    def get_home(self):
 | 
					    def get_home(self) -> str:
 | 
				
			||||||
        return os.path.expanduser("~") + self.subpath
 | 
					        return os.path.expanduser("~") + self.subpath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_path(self):
 | 
					    def get_path(self) -> str:
 | 
				
			||||||
        return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
 | 
					        return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_path_list(self):
 | 
					    def get_path_list(self) -> list:
 | 
				
			||||||
        return self.path
 | 
					        return self.path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def push_to_path(self, dir):
 | 
					    def push_to_path(self, dir: str):
 | 
				
			||||||
        self.path.append(dir)
 | 
					        self.path.append(dir)
 | 
				
			||||||
        self.load_directory()
 | 
					        self.load_directory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pop_from_path(self):
 | 
					    def pop_from_path(self) -> None:
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.path.pop()
 | 
					            self.path.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,9 +32,9 @@ class Path:
 | 
				
			|||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_path(self, path):
 | 
					    def set_path(self, path: str) -> bool:
 | 
				
			||||||
        if path == self.get_path():
 | 
					        if path == self.get_path():
 | 
				
			||||||
            return
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if os.path.isdir(path):
 | 
					        if os.path.isdir(path):
 | 
				
			||||||
            self.path = list( filter(None, path.replace("\\", "/").split('/')) )
 | 
					            self.path = list( filter(None, path.replace("\\", "/").split('/')) )
 | 
				
			||||||
@@ -43,7 +43,7 @@ class Path:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_path_with_sub_path(self, sub_path):
 | 
					    def set_path_with_sub_path(self, sub_path: str) -> bool:
 | 
				
			||||||
        path = os.path.join(self.get_home(), sub_path)
 | 
					        path = os.path.join(self.get_home(), sub_path)
 | 
				
			||||||
        if path == self.get_path():
 | 
					        if path == self.get_path():
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
@@ -55,7 +55,7 @@ class Path:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_to_home(self):
 | 
					    def set_to_home(self) -> None:
 | 
				
			||||||
        home = os.path.expanduser("~") + self.subpath
 | 
					        home = os.path.expanduser("~") + self.subpath
 | 
				
			||||||
        path = list( filter(None, home.replace("\\", "/").split('/')) )
 | 
					        path = list( filter(None, home.replace("\\", "/").split('/')) )
 | 
				
			||||||
        self.path = path
 | 
					        self.path = path
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,25 +20,25 @@ from .path import Path
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
					class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.logger      = None
 | 
					        self.logger             = None
 | 
				
			||||||
        self._id_length   = 10
 | 
					        self._id_length: int    = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._id          = ""
 | 
					        self._id: str           = ""
 | 
				
			||||||
        self._wid         = None
 | 
					        self._wid: str          = None
 | 
				
			||||||
        self._dir_watcher = None
 | 
					        self._dir_watcher       = None
 | 
				
			||||||
        self._hide_hidden = self.HIDE_HIDDEN_FILES
 | 
					        self._hide_hidden: bool = self.HIDE_HIDDEN_FILES
 | 
				
			||||||
        self._files       = []
 | 
					        self._files: list       = []
 | 
				
			||||||
        self._dirs        = []
 | 
					        self._dirs: list        = []
 | 
				
			||||||
        self._vids        = []
 | 
					        self._vids: list        = []
 | 
				
			||||||
        self._images      = []
 | 
					        self._images: list      = []
 | 
				
			||||||
        self._desktop     = []
 | 
					        self._desktop: list     = []
 | 
				
			||||||
        self._ungrouped   = []
 | 
					        self._ungrouped: list   = []
 | 
				
			||||||
        self._hidden      = []
 | 
					        self._hidden: list      = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._generate_id()
 | 
					        self._generate_id()
 | 
				
			||||||
        self.set_to_home()
 | 
					        self.set_to_home()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_directory(self):
 | 
					    def load_directory(self) -> None:
 | 
				
			||||||
        path            = self.get_path()
 | 
					        path            = self.get_path()
 | 
				
			||||||
        self._dirs      = []
 | 
					        self._dirs      = []
 | 
				
			||||||
        self._vids      = []
 | 
					        self._vids      = []
 | 
				
			||||||
@@ -97,7 +97,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_not_hidden_count(self):
 | 
					    def get_not_hidden_count(self) -> int:
 | 
				
			||||||
        return len(self._files)    + \
 | 
					        return len(self._files)    + \
 | 
				
			||||||
                len(self._dirs)    + \
 | 
					                len(self._dirs)    + \
 | 
				
			||||||
                len(self._vids)    + \
 | 
					                len(self._vids)    + \
 | 
				
			||||||
@@ -105,13 +105,13 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
                len(self._desktop) + \
 | 
					                len(self._desktop) + \
 | 
				
			||||||
                len(self._ungrouped)
 | 
					                len(self._ungrouped)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_hidden_count(self):
 | 
					    def get_hidden_count(self) -> int:
 | 
				
			||||||
        return len(self._hidden)
 | 
					        return len(self._hidden)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_files_count(self):
 | 
					    def get_files_count(self) -> int:
 | 
				
			||||||
        return len(self._files)
 | 
					        return len(self._files)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_path_part_from_hash(self, hash):
 | 
					    def get_path_part_from_hash(self, hash: str) -> str:
 | 
				
			||||||
        files = self.get_files()
 | 
					        files = self.get_files()
 | 
				
			||||||
        file  = None
 | 
					        file  = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +122,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return file
 | 
					        return file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_files_formatted(self):
 | 
					    def get_files_formatted(self) -> dict:
 | 
				
			||||||
        files     = self._hash_set(self._files),
 | 
					        files     = self._hash_set(self._files),
 | 
				
			||||||
        dirs      = self._hash_set(self._dirs),
 | 
					        dirs      = self._hash_set(self._dirs),
 | 
				
			||||||
        videos    = self.get_videos(),
 | 
					        videos    = self.get_videos(),
 | 
				
			||||||
@@ -154,7 +154,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_gtk_icon_str_combo(self):
 | 
					    def get_gtk_icon_str_combo(self) -> list:
 | 
				
			||||||
        data = []
 | 
					        data = []
 | 
				
			||||||
        dir  = self.get_current_directory()
 | 
					        dir  = self.get_current_directory()
 | 
				
			||||||
        for file in self._files:
 | 
					        for file in self._files:
 | 
				
			||||||
@@ -163,57 +163,57 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_current_directory(self):
 | 
					    def get_current_directory(self) -> str:
 | 
				
			||||||
        return self.get_path()
 | 
					        return self.get_path()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_current_sub_path(self):
 | 
					    def get_current_sub_path(self) -> str:
 | 
				
			||||||
        path = self.get_path()
 | 
					        path = self.get_path()
 | 
				
			||||||
        home = f"{self.get_home()}/"
 | 
					        home = f"{self.get_home()}/"
 | 
				
			||||||
        return path.replace(home, "")
 | 
					        return path.replace(home, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_end_of_path(self):
 | 
					    def get_end_of_path(self) -> str:
 | 
				
			||||||
        parts = self.get_current_directory().split("/")
 | 
					        parts = self.get_current_directory().split("/")
 | 
				
			||||||
        size  = len(parts)
 | 
					        size  = len(parts)
 | 
				
			||||||
        return parts[size - 1]
 | 
					        return parts[size - 1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_hiding_hidden(self, state):
 | 
					    def set_hiding_hidden(self, state: bool) -> None:
 | 
				
			||||||
        self._hide_hidden = state
 | 
					        self._hide_hidden = state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_hiding_hidden(self):
 | 
					    def is_hiding_hidden(self) -> bool:
 | 
				
			||||||
        return self._hide_hidden
 | 
					        return self._hide_hidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_dot_dots(self):
 | 
					    def get_dot_dots(self) -> list:
 | 
				
			||||||
        return self._hash_set(['.', '..'])
 | 
					        return self._hash_set(['.', '..'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_files(self):
 | 
					    def get_files(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._files)
 | 
					        return self._hash_set(self._files)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_dirs(self):
 | 
					    def get_dirs(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._dirs)
 | 
					        return self._hash_set(self._dirs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_videos(self):
 | 
					    def get_videos(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._vids)
 | 
					        return self._hash_set(self._vids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_images(self):
 | 
					    def get_images(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._images)
 | 
					        return self._hash_set(self._images)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_desktops(self):
 | 
					    def get_desktops(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._desktop)
 | 
					        return self._hash_set(self._desktop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_ungrouped(self):
 | 
					    def get_ungrouped(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._ungrouped)
 | 
					        return self._hash_set(self._ungrouped)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_hidden(self):
 | 
					    def get_hidden(self) -> list:
 | 
				
			||||||
        return self._hash_set(self._hidden)
 | 
					        return self._hash_set(self._hidden)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_id(self):
 | 
					    def get_id(self) -> str:
 | 
				
			||||||
        return self._id
 | 
					        return self._id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_wid(self, _wid):
 | 
					    def set_wid(self, _wid: str) -> None:
 | 
				
			||||||
        self._wid = _wid
 | 
					        self._wid = _wid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_wid(self):
 | 
					    def get_wid(self) -> str:
 | 
				
			||||||
        return self._wid
 | 
					        return self._wid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_dir_watcher(self, watcher):
 | 
					    def set_dir_watcher(self, watcher):
 | 
				
			||||||
@@ -228,19 +228,19 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
 | 
				
			|||||||
    def _natural_keys(self, text):
 | 
					    def _natural_keys(self, text):
 | 
				
			||||||
        return [ self._atoi(c) for c in re.split('(\d+)',text) ]
 | 
					        return [ self._atoi(c) for c in re.split('(\d+)',text) ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _hash_text(self, text):
 | 
					    def _hash_text(self, text) -> str:
 | 
				
			||||||
        return hashlib.sha256(str.encode(text)).hexdigest()[:18]
 | 
					        return hashlib.sha256(str.encode(text)).hexdigest()[:18]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _hash_set(self, arry):
 | 
					    def _hash_set(self, arry: list) -> list:
 | 
				
			||||||
        data = []
 | 
					        data = []
 | 
				
			||||||
        for arr in arry:
 | 
					        for arr in arry:
 | 
				
			||||||
            data.append([arr, self._hash_text(arr)])
 | 
					            data.append([arr, self._hash_text(arr)])
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _random_with_N_digits(self, n):
 | 
					    def _random_with_N_digits(self, n: int) -> int:
 | 
				
			||||||
        range_start = 10**(n-1)
 | 
					        range_start = 10**(n-1)
 | 
				
			||||||
        range_end = (10**n)-1
 | 
					        range_end = (10**n)-1
 | 
				
			||||||
        return randint(range_start, range_end)
 | 
					        return randint(range_start, range_end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _generate_id(self):
 | 
					    def _generate_id(self) -> str:
 | 
				
			||||||
        self._id = str(self._random_with_N_digits(self._id_length))
 | 
					        self._id = str(self._random_with_N_digits(self._id_length))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
# System import
 | 
					# System import
 | 
				
			||||||
import os, threading, subprocess, shlex
 | 
					import os, threading, subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ class Settings:
 | 
				
			|||||||
    FFMPG_THUMBNLR    = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
 | 
					    FFMPG_THUMBNLR    = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
 | 
				
			||||||
    REMUX_FOLDER      = f"{USER_HOME}/.remuxs"             # Remuxed files folder
 | 
					    REMUX_FOLDER      = f"{USER_HOME}/.remuxs"             # Remuxed files folder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ICON_DIRS         = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,]
 | 
					    ICON_DIRS         = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
 | 
				
			||||||
    BASE_THUMBS_PTH   = f"{USER_HOME}/.thumbnails"         # Used for thumbnail generation
 | 
					    BASE_THUMBS_PTH   = f"{USER_HOME}/.thumbnails"         # Used for thumbnail generation
 | 
				
			||||||
    ABS_THUMBS_PTH    = f"{BASE_THUMBS_PTH}/normal"        # Used for thumbnail generation
 | 
					    ABS_THUMBS_PTH    = f"{BASE_THUMBS_PTH}/normal"        # Used for thumbnail generation
 | 
				
			||||||
    STEAM_ICONS_PTH   = f"{BASE_THUMBS_PTH}/steam_icons"
 | 
					    STEAM_ICONS_PTH   = f"{BASE_THUMBS_PTH}/steam_icons"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,62 +11,68 @@ from .tabs.tab import Tab
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class Window:
 | 
					class Window:
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self._id_length = 10
 | 
					        self._id_length: int  = 10
 | 
				
			||||||
        self._id        = ""
 | 
					        self._id: str         = ""
 | 
				
			||||||
        self._name      = ""
 | 
					        self._name: str       = ""
 | 
				
			||||||
        self._nickname  = ""
 | 
					        self._nickname:str    = ""
 | 
				
			||||||
        self._isHidden  = False
 | 
					        self._isHidden: bool  = False
 | 
				
			||||||
        self._tabs      = []
 | 
					        self._active_tab: int = 0
 | 
				
			||||||
 | 
					        self._tabs: list      = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._generate_id()
 | 
					        self._generate_id()
 | 
				
			||||||
        self._set_name()
 | 
					        self._set_name()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_tab(self):
 | 
					    def create_tab(self) -> Tab:
 | 
				
			||||||
        tab = Tab()
 | 
					        tab = Tab()
 | 
				
			||||||
        self._tabs.append(tab)
 | 
					        self._tabs.append(tab)
 | 
				
			||||||
        return tab
 | 
					        return tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def pop_tab(self):
 | 
					    def pop_tab(self) -> None:
 | 
				
			||||||
        self._tabs.pop()
 | 
					        self._tabs.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_tab_by_id(self, tid):
 | 
					    def delete_tab_by_id(self, tid: str):
 | 
				
			||||||
        for tab in self._tabs:
 | 
					        for tab in self._tabs:
 | 
				
			||||||
            if tab.get_id() == tid:
 | 
					            if tab.get_id() == tid:
 | 
				
			||||||
                self._tabs.remove(tab)
 | 
					                self._tabs.remove(tab)
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_tab_by_id(self, tid):
 | 
					    def get_tab_by_id(self, tid: str) -> Tab:
 | 
				
			||||||
        for tab in self._tabs:
 | 
					        for tab in self._tabs:
 | 
				
			||||||
            if tab.get_id() == tid:
 | 
					            if tab.get_id() == tid:
 | 
				
			||||||
                return tab
 | 
					                return tab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_tab_by_index(self, index):
 | 
					    def get_tab_by_index(self, index) -> Tab:
 | 
				
			||||||
        return self._tabs[index]
 | 
					        return self._tabs[index]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_tabs_count(self):
 | 
					    def get_tabs_count(self) -> int:
 | 
				
			||||||
        return len(self._tabs)
 | 
					        return len(self._tabs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_all_tabs(self):
 | 
					    def get_all_tabs(self) -> list:
 | 
				
			||||||
        return self._tabs
 | 
					        return self._tabs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_id(self):
 | 
					    def get_id(self) -> str:
 | 
				
			||||||
        return self._id
 | 
					        return self._id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_name(self):
 | 
					    def get_name(self) -> str:
 | 
				
			||||||
        return self._name
 | 
					        return self._name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_nickname(self):
 | 
					    def get_nickname(self) -> str:
 | 
				
			||||||
        return self._nickname
 | 
					        return self._nickname
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_hidden(self):
 | 
					    def is_hidden(self) -> bool:
 | 
				
			||||||
        return self._isHidden
 | 
					        return self._isHidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list_files_from_tabs(self):
 | 
					    def list_files_from_tabs(self) -> None:
 | 
				
			||||||
        for tab in self._tabs:
 | 
					        for tab in self._tabs:
 | 
				
			||||||
            print(tab.get_files())
 | 
					            print(tab.get_files())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_active_tab(self, index: int):
 | 
				
			||||||
 | 
					        self._active_tab = index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_active_tab(self) -> Tab:
 | 
				
			||||||
 | 
					        return self._tabs[self._active_tab]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_nickname(self, nickname):
 | 
					    def set_nickname(self, nickname):
 | 
				
			||||||
        self._nickname = f"{nickname}"
 | 
					        self._nickname = f"{nickname}"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
"""
 | 
					 | 
				
			||||||
Trasher module
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EndpointRegistry():
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self._endpoints = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def register(self, rule, **options):
 | 
				
			||||||
 | 
					        def decorator(f):
 | 
				
			||||||
 | 
					            self._endpoints[rule] = f
 | 
				
			||||||
 | 
					            return f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_endpoints(self):
 | 
				
			||||||
 | 
					        return self._endpoints
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
 | 
					from collections import defaultdict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,57 +8,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EventSystemPushException(Exception):
 | 
					 | 
				
			||||||
    ...
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class EventSystem:
 | 
					class EventSystem:
 | 
				
			||||||
    """ Inheret IPCServerMixin. Create an pub/sub systems. """
 | 
					    """ Create event system. """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        # NOTE: The format used is list of ['who', target, (data,)] Where:
 | 
					        self.subscribers = defaultdict(list)
 | 
				
			||||||
        #             who is the sender or target ID and is used for context and control flow,
 | 
					 | 
				
			||||||
        #             method_target is the method to call,
 | 
					 | 
				
			||||||
        #             data is the method parameters OR message data to give
 | 
					 | 
				
			||||||
        #       Where data may be any kind of data
 | 
					 | 
				
			||||||
        self._gui_events    = []
 | 
					 | 
				
			||||||
        self._module_events = []
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Makeshift "events" system FIFO
 | 
					    def subscribe(self, event_type, fn):
 | 
				
			||||||
    def _pop_gui_event(self) -> None:
 | 
					        self.subscribers[event_type].append(fn)
 | 
				
			||||||
        if len(self._gui_events) > 0:
 | 
					 | 
				
			||||||
            return self._gui_events.pop(0)
 | 
					 | 
				
			||||||
        return None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _pop_module_event(self) -> None:
 | 
					    def emit(self, event_type, data = None):
 | 
				
			||||||
        if len(self._module_events) > 0:
 | 
					        if event_type in self.subscribers:
 | 
				
			||||||
            return self._module_events.pop(0)
 | 
					            for fn in self.subscribers[event_type]:
 | 
				
			||||||
        return None
 | 
					                if data:
 | 
				
			||||||
 | 
					                    if hasattr(data, '__iter__') and not type(data) is str:
 | 
				
			||||||
 | 
					                        fn(*data)
 | 
				
			||||||
    def push_gui_event(self, event: list) -> None:
 | 
					                    else:
 | 
				
			||||||
        if len(event) == 3:
 | 
					                        fn(data)
 | 
				
			||||||
            self._gui_events.append(event)
 | 
					                else:
 | 
				
			||||||
            return None
 | 
					                    fn()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        raise EventSystemPushException("Invald event format! Please do:  ['sender_id': str, method_target: method, (data,): any]")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def push_module_event(self, event: list) -> None:
 | 
					 | 
				
			||||||
        if len(event) == 3:
 | 
					 | 
				
			||||||
            self._module_events.append(event)
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        raise EventSystemPushException("Invald event format! Please do:  ['target_id': str, method_target: method, (data,): any]")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def read_gui_event(self) -> list:
 | 
					 | 
				
			||||||
        return self._gui_events[0] if self._gui_events else None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def read_module_event(self) -> list:
 | 
					 | 
				
			||||||
        return self._module_events[0] if self._module_events else None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def consume_gui_event(self) -> list:
 | 
					 | 
				
			||||||
        return self._pop_gui_event()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def consume_module_event(self) -> list:
 | 
					 | 
				
			||||||
        return self._pop_module_event()
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, threading, time
 | 
					import os, threading, time
 | 
				
			||||||
from multiprocessing.connection import Listener, Client
 | 
					from multiprocessing.connection import Client
 | 
				
			||||||
 | 
					from multiprocessing.connection import Listener
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib imports
 | 
					# Lib imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,12 +30,16 @@ class IPCServer:
 | 
				
			|||||||
        elif conn_type == "local_network_unsecured":
 | 
					        elif conn_type == "local_network_unsecured":
 | 
				
			||||||
            self._ipc_authkey = None
 | 
					            self._ipc_authkey = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._subscribe_to_events()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _subscribe_to_events(self):
 | 
				
			||||||
 | 
					        event_system.subscribe("post_file_to_ipc", self.send_ipc_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @daemon_threaded
 | 
					 | 
				
			||||||
    def create_ipc_listener(self) -> None:
 | 
					    def create_ipc_listener(self) -> None:
 | 
				
			||||||
        if self._conn_type == "socket":
 | 
					        if self._conn_type == "socket":
 | 
				
			||||||
            if os.path.exists(self._ipc_address):
 | 
					            if os.path.exists(self._ipc_address) and settings.is_dirty_start():
 | 
				
			||||||
                return
 | 
					                os.unlink(self._ipc_address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
 | 
					            listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
 | 
				
			||||||
        elif "unsecured" not in self._conn_type:
 | 
					        elif "unsecured" not in self._conn_type:
 | 
				
			||||||
@@ -44,23 +49,27 @@ class IPCServer:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.is_ipc_alive = True
 | 
					        self.is_ipc_alive = True
 | 
				
			||||||
 | 
					        self._run_ipc_loop(listener)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @daemon_threaded
 | 
				
			||||||
 | 
					    def _run_ipc_loop(self, listener) -> None:
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            conn       = listener.accept()
 | 
					            conn       = listener.accept()
 | 
				
			||||||
            start_time = time.perf_counter()
 | 
					            start_time = time.perf_counter()
 | 
				
			||||||
            self.handle_message(conn, start_time)
 | 
					            self._handle_ipc_message(conn, start_time)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        listener.close()
 | 
					        listener.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_message(self, conn, start_time) -> None:
 | 
					    def _handle_ipc_message(self, conn, start_time) -> None:
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            msg = conn.recv()
 | 
					            msg = conn.recv()
 | 
				
			||||||
            if debug:
 | 
					            if settings.is_debug():
 | 
				
			||||||
                print(msg)
 | 
					                print(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if "FILE|" in msg:
 | 
					            if "FILE|" in msg:
 | 
				
			||||||
                file = msg.split("FILE|")[1].strip()
 | 
					                file = msg.split("FILE|")[1].strip()
 | 
				
			||||||
                if file:
 | 
					                if file:
 | 
				
			||||||
                    event_system.push_gui_event([None, "handle_file_from_ipc", (file,)])
 | 
					                    event_system.emit("handle_file_from_ipc", file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                conn.close()
 | 
					                conn.close()
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, logging
 | 
					import os
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,15 @@
 | 
				
			|||||||
# Python imports
 | 
					# Python imports
 | 
				
			||||||
import os, json
 | 
					import os
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
from os import path
 | 
					from os import path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Gtk imports
 | 
					# Gtk imports
 | 
				
			||||||
import gi, cairo
 | 
					import gi, cairo
 | 
				
			||||||
gi.require_version('Gtk', '3.0')
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
gi.require_version('Gdk', '3.0')
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
 | 
					 | 
				
			||||||
from gi.repository import Gtk
 | 
					from gi.repository import Gtk
 | 
				
			||||||
from gi.repository import Gdk
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Application imports
 | 
					# Application imports
 | 
				
			||||||
from .logger import Logger
 | 
					from .logger import Logger
 | 
				
			||||||
from .keybindings import Keybindings
 | 
					from .keybindings import Keybindings
 | 
				
			||||||
@@ -31,6 +30,8 @@ class Settings:
 | 
				
			|||||||
        self._KEY_BINDINGS  = f"{self._CONFIG_PATH}/key-bindings.json"
 | 
					        self._KEY_BINDINGS  = f"{self._CONFIG_PATH}/key-bindings.json"
 | 
				
			||||||
        self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
 | 
					        self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
 | 
				
			||||||
        self._WINDOW_ICON   = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
 | 
					        self._WINDOW_ICON   = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
 | 
				
			||||||
 | 
					        self._CONTEXT_MENU  = f"{self._CONFIG_PATH}/contexct_menu.json"
 | 
				
			||||||
 | 
					        self._PID_FILE      = f"{self._CONFIG_PATH}/{app_name.lower()}.pid"
 | 
				
			||||||
        self._ICON_THEME    = Gtk.IconTheme.get_default()
 | 
					        self._ICON_THEME    = Gtk.IconTheme.get_default()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(self._CONFIG_PATH):
 | 
					        if not os.path.exists(self._CONFIG_PATH):
 | 
				
			||||||
@@ -40,6 +41,8 @@ class Settings:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if not os.path.exists(self._GLADE_FILE):
 | 
					        if not os.path.exists(self._GLADE_FILE):
 | 
				
			||||||
            self._GLADE_FILE    = f"{self._USR_SOLARFM}/Main_Window.glade"
 | 
					            self._GLADE_FILE    = f"{self._USR_SOLARFM}/Main_Window.glade"
 | 
				
			||||||
 | 
					        if not os.path.exists(self._CONTEXT_MENU):
 | 
				
			||||||
 | 
					            self._CONTEXT_MENU    = f"{self._USR_SOLARFM}/contexct_menu.json"
 | 
				
			||||||
        if not os.path.exists(self._KEY_BINDINGS):
 | 
					        if not os.path.exists(self._KEY_BINDINGS):
 | 
				
			||||||
            self._KEY_BINDINGS  = f"{self._USR_SOLARFM}/key-bindings.json"
 | 
					            self._KEY_BINDINGS  = f"{self._USR_SOLARFM}/key-bindings.json"
 | 
				
			||||||
        if not os.path.exists(self._CSS_FILE):
 | 
					        if not os.path.exists(self._CSS_FILE):
 | 
				
			||||||
@@ -58,11 +61,54 @@ class Settings:
 | 
				
			|||||||
            keybindings = json.load(file)["keybindings"]
 | 
					            keybindings = json.load(file)["keybindings"]
 | 
				
			||||||
            self._keybindings.configure(keybindings)
 | 
					            self._keybindings.configure(keybindings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with open(self._CONTEXT_MENU) as file:
 | 
				
			||||||
 | 
					            self._context_menu_data = json.load(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self._main_window    = None
 | 
					        self._main_window    = None
 | 
				
			||||||
        self._logger         = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger()
 | 
					        self._logger         = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger()
 | 
				
			||||||
        self._builder        = Gtk.Builder()
 | 
					        self._builder        = Gtk.Builder()
 | 
				
			||||||
        self._builder.add_from_file(self._GLADE_FILE)
 | 
					        self._builder.add_from_file(self._GLADE_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._trace_debug   = False
 | 
				
			||||||
 | 
					        self._debug         = False
 | 
				
			||||||
 | 
					        self._dirty_start   = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_dirty_start_check(self):
 | 
				
			||||||
 | 
					        if not os.path.exists(self._PID_FILE):
 | 
				
			||||||
 | 
					            self._write_new_pid()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            with open(self._PID_FILE, "r") as _pid:
 | 
				
			||||||
 | 
					                pid = _pid.readline().strip()
 | 
				
			||||||
 | 
					                if pid not in ("", None):
 | 
				
			||||||
 | 
					                    self._check_alive_status(int(pid))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self._write_new_pid()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """ Check For the existence of a unix pid. """
 | 
				
			||||||
 | 
					    def _check_alive_status(self, pid):
 | 
				
			||||||
 | 
					        print(f"PID Found: {pid}")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            os.kill(pid, 0)
 | 
				
			||||||
 | 
					        except OSError:
 | 
				
			||||||
 | 
					            print(f"{app_name} is starting dirty...")
 | 
				
			||||||
 | 
					            self._dirty_start = True
 | 
				
			||||||
 | 
					            self._write_new_pid()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _write_new_pid(self):
 | 
				
			||||||
 | 
					        pid = os.getpid()
 | 
				
			||||||
 | 
					        self._write_pid(pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _clean_pid(self):
 | 
				
			||||||
 | 
					        os.unlink(self._PID_FILE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _write_pid(self, pid):
 | 
				
			||||||
 | 
					        with open(self._PID_FILE, "w") as _pid:
 | 
				
			||||||
 | 
					            _pid.write(f"{pid}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_window(self) -> None:
 | 
					    def create_window(self) -> None:
 | 
				
			||||||
        # Get window and connect signals
 | 
					        # Get window and connect signals
 | 
				
			||||||
@@ -101,6 +147,8 @@ class Settings:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return monitors
 | 
					        return monitors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_context_menu_data(self) -> Gtk.Builder:  return self._context_menu_data
 | 
				
			||||||
    def get_main_window(self)   -> Gtk.ApplicationWindow: return self._main_window
 | 
					    def get_main_window(self)   -> Gtk.ApplicationWindow: return self._main_window
 | 
				
			||||||
    def get_builder(self)       -> Gtk.Builder:  return self._builder
 | 
					    def get_builder(self)       -> Gtk.Builder:  return self._builder
 | 
				
			||||||
    def get_logger(self)        -> Logger:       return self._logger
 | 
					    def get_logger(self)        -> Logger:       return self._logger
 | 
				
			||||||
@@ -111,3 +159,15 @@ class Settings:
 | 
				
			|||||||
    def get_success_color(self) -> str: return self._success_color
 | 
					    def get_success_color(self) -> str: return self._success_color
 | 
				
			||||||
    def get_warning_color(self) -> str: return self._warning_color
 | 
					    def get_warning_color(self) -> str: return self._warning_color
 | 
				
			||||||
    def get_error_color(self)   -> str: return self._error_color
 | 
					    def get_error_color(self)   -> str: return self._error_color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_trace_debug(self)    -> str: return self._trace_debug
 | 
				
			||||||
 | 
					    def is_debug(self)          -> str: return self._debug
 | 
				
			||||||
 | 
					    def is_dirty_start(self)    -> bool: return self._dirty_start
 | 
				
			||||||
 | 
					    def clear_pid(self): self._clean_pid()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_trace_debug(self, trace_debug):
 | 
				
			||||||
 | 
					        self._trace_debug = trace_debug
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_debug(self, debug):
 | 
				
			||||||
 | 
					        self._debug = debug
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    Widgets module
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ContextMenuWidget(Gtk.Menu):
 | 
				
			||||||
 | 
					    """docstring for ContextMenuWidget"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super(ContextMenuWidget, self).__init__()
 | 
				
			||||||
 | 
					        self._builder           = settings.get_builder()
 | 
				
			||||||
 | 
					        self._context_menu_data = settings.get_context_menu_data()
 | 
				
			||||||
 | 
					        self._window            = settings.get_main_window()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_submenu(self, name, data, keys):
 | 
				
			||||||
 | 
					        menu      = Gtk.Menu()
 | 
				
			||||||
 | 
					        menu_item = Gtk.MenuItem(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in keys:
 | 
				
			||||||
 | 
					            if isinstance(data, dict):
 | 
				
			||||||
 | 
					                entry = self.make_menu_item(key, data[key])
 | 
				
			||||||
 | 
					            elif isinstance(data, list):
 | 
				
			||||||
 | 
					                entry = self.make_menu_item(key, data)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            menu.append(entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        menu_item.set_submenu(menu)
 | 
				
			||||||
 | 
					        return menu_item
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def make_menu_item(self, name, data) -> Gtk.MenuItem:
 | 
				
			||||||
 | 
					        if isinstance(data, dict):
 | 
				
			||||||
 | 
					            return self.make_submenu(name, data, data.keys())
 | 
				
			||||||
 | 
					        elif isinstance(data, list):
 | 
				
			||||||
 | 
					            entry = Gtk.ImageMenuItem(name)
 | 
				
			||||||
 | 
					            icon  = getattr(Gtk, f"{data[0]}")
 | 
				
			||||||
 | 
					            entry.set_image( Gtk.Image(stock=icon) )
 | 
				
			||||||
 | 
					            entry.set_always_show_image(True)
 | 
				
			||||||
 | 
					            entry.connect("activate", self._emit, (data[1]))
 | 
				
			||||||
 | 
					            return entry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def build_context_menu(self) -> None:
 | 
				
			||||||
 | 
					        data          = self._context_menu_data
 | 
				
			||||||
 | 
					        dkeys         = data.keys()
 | 
				
			||||||
 | 
					        plugins_entry = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for dkey in dkeys:
 | 
				
			||||||
 | 
					            entry = self.make_menu_item(dkey, data[dkey])
 | 
				
			||||||
 | 
					            self.append(entry)
 | 
				
			||||||
 | 
					            if dkey == "Plugins":
 | 
				
			||||||
 | 
					                plugins_entry = entry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.attach_to_widget(self._window, None)
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					        self._builder.expose_object("context_menu", self)
 | 
				
			||||||
 | 
					        if plugins_entry:
 | 
				
			||||||
 | 
					            self._builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _emit(self, menu_item, type):
 | 
				
			||||||
 | 
					        event_system.emit("do_action_from_menu_controls", type)
 | 
				
			||||||
@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					from gi.repository import GdkPixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IconGridWidget(Gtk.IconView):
 | 
				
			||||||
 | 
					    """docstring for IconGridWidget"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super(IconGridWidget, self).__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._store = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._setup_styling()
 | 
				
			||||||
 | 
					        self._setup_signals()
 | 
				
			||||||
 | 
					        self._set_up_dnd()
 | 
				
			||||||
 | 
					        self._load_widgets()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_store(self):
 | 
				
			||||||
 | 
					        return self._store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_styling(self):
 | 
				
			||||||
 | 
					        self.set_pixbuf_column(0)
 | 
				
			||||||
 | 
					        self.set_text_column(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.set_item_orientation(1)
 | 
				
			||||||
 | 
					        self.set_selection_mode(3)
 | 
				
			||||||
 | 
					        self.set_item_width(96)
 | 
				
			||||||
 | 
					        self.set_item_padding(8)
 | 
				
			||||||
 | 
					        self.set_margin(12)
 | 
				
			||||||
 | 
					        self.set_row_spacing(18)
 | 
				
			||||||
 | 
					        self.set_columns(-1)
 | 
				
			||||||
 | 
					        self.set_spacing(12)
 | 
				
			||||||
 | 
					        self.set_column_spacing(18)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_signals(self):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_additional_signals(self, grid_icon_single_click,
 | 
				
			||||||
 | 
					                                        grid_icon_double_click,
 | 
				
			||||||
 | 
					                                        grid_set_selected_items,
 | 
				
			||||||
 | 
					                                        grid_on_drag_set,
 | 
				
			||||||
 | 
					                                        grid_on_drag_data_received,
 | 
				
			||||||
 | 
					                                        grid_on_drag_motion):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.connect("button_release_event", grid_icon_single_click)
 | 
				
			||||||
 | 
					        self.connect("item-activated",       grid_icon_double_click)
 | 
				
			||||||
 | 
					        self.connect("selection-changed",    grid_set_selected_items)
 | 
				
			||||||
 | 
					        self.connect("drag-data-get",        grid_on_drag_set)
 | 
				
			||||||
 | 
					        self.connect("drag-data-received",   grid_on_drag_data_received)
 | 
				
			||||||
 | 
					        self.connect("drag-motion",          grid_on_drag_motion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_widgets(self):
 | 
				
			||||||
 | 
					        self._store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
 | 
				
			||||||
 | 
					        self.set_model(self._store)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_up_dnd(self):
 | 
				
			||||||
 | 
					        URI_TARGET_TYPE  = 80
 | 
				
			||||||
 | 
					        uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
 | 
				
			||||||
 | 
					        targets          = [ uri_target ]
 | 
				
			||||||
 | 
					        action           = Gdk.DragAction.COPY
 | 
				
			||||||
 | 
					        self.enable_model_drag_dest(targets, action)
 | 
				
			||||||
 | 
					        self.enable_model_drag_source(0, targets, action)
 | 
				
			||||||
@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					gi.require_version('Gdk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gdk
 | 
				
			||||||
 | 
					from gi.repository import GdkPixbuf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IconTreeWidget(Gtk.TreeView):
 | 
				
			||||||
 | 
					    """docstring for IconTreeWidget"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super(IconTreeWidget, self).__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._store = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._setup_styling()
 | 
				
			||||||
 | 
					        self._setup_signals()
 | 
				
			||||||
 | 
					        self._set_up_dnd()
 | 
				
			||||||
 | 
					        self._load_widgets()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_store(self):
 | 
				
			||||||
 | 
					        return self._store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_styling(self):
 | 
				
			||||||
 | 
					        self.set_search_column(1)
 | 
				
			||||||
 | 
					        self.set_rubber_banding(True)
 | 
				
			||||||
 | 
					        self.set_headers_visible(False)
 | 
				
			||||||
 | 
					        self.set_enable_tree_lines(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_signals(self):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_additional_signals(self, grid_icon_single_click,
 | 
				
			||||||
 | 
					                                        grid_icon_double_click,
 | 
				
			||||||
 | 
					                                        grid_on_drag_set,
 | 
				
			||||||
 | 
					                                        grid_on_drag_data_received,
 | 
				
			||||||
 | 
					                                        grid_on_drag_motion):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.connect("button_release_event", self.grid_icon_single_click)
 | 
				
			||||||
 | 
					        self.connect("row-activated",        self.grid_icon_double_click)
 | 
				
			||||||
 | 
					        self.connect("drag-data-get",        self.grid_on_drag_set)
 | 
				
			||||||
 | 
					        self.connect("drag-data-received",   self.grid_on_drag_data_received)
 | 
				
			||||||
 | 
					        self.connect("drag-motion",          self.grid_on_drag_motion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_widgets(self):
 | 
				
			||||||
 | 
					        self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
 | 
				
			||||||
 | 
					        column = Gtk.TreeViewColumn("Icons")
 | 
				
			||||||
 | 
					        icon   = Gtk.CellRendererPixbuf()
 | 
				
			||||||
 | 
					        name   = Gtk.CellRendererText()
 | 
				
			||||||
 | 
					        selec  = self.get_selection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.set_model(store)
 | 
				
			||||||
 | 
					        selec.set_mode(3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        column.pack_start(icon, False)
 | 
				
			||||||
 | 
					        column.pack_start(name, True)
 | 
				
			||||||
 | 
					        column.add_attribute(icon, "pixbuf", 0)
 | 
				
			||||||
 | 
					        column.add_attribute(name, "text", 1)
 | 
				
			||||||
 | 
					        column.set_expand(False)
 | 
				
			||||||
 | 
					        column.set_sizing(2)
 | 
				
			||||||
 | 
					        column.set_min_width(120)
 | 
				
			||||||
 | 
					        column.set_max_width(74)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.append_column(column)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_up_dnd(self):
 | 
				
			||||||
 | 
					        URI_TARGET_TYPE  = 80
 | 
				
			||||||
 | 
					        uri_target       = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
 | 
				
			||||||
 | 
					        targets          = [ uri_target ]
 | 
				
			||||||
 | 
					        action           = Gdk.DragAction.COPY
 | 
				
			||||||
 | 
					        self.enable_model_drag_dest(targets, action)
 | 
				
			||||||
 | 
					        self.enable_model_drag_source(0, targets, action)
 | 
				
			||||||
@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class IOWidget(Gtk.Box):
 | 
				
			||||||
 | 
					    """docstring for IOWidget"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, action, file):
 | 
				
			||||||
 | 
					        super(IOWidget, self).__init__()
 | 
				
			||||||
 | 
					        self._action    = action
 | 
				
			||||||
 | 
					        self._file      = file
 | 
				
			||||||
 | 
					        self._basename  = self._file.get_basename()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cancle_eve = Gio.Cancellable.new()
 | 
				
			||||||
 | 
					        self.progress   = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._setup_styling()
 | 
				
			||||||
 | 
					        self._setup_signals()
 | 
				
			||||||
 | 
					        self._load_widgets()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_styling(self):
 | 
				
			||||||
 | 
					        self.set_orientation(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_signals(self):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_widgets(self):
 | 
				
			||||||
 | 
					        stats         = Gtk.Box()
 | 
				
			||||||
 | 
					        label         = Gtk.Label()
 | 
				
			||||||
 | 
					        cncl_button   = Gtk.Button(label="Cancel")
 | 
				
			||||||
 | 
					        del_button    = Gtk.Button(label="Clear")
 | 
				
			||||||
 | 
					        self.progress = Gtk.ProgressBar()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label.set_label(self._basename)
 | 
				
			||||||
 | 
					        self.progress.set_show_text(True)
 | 
				
			||||||
 | 
					        self.progress.set_text(f"{self._action.upper()}ING")
 | 
				
			||||||
 | 
					        stats.set_orientation(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stats.pack_end(del_button, False, False, 5)
 | 
				
			||||||
 | 
					        del_button.connect("clicked", self.delete_self, ())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self._action in ("create", "rename"):
 | 
				
			||||||
 | 
					            stats.pack_end(cncl_button, False, False, 5)
 | 
				
			||||||
 | 
					            cncl_button.connect("clicked", self.do_cancel, *(self, self.cancle_eve))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stats.add(self.progress)
 | 
				
			||||||
 | 
					        self.add(label)
 | 
				
			||||||
 | 
					        self.add(stats)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_cancel(self, widget, container, eve):
 | 
				
			||||||
 | 
					        print(f"Canceling: [{self._action}] of {self._basename} ...")
 | 
				
			||||||
 | 
					        eve.cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_progress(self, current, total, eve=None):
 | 
				
			||||||
 | 
					        self.progress.set_fraction(current/total)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def finish_callback(self, file, task=None, eve=None):
 | 
				
			||||||
 | 
					        if self._action == "move" or self._action == "rename":
 | 
				
			||||||
 | 
					            status = self._file.move_finish(task)
 | 
				
			||||||
 | 
					        if self._action == "copy":
 | 
				
			||||||
 | 
					            status = self._file.copy_finish(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if status:
 | 
				
			||||||
 | 
					            self.delete_self()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            print(f"{self._action} of {self._basename} failed...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_self(self, widget=None, eve=None):
 | 
				
			||||||
 | 
					        self.get_parent().remove(self)
 | 
				
			||||||
@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					# Python imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lib imports
 | 
				
			||||||
 | 
					import gi
 | 
				
			||||||
 | 
					gi.require_version('Gtk', '3.0')
 | 
				
			||||||
 | 
					from gi.repository import Gtk
 | 
				
			||||||
 | 
					from gi.repository import Gio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application imports
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TabHeaderWidget(Gtk.ButtonBox):
 | 
				
			||||||
 | 
					    """docstring for TabHeaderWidget"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, tab, close_tab):
 | 
				
			||||||
 | 
					        super(TabHeaderWidget, self).__init__()
 | 
				
			||||||
 | 
					        self._tab       = tab
 | 
				
			||||||
 | 
					        self._close_tab = close_tab # NOTE: Close method in tab_mixin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._setup_styling()
 | 
				
			||||||
 | 
					        self._setup_signals()
 | 
				
			||||||
 | 
					        self._load_widgets()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_styling(self):
 | 
				
			||||||
 | 
					        self.set_orientation(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _setup_signals(self):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_widgets(self):
 | 
				
			||||||
 | 
					        label = Gtk.Label()
 | 
				
			||||||
 | 
					        tid   = Gtk.Label()
 | 
				
			||||||
 | 
					        close = Gtk.Button()
 | 
				
			||||||
 | 
					        icon  = Gtk.Image(stock=Gtk.STOCK_CLOSE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label.set_label(f"{self._tab.get_end_of_path()}")
 | 
				
			||||||
 | 
					        label.set_width_chars(len(self._tab.get_end_of_path()))
 | 
				
			||||||
 | 
					        label.set_xalign(0.0)
 | 
				
			||||||
 | 
					        tid.set_label(f"{self._tab.get_id()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        close.connect("released", self._close_tab)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        close.add(icon)
 | 
				
			||||||
 | 
					        self.add(label)
 | 
				
			||||||
 | 
					        self.add(close)
 | 
				
			||||||
 | 
					        self.add(tid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.show_all()
 | 
				
			||||||
 | 
					        tid.hide()
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<!-- Generated with glade 3.38.2 -->
 | 
					<!-- Generated with glade 3.40.0 -->
 | 
				
			||||||
<interface>
 | 
					<interface>
 | 
				
			||||||
  <requires lib="gtk+" version="3.22"/>
 | 
					  <requires lib="gtk+" version="3.22"/>
 | 
				
			||||||
  <object class="GtkAboutDialog" id="about_page">
 | 
					  <object class="GtkAboutDialog" id="about_page">
 | 
				
			||||||
@@ -452,180 +452,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
      <action-widget response="-5">appchooser_select_btn</action-widget>
 | 
					      <action-widget response="-5">appchooser_select_btn</action-widget>
 | 
				
			||||||
    </action-widgets>
 | 
					    </action-widgets>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkTextBuffer" id="arc_command_buffer">
 | 
					 | 
				
			||||||
    <property name="text" translatable="yes">$(which 7za || echo 7zr) a %o %N</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkFileChooserDialog" id="archiver_dialogue">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="modal">True</property>
 | 
					 | 
				
			||||||
    <property name="window-position">center</property>
 | 
					 | 
				
			||||||
    <property name="type-hint">dialog</property>
 | 
					 | 
				
			||||||
    <property name="gravity">center</property>
 | 
					 | 
				
			||||||
    <property name="do-overwrite-confirmation">True</property>
 | 
					 | 
				
			||||||
    <property name="select-multiple">True</property>
 | 
					 | 
				
			||||||
    <child internal-child="vbox">
 | 
					 | 
				
			||||||
      <object class="GtkBox">
 | 
					 | 
				
			||||||
        <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
        <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
        <property name="spacing">2</property>
 | 
					 | 
				
			||||||
        <child internal-child="action_area">
 | 
					 | 
				
			||||||
          <object class="GtkButtonBox">
 | 
					 | 
				
			||||||
            <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
            <property name="layout-style">end</property>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkButton" id="button21">
 | 
					 | 
				
			||||||
                <property name="label">gtk-cancel</property>
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">True</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">0</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkButton" id="button22">
 | 
					 | 
				
			||||||
                <property name="label">gtk-ok</property>
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">True</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">1</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
          </object>
 | 
					 | 
				
			||||||
          <packing>
 | 
					 | 
				
			||||||
            <property name="expand">False</property>
 | 
					 | 
				
			||||||
            <property name="fill">False</property>
 | 
					 | 
				
			||||||
            <property name="position">0</property>
 | 
					 | 
				
			||||||
          </packing>
 | 
					 | 
				
			||||||
        </child>
 | 
					 | 
				
			||||||
        <child>
 | 
					 | 
				
			||||||
          <object class="GtkBox">
 | 
					 | 
				
			||||||
            <property name="visible">True</property>
 | 
					 | 
				
			||||||
            <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
            <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkBox">
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                <property name="homogeneous">True</property>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkLabel">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="label" translatable="yes">Compress Commands:</property>
 | 
					 | 
				
			||||||
                    <property name="xalign">0.20000000298023224</property>
 | 
					 | 
				
			||||||
                    <attributes>
 | 
					 | 
				
			||||||
                      <attribute name="gravity" value="west"/>
 | 
					 | 
				
			||||||
                    </attributes>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                  <packing>
 | 
					 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					 | 
				
			||||||
                    <property name="fill">True</property>
 | 
					 | 
				
			||||||
                    <property name="position">0</property>
 | 
					 | 
				
			||||||
                  </packing>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <placeholder/>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkLabel">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="label" translatable="yes">Archive Format:</property>
 | 
					 | 
				
			||||||
                    <property name="xalign">1</property>
 | 
					 | 
				
			||||||
                    <attributes>
 | 
					 | 
				
			||||||
                      <attribute name="gravity" value="east"/>
 | 
					 | 
				
			||||||
                    </attributes>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                  <packing>
 | 
					 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					 | 
				
			||||||
                    <property name="fill">True</property>
 | 
					 | 
				
			||||||
                    <property name="position">2</property>
 | 
					 | 
				
			||||||
                  </packing>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkComboBoxText">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="active">0</property>
 | 
					 | 
				
			||||||
                    <property name="active-id">0</property>
 | 
					 | 
				
			||||||
                    <items>
 | 
					 | 
				
			||||||
                      <item id="0" translatable="yes">7Zip (*.7z)</item>
 | 
					 | 
				
			||||||
                      <item id="1" translatable="yes">Zip (*.zip *.ZIP)</item>
 | 
					 | 
				
			||||||
                      <item id="2" translatable="yes">RAR (*.rar *.RAR)</item>
 | 
					 | 
				
			||||||
                      <item id="3" translatable="yes">Tar (*.tar)</item>
 | 
					 | 
				
			||||||
                      <item id="4" translatable="yes">Tar bzip2 (*.tar.bz2)</item>
 | 
					 | 
				
			||||||
                      <item id="5" translatable="yes">Tar Gzip (*.tar.gz *.tgz)</item>
 | 
					 | 
				
			||||||
                      <item id="6" translatable="yes">Tar xz (*.tar.xz *.txz)</item>
 | 
					 | 
				
			||||||
                      <item id="7" translatable="yes">Gzip (*.gz)</item>
 | 
					 | 
				
			||||||
                      <item id="8" translatable="yes">XZ (*.xz)</item>
 | 
					 | 
				
			||||||
                    </items>
 | 
					 | 
				
			||||||
                    <signal name="changed" handler="set_arc_buffer_text" swapped="no"/>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                  <packing>
 | 
					 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					 | 
				
			||||||
                    <property name="fill">True</property>
 | 
					 | 
				
			||||||
                    <property name="position">3</property>
 | 
					 | 
				
			||||||
                  </packing>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">False</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">0</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkTextView" id="arc_command">
 | 
					 | 
				
			||||||
                <property name="height-request">72</property>
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="buffer">arc_command_buffer</property>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">True</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">1</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
          </object>
 | 
					 | 
				
			||||||
          <packing>
 | 
					 | 
				
			||||||
            <property name="expand">False</property>
 | 
					 | 
				
			||||||
            <property name="fill">True</property>
 | 
					 | 
				
			||||||
            <property name="position">2</property>
 | 
					 | 
				
			||||||
          </packing>
 | 
					 | 
				
			||||||
        </child>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
    <action-widgets>
 | 
					 | 
				
			||||||
      <action-widget response="-6">button21</action-widget>
 | 
					 | 
				
			||||||
      <action-widget response="-5">button22</action-widget>
 | 
					 | 
				
			||||||
    </action-widgets>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkImage" id="archive_img">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="stock">gtk-save-as</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkImage" id="create_img">
 | 
					  <object class="GtkImage" id="create_img">
 | 
				
			||||||
    <property name="visible">True</property>
 | 
					    <property name="visible">True</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="stock">gtk-new</property>
 | 
					    <property name="stock">gtk-new</property>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkImage" id="exec_in_term_img">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="stock">gtk-execute</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkImage" id="image1">
 | 
					  <object class="GtkImage" id="image1">
 | 
				
			||||||
    <property name="visible">True</property>
 | 
					    <property name="visible">True</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
@@ -651,23 +482,18 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="stock">gtk-execute</property>
 | 
					    <property name="stock">gtk-execute</property>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkTextBuffer" id="message_buffer"/>
 | 
					  <object class="GtkImage" id="io_img">
 | 
				
			||||||
  <object class="GtkImage" id="open_with_img">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					    <property name="visible">True</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="stock">gtk-open</property>
 | 
					    <property name="stock">gtk-stop</property>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
 | 
					  <object class="GtkTextBuffer" id="message_buffer"/>
 | 
				
			||||||
  <object class="GtkImage" id="rename_img">
 | 
					  <object class="GtkImage" id="rename_img">
 | 
				
			||||||
    <property name="visible">True</property>
 | 
					    <property name="visible">True</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="stock">gtk-edit</property>
 | 
					    <property name="stock">gtk-edit</property>
 | 
				
			||||||
    <property name="icon_size">3</property>
 | 
					    <property name="icon_size">3</property>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkImage" id="rename_img2">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="stock">gtk-edit</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkFileChooserDialog" id="save_load_dialog">
 | 
					  <object class="GtkFileChooserDialog" id="save_load_dialog">
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="type-hint">dialog</property>
 | 
					    <property name="type-hint">dialog</property>
 | 
				
			||||||
@@ -961,17 +787,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                            <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					                            <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
				
			||||||
                          </object>
 | 
					                          </object>
 | 
				
			||||||
                        </child>
 | 
					                        </child>
 | 
				
			||||||
                        <child>
 | 
					 | 
				
			||||||
                          <object class="GtkImageMenuItem">
 | 
					 | 
				
			||||||
                            <property name="label">gtk-delete</property>
 | 
					 | 
				
			||||||
                            <property name="name">delete</property>
 | 
					 | 
				
			||||||
                            <property name="visible">True</property>
 | 
					 | 
				
			||||||
                            <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                            <property name="use-underline">True</property>
 | 
					 | 
				
			||||||
                            <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                            <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                          </object>
 | 
					 | 
				
			||||||
                        </child>
 | 
					 | 
				
			||||||
                      </object>
 | 
					                      </object>
 | 
				
			||||||
                    </child>
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
@@ -1014,7 +829,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                <property name="spacing">5</property>
 | 
					                <property name="spacing">5</property>
 | 
				
			||||||
                <property name="layout-style">start</property>
 | 
					                <property name="layout-style">start</property>
 | 
				
			||||||
                <child>
 | 
					                <child>
 | 
				
			||||||
                  <object class="GtkButton" id="plugins_buttoin">
 | 
					                  <object class="GtkButton" id="plugins_button">
 | 
				
			||||||
                    <property name="label" translatable="yes">Plugins</property>
 | 
					                    <property name="label" translatable="yes">Plugins</property>
 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
                    <property name="can-focus">True</property>
 | 
					                    <property name="can-focus">True</property>
 | 
				
			||||||
@@ -1091,6 +906,22 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <property name="position">4</property>
 | 
					                    <property name="position">4</property>
 | 
				
			||||||
                  </packing>
 | 
					                  </packing>
 | 
				
			||||||
                </child>
 | 
					                </child>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkButton" id="io_button">
 | 
				
			||||||
 | 
					                    <property name="label" translatable="yes">I/O</property>
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                    <property name="receives-default">True</property>
 | 
				
			||||||
 | 
					                    <property name="image">io_img</property>
 | 
				
			||||||
 | 
					                    <property name="always-show-image">True</property>
 | 
				
			||||||
 | 
					                    <signal name="released" handler="show_io_popup" swapped="no"/>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                  <packing>
 | 
				
			||||||
 | 
					                    <property name="expand">True</property>
 | 
				
			||||||
 | 
					                    <property name="fill">True</property>
 | 
				
			||||||
 | 
					                    <property name="position">5</property>
 | 
				
			||||||
 | 
					                  </packing>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
              </object>
 | 
					              </object>
 | 
				
			||||||
              <packing>
 | 
					              <packing>
 | 
				
			||||||
                <property name="expand">False</property>
 | 
					                <property name="expand">False</property>
 | 
				
			||||||
@@ -1243,6 +1074,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <property name="margin-bottom">5</property>
 | 
					                    <property name="margin-bottom">5</property>
 | 
				
			||||||
                    <property name="show-border">False</property>
 | 
					                    <property name="show-border">False</property>
 | 
				
			||||||
                    <property name="scrollable">True</property>
 | 
					                    <property name="scrollable">True</property>
 | 
				
			||||||
 | 
					                    <property name="group-name">sfm_windows</property>
 | 
				
			||||||
 | 
					                    <signal name="create-window" handler="on_tab_dnded" swapped="no"/>
 | 
				
			||||||
                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
					                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
				
			||||||
                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
					                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
				
			||||||
                    <child>
 | 
					                    <child>
 | 
				
			||||||
@@ -1263,6 +1096,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <child type="tab">
 | 
					                    <child type="tab">
 | 
				
			||||||
                      <placeholder/>
 | 
					                      <placeholder/>
 | 
				
			||||||
                    </child>
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child type="action-end">
 | 
				
			||||||
 | 
					                      <object class="GtkSearchEntry" id="win1_search_field">
 | 
				
			||||||
 | 
					                        <property name="name">window_1</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Search...</property>
 | 
				
			||||||
 | 
					                        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="tab-fill">False</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="resize">False</property>
 | 
					                    <property name="resize">False</property>
 | 
				
			||||||
@@ -1281,6 +1129,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <property name="margin-bottom">5</property>
 | 
					                    <property name="margin-bottom">5</property>
 | 
				
			||||||
                    <property name="show-border">False</property>
 | 
					                    <property name="show-border">False</property>
 | 
				
			||||||
                    <property name="scrollable">True</property>
 | 
					                    <property name="scrollable">True</property>
 | 
				
			||||||
 | 
					                    <property name="group-name">sfm_windows</property>
 | 
				
			||||||
 | 
					                    <signal name="create-window" handler="on_tab_dnded" swapped="no"/>
 | 
				
			||||||
                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
					                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
				
			||||||
                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
					                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
				
			||||||
                    <child>
 | 
					                    <child>
 | 
				
			||||||
@@ -1301,6 +1151,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <child type="tab">
 | 
					                    <child type="tab">
 | 
				
			||||||
                      <placeholder/>
 | 
					                      <placeholder/>
 | 
				
			||||||
                    </child>
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child type="action-end">
 | 
				
			||||||
 | 
					                      <object class="GtkSearchEntry" id="win2_search_field">
 | 
				
			||||||
 | 
					                        <property name="name">window_2</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Search...</property>
 | 
				
			||||||
 | 
					                        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="tab-fill">False</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="resize">False</property>
 | 
					                    <property name="resize">False</property>
 | 
				
			||||||
@@ -1333,6 +1198,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <property name="margin-bottom">5</property>
 | 
					                    <property name="margin-bottom">5</property>
 | 
				
			||||||
                    <property name="show-border">False</property>
 | 
					                    <property name="show-border">False</property>
 | 
				
			||||||
                    <property name="scrollable">True</property>
 | 
					                    <property name="scrollable">True</property>
 | 
				
			||||||
 | 
					                    <property name="group-name">sfm_windows</property>
 | 
				
			||||||
 | 
					                    <signal name="create-window" handler="on_tab_dnded" swapped="no"/>
 | 
				
			||||||
                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
					                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
				
			||||||
                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
					                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
				
			||||||
                    <child>
 | 
					                    <child>
 | 
				
			||||||
@@ -1353,6 +1220,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <child type="tab">
 | 
					                    <child type="tab">
 | 
				
			||||||
                      <placeholder/>
 | 
					                      <placeholder/>
 | 
				
			||||||
                    </child>
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child type="action-end">
 | 
				
			||||||
 | 
					                      <object class="GtkSearchEntry" id="win3_search_field">
 | 
				
			||||||
 | 
					                        <property name="name">window_3</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Search...</property>
 | 
				
			||||||
 | 
					                        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="tab-fill">False</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="resize">False</property>
 | 
					                    <property name="resize">False</property>
 | 
				
			||||||
@@ -1370,6 +1252,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <property name="margin-bottom">5</property>
 | 
					                    <property name="margin-bottom">5</property>
 | 
				
			||||||
                    <property name="show-border">False</property>
 | 
					                    <property name="show-border">False</property>
 | 
				
			||||||
                    <property name="scrollable">True</property>
 | 
					                    <property name="scrollable">True</property>
 | 
				
			||||||
 | 
					                    <property name="group-name">sfm_windows</property>
 | 
				
			||||||
 | 
					                    <signal name="create-window" handler="on_tab_dnded" swapped="no"/>
 | 
				
			||||||
                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
					                    <signal name="page-reordered" handler="on_tab_reorder" swapped="no"/>
 | 
				
			||||||
                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
					                    <signal name="switch-page" handler="on_tab_switch_update" swapped="no"/>
 | 
				
			||||||
                    <child>
 | 
					                    <child>
 | 
				
			||||||
@@ -1390,6 +1274,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
                    <child type="tab">
 | 
					                    <child type="tab">
 | 
				
			||||||
                      <placeholder/>
 | 
					                      <placeholder/>
 | 
				
			||||||
                    </child>
 | 
					                    </child>
 | 
				
			||||||
 | 
					                    <child type="action-end">
 | 
				
			||||||
 | 
					                      <object class="GtkSearchEntry" id="win4_search_field">
 | 
				
			||||||
 | 
					                        <property name="name">window_4</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can-focus">True</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-name">edit-find-symbolic</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-activatable">False</property>
 | 
				
			||||||
 | 
					                        <property name="primary-icon-sensitive">False</property>
 | 
				
			||||||
 | 
					                        <property name="placeholder-text" translatable="yes">Search...</property>
 | 
				
			||||||
 | 
					                        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                      <packing>
 | 
				
			||||||
 | 
					                        <property name="tab-fill">False</property>
 | 
				
			||||||
 | 
					                      </packing>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="resize">False</property>
 | 
					                    <property name="resize">False</property>
 | 
				
			||||||
@@ -1954,6 +1853,23 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
      <class name="alert-border"/>
 | 
					      <class name="alert-border"/>
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
 | 
					  <object class="GtkPopover" id="io_popup">
 | 
				
			||||||
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					    <property name="relative-to">io_button</property>
 | 
				
			||||||
 | 
					    <property name="position">bottom</property>
 | 
				
			||||||
 | 
					    <child>
 | 
				
			||||||
 | 
					      <object class="GtkBox" id="io_list">
 | 
				
			||||||
 | 
					        <property name="width-request">320</property>
 | 
				
			||||||
 | 
					        <property name="height-request">320</property>
 | 
				
			||||||
 | 
					        <property name="visible">True</property>
 | 
				
			||||||
 | 
					        <property name="can-focus">False</property>
 | 
				
			||||||
 | 
					        <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					        <child>
 | 
				
			||||||
 | 
					          <placeholder/>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					      </object>
 | 
				
			||||||
 | 
					    </child>
 | 
				
			||||||
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkPopover" id="message_popup_widget">
 | 
					  <object class="GtkPopover" id="message_popup_widget">
 | 
				
			||||||
    <property name="width-request">320</property>
 | 
					    <property name="width-request">320</property>
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
@@ -2221,7 +2137,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkPopover" id="plugin_controls">
 | 
					  <object class="GtkPopover" id="plugin_controls">
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="relative-to">plugins_buttoin</property>
 | 
					    <property name="relative-to">plugins_button</property>
 | 
				
			||||||
    <signal name="button-release-event" handler="hide_plugins_popup" swapped="no"/>
 | 
					    <signal name="button-release-event" handler="hide_plugins_popup" swapped="no"/>
 | 
				
			||||||
    <signal name="key-release-event" handler="hide_plugins_popup" swapped="no"/>
 | 
					    <signal name="key-release-event" handler="hide_plugins_popup" swapped="no"/>
 | 
				
			||||||
    <child>
 | 
					    <child>
 | 
				
			||||||
@@ -2235,503 +2151,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
 | 
				
			|||||||
      </object>
 | 
					      </object>
 | 
				
			||||||
    </child>
 | 
					    </child>
 | 
				
			||||||
  </object>
 | 
					  </object>
 | 
				
			||||||
  <object class="GtkPopover" id="win1_search">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="margin-start">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-end">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
    <property name="relative-to">window_1</property>
 | 
					 | 
				
			||||||
    <property name="position">bottom</property>
 | 
					 | 
				
			||||||
    <property name="constrain-to">none</property>
 | 
					 | 
				
			||||||
    <signal name="closed" handler="stop_file_searching" swapped="no"/>
 | 
					 | 
				
			||||||
    <child>
 | 
					 | 
				
			||||||
      <object class="GtkEntry" id="win1_search_field">
 | 
					 | 
				
			||||||
        <property name="width-request">52</property>
 | 
					 | 
				
			||||||
        <property name="visible">True</property>
 | 
					 | 
				
			||||||
        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
      <class name="search-border"/>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkPopover" id="win2_search">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="margin-start">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-end">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
    <property name="relative-to">window_2</property>
 | 
					 | 
				
			||||||
    <property name="position">bottom</property>
 | 
					 | 
				
			||||||
    <property name="constrain-to">none</property>
 | 
					 | 
				
			||||||
    <signal name="closed" handler="stop_file_searching" swapped="no"/>
 | 
					 | 
				
			||||||
    <child>
 | 
					 | 
				
			||||||
      <object class="GtkEntry" id="win2_search_field">
 | 
					 | 
				
			||||||
        <property name="width-request">96</property>
 | 
					 | 
				
			||||||
        <property name="visible">True</property>
 | 
					 | 
				
			||||||
        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
      <class name="search-border"/>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkPopover" id="win3_search">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="margin-start">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-end">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
    <property name="relative-to">window_3</property>
 | 
					 | 
				
			||||||
    <property name="constrain-to">none</property>
 | 
					 | 
				
			||||||
    <signal name="closed" handler="stop_file_searching" swapped="no"/>
 | 
					 | 
				
			||||||
    <child>
 | 
					 | 
				
			||||||
      <object class="GtkEntry" id="win3_search_field">
 | 
					 | 
				
			||||||
        <property name="width-request">96</property>
 | 
					 | 
				
			||||||
        <property name="visible">True</property>
 | 
					 | 
				
			||||||
        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
      <class name="search-border"/>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkPopover" id="win4_search">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="margin-start">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-end">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
    <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
    <property name="relative-to">window_4</property>
 | 
					 | 
				
			||||||
    <property name="constrain-to">none</property>
 | 
					 | 
				
			||||||
    <signal name="closed" handler="stop_file_searching" swapped="no"/>
 | 
					 | 
				
			||||||
    <child>
 | 
					 | 
				
			||||||
      <object class="GtkEntry" id="win4_search_field">
 | 
					 | 
				
			||||||
        <property name="width-request">96</property>
 | 
					 | 
				
			||||||
        <property name="visible">True</property>
 | 
					 | 
				
			||||||
        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
        <signal name="changed" handler="do_file_search" swapped="no"/>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
      <class name="search-border"/>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkImage" id="trash_img">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="icon-name">user-trash</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkImage" id="trash_img2">
 | 
					 | 
				
			||||||
    <property name="visible">True</property>
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="icon-name">user-trash</property>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkDialog" id="context_menu_popup">
 | 
					 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
    <property name="resizable">False</property>
 | 
					 | 
				
			||||||
    <property name="window-position">mouse</property>
 | 
					 | 
				
			||||||
    <property name="type-hint">splashscreen</property>
 | 
					 | 
				
			||||||
    <property name="skip-pager-hint">True</property>
 | 
					 | 
				
			||||||
    <property name="decorated">False</property>
 | 
					 | 
				
			||||||
    <property name="deletable">False</property>
 | 
					 | 
				
			||||||
    <property name="gravity">static</property>
 | 
					 | 
				
			||||||
    <signal name="focus-out-event" handler="hide_context_menu" swapped="no"/>
 | 
					 | 
				
			||||||
    <child internal-child="vbox">
 | 
					 | 
				
			||||||
      <object class="GtkBox">
 | 
					 | 
				
			||||||
        <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
        <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
        <property name="spacing">2</property>
 | 
					 | 
				
			||||||
        <child internal-child="action_area">
 | 
					 | 
				
			||||||
          <object class="GtkButtonBox">
 | 
					 | 
				
			||||||
            <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
            <property name="layout-style">end</property>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <placeholder/>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <placeholder/>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
          </object>
 | 
					 | 
				
			||||||
          <packing>
 | 
					 | 
				
			||||||
            <property name="expand">False</property>
 | 
					 | 
				
			||||||
            <property name="fill">False</property>
 | 
					 | 
				
			||||||
            <property name="position">0</property>
 | 
					 | 
				
			||||||
          </packing>
 | 
					 | 
				
			||||||
        </child>
 | 
					 | 
				
			||||||
        <child>
 | 
					 | 
				
			||||||
          <object class="GtkBox" id="context_menu">
 | 
					 | 
				
			||||||
            <property name="visible">True</property>
 | 
					 | 
				
			||||||
            <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
            <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkExpander">
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
                <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
                <property name="expanded">True</property>
 | 
					 | 
				
			||||||
                <property name="label-fill">True</property>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkBox">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-open</property>
 | 
					 | 
				
			||||||
                        <property name="name">open</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Open...</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">0</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Open With</property>
 | 
					 | 
				
			||||||
                        <property name="name">open_with</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="image">open_with_img</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">1</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-execute</property>
 | 
					 | 
				
			||||||
                        <property name="name">execute</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">2</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Execute in Terminal</property>
 | 
					 | 
				
			||||||
                        <property name="name">execute_in_terminal</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="image">exec_in_term_img</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">3</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child type="label">
 | 
					 | 
				
			||||||
                  <object class="GtkLabel">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="hexpand">False</property>
 | 
					 | 
				
			||||||
                    <property name="label" translatable="yes">Open</property>
 | 
					 | 
				
			||||||
                    <property name="justify">center</property>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">False</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">4</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkExpander">
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
                <property name="margin-bottom">5</property>
 | 
					 | 
				
			||||||
                <property name="expanded">True</property>
 | 
					 | 
				
			||||||
                <property name="label-fill">True</property>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkBox">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-new</property>
 | 
					 | 
				
			||||||
                        <property name="name">create</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">New File/Folder...</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">0</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Rename</property>
 | 
					 | 
				
			||||||
                        <property name="name">rename</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Rename...</property>
 | 
					 | 
				
			||||||
                        <property name="image">rename_img2</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">1</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-cut</property>
 | 
					 | 
				
			||||||
                        <property name="name">cut</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Cut...</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">2</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-copy</property>
 | 
					 | 
				
			||||||
                        <property name="name">copy</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Copy...</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">3</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-paste</property>
 | 
					 | 
				
			||||||
                        <property name="name">paste</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Paste...</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">4</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Archive</property>
 | 
					 | 
				
			||||||
                        <property name="name">archive</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Archive...</property>
 | 
					 | 
				
			||||||
                        <property name="image">archive_img</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">5</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child type="label">
 | 
					 | 
				
			||||||
                  <object class="GtkLabel">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="hexpand">False</property>
 | 
					 | 
				
			||||||
                    <property name="label" translatable="yes">File Actions</property>
 | 
					 | 
				
			||||||
                    <property name="justify">center</property>
 | 
					 | 
				
			||||||
                    <property name="ellipsize">end</property>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">False</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">5</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
            <child>
 | 
					 | 
				
			||||||
              <object class="GtkExpander">
 | 
					 | 
				
			||||||
                <property name="visible">True</property>
 | 
					 | 
				
			||||||
                <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                <property name="margin-top">5</property>
 | 
					 | 
				
			||||||
                <property name="margin-bottom">10</property>
 | 
					 | 
				
			||||||
                <property name="label-fill">True</property>
 | 
					 | 
				
			||||||
                <child>
 | 
					 | 
				
			||||||
                  <object class="GtkBox">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="orientation">vertical</property>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton" id="restore_from_trash">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Restore From Trash</property>
 | 
					 | 
				
			||||||
                        <property name="name">restore_from_trash</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Restore From Trash...</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">0</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton" id="empty_trash">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Empty Trash</property>
 | 
					 | 
				
			||||||
                        <property name="name">empty_trash</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Empty Trash...</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">1</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Trash</property>
 | 
					 | 
				
			||||||
                        <property name="name">trash</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Move to Trash...</property>
 | 
					 | 
				
			||||||
                        <property name="margin-top">20</property>
 | 
					 | 
				
			||||||
                        <property name="image">trash_img</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">2</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label" translatable="yes">Go To Trash</property>
 | 
					 | 
				
			||||||
                        <property name="name">go_to_trash</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Go To Trash...</property>
 | 
					 | 
				
			||||||
                        <property name="image">trash_img2</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">3</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                    <child>
 | 
					 | 
				
			||||||
                      <object class="GtkButton">
 | 
					 | 
				
			||||||
                        <property name="label">gtk-delete</property>
 | 
					 | 
				
			||||||
                        <property name="name">delete</property>
 | 
					 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					 | 
				
			||||||
                        <property name="can-focus">True</property>
 | 
					 | 
				
			||||||
                        <property name="receives-default">True</property>
 | 
					 | 
				
			||||||
                        <property name="tooltip-text" translatable="yes">Delete...</property>
 | 
					 | 
				
			||||||
                        <property name="margin-top">20</property>
 | 
					 | 
				
			||||||
                        <property name="use-stock">True</property>
 | 
					 | 
				
			||||||
                        <property name="always-show-image">True</property>
 | 
					 | 
				
			||||||
                        <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
 | 
					 | 
				
			||||||
                      </object>
 | 
					 | 
				
			||||||
                      <packing>
 | 
					 | 
				
			||||||
                        <property name="expand">False</property>
 | 
					 | 
				
			||||||
                        <property name="fill">True</property>
 | 
					 | 
				
			||||||
                        <property name="position">4</property>
 | 
					 | 
				
			||||||
                      </packing>
 | 
					 | 
				
			||||||
                    </child>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
                <child type="label">
 | 
					 | 
				
			||||||
                  <object class="GtkLabel">
 | 
					 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					 | 
				
			||||||
                    <property name="can-focus">False</property>
 | 
					 | 
				
			||||||
                    <property name="hexpand">False</property>
 | 
					 | 
				
			||||||
                    <property name="label" translatable="yes">Trash</property>
 | 
					 | 
				
			||||||
                    <property name="justify">center</property>
 | 
					 | 
				
			||||||
                  </object>
 | 
					 | 
				
			||||||
                </child>
 | 
					 | 
				
			||||||
              </object>
 | 
					 | 
				
			||||||
              <packing>
 | 
					 | 
				
			||||||
                <property name="expand">False</property>
 | 
					 | 
				
			||||||
                <property name="fill">True</property>
 | 
					 | 
				
			||||||
                <property name="position">6</property>
 | 
					 | 
				
			||||||
              </packing>
 | 
					 | 
				
			||||||
            </child>
 | 
					 | 
				
			||||||
          </object>
 | 
					 | 
				
			||||||
          <packing>
 | 
					 | 
				
			||||||
            <property name="expand">False</property>
 | 
					 | 
				
			||||||
            <property name="fill">True</property>
 | 
					 | 
				
			||||||
            <property name="position">1</property>
 | 
					 | 
				
			||||||
          </packing>
 | 
					 | 
				
			||||||
        </child>
 | 
					 | 
				
			||||||
      </object>
 | 
					 | 
				
			||||||
    </child>
 | 
					 | 
				
			||||||
  </object>
 | 
					 | 
				
			||||||
  <object class="GtkMessageDialog" id="warning_alert">
 | 
					  <object class="GtkMessageDialog" id="warning_alert">
 | 
				
			||||||
    <property name="can-focus">False</property>
 | 
					    <property name="can-focus">False</property>
 | 
				
			||||||
    <property name="resizable">False</property>
 | 
					    <property name="resizable">False</property>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								user_config/usr/share/solarfm/contexct_menu.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "Open Actions": {
 | 
				
			||||||
 | 
					        "Open":      ["STOCK_OPEN", "open"],
 | 
				
			||||||
 | 
					        "Open With": ["STOCK_OPEN", "open_with"],
 | 
				
			||||||
 | 
					        "Execute":   ["STOCK_EXECUTE", "execute"],
 | 
				
			||||||
 | 
					        "Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "File Actions": {
 | 
				
			||||||
 | 
					        "New":    ["STOCK_ADD", "create"],
 | 
				
			||||||
 | 
					        "Rename": ["STOCK_EDIT", "rename"],
 | 
				
			||||||
 | 
					        "Cut":    ["STOCK_CUT", "cut"],
 | 
				
			||||||
 | 
					        "Copy":   ["STOCK_COPY", "copy"],
 | 
				
			||||||
 | 
					        "Paste":  ["STOCK_PASTE", "paste"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Plugins": {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/3g2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/3gp.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/ai.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/air.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/asf.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/avi.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/bib.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/cls.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/csv.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/deb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/djvu.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/dmg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								user_config/usr/share/solarfm/fileicons/doc.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 KiB  |