support of "Home" and "End" keys, new variant of move in lists

This commit is contained in:
Dmitriy Yefremov
2018-04-16 18:50:48 +03:00
parent d9071632d2
commit 03e5909c23
16 changed files with 140 additions and 93 deletions

View File

@@ -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!

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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 *******************#

View File

@@ -2248,6 +2248,7 @@
<property name="headers_clickable">False</property>
<property name="search_column">0</property>
<property name="rubber_banding">True</property>
<property name="enable_tree_lines">True</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_view_popup_menu" object="bouquets_popup_menu" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

52
app/ui/uicommons.py Normal file
View File

@@ -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