1#!/usr/bin/env python 2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- 3# Catfish - a versatile file searching tool 4# Copyright (C) 2007-2012 Christian Dywan <christian@twotoasts.de> 5# Copyright (C) 2012-2020 Sean Davis <bluesabre@xfce.org> 6# 7# This program is free software: you can redistribute it and/or modify it 8# under the terms of the GNU General Public License version 2, as published 9# by the Free Software Foundation. 10# 11# This program is distributed in the hope that it will be useful, but 12# WITHOUT ANY WARRANTY; without even the implied warranties of 13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 14# PURPOSE. See the GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License along 17# with this program. If not, see <https://www.gnu.org/licenses/>. 18 19# pylint: disable=C0114 20# pylint: disable=C0116 21 22import logging 23from locale import gettext as _ 24 25from gi.repository import Gtk, Gdk # pylint: disable=E0611 26 27from catfish_lib import CatfishSettings 28from . helpers import get_builder 29 30logger = logging.getLogger('catfish_lib') 31 32# GtkBuilder Mappings 33__builder__ = { 34 # Builder name 35 "ui_file": "CatfishWindow", 36 37 "window": { 38 "main": "Catfish", 39 "sidebar": "catfish_window_sidebar", 40 "paned": "catfish_window_paned" 41 }, 42 43 # Toolbar 44 "toolbar": { 45 "folderchooser": "toolbar_folderchooser", 46 "search": "toolbar_search", 47 "view": { 48 "list": "toolbar_view_list", 49 "thumbs": "toolbar_view_thumbnails" 50 }, 51 }, 52 53 # Menus 54 "menus": { 55 # Application (AppMenu) 56 "application": { 57 "menu": "application_menu", 58 "placeholder": "toolbar_custom_appmenu", 59 "exact": "application_menu_exact", 60 "hidden": "application_menu_hidden", 61 "fulltext": "application_menu_fulltext", 62 "advanced": "application_menu_advanced", 63 "update": "application_menu_update", 64 "preferences": "application_menu_prefs", 65 }, 66 # File Context Menu 67 "file": { 68 "menu": "file_menu", 69 "save": "file_menu_save", 70 "delete": "file_menu_delete" 71 } 72 }, 73 74 # Locate Infobar 75 "infobar": { 76 "infobar": "catfish_window_infobar" 77 }, 78 79 # Sidebar 80 "sidebar": { 81 "modified": { 82 "options": "sidebar_filter_custom_date_options", 83 "icons": { 84 "any": "sidebar_filter_modified_any_icon", 85 "today": "sidebar_filter_modified_day_icon", 86 "week": "sidebar_filter_modified_week_icon", 87 "month": "sidebar_filter_modified_month_icon", 88 "custom": "sidebar_filter_custom_date_icon", 89 "options": "sidebar_filter_custom_date_options_icon" 90 } 91 }, 92 "filetype": { 93 "options": "sidebar_filter_custom_options", 94 "icons": { 95 "documents": "sidebar_filter_documents_icon", 96 "folders": "sidebar_filter_folders_icon", 97 "photos": "sidebar_filter_images_icon", 98 "music": "sidebar_filter_music_icon", 99 "videos": "sidebar_filter_videos_icon", 100 "applications": "sidebar_filter_applications_icon", 101 "custom": "sidebar_filter_custom_filetype_icon", 102 "options": "sidebar_filter_custom_options_icon" 103 } 104 } 105 }, 106 107 # Results Window 108 "results": { 109 "scrolled_window": "results_scrolledwindow", 110 "treeview": "results_treeview" 111 }, 112 113 "dialogs": { 114 # Custom Filetypes 115 "filetype": { 116 "dialog": "filetype_dialog", 117 "mimetypes": { 118 "radio": "filetype_mimetype_radio", 119 "box": "filetype_mimetype_box", 120 "categories": "filetype_mimetype_categories", 121 "types": "filetype_mimetype_types" 122 }, 123 "extensions": { 124 "radio": "filetype_extension_radio", 125 "entry": "filetype_extension_entry" 126 } 127 }, 128 129 # Custom Date Range 130 "date": { 131 "dialog": "date_dialog", 132 "start_calendar": "date_start_calendar", 133 "end_calendar": "date_end_calendar", 134 }, 135 136 # Update Search Index 137 "update": { 138 "dialog": "update_dialog", 139 "database_label": "update_dialog_database_details_label", 140 "modified_label": "update_dialog_modified_details_label", 141 "status_infobar": "update_dialog_infobar", 142 "status_icon": "update_dialog_infobar_icon", 143 "status_label": "update_dialog_infobar_label", 144 "close_button": "update_close", 145 "unlock_button": "update_unlock" 146 } 147 } 148} 149 150 151class Window(Gtk.Window): 152 153 """This class is meant to be subclassed by CatfishWindow. It provides 154 common functions and some boilerplate.""" 155 __gtype_name__ = "Window" 156 157 # To construct a new instance of this method, the following notable 158 # methods are called in this order: 159 # __new__(cls) 160 # __init__(self) 161 # finish_initializing(self, builder) 162 # __init__(self) 163 # 164 # For this reason, it's recommended you leave __init__ empty and put 165 # your initialization code in finish_initializing 166 167 def __new__(cls): 168 """Special static method that's automatically called by Python when 169 constructing a new instance of this class. 170 171 Returns a fully instantiated BaseCatfishWindow object. 172 """ 173 builder = get_builder(__builder__['ui_file']) 174 builder.add_name_mapping(__builder__) 175 new_object = builder.get_named_object("window.main") 176 new_object.finish_initializing(builder) 177 return new_object 178 179 def finish_initializing(self, builder): 180 """Called while initializing this instance in __new__ 181 182 finish_initializing should be called after parsing the UI definition 183 and creating a CatfishWindow object with it in order to finish 184 initializing the start of the new CatfishWindow instance. 185 """ 186 # Get a reference to the builder and set up the signals. 187 self.builder = builder 188 self.ui = builder.get_ui(self, True) 189 self.AboutDialog = None # class 190 191 self.sidebar = builder.get_named_object("window.sidebar") 192 193 # Widgets 194 # Folder Chooser 195 chooser = self.builder.get_named_object("toolbar.folderchooser") 196 # Search 197 search = self.builder.get_named_object("toolbar.search") 198 199 # AppMenu 200 button = Gtk.MenuButton() 201 button.set_size_request(32, 32) 202 image = Gtk.Image.new_from_icon_name("open-menu-symbolic", 203 Gtk.IconSize.MENU) 204 button.set_image(image) 205 popover = Gtk.Popover.new(button) 206 appmenu = self.builder.get_named_object("menus.application.menu") 207 popover.add(appmenu) 208 button.set_popover(popover) 209 210 settings = CatfishSettings.CatfishSettings() 211 if settings.get_setting('use-headerbar'): 212 self.setup_headerbar(chooser, search, button) 213 else: 214 self.setup_toolbar(chooser, search, button) 215 216 search.grab_focus() 217 self.keys_pressed = [] 218 219 self.search_engine = None 220 self.settings = None 221 self.hidden_files = None 222 223 def on_sidebar_toggle_toggled(self, widget): 224 pass 225 226 def setup_headerbar(self, chooser, search, button): 227 headerbar = Gtk.HeaderBar.new() 228 headerbar.set_show_close_button(True) 229 230 headerbar.pack_start(chooser) 231 headerbar.set_title(_("Catfish")) 232 headerbar.set_custom_title(search) 233 headerbar.pack_end(button) 234 235 self.set_titlebar(headerbar) 236 headerbar.show_all() 237 238 def setup_toolbar(self, chooser, search, button): 239 toolbar = Gtk.Toolbar.new() 240 241 toolitem = Gtk.ToolItem.new() 242 toolitem.add(chooser) 243 toolitem.set_margin_end(6) 244 toolbar.insert(toolitem, 0) 245 246 toolitem = Gtk.ToolItem.new() 247 toolitem.add(search) 248 search.set_hexpand(True) 249 toolitem.set_expand(True) 250 toolitem.set_margin_end(6) 251 toolbar.insert(toolitem, 1) 252 253 toolitem = Gtk.ToolItem.new() 254 toolitem.add(button) 255 toolbar.insert(toolitem, 2) 256 257 self.get_children()[0].pack_start(toolbar, False, False, 0) 258 self.get_children()[0].reorder_child(toolbar, 0) 259 toolbar.show_all() 260 261 def on_mnu_about_activate(self, widget, data=None): # pylint: disable=W0613 262 """Display the about box for catfish.""" 263 if self.AboutDialog is not None: 264 about = self.AboutDialog() # pylint: disable=E1102 265 about.set_transient_for(self) 266 about.run() 267 about.destroy() 268 269 def on_destroy(self, widget, data=None): # pylint: disable=W0613 270 """Called when the CatfishWindow is closed.""" 271 self.search_engine.stop() 272 self.settings.write() 273 Gtk.main_quit() 274 275 def on_catfish_window_window_state_event(self, widget, event): # pylint: disable=W0613 276 """Properly handle window-manager fullscreen events.""" 277 self.window_is_fullscreen = bool(event.new_window_state & 278 Gdk.WindowState.FULLSCREEN) 279 280 def get_keys_from_event(self, event): 281 keys = [] 282 keys.append(Gdk.keyval_name(event.keyval)) 283 if event.get_state() & Gdk.ModifierType.CONTROL_MASK: 284 keys.append("Control") 285 if event.get_state() & Gdk.ModifierType.SHIFT_MASK: 286 keys.append("Shift") 287 if event.get_state() & Gdk.ModifierType.SUPER_MASK: 288 keys.append("Super") 289 if event.get_state() & Gdk.ModifierType.MOD1_MASK: 290 keys.append("Alt") 291 return keys 292 293 def map_key(self, key): 294 if key.endswith("_L"): 295 return key.replace("_L", "") 296 if key.endswith("_R"): 297 return key.replace("_R", "") 298 return key 299 300 def add_keys(self, keys): 301 for key in keys: 302 self.add_key(key) 303 304 def add_key(self, key): 305 if key is None: 306 return 307 key = self.map_key(key) 308 if key in ["Escape"]: 309 return 310 if key not in self.keys_pressed: 311 self.keys_pressed.append(key) 312 313 def remove_keys(self, keys): 314 for key in keys: 315 if key in self.keys_pressed: 316 self.remove_key(key) 317 self.remove_key(key.upper()) 318 319 def remove_key(self, key): 320 if key is None: 321 return 322 key = self.map_key(key) 323 try: 324 self.keys_pressed.remove(key) 325 except ValueError: 326 pass 327 328 def on_catfish_window_key_press_event(self, widget, event): 329 """Handle keypresses for the Catfish window.""" 330 keys = self.get_keys_from_event(event) 331 self.add_keys(keys) 332 333 if "Escape" in keys: 334 self.search_engine.stop() 335 return True 336 if "Control" in keys and ("q" in keys or "Q" in keys): 337 self.destroy() 338 return True 339 if "Control" in keys and ("h" in keys or "H" in keys): 340 self.hidden_files.activate() 341 return True 342 if 'F9' in keys: 343 self.on_sidebar_toggle_toggled(widget) 344 return True 345 if 'F11' in keys: 346 if self.window_is_fullscreen: 347 self.unfullscreen() 348 else: 349 self.fullscreen() 350 return True 351 return False 352 353 def on_catfish_window_key_release_event(self, widget, event): # pylint: disable=W0613 354 """Handle key releases for the Catfish window.""" 355 keys = self.get_keys_from_event(event) 356 self.remove_keys(keys) 357 return False 358 359 def on_catfish_window_size_allocate(self, widget, allocation): # pylint: disable=W0613 360 paned = self.builder.get_named_object("window.paned") 361 allocation = paned.get_allocation() 362 self.settings.set_setting('window-height', allocation.height) 363 self.settings.set_setting('window-width', allocation.width) 364 paned.set_property('height_request', -1) 365 paned.set_property('width_request', -1) 366 367 def on_catfish_window_configure_event(self, widget, event): # pylint: disable=W0613 368 pos = self.get_position() 369 self.settings.set_setting('window-x', pos.root_x) 370 self.settings.set_setting('window-y', pos.root_y) 371