plugin work #7
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"manifest": {
|
"manifest": {
|
||||||
"name": "Favorites Plugin",
|
"name": "Favorites",
|
||||||
"author": "ITDominator",
|
"author": "ITDominator",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"support": "",
|
"support": "",
|
||||||
"requests": {
|
"requests": {
|
||||||
"ui_target": "plugin_control_list",
|
"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": []
|
||||||
|
|
|
@ -26,7 +26,7 @@ def daemon_threaded(fn):
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.name = "Favorites Plugin" # 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"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Pligin Module
|
||||||
|
"""
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Pligin Package
|
||||||
|
"""
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"manifest": {
|
||||||
|
"name": "Movie/TV Info",
|
||||||
|
"author": "ITDominator",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"support": "",
|
||||||
|
"requests": {
|
||||||
|
"ui_target": "context_menu",
|
||||||
|
"pass_fm_events": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.38.2 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.16"/>
|
||||||
|
<object class="GtkAdjustment" id="scrub_step_adjuster">
|
||||||
|
<property name="lower">1</property>
|
||||||
|
<property name="upper">100</property>
|
||||||
|
<property name="value">65</property>
|
||||||
|
<property name="step-increment">1</property>
|
||||||
|
<property name="page-increment">10</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkTextBuffer" id="textbuffer"/>
|
||||||
|
<object class="GtkDialog" id="info_dialog">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="border-width">6</property>
|
||||||
|
<property name="title" translatable="yes">Movie / TV Info</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="window-position">center-on-parent</property>
|
||||||
|
<property name="default-width">420</property>
|
||||||
|
<property name="destroy-with-parent">True</property>
|
||||||
|
<property name="type-hint">dialog</property>
|
||||||
|
<property name="skip-taskbar-hint">True</property>
|
||||||
|
<property name="skip-pager-hint">True</property>
|
||||||
|
<property name="gravity">center</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox" id="dialog_vbox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child internal-child="action_area">
|
||||||
|
<object class="GtkButtonBox" id="dialog_action_area">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="layout-style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="cancel_button">
|
||||||
|
<property name="label">gtk-close</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="use-stock">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="pack-type">end</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="thumbnail_preview_img">
|
||||||
|
<property name="height-request">320</property>
|
||||||
|
<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="stock">gtk-missing-image</property>
|
||||||
|
<property name="icon_size">6</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</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="GtkTextView" id="overview_textview">
|
||||||
|
<property name="height-request">120</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="margin-start">5</property>
|
||||||
|
<property name="margin-end">5</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-bottom">10</property>
|
||||||
|
<property name="wrap-mode">word</property>
|
||||||
|
<property name="cursor-visible">False</property>
|
||||||
|
<property name="buffer">textbuffer</property>
|
||||||
|
<property name="overwrite">True</property>
|
||||||
|
<property name="monospace">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTable" id="general_table">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="border-width">4</property>
|
||||||
|
<property name="n-rows">2</property>
|
||||||
|
<property name="n-columns">2</property>
|
||||||
|
<property name="column-spacing">12</property>
|
||||||
|
<property name="row-spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes"><b>File _Name:</b></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
<property name="use-underline">True</property>
|
||||||
|
<property name="mnemonic-widget">file_name</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="x-options">GTK_FILL</property>
|
||||||
|
<property name="y-options"/>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="file_name">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="right-attach">2</property>
|
||||||
|
<property name="y-options"/>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<property name="label" translatable="yes"><b>_Location:</b></property>
|
||||||
|
<property name="use-markup">True</property>
|
||||||
|
<property name="use-underline">True</property>
|
||||||
|
<property name="mnemonic-widget">file_location</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
<property name="bottom-attach">2</property>
|
||||||
|
<property name="x-options">GTK_FILL</property>
|
||||||
|
<property name="y-options"/>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="file_location">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="right-attach">2</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
<property name="bottom-attach">2</property>
|
||||||
|
<property name="x-options">GTK_FILL</property>
|
||||||
|
<property name="y-options"/>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="-7">cancel_button</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,192 @@
|
||||||
|
# Python imports
|
||||||
|
import os, threading, subprocess, time, inspect, requests, shutil
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
gi.require_version('GdkPixbuf', '2.0')
|
||||||
|
from gi.repository import Gtk, GLib, GdkPixbuf
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from tmdbscraper import scraper
|
||||||
|
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
def __init__(self):
|
||||||
|
self.path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
self.name = "Movie/TV Info" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
||||||
|
# where self.name should not be needed for message comms
|
||||||
|
self._GLADE_FILE = f"{self.path}/movie_tv_info.glade"
|
||||||
|
|
||||||
|
self._builder = None
|
||||||
|
self._dialog = None
|
||||||
|
self._thumbnail_preview_img = None
|
||||||
|
self._tmdb = scraper.get_tmdb_scraper()
|
||||||
|
self._overview = None
|
||||||
|
self._file_name = None
|
||||||
|
self._file_location = None
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
self._event_system = None
|
||||||
|
self._event_sleep_time = .5
|
||||||
|
self._event_message = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_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._thumbnailer_dialog = self._builder.get_object("info_dialog")
|
||||||
|
self._overview = self._builder.get_object("textbuffer")
|
||||||
|
self._file_name = self._builder.get_object("file_name")
|
||||||
|
self._file_location = self._builder.get_object("file_location")
|
||||||
|
self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img")
|
||||||
|
self._file_hash = self._builder.get_object("file_hash")
|
||||||
|
|
||||||
|
button = Gtk.Button(label=self.name)
|
||||||
|
button.connect("button-release-event", self._show_info_page)
|
||||||
|
return button
|
||||||
|
|
||||||
|
def set_fm_event_system(self, fm_event_system):
|
||||||
|
self._event_system = fm_event_system
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self._module_event_observer()
|
||||||
|
|
||||||
|
|
||||||
|
@threaded
|
||||||
|
def _show_info_page(self, widget=None, eve=None):
|
||||||
|
self._event_system.push_gui_event([self.name, "get_current_state", ()])
|
||||||
|
self.wait_for_fm_message()
|
||||||
|
|
||||||
|
state = self._event_message
|
||||||
|
self._event_message = None
|
||||||
|
|
||||||
|
GLib.idle_add(self._process_changes, (state))
|
||||||
|
|
||||||
|
def _process_changes(self, state):
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
if len(state.selected_files) == 1:
|
||||||
|
self._state = state
|
||||||
|
self._set_ui_data()
|
||||||
|
response = self._thumbnailer_dialog.run()
|
||||||
|
if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
|
||||||
|
self._thumbnailer_dialog.hide()
|
||||||
|
|
||||||
|
def _set_ui_data(self):
|
||||||
|
title, path, video_data = self.get_video_data()
|
||||||
|
keys = video_data.keys() if video_data else None
|
||||||
|
|
||||||
|
overview_text = video_data["overview"] if video_data else f"...NO {self.name.upper()} DATA..."
|
||||||
|
|
||||||
|
self.set_text_data(title, path, overview_text)
|
||||||
|
self.set_thumbnail(video_data) if video_data else ...
|
||||||
|
|
||||||
|
print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ...
|
||||||
|
|
||||||
|
def get_video_data(self):
|
||||||
|
uri = self._state.selected_files[0]
|
||||||
|
path = self._state.tab.get_current_directory()
|
||||||
|
parts = uri.split("/")
|
||||||
|
_title = parts[ len(parts) - 1 ]
|
||||||
|
|
||||||
|
try:
|
||||||
|
title = _title.split("(")[0].strip()
|
||||||
|
startIndex = _title.index('(') + 1
|
||||||
|
endIndex = _title.index(')')
|
||||||
|
date = title[startIndex:endIndex]
|
||||||
|
except Exception as e:
|
||||||
|
title = _title
|
||||||
|
date = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
video_data = self._tmdb.search(title, date)[0]
|
||||||
|
except Exception as e:
|
||||||
|
video_data = None
|
||||||
|
|
||||||
|
return title, path, video_data
|
||||||
|
|
||||||
|
|
||||||
|
def set_text_data(self, title, path, overview_text):
|
||||||
|
self._file_name.set_text(title)
|
||||||
|
self._file_location.set_text(path)
|
||||||
|
self._overview.set_text(overview_text)
|
||||||
|
|
||||||
|
@threaded
|
||||||
|
def set_thumbnail(self, video_data):
|
||||||
|
background_url = video_data["backdrop_path"]
|
||||||
|
# background_url = video_data["poster_path"]
|
||||||
|
background_pth = "/tmp/sfm_mvtv_info.jpg"
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(background_pth)
|
||||||
|
except Exception as e:
|
||||||
|
...
|
||||||
|
|
||||||
|
r = requests.get(background_url, stream = True)
|
||||||
|
|
||||||
|
if r.status_code == 200:
|
||||||
|
r.raw.decode_content = True
|
||||||
|
with open(background_pth,'wb') as f:
|
||||||
|
shutil.copyfileobj(r.raw, f)
|
||||||
|
|
||||||
|
print('Cover Background Image sucessfully retreived...')
|
||||||
|
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(background_pth)
|
||||||
|
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
|
||||||
|
else:
|
||||||
|
print('Cover Background Image Couldn\'t be retreived...')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_fm_message(self):
|
||||||
|
while not self._event_message:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@daemon_threaded
|
||||||
|
def _module_event_observer(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(self._event_sleep_time)
|
||||||
|
event = self._event_system.read_module_event()
|
||||||
|
if event:
|
||||||
|
try:
|
||||||
|
if event[0] == self.name:
|
||||||
|
target_id, method_target, data = self._event_system.consume_module_event()
|
||||||
|
|
||||||
|
if not method_target:
|
||||||
|
self._event_message = data
|
||||||
|
else:
|
||||||
|
method = getattr(self.__class__, f"{method_target}")
|
||||||
|
if data:
|
||||||
|
data = method(*(self, *data))
|
||||||
|
else:
|
||||||
|
method(*(self,))
|
||||||
|
except Exception as e:
|
||||||
|
print(repr(e))
|
|
@ -0,0 +1,22 @@
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .lib.tmdbscraper.tmdb import TMDBMovieScraper
|
||||||
|
from .lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork
|
||||||
|
from .lib.tmdbscraper.imdbratings import get_details as get_imdb_details
|
||||||
|
from .lib.tmdbscraper.traktratings import get_trakt_ratinginfo
|
||||||
|
from .scraper_datahelper import combine_scraped_details_info_and_ratings, \
|
||||||
|
combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params
|
||||||
|
from .scraper_config import configure_scraped_details, PathSpecificSettings, \
|
||||||
|
configure_tmdb_artwork, is_fanarttv_configured
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_tmdb_scraper():
|
||||||
|
language = 'en-US'
|
||||||
|
certcountry = 'us'
|
||||||
|
ADDON_SETTINGS = None
|
||||||
|
return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry)
|
|
@ -0,0 +1,111 @@
|
||||||
|
def configure_scraped_details(details, settings):
|
||||||
|
details = _configure_rating_prefix(details, settings)
|
||||||
|
details = _configure_keeporiginaltitle(details, settings)
|
||||||
|
details = _configure_trailer(details, settings)
|
||||||
|
details = _configure_multiple_studios(details, settings)
|
||||||
|
details = _configure_default_rating(details, settings)
|
||||||
|
details = _configure_tags(details, settings)
|
||||||
|
return details
|
||||||
|
|
||||||
|
def configure_tmdb_artwork(details, settings):
|
||||||
|
if 'available_art' not in details:
|
||||||
|
return details
|
||||||
|
|
||||||
|
art = details['available_art']
|
||||||
|
fanart_enabled = settings.getSettingBool('fanart')
|
||||||
|
if not fanart_enabled:
|
||||||
|
if 'fanart' in art:
|
||||||
|
del art['fanart']
|
||||||
|
if 'set.fanart' in art:
|
||||||
|
del art['set.fanart']
|
||||||
|
if not settings.getSettingBool('landscape'):
|
||||||
|
if 'landscape' in art:
|
||||||
|
if fanart_enabled:
|
||||||
|
art['fanart'] = art.get('fanart', []) + art['landscape']
|
||||||
|
del art['landscape']
|
||||||
|
if 'set.landscape' in art:
|
||||||
|
if fanart_enabled:
|
||||||
|
art['set.fanart'] = art.get('set.fanart', []) + art['set.landscape']
|
||||||
|
del art['set.landscape']
|
||||||
|
|
||||||
|
return details
|
||||||
|
|
||||||
|
def is_fanarttv_configured(settings):
|
||||||
|
return settings.getSettingBool('enable_fanarttv_artwork')
|
||||||
|
|
||||||
|
def _configure_rating_prefix(details, settings):
|
||||||
|
if details['info'].get('mpaa'):
|
||||||
|
details['info']['mpaa'] = settings.getSettingString('certprefix') + details['info']['mpaa']
|
||||||
|
return details
|
||||||
|
|
||||||
|
def _configure_keeporiginaltitle(details, settings):
|
||||||
|
if settings.getSettingBool('keeporiginaltitle'):
|
||||||
|
details['info']['title'] = details['info']['originaltitle']
|
||||||
|
return details
|
||||||
|
|
||||||
|
def _configure_trailer(details, settings):
|
||||||
|
if details['info'].get('trailer') and not settings.getSettingBool('trailer'):
|
||||||
|
del details['info']['trailer']
|
||||||
|
return details
|
||||||
|
|
||||||
|
def _configure_multiple_studios(details, settings):
|
||||||
|
if not settings.getSettingBool('multiple_studios'):
|
||||||
|
details['info']['studio'] = details['info']['studio'][:1]
|
||||||
|
return details
|
||||||
|
|
||||||
|
def _configure_default_rating(details, settings):
|
||||||
|
imdb_default = bool(details['ratings'].get('imdb')) and settings.getSettingString('RatingS') == 'IMDb'
|
||||||
|
trakt_default = bool(details['ratings'].get('trakt')) and settings.getSettingString('RatingS') == 'Trakt'
|
||||||
|
default_rating = 'themoviedb'
|
||||||
|
if imdb_default:
|
||||||
|
default_rating = 'imdb'
|
||||||
|
elif trakt_default:
|
||||||
|
default_rating = 'trakt'
|
||||||
|
if default_rating not in details['ratings']:
|
||||||
|
default_rating = list(details['ratings'].keys())[0] if details['ratings'] else None
|
||||||
|
for rating_type in details['ratings'].keys():
|
||||||
|
details['ratings'][rating_type]['default'] = rating_type == default_rating
|
||||||
|
return details
|
||||||
|
|
||||||
|
def _configure_tags(details, settings):
|
||||||
|
if not settings.getSettingBool('add_tags'):
|
||||||
|
del details['info']['tag']
|
||||||
|
return details
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
try:
|
||||||
|
basestring
|
||||||
|
except NameError: # py2 / py3
|
||||||
|
basestring = str
|
||||||
|
|
||||||
|
#pylint: disable=redefined-builtin
|
||||||
|
class PathSpecificSettings(object):
|
||||||
|
# read-only shim for typed `xbmcaddon.Addon().getSetting*` methods
|
||||||
|
def __init__(self, settings_dict, log_fn):
|
||||||
|
self.data = settings_dict
|
||||||
|
self.log = log_fn
|
||||||
|
|
||||||
|
def getSettingBool(self, id):
|
||||||
|
return self._inner_get_setting(id, bool, False)
|
||||||
|
|
||||||
|
def getSettingInt(self, id):
|
||||||
|
return self._inner_get_setting(id, int, 0)
|
||||||
|
|
||||||
|
def getSettingNumber(self, id):
|
||||||
|
return self._inner_get_setting(id, float, 0.0)
|
||||||
|
|
||||||
|
def getSettingString(self, id):
|
||||||
|
return self._inner_get_setting(id, basestring, '')
|
||||||
|
|
||||||
|
def _inner_get_setting(self, setting_id, setting_type, default):
|
||||||
|
value = self.data.get(setting_id)
|
||||||
|
if isinstance(value, setting_type):
|
||||||
|
return value
|
||||||
|
self._log_bad_value(value, setting_id)
|
||||||
|
return default
|
||||||
|
|
||||||
|
def _log_bad_value(self, value, setting_id):
|
||||||
|
if value is None:
|
||||||
|
self.log("requested setting ({0}) was not found.".format(setting_id))
|
||||||
|
else:
|
||||||
|
self.log('failed to load value "{0}" for setting {1}'.format(value, setting_id))
|
|
@ -0,0 +1,54 @@
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
from urlparse import parse_qsl
|
||||||
|
except ImportError: # py2 / py3
|
||||||
|
from urllib.parse import parse_qsl
|
||||||
|
|
||||||
|
# get addon params from the plugin path querystring
|
||||||
|
def get_params(argv):
|
||||||
|
result = {'handle': int(argv[0])}
|
||||||
|
if len(argv) < 2 or not argv[1]:
|
||||||
|
return result
|
||||||
|
|
||||||
|
result.update(parse_qsl(argv[1].lstrip('?')))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def combine_scraped_details_info_and_ratings(original_details, additional_details):
|
||||||
|
def update_or_set(details, key, value):
|
||||||
|
if key in details:
|
||||||
|
details[key].update(value)
|
||||||
|
else:
|
||||||
|
details[key] = value
|
||||||
|
|
||||||
|
if additional_details:
|
||||||
|
if additional_details.get('info'):
|
||||||
|
update_or_set(original_details, 'info', additional_details['info'])
|
||||||
|
if additional_details.get('ratings'):
|
||||||
|
update_or_set(original_details, 'ratings', additional_details['ratings'])
|
||||||
|
return original_details
|
||||||
|
|
||||||
|
def combine_scraped_details_available_artwork(original_details, additional_details):
|
||||||
|
if additional_details and additional_details.get('available_art'):
|
||||||
|
available_art = additional_details['available_art']
|
||||||
|
if not original_details.get('available_art'):
|
||||||
|
original_details['available_art'] = available_art
|
||||||
|
else:
|
||||||
|
for arttype, artlist in available_art.items():
|
||||||
|
original_details['available_art'][arttype] = \
|
||||||
|
artlist + original_details['available_art'].get(arttype, [])
|
||||||
|
|
||||||
|
return original_details
|
||||||
|
|
||||||
|
def find_uniqueids_in_text(input_text):
|
||||||
|
result = {}
|
||||||
|
res = re.search(r'(themoviedb.org/movie/)([0-9]+)', input_text)
|
||||||
|
if (res):
|
||||||
|
result['tmdb'] = res.group(2)
|
||||||
|
res = re.search(r'imdb....?/title/tt([0-9]+)', input_text)
|
||||||
|
if (res):
|
||||||
|
result['imdb'] = 'tt' + res.group(1)
|
||||||
|
else:
|
||||||
|
res = re.search(r'imdb....?/Title\?t{0,2}([0-9]+)', input_text)
|
||||||
|
if (res):
|
||||||
|
result['imdb'] = 'tt' + res.group(1)
|
||||||
|
return result
|
|
@ -0,0 +1,175 @@
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import xbmc
|
||||||
|
import xbmcaddon
|
||||||
|
import xbmcgui
|
||||||
|
import xbmcplugin
|
||||||
|
|
||||||
|
from lib.tmdbscraper.tmdb import TMDBMovieScraper
|
||||||
|
from lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork
|
||||||
|
from lib.tmdbscraper.imdbratings import get_details as get_imdb_details
|
||||||
|
from lib.tmdbscraper.traktratings import get_trakt_ratinginfo
|
||||||
|
from scraper_datahelper import combine_scraped_details_info_and_ratings, \
|
||||||
|
combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params
|
||||||
|
from scraper_config import configure_scraped_details, PathSpecificSettings, \
|
||||||
|
configure_tmdb_artwork, is_fanarttv_configured
|
||||||
|
|
||||||
|
ADDON_SETTINGS = xbmcaddon.Addon()
|
||||||
|
ID = ADDON_SETTINGS.getAddonInfo('id')
|
||||||
|
|
||||||
|
def log(msg, level=xbmc.LOGDEBUG):
|
||||||
|
xbmc.log(msg='[{addon}]: {msg}'.format(addon=ID, msg=msg), level=level)
|
||||||
|
|
||||||
|
def get_tmdb_scraper(settings):
|
||||||
|
language = settings.getSettingString('language')
|
||||||
|
certcountry = settings.getSettingString('tmdbcertcountry')
|
||||||
|
return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry)
|
||||||
|
|
||||||
|
def search_for_movie(title, year, handle, settings):
|
||||||
|
log("Find movie with title '{title}' from year '{year}'".format(title=title, year=year), xbmc.LOGINFO)
|
||||||
|
title = _strip_trailing_article(title)
|
||||||
|
search_results = get_tmdb_scraper(settings).search(title, year)
|
||||||
|
if not search_results:
|
||||||
|
return
|
||||||
|
if 'error' in search_results:
|
||||||
|
header = "The Movie Database Python error searching with web service TMDB"
|
||||||
|
xbmcgui.Dialog().notification(header, search_results['error'], xbmcgui.NOTIFICATION_WARNING)
|
||||||
|
log(header + ': ' + search_results['error'], xbmc.LOGWARNING)
|
||||||
|
return
|
||||||
|
|
||||||
|
for movie in search_results:
|
||||||
|
listitem = _searchresult_to_listitem(movie)
|
||||||
|
uniqueids = {'tmdb': str(movie['id'])}
|
||||||
|
xbmcplugin.addDirectoryItem(handle=handle, url=build_lookup_string(uniqueids),
|
||||||
|
listitem=listitem, isFolder=True)
|
||||||
|
|
||||||
|
_articles = [prefix + article for prefix in (', ', ' ') for article in ("the", "a", "an")]
|
||||||
|
def _strip_trailing_article(title):
|
||||||
|
title = title.lower()
|
||||||
|
for article in _articles:
|
||||||
|
if title.endswith(article):
|
||||||
|
return title[:-len(article)]
|
||||||
|
return title
|
||||||
|
|
||||||
|
def _searchresult_to_listitem(movie):
|
||||||
|
movie_info = {'title': movie['title']}
|
||||||
|
movie_label = movie['title']
|
||||||
|
|
||||||
|
movie_year = movie['release_date'].split('-')[0] if movie.get('release_date') else None
|
||||||
|
if movie_year:
|
||||||
|
movie_label += ' ({})'.format(movie_year)
|
||||||
|
movie_info['year'] = movie_year
|
||||||
|
|
||||||
|
listitem = xbmcgui.ListItem(movie_label, offscreen=True)
|
||||||
|
|
||||||
|
listitem.setInfo('video', movie_info)
|
||||||
|
if movie['poster_path']:
|
||||||
|
listitem.setArt({'thumb': movie['poster_path']})
|
||||||
|
|
||||||
|
return listitem
|
||||||
|
|
||||||
|
# Low limit because a big list of artwork can cause trouble in some cases
|
||||||
|
# (a column can be too large for the MySQL integration),
|
||||||
|
# and how useful is a big list anyway? Not exactly rhetorical, this is an experiment.
|
||||||
|
IMAGE_LIMIT = 10
|
||||||
|
|
||||||
|
def add_artworks(listitem, artworks):
|
||||||
|
for arttype, artlist in artworks.items():
|
||||||
|
if arttype == 'fanart':
|
||||||
|
continue
|
||||||
|
for image in artlist[:IMAGE_LIMIT]:
|
||||||
|
listitem.addAvailableArtwork(image['url'], arttype)
|
||||||
|
|
||||||
|
fanart_to_set = [{'image': image['url'], 'preview': image['preview']}
|
||||||
|
for image in artworks['fanart'][:IMAGE_LIMIT]]
|
||||||
|
listitem.setAvailableFanart(fanart_to_set)
|
||||||
|
|
||||||
|
def get_details(input_uniqueids, handle, settings):
|
||||||
|
if not input_uniqueids:
|
||||||
|
return False
|
||||||
|
details = get_tmdb_scraper(settings).get_details(input_uniqueids)
|
||||||
|
if not details:
|
||||||
|
return False
|
||||||
|
if 'error' in details:
|
||||||
|
header = "The Movie Database Python error with web service TMDB"
|
||||||
|
xbmcgui.Dialog().notification(header, details['error'], xbmcgui.NOTIFICATION_WARNING)
|
||||||
|
log(header + ': ' + details['error'], xbmc.LOGWARNING)
|
||||||
|
return False
|
||||||
|
|
||||||
|
details = configure_tmdb_artwork(details, settings)
|
||||||
|
|
||||||
|
if settings.getSettingString('RatingS') == 'IMDb' or settings.getSettingBool('imdbanyway'):
|
||||||
|
imdbinfo = get_imdb_details(details['uniqueids'])
|
||||||
|
if 'error' in imdbinfo:
|
||||||
|
header = "The Movie Database Python error with website IMDB"
|
||||||
|
log(header + ': ' + imdbinfo['error'], xbmc.LOGWARNING)
|
||||||
|
else:
|
||||||
|
details = combine_scraped_details_info_and_ratings(details, imdbinfo)
|
||||||
|
|
||||||
|
if settings.getSettingString('RatingS') == 'Trakt' or settings.getSettingBool('traktanyway'):
|
||||||
|
traktinfo = get_trakt_ratinginfo(details['uniqueids'])
|
||||||
|
details = combine_scraped_details_info_and_ratings(details, traktinfo)
|
||||||
|
|
||||||
|
if is_fanarttv_configured(settings):
|
||||||
|
fanarttv_info = get_fanarttv_artwork(details['uniqueids'],
|
||||||
|
settings.getSettingString('fanarttv_clientkey'),
|
||||||
|
settings.getSettingString('fanarttv_language'),
|
||||||
|
details['_info']['set_tmdbid'])
|
||||||
|
details = combine_scraped_details_available_artwork(details, fanarttv_info)
|
||||||
|
|
||||||
|
details = configure_scraped_details(details, settings)
|
||||||
|
|
||||||
|
listitem = xbmcgui.ListItem(details['info']['title'], offscreen=True)
|
||||||
|
listitem.setInfo('video', details['info'])
|
||||||
|
listitem.setCast(details['cast'])
|
||||||
|
listitem.setUniqueIDs(details['uniqueids'], 'tmdb')
|
||||||
|
add_artworks(listitem, details['available_art'])
|
||||||
|
|
||||||
|
for rating_type, value in details['ratings'].items():
|
||||||
|
if 'votes' in value:
|
||||||
|
listitem.setRating(rating_type, value['rating'], value['votes'], value['default'])
|
||||||
|
else:
|
||||||
|
listitem.setRating(rating_type, value['rating'], defaultt=value['default'])
|
||||||
|
|
||||||
|
xbmcplugin.setResolvedUrl(handle=handle, succeeded=True, listitem=listitem)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def find_uniqueids_in_nfo(nfo, handle):
|
||||||
|
uniqueids = find_uniqueids_in_text(nfo)
|
||||||
|
if uniqueids:
|
||||||
|
listitem = xbmcgui.ListItem(offscreen=True)
|
||||||
|
xbmcplugin.addDirectoryItem(
|
||||||
|
handle=handle, url=build_lookup_string(uniqueids), listitem=listitem, isFolder=True)
|
||||||
|
|
||||||
|
def build_lookup_string(uniqueids):
|
||||||
|
return json.dumps(uniqueids)
|
||||||
|
|
||||||
|
def parse_lookup_string(uniqueids):
|
||||||
|
try:
|
||||||
|
return json.loads(uniqueids)
|
||||||
|
except ValueError:
|
||||||
|
log("Can't parse this lookup string, is it from another add-on?\n" + uniqueids, xbmc.LOGWARNING)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def run():
|
||||||
|
params = get_params(sys.argv[1:])
|
||||||
|
enddir = True
|
||||||
|
if 'action' in params:
|
||||||
|
settings = ADDON_SETTINGS if not params.get('pathSettings') else \
|
||||||
|
PathSpecificSettings(json.loads(params['pathSettings']), lambda msg: log(msg, xbmc.LOGWARNING))
|
||||||
|
action = params["action"]
|
||||||
|
if action == 'find' and 'title' in params:
|
||||||
|
search_for_movie(params["title"], params.get("year"), params['handle'], settings)
|
||||||
|
elif action == 'getdetails' and 'url' in params:
|
||||||
|
enddir = not get_details(parse_lookup_string(params["url"]), params['handle'], settings)
|
||||||
|
elif action == 'NfoUrl' and 'nfo' in params:
|
||||||
|
find_uniqueids_in_nfo(params["nfo"], params['handle'])
|
||||||
|
else:
|
||||||
|
log("unhandled action: " + action, xbmc.LOGWARNING)
|
||||||
|
else:
|
||||||
|
log("No action in 'params' to act on", xbmc.LOGWARNING)
|
||||||
|
if enddir:
|
||||||
|
xbmcplugin.endOfDirectory(params['handle'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
|
@ -50,7 +50,8 @@ class ManifestProcessor:
|
||||||
|
|
||||||
if "ui_target" in keys:
|
if "ui_target" in keys:
|
||||||
if requests["ui_target"] in [
|
if requests["ui_target"] in [
|
||||||
"none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list",
|
"none", "other", "main_Window", "main_menu_bar",
|
||||||
|
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
|
||||||
"context_menu", "window_1", "window_2", "window_3", "window_4"
|
"context_menu", "window_1", "window_2", "window_3", "window_4"
|
||||||
]:
|
]:
|
||||||
if requests["ui_target"] == "other":
|
if requests["ui_target"] == "other":
|
||||||
|
|
|
@ -65,7 +65,9 @@ class Plugins:
|
||||||
|
|
||||||
def load_plugin_module(self, path, folder, target):
|
def load_plugin_module(self, path, folder, target):
|
||||||
os.chdir(path)
|
os.chdir(path)
|
||||||
sys.path.insert(0, path)
|
sys.path.insert(0, path) # NOTE: I think I'm not using this correctly...
|
||||||
|
# The folder and target aren't working to create parent package references, so using as stopgap.
|
||||||
|
# The above is probably polutling import logic and will cause unforseen import issues.
|
||||||
spec = importlib.util.spec_from_file_location(folder, target)
|
spec = importlib.util.spec_from_file_location(folder, target)
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
|
@ -1008,7 +1008,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButtonBox">
|
<object class="GtkButtonBox" id="main_menu_bttn_box_bar">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="spacing">5</property>
|
<property name="spacing">5</property>
|
||||||
|
@ -2364,12 +2364,20 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox" id="context_menu">
|
||||||
<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>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="context_menu">
|
<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="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>
|
||||||
|
@ -2439,6 +2447,37 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="position">3</property>
|
<property name="position">3</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="label">gtk-new</property>
|
<property name="label">gtk-new</property>
|
||||||
|
@ -2447,14 +2486,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">True</property>
|
<property name="receives-default">True</property>
|
||||||
<property name="tooltip-text" translatable="yes">New File/Folder...</property>
|
<property name="tooltip-text" translatable="yes">New File/Folder...</property>
|
||||||
<property name="margin-top">20</property>
|
|
||||||
<property name="use-stock">True</property>
|
<property name="use-stock">True</property>
|
||||||
<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>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">4</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2472,7 +2510,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">5</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2490,7 +2528,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">6</property>
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2508,7 +2546,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">7</property>
|
<property name="position">3</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2526,7 +2564,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">8</property>
|
<property name="position">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2544,9 +2582,40 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">9</property>
|
<property name="position">5</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
<child>
|
||||||
<object class="GtkButton" id="restore_from_trash">
|
<object class="GtkButton" id="restore_from_trash">
|
||||||
<property name="label" translatable="yes">Restore From Trash</property>
|
<property name="label" translatable="yes">Restore From Trash</property>
|
||||||
|
@ -2555,13 +2624,12 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">True</property>
|
<property name="receives-default">True</property>
|
||||||
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
|
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
|
||||||
<property name="margin-top">20</property>
|
|
||||||
<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>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">10</property>
|
<property name="position">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2577,7 +2645,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">11</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2596,7 +2664,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">12</property>
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2614,7 +2682,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">13</property>
|
<property name="position">3</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2633,14 +2701,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">14</property>
|
<property name="position">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</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>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">2</property>
|
<property name="position">6</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
Loading…
Reference in New Issue