diff --git a/README.md b/README.md
index b2a51377..836ef7df 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
### Keyboard shortcuts:
-Ctrl + X, C, V, Up, Down, PageUp, PageDown, S, T, E, L, H, Space; Insert, Delete, F2.
+####Ctrl + X, C, V, Up, Down, PageUp, PageDown, Home, End, S, T, E, L, H, Space; Insert, Delete, F2.
Insert - copies the selected channels from the main list to the bouquet or inserts (creates) a new bouquet.
Ctrl + X - only in bouquet list. Ctrl + C - only in services list.
Clipboard is "rubber". There is an accumulation before the insertion!
diff --git a/app/eparser/enigma/lamedb.py b/app/eparser/enigma/lamedb.py
index 7e632941..bcec6e6d 100644
--- a/app/eparser/enigma/lamedb.py
+++ b/app/eparser/enigma/lamedb.py
@@ -4,7 +4,7 @@
Description of format taken from here: http://www.satsupreme.com/showthread.php/194074-Lamedb-format-explained
"""
from app.commons import log
-from app.ui import CODED_ICON, LOCKED_ICON, HIDE_ICON
+from app.ui.uicommons import CODED_ICON, LOCKED_ICON, HIDE_ICON
from .blacklist import get_blacklist
from ..ecommons import Service, POLARIZATION, SYSTEM, FEC, SERVICE_TYPE, Flag
diff --git a/app/eparser/iptv.py b/app/eparser/iptv.py
index 71b43248..b068d945 100644
--- a/app/eparser/iptv.py
+++ b/app/eparser/iptv.py
@@ -2,7 +2,7 @@
from enum import Enum
from app.properties import Profile
-from app.ui import IPTV_ICON
+from app.ui.uicommons import IPTV_ICON
from .ecommons import BqServiceType, Service
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
diff --git a/app/eparser/neutrino/bouquets.py b/app/eparser/neutrino/bouquets.py
index 020ced51..a57fbd56 100644
--- a/app/eparser/neutrino/bouquets.py
+++ b/app/eparser/neutrino/bouquets.py
@@ -2,7 +2,7 @@ import os
from xml.dom.minidom import parse, Document
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT
-from app.ui import LOCKED_ICON, HIDE_ICON
+from app.ui.uicommons import LOCKED_ICON, HIDE_ICON
from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDER, BqType
_FILE = "bouquets.xml"
diff --git a/app/ui/__init__.py b/app/ui/__init__.py
index e2a34d62..8b137891 100644
--- a/app/ui/__init__.py
+++ b/app/ui/__init__.py
@@ -1,28 +1 @@
-import locale
-import gi
-import os
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, Gdk
-
-# path to *.glade files
-UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
-
-# translation
-TEXT_DOMAIN = "demon-editor"
-if UI_RESOURCES_PATH == "app/ui/":
- LANG_DIR = UI_RESOURCES_PATH + "lang"
- locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
-
-theme = Gtk.IconTheme.get_default()
-_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
-CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
- "emblem-readonly", 16, 0) else _IMAGE_MISSING
-LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
- "system-lock-screen", 16, 0) else _IMAGE_MISSING
-HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
-TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
-IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.load_icon("emblem-shared", 16, 0) else None
-
-if __name__ == "__main__":
- pass
diff --git a/app/ui/dialogs.py b/app/ui/dialogs.py
index be427c2a..cf35d0e3 100644
--- a/app/ui/dialogs.py
+++ b/app/ui/dialogs.py
@@ -3,7 +3,7 @@ import locale
from enum import Enum
from app.commons import run_idle
-from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
class Action(Enum):
diff --git a/app/ui/download_dialog.py b/app/ui/download_dialog.py
index 49c1d942..e364d0bd 100644
--- a/app/ui/download_dialog.py
+++ b/app/ui/download_dialog.py
@@ -1,7 +1,7 @@
from app.commons import run_idle, run_task
from app.ftp import download_data, DownloadDataType, upload_data
from app.properties import Profile
-from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
diff --git a/app/ui/iptv.py b/app/ui/iptv.py
index 6412827a..9063ea51 100644
--- a/app/ui/iptv.py
+++ b/app/ui/iptv.py
@@ -4,7 +4,7 @@ from urllib.parse import urlparse
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT
from app.properties import Profile
-from . import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
+from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON
from .dialogs import Action, show_dialog, DialogType
from .main_helper import get_base_model
diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py
index 77c4892c..4b26d809 100644
--- a/app/ui/main_app_window.py
+++ b/app/ui/main_app_window.py
@@ -1,9 +1,9 @@
import os
+import shutil
+
from contextlib import suppress
from functools import lru_cache
-import shutil
-
from app.commons import run_idle, log
from app.eparser import get_blacklist, write_blacklist, parse_m3u
from app.eparser import get_services, get_bouquets, write_bouquets, write_services, Bouquets, Bouquet, Service
@@ -13,7 +13,7 @@ from app.eparser.neutrino.bouquets import BqType
from app.properties import get_config, write_config, Profile
from .iptv import IptvDialog
from .search import SearchProvider
-from . import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON
+from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, LOCKED_ICON, HIDE_ICON, IPTV_ICON, MOVE_KEYS
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog, get_message
from .download_dialog import show_download_dialog
from .main_helper import edit_marker, insert_marker, move_items, rename, ViewTarget, set_flags, locate_in_services, \
@@ -228,10 +228,7 @@ class MainAppWindow:
""" Move items in fav or bouquets tree view """
if self._services_view.is_focus():
return
- elif self._fav_view.is_focus():
- move_items(key, self._fav_view)
- elif self._bouquets_view and key not in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down):
- move_items(key, self._bouquets_view)
+ move_items(key, self._fav_view if self._fav_view.is_focus() else self._bouquets_view)
def on_cut(self, view):
for row in tuple(self.on_delete(view)):
@@ -736,9 +733,7 @@ class MainAppWindow:
if key == Gdk.KEY_Delete:
self.on_delete(view)
- elif ctrl and key in (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up): # KEY_KP_Page_Up for laptop!
- self.move_items(key)
- elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
+ elif ctrl and key in MOVE_KEYS:
self.move_items(key)
elif model_name == self._FAV_LIST_NAME and key == Gdk.KEY_Control_L or key == Gdk.KEY_Control_R:
self.update_fav_num_column(model)
diff --git a/app/ui/main_helper.py b/app/ui/main_helper.py
index 8291a88a..a2c13de4 100644
--- a/app/ui/main_helper.py
+++ b/app/ui/main_helper.py
@@ -1,7 +1,6 @@
""" This is helper module for ui """
import os
import shutil
-from enum import Enum
from gi.repository import GdkPixbuf
from app.commons import run_task
@@ -9,27 +8,10 @@ from app.eparser import Service
from app.eparser.ecommons import Flag, BouquetService, Bouquet, BqType
from app.eparser.enigma.bouquets import BqServiceType, to_bouquet_id
from app.properties import Profile
-from . import Gtk, Gdk, HIDE_ICON, LOCKED_ICON
+from .uicommons import ViewTarget, BqGenType, Gtk, Gdk, HIDE_ICON, LOCKED_ICON
from .dialogs import show_dialog, DialogType, get_chooser_dialog, WaitDialog
-class ViewTarget(Enum):
- """ Used for set target view """
- BOUQUET = 0
- FAV = 1
- SERVICES = 2
-
-
-class BqGenType(Enum):
- """ Bouquet generation type """
- SAT = 0
- EACH_SAT = 1
- PACKAGE = 2
- EACH_PACKAGE = 3
- TYPE = 4
- EACH_TYPE = 5
-
-
# ***************** Markers *******************#
def insert_marker(view, bouquets, selected_bouquet, channels, parent_window):
@@ -76,35 +58,81 @@ def edit_marker(view, bouquets, selected_bouquet, channels, parent_window):
# ***************** Movement *******************#
-def move_items(key, view):
- """ Move items in tree view """
+def move_items(key, view: Gtk.TreeView):
+ """ Move items in the tree view """
selection = view.get_selection()
model, paths = selection.get_selected_rows()
if paths:
- # grouping the scattered rows
- if len(paths) > 1:
- top_iter = model.get_iter(paths[0])
- for i in range(1, len(paths)):
- itr = model.get_iter(paths[i])
- model.move_after(itr, top_iter)
- top_iter = itr
+ mod_length = len(model)
+ cursor_path = view.get_cursor()[0]
+ max_path = Gtk.TreePath.new_from_indices((mod_length,))
+ min_path = Gtk.TreePath.new_from_indices((0,))
+ is_tree_store = False
- model, paths = selection.get_selected_rows()
- # for correct down move!
- if key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
- paths = reversed(paths)
+ if type(model) is Gtk.TreeStore:
+ parent_paths = list(filter(lambda p: p.get_depth() == 1, paths))
+ if parent_paths:
+ paths = parent_paths
+ min_path = model.get_path(model.get_iter_first())
+ else:
+ if not is_some_level(paths):
+ return
+ parent_itr = model.iter_parent(model.get_iter(paths[0]))
+ parent_index = model.get_path(parent_itr)
+ children_num = model.iter_n_children(parent_itr)
+ if key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
+ children_num -= 1
+ min_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, 0))
+ max_path = Gtk.TreePath.new_from_string("{}:{}".format(parent_index, children_num))
+ is_tree_store = True
- for path in paths:
- itr = model.get_iter(path)
- if key == Gdk.KEY_Down:
- model.move_after(itr, model.iter_next(itr))
- elif key == Gdk.KEY_Up:
- model.move_before(itr, model.iter_previous(itr))
- elif key == Gdk.KEY_Page_Up or key == Gdk.KEY_KP_Page_Up:
- model.move_before(itr, model.get_iter(view.get_cursor()[0]))
- elif key == Gdk.KEY_Page_Down or key == Gdk.KEY_KP_Page_Down:
- model.move_after(itr, model.get_iter(view.get_cursor()[0]))
+ if mod_length == len(paths):
+ return
+
+ if key == Gdk.KEY_Up:
+ top_path = Gtk.TreePath(paths[0])
+ top_path.prev()
+ move_up(top_path, model, paths)
+ elif key == Gdk.KEY_Down:
+ down_path = Gtk.TreePath(paths[-1])
+ down_path.next()
+ if down_path < max_path:
+ move_down(down_path, model, paths)
+ else:
+ max_path.prev()
+ move_down(max_path, model, paths)
+ elif key in (Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up, Gdk.KEY_Home):
+ move_up(min_path if is_tree_store else cursor_path, model, paths)
+ elif key in (Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down, Gdk.KEY_End):
+ move_down(max_path if is_tree_store else cursor_path, model, paths)
+
+
+def move_up(top_path, model, paths):
+ top_iter = model.get_iter(top_path)
+ for path in paths:
+ itr = model.get_iter(path)
+ model.move_before(itr, top_iter)
+ top_path.next()
+ top_iter = model.get_iter(top_path)
+
+
+def move_down(down_path, model, paths):
+ top_iter = model.get_iter(down_path)
+ for path in reversed(paths):
+ itr = model.get_iter(path)
+ model.move_after(itr, top_iter)
+ down_path.prev()
+ top_iter = model.get_iter(down_path)
+
+
+def is_some_level(paths):
+ for i in range(1, len(paths)):
+ prev = paths[i - 1]
+ current = paths[i]
+ if len(prev) != len(current) or (len(prev) == 2 and len(current) == 2 and prev[0] != current[0]):
+ return
+ return True
# ***************** Rename *******************#
diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade
index 14bd86dc..3a945e62 100644
--- a/app/ui/main_window.glade
+++ b/app/ui/main_window.glade
@@ -2248,6 +2248,7 @@
False
0
True
+ True
True
diff --git a/app/ui/picons_dialog.py b/app/ui/picons_dialog.py
index a30afa37..eb57185b 100644
--- a/app/ui/picons_dialog.py
+++ b/app/ui/picons_dialog.py
@@ -9,7 +9,7 @@ from app.commons import run_idle, run_task
from app.ftp import upload_data, DownloadDataType
from app.picons.picons import PiconsParser, parse_providers, Provider, convert_to
from app.properties import Profile
-from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data
diff --git a/app/ui/satellites_dialog.py b/app/ui/satellites_dialog.py
index 69ef5d46..cccb70c8 100644
--- a/app/ui/satellites_dialog.py
+++ b/app/ui/satellites_dialog.py
@@ -3,7 +3,7 @@ from math import fabs
from app.commons import run_idle
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
-from . import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .dialogs import show_dialog, DialogType, WaitDialog
from .main_helper import move_items, scroll_to
@@ -125,9 +125,7 @@ class SatellitesDialog:
self.on_transponder()
elif key == Gdk.KEY_space:
pass
- elif ctrl and key in (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_KP_Page_Up): # KEY_KP_Page_Up for laptop!
- move_items(key, self._sat_view)
- elif ctrl and key in (Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_KP_Page_Down):
+ elif ctrl and key in _MOVE_KEYS:
move_items(key, self._sat_view)
elif key == Gdk.KEY_Left or key == Gdk.KEY_Right:
view.do_unselect_all(view)
diff --git a/app/ui/service_details_dialog.py b/app/ui/service_details_dialog.py
index abf0edce..a0ad21ce 100644
--- a/app/ui/service_details_dialog.py
+++ b/app/ui/service_details_dialog.py
@@ -6,7 +6,7 @@ from app.eparser import Service
from app.eparser.ecommons import MODULATION, Inversion, ROLL_OFF, Pilot, Flag, Pids, POLARIZATION, \
get_key_by_value, get_value_by_name, FEC_DEFAULT, PLS_MODE, SERVICE_TYPE
from app.properties import Profile
-from . import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON
+from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, HIDE_ICON, TEXT_DOMAIN, CODED_ICON
from .dialogs import show_dialog, DialogType, Action
from .main_helper import get_base_model
diff --git a/app/ui/settings_dialog.py b/app/ui/settings_dialog.py
index 24234d1f..f6139e94 100644
--- a/app/ui/settings_dialog.py
+++ b/app/ui/settings_dialog.py
@@ -1,5 +1,5 @@
from app.properties import write_config, Profile, get_default_settings
-from . import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
+from .uicommons import Gtk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .main_helper import update_entry_data
diff --git a/app/ui/uicommons.py b/app/ui/uicommons.py
new file mode 100644
index 00000000..978b9a89
--- /dev/null
+++ b/app/ui/uicommons.py
@@ -0,0 +1,52 @@
+import locale
+import os
+
+import gi
+from enum import Enum
+
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gdk
+
+# path to *.glade files
+UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
+
+# translation
+TEXT_DOMAIN = "demon-editor"
+if UI_RESOURCES_PATH == "app/ui/":
+ LANG_DIR = UI_RESOURCES_PATH + "lang"
+ locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
+
+theme = Gtk.IconTheme.get_default()
+_IMAGE_MISSING = theme.load_icon("image-missing", 16, 0) if theme.lookup_icon("image-missing", 16, 0) else None
+CODED_ICON = theme.load_icon("emblem-readonly", 16, 0) if theme.lookup_icon(
+ "emblem-readonly", 16, 0) else _IMAGE_MISSING
+LOCKED_ICON = theme.load_icon("system-lock-screen", 16, 0) if theme.lookup_icon(
+ "system-lock-screen", 16, 0) else _IMAGE_MISSING
+HIDE_ICON = theme.load_icon("go-jump", 16, 0) if theme.lookup_icon("go-jump", 16, 0) else _IMAGE_MISSING
+TV_ICON = theme.load_icon("tv-symbolic", 16, 0) if theme.lookup_icon("tv-symbolic", 16, 0) else _IMAGE_MISSING
+IPTV_ICON = theme.load_icon("emblem-shared", 16, 0) if theme.load_icon("emblem-shared", 16, 0) else None
+
+# keys for move in lists
+MOVE_KEYS = (Gdk.KEY_Up, Gdk.KEY_Page_Up, Gdk.KEY_Down, Gdk.KEY_Page_Down, Gdk.KEY_Home, Gdk.KEY_End,
+ Gdk.KEY_KP_Page_Up, Gdk.KEY_KP_Page_Down) # KEY_KP_Page_Up(Down) for laptop!
+
+
+class ViewTarget(Enum):
+ """ Used for set target view """
+ BOUQUET = 0
+ FAV = 1
+ SERVICES = 2
+
+
+class BqGenType(Enum):
+ """ Bouquet generation type """
+ SAT = 0
+ EACH_SAT = 1
+ PACKAGE = 2
+ EACH_PACKAGE = 3
+ TYPE = 4
+ EACH_TYPE = 5
+
+
+if __name__ == "__main__":
+ pass