diff --git a/README.md b/README.md
index 368ad9f..f26a6bd 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,19 @@
# BulkR
+Bulk renaming utility written with python and Gtk.
-Bulk renaming utility written with python and Gtk.
\ No newline at end of file
+# Notes
+
+
+
Install Setup
+```
+sudo apt-get install python3.8 python3-setproctitle python3-gi
+```
+
+# TODO
+
+- Add logic for stub instances.
+
+
+# Images
+![1 BulkR clean slate. ](images/pic1.png)
+![2 BulkR loaded dir and some actions. ](images/pic2.png)
diff --git a/images/pic1.png b/images/pic1.png
new file mode 100644
index 0000000..5de477d
Binary files /dev/null and b/images/pic1.png differ
diff --git a/images/pic2.png b/images/pic2.png
new file mode 100644
index 0000000..19c5f84
Binary files /dev/null and b/images/pic2.png differ
diff --git a/src/Window.py b/src/Window.py
new file mode 100644
index 0000000..c06b4f9
--- /dev/null
+++ b/src/Window.py
@@ -0,0 +1,29 @@
+#!/usr/bin/python3
+
+
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from __builtins__ import Builtins
+from controller import Controller
+
+
+class Window(Gtk.Window, Builtins):
+ """docstring for Main."""
+
+ def __init__(self, args):
+ super(Window, self).__init__()
+
+ self.add(Controller(args))
+ self.connect("delete-event", Gtk.main_quit)
+ self.set_default_size(800, 600)
+ self.set_title(f"{app_name}")
+ self.set_icon_from_file("/usr/share/bulkr/bulkr.png")
+ self.set_gravity(5) # 5 = CENTER
+ self.set_position(3) # 4 = CENTER_ALWAYS
+ self.show_all()
diff --git a/src/__builtins__.py b/src/__builtins__.py
new file mode 100644
index 0000000..3ad41bc
--- /dev/null
+++ b/src/__builtins__.py
@@ -0,0 +1,94 @@
+# Python imports
+import builtins, os
+from os import path
+
+# Lib imports
+
+# Application imports
+
+
+class Builtins:
+ """ Create an pub/sub systems. """
+
+ def __init__(self):
+ self.USER_HOME = path.expanduser('~')
+ self.block_from_update = False
+ self.block_to_update = False
+ self.active_path = None
+ self.from_changes = []
+ self.to_changes = []
+
+ # NOTE: The format used is list of [type, target, (data,)] Where:
+ # type is useful context for control flow,
+ # target is the method to call,
+ # data is the method parameters to give
+ # Where data may be any kind of data
+ self._gui_events = []
+ self._module_events = []
+
+
+ # Makeshift fake "events" type system FIFO
+ def _pop_gui_event(self):
+ if len(self._gui_events) > 0:
+ return self._gui_events.pop(0)
+ return None
+
+ def _pop_module_event(self):
+ if len(self._module_events) > 0:
+ return self._module_events.pop(0)
+ return None
+
+
+ def set_active_path(self, _file):
+ if os.path.isdir(_file) :
+ self.from_changes.clear()
+ self.active_path = _file
+ for f in os.listdir(_file):
+ self.from_changes.append(f)
+
+ self.to_changes = self.from_changes
+ event_system.push_gui_event(["update-from", None, ()])
+ event_system.push_gui_event(["update-to", None, ()])
+
+ def reset_to_view(self):
+ self.to_changes = self.from_changes
+ event_system.push_gui_event(["update-to", None, ()])
+
+ def reset_from_view(self):
+ self.set_active_path(self.active_path)
+
+ def push_gui_event(self, event):
+ if len(event) == 3:
+ self._gui_events.append(event)
+ return None
+
+ raise Exception("Invald event format! Please do: [type, target, (data,)]")
+
+ def push_module_event(self, event):
+ if len(event) == 3:
+ self._module_events.append(event)
+ return None
+
+ raise Exception("Invald event format! Please do: [type, target, (data,)]")
+
+ def read_gui_event(self):
+ return self._gui_events[0]
+
+ def read_module_event(self):
+ return self._module_events[0]
+
+ def consume_gui_event(self):
+ return self._pop_gui_event()
+
+ def consume_module_event(self):
+ return self._pop_module_event()
+
+
+
+# NOTE: Just reminding myself we can add to builtins two different ways...
+# __builtins__.update({"event_system": Builtins()})
+builtins.app_name = "Bulk-Renamer"
+builtins.event_system = Builtins()
+builtins.event_sleep_time = 0.1
+builtins.debug = False
+builtins.trace_debug = False
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..db3945b
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,21 @@
+#!/usr/bin/python3
+
+
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from __builtins__ import Builtins
+from Window import Window
+from controller import Controller
+
+
+class Main(Window):
+ """docstring for Main."""
+
+ def __init__(self, args):
+ super(Main, self).__init__(args)
diff --git a/src/__main__.py b/src/__main__.py
new file mode 100644
index 0000000..055337d
--- /dev/null
+++ b/src/__main__.py
@@ -0,0 +1,34 @@
+#!/usr/bin/python3
+
+
+# Python imports
+import sys, argparse
+from setproctitle import setproctitle
+
+# Gtk imports
+import gi, faulthandler, signal
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GLib
+
+# Application imports
+from __init__ import Main
+
+
+
+
+if __name__ == "__main__":
+ try:
+ setproctitle('Bulk-Renamer')
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
+ faulthandler.enable() # For better debug info
+ parser = argparse.ArgumentParser()
+ # Add long and short arguments
+ parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
+
+ # Read arguments (If any...)
+ args = parser.parse_args()
+ main = Main(args)
+ Gtk.main()
+ except Exception as e:
+ print( repr(e) )
diff --git a/src/controller/ChangeView.py b/src/controller/ChangeView.py
new file mode 100644
index 0000000..bce4e68
--- /dev/null
+++ b/src/controller/ChangeView.py
@@ -0,0 +1,57 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class ChangeView(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(ChangeView, self).__init__()
+
+ from_container = Gtk.Box()
+ to_container = Gtk.Box()
+ from_scroll_vw, \
+ self.from_store = self._create_treeview_widget(title="From:")
+ to_scroll_vw, \
+ self.to_store = self._create_treeview_widget(title="To:")
+
+ from_container.add(from_scroll_vw)
+ to_container.add(to_scroll_vw)
+
+ from_container.set_orientation(1)
+ to_container.set_orientation(1)
+
+ self.set_spacing(20)
+ self.set_border_width(2)
+ self.set_homogeneous(True)
+ self.add(from_container)
+ self.add(to_container)
+ self.show_all()
+
+
+ def update_from_list(self):
+ if event_system.block_from_update:
+ return
+
+ print("Updating From List...")
+ if self.from_store:
+ self.from_store.clear()
+
+ for i, change in enumerate(event_system.from_changes):
+ self.from_store.insert(i, [change])
+
+ def update_to_list(self):
+ if event_system.block_to_update:
+ return
+
+ print("Updating To List...")
+ if self.to_store:
+ self.to_store.clear()
+
+ for i, change in enumerate(event_system.to_changes):
+ self.to_store.insert(i, [change])
diff --git a/src/controller/Controller.py b/src/controller/Controller.py
new file mode 100644
index 0000000..00ac263
--- /dev/null
+++ b/src/controller/Controller.py
@@ -0,0 +1,167 @@
+# Python imports
+import os, sys, threading, time
+
+# lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+from gi.repository import GLib
+
+# Application imports
+from mixins import CommonActionsMixin
+from . import ChangeView
+from .widgets import *
+
+
+def threaded(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
+ return wrapper
+
+
+class Controller(Gtk.Box, CommonActionsMixin):
+ def __init__(self, args):
+ super(Controller, self).__init__()
+
+ # Add header
+ self.change_view = ChangeView()
+ action_bar = Gtk.Box()
+ file_choser = Gtk.FileChooserButton(title="Directory Chooser", action=2) # 2 = SELECT_FOLDER
+ file_filter = Gtk.FileFilter()
+ file_choser.show()
+ file_choser.set_filename(event_system.USER_HOME)
+ file_filter.add_mime_type("inode/directory")
+ file_choser.add_filter(file_filter)
+
+ label = Gtk.Label(label="Bulk Action Type: ")
+ data = ["Insert", "Replace", "Remove", "Remove From / To", "Case"]
+ self.store, self.combo_box = self._create_combobox_widget(data)
+
+ add_button = Gtk.Button(label="Add Action")
+ test_all_button = Gtk.Button(label="Preview")
+ reset_button = Gtk.Button(label="Reset")
+ run_button = Gtk.Button(label="Run")
+
+ action_bar.add(label)
+ action_bar.add(self.combo_box)
+ action_bar.add(add_button)
+ action_bar.add(test_all_button)
+ action_bar.add(reset_button)
+ action_bar.set_homogeneous(True)
+ action_bar.set_spacing(20)
+ action_bar.show_all()
+
+ run_button.connect("clicked", self._run_all)
+ add_button.connect("clicked", self._add_action)
+ test_all_button.connect("clicked", self._test_all)
+ reset_button.connect("clicked", self._reset_to_view)
+ file_choser.connect("file-set", self.update_dir_path)
+
+ actions_scroll_label = Gtk.Label(label="Actions:")
+ actions_scroll_label.set_xalign(-20)
+ actions_scroll_view, self.actions_list_view = self._create_listBox_widget()
+
+ self.set_spacing(20)
+ self.set_margin_top(5)
+ self.set_margin_bottom(10)
+ self.set_margin_left(15)
+ self.set_margin_right(15)
+ self.set_orientation(1)
+
+ self.add(file_choser)
+ self.add(action_bar)
+ self.add(self.change_view)
+ self.add(actions_scroll_label)
+ self.add(actions_scroll_view)
+ self.add(run_button)
+ self.show_all()
+
+ self.gui_event_observer()
+ self.action_collection = []
+
+
+ @threaded
+ def gui_event_observer(self):
+ while True:
+ time.sleep(event_sleep_time)
+ event = event_system.consume_gui_event()
+ if event:
+ try:
+ type, target, data = event
+ if type:
+ method = getattr(self.__class__, "_handle_gui_event")
+ GLib.idle_add(method, *(self, type, target, data))
+ else:
+ method = getattr(self.__class__, target)
+ GLib.idle_add(method, *(self, *data,))
+ except Exception as e:
+ print(repr(e))
+
+
+ def update_dir_path(self, widget):
+ path = widget.get_filename()
+ event_system.set_active_path(path)
+
+ def _handle_gui_event(self, type, target, parameters):
+ if type == "update-from":
+ self.change_view.update_from_list()
+ return
+
+ if type == "update-to":
+ self.change_view.update_to_list()
+ return
+
+ for action in self.action_collection:
+ if action == target:
+ if type == "delete":
+ self.action_collection.remove(target)
+ target.delete()
+ if type == "run":
+ target.run()
+
+
+ def _add_action(self, widget):
+ itr = self.combo_box.get_active_iter()
+ text = self.store.get(itr, 0)[0]
+ widget = self._str_to_class( self._clean_text(text) )
+
+ print(f"Adding: {self._clean_text(text)}")
+ self.actions_list_view.add(widget)
+ self.action_collection.append(widget)
+
+ def _test_all(self, widget=None):
+ event_system.block_to_update = True
+ event_system.reset_to_view()
+ for action in self.action_collection:
+ action.run()
+
+ event_system.block_to_update = False
+ event_system.push_gui_event(["update-to", self, ()])
+
+ def _reset_to_view(self, widget):
+ event_system.reset_to_view()
+
+ def _run_all(self, widget):
+ if not event_system.active_path:
+ print("No active path set. Returning...")
+ return
+
+ self._test_all()
+ dir = event_system.active_path
+ for i, file in enumerate(event_system.from_changes):
+ fPath = f"{dir}/{file}"
+ tPath = f"{dir}/{event_system.to_changes[i]}"
+ if fPath != tPath:
+ try:
+ os.rename(fPath, tPath)
+ except Exception as e:
+ print(f"Cant Move: {fPath}\nTo File: {tPath}")
+
+ event_system.reset_from_view()
+
+ def _clean_text(self, text):
+ return text.replace(" ", "") \
+ .replace("/", "")
+
+ def _str_to_class(self, class_name):
+ return getattr(sys.modules[__name__], class_name)()
diff --git a/src/controller/__init__.py b/src/controller/__init__.py
new file mode 100644
index 0000000..b290d33
--- /dev/null
+++ b/src/controller/__init__.py
@@ -0,0 +1,2 @@
+from .ChangeView import ChangeView
+from .Controller import Controller
diff --git a/src/controller/widgets/Case.py b/src/controller/widgets/Case.py
new file mode 100644
index 0000000..ddfe5f7
--- /dev/null
+++ b/src/controller/widgets/Case.py
@@ -0,0 +1,47 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class Case(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(Case, self).__init__()
+
+ label = Gtk.Label(label="Case")
+ data = ["Title Case", "UPPER", "lower", "InVert CaSe --> iNvERT cAsE"]
+ self.store, self.combo_box = self._create_combobox_widget(data)
+
+ label.set_hexpand(True)
+
+ self.add_widgets([label, self.combo_box])
+ self.set_spacing(20)
+ self.show_all()
+
+
+ def run(self):
+ new_collection = []
+ itr = self.combo_box.get_active_iter()
+ type = self.store.get(itr, 0)[0]
+
+ print(f"Changing Case...")
+ if type == "Title Case":
+ for name in event_system.to_changes:
+ new_collection.append(name.title())
+ if type == "UPPER":
+ for name in event_system.to_changes:
+ new_collection.append(name.upper())
+ if type == "lower":
+ for name in event_system.to_changes:
+ new_collection.append(name.lower())
+ if type == "InVert CaSe --> iNvERT cAsE":
+ for name in event_system.to_changes:
+ new_collection.append(name.swapcase())
+
+ event_system.to_changes = new_collection
+ event_system.push_gui_event(["update-to", self, ()])
diff --git a/src/controller/widgets/Insert.py b/src/controller/widgets/Insert.py
new file mode 100644
index 0000000..97afb68
--- /dev/null
+++ b/src/controller/widgets/Insert.py
@@ -0,0 +1,63 @@
+# Python imports
+import pathlib
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class Insert(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(Insert, self).__init__()
+
+ label = Gtk.Label(label="Insert: ")
+ self.insert_entry = Gtk.Entry()
+ self.insert_entry.set_hexpand(True)
+ self.insert_entry.set_placeholder_text("Insert...")
+
+ data = ["Start", "End", "Position"]
+ self.store, self.combo_box = self._create_combobox_widget(data)
+
+ self.spin_button = self._create_spinbutton_widget()
+
+ self.add_widgets([label, self.insert_entry, self.combo_box, self.spin_button])
+ self.set_spacing(20)
+ self.show_all()
+
+
+ def run(self):
+ new_collection = []
+ insert_str = self.insert_entry.get_text()
+ itr = self.combo_box.get_active_iter()
+ type = self.store.get(itr, 0)[0]
+
+ print(f"Inserting...")
+ if type == "Start":
+ for name in event_system.to_changes:
+ new_collection.append(f"{insert_str}{name}")
+ if type == "End":
+ for name in event_system.to_changes:
+ base, file_extension = self.get_file_parts()
+ new_collection.append(f"{base}{insert_str}{file_extension}")
+ if type == "Position":
+ position = self.spin_button.get_value_as_int()
+ for name in event_system.to_changes:
+ name = f"{name[:position]}{insert_str}{name[position:]}"
+ new_collection.append(f"{name}")
+
+ event_system.to_changes = new_collection
+ event_system.push_gui_event(["update-to", self, ()])
+
+
+ def _combo_box_changed(self, widget, eve=None):
+ itr = widget.get_active_iter()
+ type = self.store.get(itr, 0)[0]
+
+ if type == "Position":
+ self.spin_button.set_sensitive(True)
+ else:
+ self.spin_button.set_sensitive(False)
diff --git a/src/controller/widgets/Remove.py b/src/controller/widgets/Remove.py
new file mode 100644
index 0000000..fe7b38b
--- /dev/null
+++ b/src/controller/widgets/Remove.py
@@ -0,0 +1,55 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class Remove(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(Remove, self).__init__()
+
+ label = Gtk.Label(label="Remove: ")
+ self.entry_from = Gtk.Entry()
+
+ data = ["All", "Word Start", "Word End", "First Instance", "Last Instance", "RegEx"]
+ self.store, self.combo_box = self._create_combobox_widget(data)
+
+ self.entry_from.set_hexpand(True)
+ self.entry_from.set_placeholder_text("Remove...")
+
+ self.add_widgets([label, self.entry_from, self.combo_box])
+ self.set_spacing(20)
+ self.show_all()
+
+
+ def run(self):
+ from_str = self.entry_from.get_text()
+ if from_str:
+ new_collection = []
+ itr = self.combo_box.get_active_iter()
+ type = self.store.get(itr, 0)[0]
+ print(f"To Remove: {from_str}")
+
+ if type == "All":
+ for name in event_system.to_changes:
+ new_collection.append(name.replace(from_str, ''))
+ if type == "Word Start":
+ print("Stub...")
+ if type == "Word End":
+ print("Stub...")
+ if type == "First Instance":
+ for name in event_system.to_changes:
+ new_collection.append( name.replace(from_str, "", 1) )
+ if type == "Last Instance":
+ for name in event_system.to_changes:
+ new_collection.append( self._replace_last(name, from_str, "") )
+ if type == "RegEx":
+ print("Stub...")
+
+ event_system.to_changes = new_collection
+ event_system.push_gui_event(["update-to", self, ()])
diff --git a/src/controller/widgets/RemoveFromTo.py b/src/controller/widgets/RemoveFromTo.py
new file mode 100644
index 0000000..ebd1fbb
--- /dev/null
+++ b/src/controller/widgets/RemoveFromTo.py
@@ -0,0 +1,45 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class RemoveFromTo(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(RemoveFromTo, self).__init__()
+
+ label = Gtk.Label(label="Remove From / To: ")
+ self.entry_from = Gtk.Entry()
+ self.entry_to = Gtk.Entry()
+
+ self.entry_from.set_hexpand(True)
+ self.entry_to.set_hexpand(True)
+ self.entry_from.set_placeholder_text("From...")
+ self.entry_to.set_placeholder_text("To...")
+
+ self.add_widgets([label, self.entry_from, self.entry_to])
+
+ self.set_spacing(20)
+ self.show_all()
+
+
+ def run(self):
+ fsub = self.entry_from.get_text()
+ tsub = self.entry_to.get_text()
+
+ if fsub and tsub:
+ new_collection = []
+ print(f"From: {fsub}\nTo: {tsub}")
+ for name in event_system.to_changes:
+ startIndex = name.index(fsub) + 1
+ endIndex = name.index(tsub)
+ toRemove = name[startIndex:endIndex]
+ new_collection.append(name.replace(toRemove, ''))
+
+ event_system.to_changes = new_collection
+ event_system.push_gui_event(["update-to", self, ()])
diff --git a/src/controller/widgets/Replace.py b/src/controller/widgets/Replace.py
new file mode 100644
index 0000000..821d131
--- /dev/null
+++ b/src/controller/widgets/Replace.py
@@ -0,0 +1,41 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+from mixins import CommonActionsMixin
+
+
+class Replace(Gtk.Box, CommonActionsMixin):
+ def __init__(self):
+ super(Replace, self).__init__()
+
+ label = Gtk.Label(label="Replace With: ")
+ self.entry_from = Gtk.Entry()
+ self.entry_to = Gtk.Entry()
+
+ self.entry_from.set_hexpand(True)
+ self.entry_to.set_hexpand(True)
+ self.entry_from.set_placeholder_text("From...")
+ self.entry_to.set_placeholder_text("To...")
+
+ self.add_widgets([label, self.entry_from, self.entry_to])
+
+ self.set_spacing(20)
+ self.show_all()
+
+
+ def run(self):
+ fsub = self.entry_from.get_text()
+ tsub = self.entry_to.get_text()
+ if fsub and tsub:
+ new_collection = []
+ print(f"From: {fsub}\nTo: {tsub}")
+ for name in event_system.to_changes:
+ new_collection.append(name.replace(fsub, tsub))
+
+ event_system.to_changes = new_collection
+ event_system.push_gui_event(["update-to", self, ()])
diff --git a/src/controller/widgets/__init__.py b/src/controller/widgets/__init__.py
new file mode 100644
index 0000000..4688b2e
--- /dev/null
+++ b/src/controller/widgets/__init__.py
@@ -0,0 +1,5 @@
+from .Insert import Insert
+from .Replace import Replace
+from .Remove import Remove
+from .RemoveFromTo import RemoveFromTo
+from .Case import Case
diff --git a/src/mixins/CommonActionsMixin.py b/src/mixins/CommonActionsMixin.py
new file mode 100644
index 0000000..f8afbee
--- /dev/null
+++ b/src/mixins/CommonActionsMixin.py
@@ -0,0 +1,124 @@
+# Python imports
+import pathlib
+
+# lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+
+
+class CommonActionsMixin:
+ def add_widgets(self, widgets):
+ for widget in widgets:
+ self.add(widget)
+
+ remove_button = Gtk.Button(label="X")
+ test_button = Gtk.Button(label="Test")
+
+ remove_button.connect("clicked", self._remove_self)
+ test_button.connect("clicked", self._do_run)
+
+ remove_button.set_size_request(120, 32)
+ test_button.set_size_request(120, 32)
+
+ self.add(test_button)
+ self.add(remove_button)
+
+ def delete(self):
+ self.get_parent().destroy()
+
+
+
+
+ def get_file_parts(self, name):
+ file_extension = pathlib.Path(name).suffix
+ base = name.split(file_extension)[0]
+ return base, file_extension
+
+ def _has_method(self, obj, name):
+ ''' Checks if a given method exists. '''
+ return callable(getattr(obj, name, None))
+
+ def _replace_last(self, string, find, replace):
+ reversed = string[::-1]
+ replaced = reversed.replace(find[::-1], replace[::-1], 1)
+ return replaced[::-1]
+
+
+
+
+ def _remove_self(self, widget):
+ event_system.push_gui_event(["delete", self, ()])
+
+ def _do_run(self, widget):
+ event_system.push_gui_event(["run", self, ()])
+
+
+ def _create_spinbutton_widget(self):
+ spin_button = Gtk.SpinButton()
+ spin_button.set_numeric(True)
+ spin_button.set_wrap(True)
+ spin_button.set_digits(0)
+ spin_button.set_increments(1.0, 1.0)
+ spin_button.set_range(1, 1000000)
+ spin_button.set_sensitive(False)
+
+ return spin_button
+
+ def _create_combobox_widget(self, data):
+ cell = Gtk.CellRendererText()
+ store = Gtk.ListStore(str)
+
+ for row in data:
+ store.append([row])
+
+ combo_box = Gtk.ComboBox()
+ combo_box.set_model(store)
+ combo_box.pack_start(cell, True)
+ combo_box.add_attribute(cell, 'text', 0)
+ combo_box.set_active(0)
+
+ if self._has_method(self, "_combo_box_changed"):
+ combo_box.connect("changed", self._combo_box_changed)
+
+ return store, combo_box
+
+ def _create_treeview_widget(self, title = "Not Set"):
+ scroll = Gtk.ScrolledWindow()
+ grid = Gtk.TreeView()
+ store = Gtk.ListStore(str)
+ column = Gtk.TreeViewColumn(title)
+ name = Gtk.CellRendererText()
+ selec = grid.get_selection()
+
+ grid.set_model(store)
+ selec.set_mode(2)
+
+ column.pack_start(name, True)
+ column.add_attribute(name, "text", 0)
+ column.set_expand(False)
+
+ grid.append_column(column)
+ grid.set_search_column(0)
+ grid.set_headers_visible(True)
+ grid.set_enable_tree_lines(False)
+
+ grid.show_all()
+ scroll.add(grid)
+ grid.columns_autosize()
+ scroll.set_size_request(360, 240)
+ return scroll, store
+
+ def _create_listBox_widget(self,):
+ scroll = Gtk.ScrolledWindow()
+ grid = Gtk.ListBox()
+ viewport = Gtk.Viewport()
+
+ grid.show_all()
+ viewport.add(grid)
+ scroll.add(viewport)
+
+ scroll.set_size_request(360, 200)
+ return scroll, grid
diff --git a/src/mixins/__init__.py b/src/mixins/__init__.py
new file mode 100644
index 0000000..55cf9b4
--- /dev/null
+++ b/src/mixins/__init__.py
@@ -0,0 +1 @@
+from .CommonActionsMixin import CommonActionsMixin
diff --git a/user_config/usr/share/bulkr/bulkr.png b/user_config/usr/share/bulkr/bulkr.png
new file mode 100644
index 0000000..c8f652f
Binary files /dev/null and b/user_config/usr/share/bulkr/bulkr.png differ