def __create_hostinfo(self): label_top = WINDOW_HEIGHT - MAIN_PADDING - HOSTINFO_LABEL_HEIGHT utils_gui.create_separator(container=self.container, x=0, y=label_top - MAIN_PADDING, w=SIDEBAR_WIDTH, h=1, orientation=Gtk.Orientation.HORIZONTAL) hostname_label = Gtk.Label() hostname_label.set_size_request(SIDEBAR_WIDTH, HOSTINFO_LABEL_HEIGHT) hostname_label.set_ellipsize(Pango.EllipsizeMode.END) hostname_label.set_justify(Gtk.Justification.CENTER) hostname_label.set_alignment(0.5, 0.5) hostname_label.set_use_markup(True) # FIXME: "big" and "small" are not good sizes, we need to be explicit hostname_label.set_markup( '<big>{0}</big>\n<small><a href="{3}" title="{4}">{1}</a> ({2})</small>' .format( utils.get_file_contents('/etc/puavo/hostname'), utils.get_file_contents('/etc/puavo-image/release'), utils.get_file_contents('/etc/puavo/hosttype'), '', utils.localize(STRINGS['sb_changelog_title'], self.__settings.language))) hostname_label.connect('activate-link', self.__clicked_changelog) hostname_label.show() self.container.put(hostname_label, 0, label_top)
def __create_buttons(self): utils_gui.create_separator(container=self.container, x=0, y=MAIN_PADDING + USER_AVATAR_SIZE + MAIN_PADDING, w=SIDEBAR_WIDTH, h=-1, orientation=Gtk.Orientation.HORIZONTAL) y = MAIN_PADDING + USER_AVATAR_SIZE + MAIN_PADDING * 2 + SEPARATOR_SIZE # Since Python won't let you modify arguments (no pass-by-reference), # each of these returns the next Y position. X coordinates are fixed. if not (self.__settings.is_guest or self.__settings.is_webkiosk): y = self.__create_button(y, SB_CHANGE_PASSWORD) y = self.__create_button(y, SB_SUPPORT) y = self.__create_button(y, SB_SYSTEM_SETTINGS) y = self.__create_separator(y) if not (self.__settings.is_guest or self.__settings.is_webkiosk): y = self.__create_button(y, SB_LOCK_SCREEN) if not (self.__settings.is_fatclient or self.__settings.is_webkiosk): y = self.__create_button(y, SB_SLEEP_MODE) y = self.__create_button(y, SB_LOGOUT) y = self.__create_separator(y) y = self.__create_button(y, SB_RESTART) if not self.__settings.is_webkiosk: y = self.__create_button(y, SB_SHUTDOWN) logging.info('Support page URL: "%s"', SB_SUPPORT['command']['args'])
def __create_separator(self, y): padding = 20 utils_gui.create_separator(container=self.container, x=padding, y=y + MAIN_PADDING, w=SIDEBAR_WIDTH - padding * 2, h=-1, orientation=Gtk.Orientation.HORIZONTAL) # the next available Y coordinate return y + MAIN_PADDING * 2 + SEPARATOR_SIZE
def __init__(self, settings): """This is where the magic happens.""" start_time = time.clock() super().__init__() self.__settings = settings # Ensure the window is not visible until it's ready self.set_visible(False) self.set_type_hint(Gtk.WindowType.TOPLEVEL) # ---------------------------------------------------------------------- # Set up a domain socket for show/hide messages from the panel # button shell extension. This is done early, because if it # fails, we simply exit. We can't do *anything* without it. # (The only other choice would be to always display the menu, # never allowing the user to hide it because without the socket # it cannot be reopened.) try: # Clean leftovers os.unlink(self.__settings.socket) except OSError: pass try: self.__socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) self.__socket.bind(self.__settings.socket) except Exception as exception: # Oh dear... logging.error('Unable to create a domain socket for IPC!') logging.error('Reason: %s', str(exception)) logging.error('Socket name: "%s"', self.__settings.socket) logging.error('This is a fatal error, stopping here.') syslog.syslog(syslog.LOG_CRIT, 'PuavoMenu IPC socket "%s" creation failed: %s', self.__settings.socket, str(exception)) syslog.syslog(syslog.LOG_CRIT, 'PuavoMenu stops here. Contact Opinsys support.') exit(1) # Start listening the socket. Use glib's watch functions, then # we don't need to use threads (which work too, but are harder # to clean up and we'll run into problems with multihtreaded # xlib programs). # https://developer.gnome.org/pygobject/stable/glib-functions.html# # function-glib--io-add-watch GLib.io_add_watch(self.__socket, GLib.IO_IN, self.__socket_watcher) # ---------------------------------------------------------------------- # Exit stuff. By default the program cannot be exited normally. self.__exit_permitted = False self.connect('delete-event', self.__try_exit) # Program, menu and category data self.menudata = None # Current category (index to menudata.category_index) self.current_category = -1 # The current menu, if any (None if on category top-level) self.current_menu = None # The current menu/program buttons in the current # category and menu, if any self.__buttons = [] # Storage for 48x48 -pixel program and menu icons. Maintained # separately from the menu data. self.__icons = iconcache.IconCache(48, 48 * 20) # Background image for top-level menus try: if self.__settings.dark_theme: image_name = 'folder_dark.png' else: image_name = 'folder.png' # WARNING: Hardcoded image size! self.__menu_background = \ utils_gui.load_image_at_size(self.__settings.res_dir + image_name, 150, 110) except Exception as exception: logging.error("Can't load the menu background image: %s", str(exception)) self.__menu_background = None # ---------------------------------------------------------------------- # Create the window elements # Set window style if self.__settings.prod_mode: self.set_skip_taskbar_hint(True) self.set_skip_pager_hint(True) self.set_deletable(False) # no close button self.set_decorated(False) else: # makes developing slightly easier self.set_skip_taskbar_hint(False) self.set_skip_pager_hint(False) self.set_deletable(True) self.set_decorated(True) self.__exit_permitted = True # Don't mess with the real menu when running in development mode if self.__settings.prod_mode: self.set_title('PuavoMenuUniqueName') else: self.set_title('DevModePuavoMenu') self.set_resizable(False) self.set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT) self.set_position(Gtk.WindowPosition.CENTER) # Top-level container for all widgets. This is needed, because # a window can contain only one child widget and we can have # dozens of them. Every widget has a fixed position and size, # because the menu isn't user-resizable. self.__main_container = Gtk.Fixed() self.__main_container.set_size_request(WINDOW_WIDTH, WINDOW_HEIGHT) if not self.__settings.prod_mode: # Create the devtools popup menu self.menu_signal = \ self.connect('button-press-event', self.__devtools_menu) # ---------------------------------------------------------------------- # Menus/programs list # TODO: Gtk.Notebook isn't the best choice for this # Category tabs self.__category_buttons = Gtk.Notebook() self.__category_buttons.set_size_request(CATEGORIES_WIDTH, -1) self.__category_buttons.connect('switch-page', self.__clicked_category) self.__main_container.put(self.__category_buttons, PROGRAMS_LEFT, MAIN_PADDING) # The back button self.__back_button = Gtk.Button() self.__back_button.set_label('<<') self.__back_button.connect('clicked', self.__clicked_back_button) self.__back_button.set_size_request(BACK_BUTTON_WIDTH, -1) self.__main_container.put(self.__back_button, BACK_BUTTON_X, BACK_BUTTON_Y) # The search box # filter unwanted characters from queries self.__translation_table = \ dict.fromkeys(map(ord, "*^?{[]}/\\_+=\"\'#%&()'`@$<>|~"), None) self.__search = Gtk.SearchEntry() self.__search.set_size_request(SEARCH_WIDTH, SEARCH_HEIGHT) self.__search.set_max_length(10) # you won't need more than this self.__search_changed_signal = \ self.__search.connect('changed', self.__do_search) self.__search_keypress_signal = \ self.__search.connect('key-press-event', self.__search_keypress) self.__search.set_placeholder_text( utils.localize(STRINGS['search_placeholder'], self.__settings.language)) self.__main_container.put( self.__search, PROGRAMS_LEFT + PROGRAMS_WIDTH - SEARCH_WIDTH - MAIN_PADDING, MAIN_PADDING) # Menu label and description self.__menu_title = Gtk.Label() self.__menu_title.set_size_request(PROGRAMS_WIDTH, -1) self.__menu_title.set_ellipsize(Pango.EllipsizeMode.END) self.__menu_title.set_justify(Gtk.Justification.CENTER) self.__menu_title.set_alignment(0.0, 0.0) self.__menu_title.set_use_markup(True) self.__main_container.put( self.__menu_title, BACK_BUTTON_X + BACK_BUTTON_WIDTH + MAIN_PADDING, BACK_BUTTON_Y + 3) # The main programs list self.__programs_container = Gtk.ScrolledWindow() self.__programs_container.set_size_request(PROGRAMS_WIDTH, PROGRAMS_HEIGHT) self.__programs_container.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.__programs_container.set_shadow_type(Gtk.ShadowType.NONE) self.__programs_icons = Gtk.Fixed() self.__programs_container.add_with_viewport(self.__programs_icons) self.__main_container.put(self.__programs_container, PROGRAMS_LEFT, PROGRAMS_TOP) # Placeholder for empty categories, menus and search results self.__empty = Gtk.Label() self.__empty.set_size_request(PROGRAMS_WIDTH, PROGRAMS_HEIGHT) self.__empty.set_use_markup(True) self.__empty.set_justify(Gtk.Justification.CENTER) self.__empty.set_alignment(0.5, 0.5) self.__main_container.put(self.__empty, PROGRAMS_LEFT, PROGRAMS_TOP) # Faves list self.__faves_sep = Gtk.Separator( orientation=Gtk.Orientation.HORIZONTAL) self.__faves_sep.set_size_request(PROGRAMS_WIDTH, 1) self.__main_container.put( self.__faves_sep, PROGRAMS_LEFT, PROGRAMS_TOP + PROGRAMS_HEIGHT + MAIN_PADDING) self.__faves = faves.FavesList(self, self.__settings) self.__faves.set_size_request(PROGRAMS_WIDTH, PROGRAM_BUTTON_HEIGHT + 2) self.__main_container.put(self.__faves, PROGRAMS_LEFT, FAVES_TOP) # ---------------------------------------------------------------------- # The sidebar: the user avatar, buttons, host infos utils_gui.create_separator(container=self.__main_container, x=SIDEBAR_LEFT - MAIN_PADDING, y=MAIN_PADDING, w=1, h=WINDOW_HEIGHT - (MAIN_PADDING * 2), orientation=Gtk.Orientation.VERTICAL) self.__sidebar = sidebar.Sidebar(self, self.__settings) self.__main_container.put(self.__sidebar.container, SIDEBAR_LEFT, SIDEBAR_TOP) # ---------------------------------------------------------------------- # Setup GTK signal handlers # Listen for Esc keypresses for manually hiding the window self.__main_keypress_signal = \ self.connect('key-press-event', self.__check_for_esc) self.__focus_signal = None if not self.__settings.autohide: # Keep the window on top of everything and show it self.set_visible(True) self.set_keep_above(True) else: # In auto-hide mode, hide the window when it loses focus self.enable_out_of_focus_hide() self.__search.connect('focus-out-event', self.__search_out) # ---------------------------------------------------------------------- # UI done self.add(self.__main_container) self.__main_container.show() # DO NOT CALL self.show_all() HERE, the window has hidden elements # that are shown/hidden on demand. And we don't even have any # menu data yet to show. end_time = time.clock() utils.log_elapsed_time('Window init time', start_time, end_time) # ---------------------------------------------------------------------- # Load menu data # Finally, load the menu data and show the UI self.load_menu_data() # This is a bad, bad situation that should never happen in production. # It will happen one day. if self.menudata is None or len(self.menudata.programs) == 0: if self.__settings.prod_mode: self.__show_empty_message(STRINGS['menu_no_data_at_all_prod']) else: self.__show_empty_message(STRINGS['menu_no_data_at_all_dev'])