1#!/usr/local/bin/python3.8 2 3import getopt 4import sys 5 6import os 7import glob 8import gettext 9import time 10import traceback 11import locale 12import urllib.request as urllib 13from functools import cmp_to_key 14import unicodedata 15import config 16from setproctitle import setproctitle 17 18import gi 19gi.require_version('Gtk', '3.0') 20gi.require_version('XApp', '1.0') 21from gi.repository import Gio, Gtk, Pango, Gdk, XApp 22 23sys.path.append(config.currentPath + "/modules") 24sys.path.append(config.currentPath + "/bin") 25import capi 26import proxygsettings 27import SettingsWidgets 28 29# i18n 30gettext.install("cinnamon", "/usr/local/share/locale", names=["ngettext"]) 31 32# Standard setting pages... this can be expanded to include applet dirs maybe? 33mod_files = glob.glob(config.currentPath + "/modules/*.py") 34mod_files.sort() 35if len(mod_files) == 0: 36 print("No settings modules found!!") 37 sys.exit(1) 38 39mod_files = [x.split('/')[-1].split('.')[0] for x in mod_files] 40 41for mod_file in mod_files: 42 if mod_file[0:3] != "cs_": 43 raise Exception("Settings modules must have a prefix of 'cs_' !!") 44 45modules = map(__import__, mod_files) 46 47# i18n for menu item 48menuName = _("System Settings") 49menuComment = _("Control Center") 50 51WIN_WIDTH = 800 52WIN_HEIGHT = 600 53WIN_H_PADDING = 20 54 55MIN_LABEL_WIDTH = 16 56MAX_LABEL_WIDTH = 25 57MIN_PIX_WIDTH = 100 58MAX_PIX_WIDTH = 160 59 60MOUSE_BACK_BUTTON = 8 61 62CATEGORIES = [ 63 # Display name ID Show it? Always False to start Icon 64 {"label": _("Appearance"), "id": "appear", "show": False, "icon": "cs-cat-appearance"}, 65 {"label": _("Preferences"), "id": "prefs", "show": False, "icon": "cs-cat-prefs"}, 66 {"label": _("Hardware"), "id": "hardware", "show": False, "icon": "cs-cat-hardware"}, 67 {"label": _("Administration"), "id": "admin", "show": False, "icon": "cs-cat-admin"} 68] 69 70CONTROL_CENTER_MODULES = [ 71 # Label Module ID Icon Category Keywords for filter 72 [_("Color"), "color", "cs-color", "hardware", _("color, profile, display, printer, output")], 73 [_("Graphics Tablet"), "wacom", "cs-tablet", "hardware", _("wacom, digitize, tablet, graphics, calibrate, stylus")] 74] 75 76STANDALONE_MODULES = [ 77 # Label Executable Icon Category Keywords for filter 78 [_("Printers"), "system-config-printer", "cs-printer", "hardware", _("printers, laser, inkjet")], 79 [_("Firewall"), "gufw", "cs-firewall", "admin", _("firewall, block, filter, programs")], 80 [_("Firewall"), "firewall-config", "cs-firewall", "admin", _("firewall, block, filter, programs")], 81 [_("Languages"), "mintlocale", "cs-language", "prefs", _("language, install, foreign")], 82 [_("Input Method"), "mintlocale-im", "cs-input-method", "prefs", _("language, install, foreign, input, method, chinese, korean, japanese, typing")], 83 [_("Login Window"), "pkexec lightdm-settings", "cs-login", "admin", _("login, lightdm, mdm, gdm, manager, user, password, startup, switch")], 84 [_("Login Window"), "lightdm-gtk-greeter-settings-pkexec", "cs-login", "admin", _("login, lightdm, manager, settings, editor")], 85 [_("Driver Manager"), "pkexec driver-manager", "cs-drivers", "admin", _("video, driver, wifi, card, hardware, proprietary, nvidia, radeon, nouveau, fglrx")], 86 [_("Nvidia Settings"), "nvidia-settings", "cs-drivers", "admin", _("video, driver, proprietary, nvidia, settings")], 87 [_("Software Sources"), "pkexec mintsources", "cs-sources", "admin", _("ppa, repository, package, source, download")], 88 [_("Package Management"), "dnfdragora", "cs-sources", "admin", _("update, install, repository, package, source, download")], 89 [_("Package Management"), "yumex-dnf", "cs-sources", "admin", _("update, install, repository, package, source, download")], 90 [_("Users and Groups"), "cinnamon-settings-users", "cs-user-accounts", "admin", _("user, users, account, accounts, group, groups, password")], 91 [_("Manage Services and Units"), "systemd-manager-pkexec", "cs-sources", "admin", _("systemd, units, services, systemctl, init")], 92 [_("Disks"), "gnome-disks", "org.gnome.DiskUtility", "hardware", _("disks, manage, hardware, management, hard, hdd, pendrive, format, erase, test, create, iso, ISO, disk, image")] 93] 94 95TABS = { 96 # KEY (cs_KEY.py) : {"tab_name": tab_number, ... } 97 "universal-access": {"visual": 0, "keyboard": 1, "typing": 2, "mouse": 3}, 98 "applets": {"installed": 0, "more": 1, "download": 1}, 99 "backgrounds": {"images": 0, "settings": 1}, 100 "default": {"preferred": 0, "removable": 1}, 101 "desklets": {"installed": 0, "more": 1, "download": 1, "general": 2}, 102 "display": {"layout": 0, "settings": 1}, 103 "effects": {"effects": 0, "customize": 1}, 104 "extensions": {"installed": 0, "more": 1, "download": 1}, 105 "keyboard": {"typing": 0, "shortcuts": 1, "layouts": 2}, 106 "mouse": {"mouse": 0, "touchpad": 1}, 107 "power": {"power": 0, "batteries": 1, "brightness": 2}, 108 "screensaver": {"settings": 0, "customize": 1}, 109 "sound": {"output": 0, "input": 1, "sounds": 2, "applications": 3, "settings": 4}, 110 "themes": {"themes": 0, "download": 1, "options": 2}, 111 "windows": {"titlebar": 0, "behavior": 1, "alttab": 2}, 112 "workspaces": {"osd": 0, "settings": 1} 113} 114 115ARG_REWRITE = { 116 'accessibility': 'universal-access', 117 'screen': 'display', 118 'screens': 'display', 119 'bluetooth': 'blueberry', 120 'hotcorners': 'hotcorner', 121 'accounts': 'online-accounts', 122 'colors': 'color', 123 'me': 'user', 124 'lightdm-settings': 'pkexec lightdm-settings', 125 'login-screen': 'pkexec lightdm-settings', 126 'window': 'windows', 127 'background': 'backgrounds', 128 'driver-manager': 'pkexec driver-manager', 129 'drivers': 'pkexec driver-manager', 130 'printers': 'system-config-printer', 131 'printer': 'system-config-printer', 132 'infos': 'info', 133 'locale': 'mintlocale', 134 'language': 'mintlocale', 135 'input-method': 'mintlocale-im', 136 'nvidia': 'nvidia-settings', 137 'firewall': 'gufw', 138 'networks': 'network', 139 'sources': 'pkexec mintsources', 140 'mintsources': 'pkexec mintsources', 141 'panels': 'panel', 142 'tablet': 'wacom', 143 'users': 'cinnamon-settings-users' 144} 145 146 147def print_timing(func): 148 # decorate functions with @print_timing to output how long they take to run. 149 def wrapper(*arg): 150 t1 = time.time() 151 res = func(*arg) 152 t2 = time.time() 153 print('%s took %0.3f ms' % (func.func_name, (t2-t1)*1000.0)) 154 return res 155 return wrapper 156 157 158def touch(fname, times=None): 159 with open(fname, 'a'): 160 os.utime(fname, times) 161 162 163class MainWindow: 164 # Change pages 165 def side_view_nav(self, side_view, path, cat): 166 selected_items = side_view.get_selected_items() 167 if len(selected_items) > 0: 168 self.deselect(cat) 169 filtered_path = side_view.get_model().convert_path_to_child_path(selected_items[0]) 170 if filtered_path is not None: 171 self.go_to_sidepage(cat, filtered_path, user_action=True) 172 173 def _on_sidepage_hide_stack(self): 174 self.stack_switcher.set_opacity(0) 175 176 def _on_sidepage_show_stack(self): 177 self.stack_switcher.set_opacity(1) 178 179 def go_to_sidepage(self, cat, path, user_action=True): 180 iterator = self.store[cat].get_iter(path) 181 sidePage = self.store[cat].get_value(iterator, 2) 182 if not sidePage.is_standalone: 183 if not user_action: 184 self.window.set_title(sidePage.name) 185 self.window.set_icon_name(sidePage.icon) 186 sidePage.build() 187 if sidePage.stack: 188 self.stack_switcher.set_stack(sidePage.stack) 189 l = sidePage.stack.get_children() 190 if len(l) > 0: 191 if self.tab in range(len(l)): 192 sidePage.stack.set_visible_child(l[self.tab]) 193 visible_child = sidePage.stack.get_visible_child() 194 if self.tab == 1 \ 195 and hasattr(visible_child, 'sort_combo') \ 196 and self.sort in range(5): 197 visible_child.sort_combo.set_active(self.sort) 198 visible_child.sort_changed() 199 else: 200 sidePage.stack.set_visible_child(l[0]) 201 if sidePage.stack.get_visible(): 202 self.stack_switcher.set_opacity(1) 203 else: 204 self.stack_switcher.set_opacity(0) 205 if hasattr(sidePage, "connect_proxy"): 206 sidePage.connect_proxy("hide_stack", self._on_sidepage_hide_stack) 207 sidePage.connect_proxy("show_stack", self._on_sidepage_show_stack) 208 else: 209 self.stack_switcher.set_opacity(0) 210 else: 211 self.stack_switcher.set_opacity(0) 212 213 if user_action: 214 self.main_stack.set_visible_child_name("content_box_page") 215 self.header_stack.set_visible_child_name("content_box") 216 217 else: 218 self.main_stack.set_visible_child_full("content_box_page", Gtk.StackTransitionType.NONE) 219 self.header_stack.set_visible_child_full("content_box", Gtk.StackTransitionType.NONE) 220 221 self.current_sidepage = sidePage 222 width = 0 223 for widget in self.top_bar: 224 m, n = widget.get_preferred_width() 225 width += n 226 self.top_bar.set_size_request(width + 20, -1) 227 self.maybe_resize(sidePage) 228 else: 229 sidePage.build() 230 231 def maybe_resize(self, sidePage): 232 m, n = self.content_box.get_preferred_size() 233 234 # Resize vertically depending on the height requested by the module 235 use_height = WIN_HEIGHT 236 total_height = n.height + self.bar_heights + WIN_H_PADDING 237 if not sidePage.size: 238 # No height requested, resize vertically if the module is taller than the window 239 if total_height > WIN_HEIGHT: 240 use_height = total_height 241 elif sidePage.size > 0: 242 # Height hardcoded by the module 243 use_height = sidePage.size + self.bar_heights + WIN_H_PADDING 244 elif sidePage.size == -1: 245 # Module requested the window to fit it (i.e. shrink the window if necessary) 246 use_height = total_height 247 248 self.window.resize(WIN_WIDTH, use_height) 249 250 def deselect(self, cat): 251 for key in self.side_view: 252 if key is not cat: 253 self.side_view[key].unselect_all() 254 255 # Create the UI 256 def __init__(self): 257 self.builder = Gtk.Builder() 258 self.builder.set_translation_domain('cinnamon') # let it translate! 259 self.builder.add_from_file(config.currentPath + "/cinnamon-settings.ui") 260 self.window = XApp.GtkWindow(window_position=Gtk.WindowPosition.CENTER, 261 default_width=800, default_height=600) 262 263 main_box = self.builder.get_object("main_box") 264 self.window.add(main_box) 265 self.top_bar = self.builder.get_object("top_bar") 266 self.side_view = {} 267 self.main_stack = self.builder.get_object("main_stack") 268 self.main_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 269 self.main_stack.set_transition_duration(150) 270 self.header_stack = self.builder.get_object("header_stack") 271 self.header_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 272 self.header_stack.set_transition_duration(150) 273 self.side_view_container = self.builder.get_object("category_box") 274 self.side_view_sw = self.builder.get_object("side_view_sw") 275 context = self.side_view_sw.get_style_context() 276 context.add_class("cs-category-view") 277 context.add_class("view") 278 self.side_view_sw.show_all() 279 self.content_box = self.builder.get_object("content_box") 280 self.content_box_sw = self.builder.get_object("content_box_sw") 281 self.content_box_sw.show_all() 282 self.button_back = self.builder.get_object("button_back") 283 self.button_back.set_tooltip_text(_("Back to all settings")) 284 button_image = self.builder.get_object("image1") 285 button_image.props.icon_size = Gtk.IconSize.MENU 286 287 self.stack_switcher = self.builder.get_object("stack_switcher") 288 289 self.search_entry = self.builder.get_object("search_box") 290 self.search_entry.set_placeholder_text(_("Search")) 291 self.search_entry.connect("changed", self.onSearchTextChanged) 292 self.search_entry.connect("icon-press", self.onClearSearchBox) 293 294 self.window.connect("destroy", self.quit) 295 296 self.builder.connect_signals(self) 297 self.unsortedSidePages = [] 298 self.sidePages = [] 299 self.settings = Gio.Settings.new("org.cinnamon") 300 self.current_cat_widget = None 301 302 self.current_sidepage = None 303 self.c_manager = capi.CManager() 304 self.content_box.c_manager = self.c_manager 305 self.bar_heights = 0 306 307 for module in modules: 308 try: 309 mod = module.Module(self.content_box) 310 if self.loadCheck(mod) and self.setParentRefs(mod): 311 self.unsortedSidePages.append((mod.sidePage, mod.name, mod.category)) 312 except: 313 print("Failed to load module %s" % module) 314 traceback.print_exc() 315 316 for item in CONTROL_CENTER_MODULES: 317 ccmodule = SettingsWidgets.CCModule(item[0], item[1], item[2], item[3], item[4], self.content_box) 318 if ccmodule.process(self.c_manager): 319 self.unsortedSidePages.append((ccmodule.sidePage, ccmodule.name, ccmodule.category)) 320 321 for item in STANDALONE_MODULES: 322 samodule = SettingsWidgets.SAModule(item[0], item[1], item[2], item[3], item[4], self.content_box) 323 if samodule.process(): 324 self.unsortedSidePages.append((samodule.sidePage, samodule.name, samodule.category)) 325 326 # sort the modules alphabetically according to the current locale 327 localeStrKey = cmp_to_key(locale.strcoll) 328 # Apply locale key to the field name of each side page. 329 sidePagesKey = lambda m: localeStrKey(m[0].name) 330 self.sidePages = sorted(self.unsortedSidePages, key=sidePagesKey) 331 332 # create the backing stores for the side nav-view. 333 sidePagesIters = {} 334 self.store = {} 335 self.storeFilter = {} 336 for sidepage in self.sidePages: 337 sp, sp_id, sp_cat = sidepage 338 if sp_cat not in self.store: # Label Icon sidePage Category 339 self.store[sidepage[2]] = Gtk.ListStore(str, str, object, str) 340 for category in CATEGORIES: 341 if category["id"] == sp_cat: 342 category["show"] = True 343 344 # Don't allow item names (and their translations) to be more than 30 chars long. It looks ugly and it creates huge gaps in the icon views 345 name = sp.name 346 if len(name) > 30: 347 name = "%s..." % name[:30] 348 sidePagesIters[sp_id] = (self.store[sp_cat].append([name, sp.icon, sp, sp_cat]), sp_cat) 349 350 self.min_label_length = 0 351 self.min_pix_length = 0 352 353 for key in self.store: 354 char, pix = self.get_label_min_width(self.store[key]) 355 self.min_label_length = max(char, self.min_label_length) 356 self.min_pix_length = max(pix, self.min_pix_length) 357 self.storeFilter[key] = self.store[key].filter_new() 358 self.storeFilter[key].set_visible_func(self.filter_visible_function) 359 360 self.min_label_length += 2 361 self.min_pix_length += 4 362 363 self.min_label_length = max(self.min_label_length, MIN_LABEL_WIDTH) 364 self.min_pix_length = max(self.min_pix_length, MIN_PIX_WIDTH) 365 366 self.min_label_length = min(self.min_label_length, MAX_LABEL_WIDTH) 367 self.min_pix_length = min(self.min_pix_length, MAX_PIX_WIDTH) 368 369 self.displayCategories() 370 371 # set up larger components. 372 self.window.set_title(_("System Settings")) 373 self.button_back.connect('clicked', self.back_to_icon_view) 374 375 self.calculate_bar_heights() 376 377 self.tab = 0 # open 'manage' tab by default 378 self.sort = 1 # sorted by 'score' by default 379 380 # Select the first sidePage 381 if len(sys.argv) > 1: 382 arg1 = sys.argv[1] 383 if arg1 in ARG_REWRITE.keys(): 384 arg1 = ARG_REWRITE[arg1] 385 if len(sys.argv) > 1 and arg1 in sidePagesIters: 386 # Analyses arguments to know the tab to open 387 # and the sort to apply if the tab is the 'more' one. 388 # Examples: 389 # cinnamon-settings.py applets --tab=more --sort=date 390 # cinnamon-settings.py applets --tab=1 --sort=2 391 # cinnamon-settings.py applets --tab=more --sort=date 392 # cinnamon-settings.py applets --tab=1 -s 2 393 # cinnamon-settings.py applets -t 1 -s installed 394 # cinnamon-settings.py desklets -t 2 395 # Please note that useless or wrong arguments are ignored. 396 opts = [] 397 sorts_literal = {"name":0, "score":1, "date":2, "installed":3, "update":4} 398 tabs_literal = {"default":0} 399 if arg1 in TABS.keys(): 400 tabs_literal = TABS[arg1] 401 402 try: 403 if len(sys.argv) > 2: 404 opts = getopt.getopt(sys.argv[2:], "t:s:", ["tab=", "sort="])[0] 405 except getopt.GetoptError: 406 pass 407 408 for opt, arg in opts: 409 if opt in ("-t", "--tab"): 410 if arg.isdecimal(): 411 self.tab = int(arg) 412 elif arg in tabs_literal.keys(): 413 self.tab = tabs_literal[arg] 414 if opt in ("-s", "--sort"): 415 if arg.isdecimal(): 416 self.sort = int(arg) 417 elif arg in sorts_literal.keys(): 418 self.sort = sorts_literal[arg] 419 420 # If we're launching a module directly, set the WM class so GWL 421 # can consider it as a standalone app and give it its own 422 # group. 423 wm_class = "cinnamon-settings %s" % arg1 424 self.window.set_wmclass(wm_class, wm_class) 425 self.button_back.hide() 426 (iter, cat) = sidePagesIters[arg1] 427 path = self.store[cat].get_path(iter) 428 if path: 429 self.go_to_sidepage(cat, path, user_action=False) 430 self.window.show() 431 if arg1 in ("mintlocale", "blueberry", "system-config-printer", "mintlocale-im", "nvidia-settings"): 432 # These modules do not need to leave the System Settings window open, 433 # when selected by command line argument. 434 self.window.close() 435 else: 436 self.search_entry.grab_focus() 437 self.window.show() 438 else: 439 self.search_entry.grab_focus() 440 self.window.connect("key-press-event", self.on_keypress) 441 self.window.connect("button-press-event", self.on_buttonpress) 442 443 self.window.show() 444 445 def on_keypress(self, widget, event): 446 grab = False 447 device = Gtk.get_current_event_device() 448 if device.get_source() == Gdk.InputSource.KEYBOARD: 449 grab = Gdk.Display.get_default().device_is_grabbed(device) 450 if not grab and event.keyval == Gdk.KEY_BackSpace and (type(self.window.get_focus()) not in 451 (Gtk.TreeView, Gtk.Entry, Gtk.SpinButton, Gtk.TextView)): 452 self.back_to_icon_view(None) 453 return True 454 return False 455 456 def on_buttonpress(self, widget, event): 457 if event.button == MOUSE_BACK_BUTTON: 458 self.back_to_icon_view(None) 459 return True 460 return False 461 462 def calculate_bar_heights(self): 463 h = 0 464 m, n = self.top_bar.get_preferred_size() 465 h += n.height 466 self.bar_heights = h 467 468 def onSearchTextChanged(self, widget): 469 self.displayCategories() 470 471 def onClearSearchBox(self, widget, position, event): 472 if position == Gtk.EntryIconPosition.SECONDARY: 473 self.search_entry.set_text("") 474 475 def strip_accents(self, text): 476 text = unicodedata.normalize('NFKD', text) 477 return ''.join([c for c in text if not unicodedata.combining(c)]) 478 479 def filter_visible_function(self, model, iter, user_data = None): 480 sidePage = model.get_value(iter, 2) 481 text = self.strip_accents(self.search_entry.get_text().lower()) 482 if self.strip_accents(sidePage.name.lower()).find(text) > -1 or \ 483 self.strip_accents(sidePage.keywords.lower()).find(text) > -1: 484 return True 485 else: 486 return False 487 488 def displayCategories(self): 489 widgets = self.side_view_container.get_children() 490 for widget in widgets: 491 widget.destroy() 492 self.first_category_done = False # This is just to prevent an extra separator showing up before the first category 493 for category in CATEGORIES: 494 if category["show"] is True: 495 self.prepCategory(category) 496 self.side_view_container.show_all() 497 498 def get_label_min_width(self, model): 499 min_width_chars = 0 500 min_width_pixels = 0 501 icon_view = Gtk.IconView() 502 iter = model.get_iter_first() 503 while iter != None: 504 string = model.get_value(iter, 0) 505 split_by_word = string.split(" ") 506 for word in split_by_word: 507 layout = icon_view.create_pango_layout(word) 508 item_width, item_height = layout.get_pixel_size() 509 if item_width > min_width_pixels: 510 min_width_pixels = item_width 511 if len(word) > min_width_chars: 512 min_width_chars = len(word) 513 iter = model.iter_next(iter) 514 return min_width_chars, min_width_pixels 515 516 def pixbuf_data_func(self, column, cell, model, iter, data=None): 517 wrapper = model.get_value(iter, 1) 518 if wrapper: 519 cell.set_property('surface', wrapper.surface) 520 521 def prepCategory(self, category): 522 self.storeFilter[category["id"]].refilter() 523 if not self.anyVisibleInCategory(category): 524 return 525 if self.first_category_done: 526 widget = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL) 527 self.side_view_container.pack_start(widget, False, False, 10) 528 529 box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 4) 530 img = Gtk.Image.new_from_icon_name(category["icon"], Gtk.IconSize.BUTTON) 531 box.pack_start(img, False, False, 4) 532 533 widget = Gtk.Label(yalign=0.5) 534 widget.set_use_markup(True) 535 widget.set_markup('<span size="12000">%s</span>' % category["label"]) 536 box.pack_start(widget, False, False, 1) 537 self.side_view_container.pack_start(box, False, False, 0) 538 widget = Gtk.IconView.new_with_model(self.storeFilter[category["id"]]) 539 540 area = widget.get_area() 541 542 widget.set_item_width(self.min_pix_length) 543 widget.set_item_padding(0) 544 widget.set_column_spacing(18) 545 widget.set_row_spacing(18) 546 widget.set_margin(20) 547 548 pixbuf_renderer = Gtk.CellRendererPixbuf() 549 text_renderer = Gtk.CellRendererText(ellipsize=Pango.EllipsizeMode.NONE, wrap_mode=Pango.WrapMode.WORD_CHAR, wrap_width=0, width_chars=self.min_label_length, alignment=Pango.Alignment.CENTER, xalign=0.5) 550 551 area.pack_start(pixbuf_renderer, True, True, False) 552 area.pack_start(text_renderer, True, True, False) 553 area.add_attribute(pixbuf_renderer, "icon-name", 1) 554 pixbuf_renderer.set_property("stock-size", Gtk.IconSize.DIALOG) 555 pixbuf_renderer.set_property("follow-state", True) 556 557 area.add_attribute(text_renderer, "text", 0) 558 559 self.side_view[category["id"]] = widget 560 self.side_view_container.pack_start(self.side_view[category["id"]], False, False, 0) 561 self.first_category_done = True 562 self.side_view[category["id"]].connect("item-activated", self.side_view_nav, category["id"]) 563 self.side_view[category["id"]].connect("button-release-event", self.button_press, category["id"]) 564 self.side_view[category["id"]].connect("keynav-failed", self.on_keynav_failed, category["id"]) 565 self.side_view[category["id"]].connect("selection-changed", self.on_selection_changed, category["id"]) 566 567 def bring_selection_into_view(self, iconview): 568 sel = iconview.get_selected_items() 569 570 if sel: 571 path = sel[0] 572 found, rect = iconview.get_cell_rect(path, None) 573 574 cw = self.side_view_container.get_window() 575 cw_x, cw_y = cw.get_position() 576 577 ivw = iconview.get_window() 578 iv_x, iv_y = ivw.get_position() 579 580 final_y = rect.y + cw_y + iv_y 581 582 adj = self.side_view_sw.get_vadjustment() 583 page = adj.get_page_size() 584 current_pos = adj.get_value() 585 586 if (final_y > 0) and ((final_y + rect.height) < page): 587 return 588 589 if ((final_y + rect.height) > page): 590 adj.set_value(current_pos + final_y + rect.height - page + 10) 591 elif final_y < 0: 592 # We can just add a negative here (since final_y < 0), but it's less 593 # confusing to be explicit that we're decreasing current_pos. 594 adj.set_value(current_pos - abs(final_y) - 10) 595 596 def on_selection_changed(self, widget, category): 597 sel = widget.get_selected_items() 598 if len(sel) > 0: 599 self.current_cat_widget = widget 600 self.bring_selection_into_view(widget) 601 for iv in self.side_view: 602 if self.side_view[iv] == self.current_cat_widget: 603 continue 604 self.side_view[iv].unselect_all() 605 606 def get_cur_cat_index(self, category): 607 i = 0 608 for cat in CATEGORIES: 609 if category == cat["id"]: 610 return i 611 i += 1 612 613 def get_cur_column(self, iconview): 614 s, path, cell = iconview.get_cursor() 615 if path: 616 col = iconview.get_item_column(path) 617 return col 618 619 def reposition_new_cat(self, sel, iconview): 620 iconview.set_cursor(sel, None, False) 621 iconview.select_path(sel) 622 iconview.grab_focus() 623 624 def on_keynav_failed(self, widget, direction, category): 625 num_cats = len(CATEGORIES) 626 current_idx = self.get_cur_cat_index(category) 627 ret = False 628 dist = 1000 629 sel = None 630 631 if direction == Gtk.DirectionType.DOWN and current_idx < num_cats - 1: 632 new_cat = CATEGORIES[current_idx + 1] 633 col = self.get_cur_column(widget) 634 new_cat_view = self.side_view[new_cat["id"]] 635 model = new_cat_view.get_model() 636 iter = model.get_iter_first() 637 while iter is not None: 638 path = model.get_path(iter) 639 c = new_cat_view.get_item_column(path) 640 d = abs(c - col) 641 if d < dist: 642 sel = path 643 dist = d 644 iter = model.iter_next(iter) 645 self.reposition_new_cat(sel, new_cat_view) 646 ret = True 647 elif direction == Gtk.DirectionType.UP and current_idx > 0: 648 new_cat = CATEGORIES[current_idx - 1] 649 col = self.get_cur_column(widget) 650 new_cat_view = self.side_view[new_cat["id"]] 651 model = new_cat_view.get_model() 652 iter = model.get_iter_first() 653 while iter is not None: 654 path = model.get_path(iter) 655 c = new_cat_view.get_item_column(path) 656 d = abs(c - col) 657 if d <= dist: 658 sel = path 659 dist = d 660 iter = model.iter_next(iter) 661 self.reposition_new_cat(sel, new_cat_view) 662 ret = True 663 return ret 664 665 def button_press(self, widget, event, category): 666 if event.button == 1: 667 self.side_view_nav(widget, None, category) 668 669 def anyVisibleInCategory(self, category): 670 id = category["id"] 671 iter = self.storeFilter[id].get_iter_first() 672 visible = False 673 while iter is not None: 674 cat = self.storeFilter[id].get_value(iter, 3) 675 visible = cat == category["id"] 676 iter = self.storeFilter[id].iter_next(iter) 677 return visible 678 679 def setParentRefs (self, mod): 680 try: 681 mod._setParentRef(self.window) 682 except AttributeError: 683 pass 684 return True 685 686 def loadCheck (self, mod): 687 try: 688 return mod._loadCheck() 689 except: 690 return True 691 692 def back_to_icon_view(self, widget): 693 self.window.set_title(_("System Settings")) 694 self.window.set_icon_name("preferences-desktop") 695 self.window.resize(WIN_WIDTH, WIN_HEIGHT) 696 children = self.content_box.get_children() 697 for child in children: 698 child.hide() 699 if child.get_name() == "c_box": 700 c_widgets = child.get_children() 701 for c_widget in c_widgets: 702 c_widget.hide() 703 self.main_stack.set_visible_child_name("side_view_page") 704 self.header_stack.set_visible_child_name("side_view") 705 self.search_entry.grab_focus() 706 707 if self.current_sidepage.module and hasattr(self.current_sidepage.module, "on_navigate_out_of_module"): 708 self.current_sidepage.module.on_navigate_out_of_module() 709 710 self.current_sidepage = None 711 712 def quit(self, *args): 713 self.window.destroy() 714 Gtk.main_quit() 715 716 717if __name__ == "__main__": 718 setproctitle("cinnamon-settings") 719 import signal 720 721 ps = proxygsettings.get_proxy_settings() 722 if ps: 723 proxy = urllib.ProxyHandler(ps) 724 else: 725 proxy = urllib.ProxyHandler() 726 urllib.install_opener(urllib.build_opener(proxy)) 727 728 window = MainWindow() 729 signal.signal(signal.SIGINT, signal.SIG_DFL) 730 Gtk.main() 731