def show_gui(): pycam.Utils.set_application_key("pycam-gtk") deps_gtk = GuiCommon.requirements_details_gtk() report_gtk = GuiCommon.get_dependency_report(deps_gtk, prefix="\t") if GuiCommon.check_dependencies(deps_gtk): from pycam.Gui.Project import ProjectGui gui_class = ProjectGui else: full_report = [] full_report.append("PyCAM dependency problem") full_report.append("Error: Failed to load the GTK interface.") full_report.append("Details:") full_report.append(report_gtk) full_report.append("") full_report.append("Detailed list of requirements: %s" % GuiCommon.REQUIREMENTS_LINK) log.critical(os.linesep.join(full_report)) return EXIT_CODES["requirements"] event_manager = get_event_handler() event_manager.set("history", DataHistory()) with merge_history_and_block_events(event_manager): gui = gui_class(event_manager) # initialize plugins plugin_manager = pycam.Plugins.PluginManager(core=event_manager) plugin_manager.import_plugins() # some more initialization gui.reset_preferences() gui.load_preferences() gui.load_startup_workspace() event_manager.emit_event("notify-initialization-finished") # open the GUI get_mainloop(use_gtk=True).run() # optionally save workspace (based on configuration or dialog response) if event_manager.get("save_workspace_on_exit") == QuestionStatus.ASK.value: response = gui.get_question_response("Save Workspace?", True, allow_memorize=True) if response.should_memorize: event_manager.set("save_workspace_on_exit", (QuestionStatus.YES if response.is_yes else QuestionStatus.NO).value) should_store = response.is_yes elif event_manager.get( "save_workspace_on_exit") == QuestionStatus.YES.value: should_store = True else: should_store = False if should_store: gui.save_startup_workspace() gui.save_preferences() # no error -> return no error code return None
def initialize_multiprocessing(self, widget=None): complete_area = self.gui.get_object("MultiprocessingFrame") # prevent any further actions while the connection is established complete_area.set_sensitive(False) # wait for the above "set_sensitive" to finish get_mainloop().update() enable_parallel = self.enable_parallel_processes.get_active() enable_server_obj = self.gui.get_object("EnableServerMode") enable_server = enable_server_obj.get_active() remote_host = self.gui.get_object("RemoteServerHostname").get_text() if remote_host: remote_port = int(self.server_port_remote_obj.get_value()) remote = "%s:%s" % (remote_host, remote_port) else: remote = None local_port = int(self.server_port_local_obj.get_value()) auth_key = self.auth_key_obj.get_text() auth_key = None if auth_key is None else auth_key.encode("utf-8") if not auth_key and enable_parallel and enable_server: self.log.error( "You need to provide a password for this connection.") enable_server_obj.set_active(False) elif enable_parallel: if enable_server and \ (pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS): if self.number_of_processes.get_value() > 0: self.log.warn( "Mixed local and remote processes are currently not available " "on the Windows platform. Setting the number of local processes " "to zero.") self.number_of_processes.set_value(0) self.number_of_processes.set_sensitive(False) else: self.number_of_processes.set_sensitive(True) num_of_processes = int(self.number_of_processes.get_value()) error = pycam.Utils.threading.init_threading( number_of_processes=num_of_processes, enable_server=enable_server, remote=remote, server_credentials=auth_key, local_port=local_port) if error: self.log.error("Failed to start server: %s", error) pycam.Utils.threading.cleanup() enable_server_obj.set_active(False) else: pycam.Utils.threading.cleanup() self.log.info("Multiprocessing disabled") # set the label of the "connect" button if enable_server_obj.get_active(): info = self._gtk.stock_lookup(self._gtk.STOCK_DISCONNECT) else: info = self._gtk.stock_lookup(self._gtk.STOCK_CONNECT) enable_server_obj.set_label(info.label) complete_area.set_sensitive(True) self.update_parallel_processes_settings()
def update(self, *args, **kwargs): mainloop = get_mainloop() if mainloop is None: return False mainloop.update() if self._progress: return self._progress.update(*args, **kwargs) else: return False
def __init__(self, event_manager): super().__init__(event_manager) self._event_handlers = [] self.gui_is_active = False self.gui = Gtk.Builder() self._mainloop_is_running = False self.mainloop = get_mainloop(use_gtk=True) gtk_build_file = get_ui_file_location(GTKBUILD_FILE) if gtk_build_file is None: raise InitializationError( "Failed to load GTK layout specification file: {}".format( gtk_build_file)) self.gui.add_from_file(gtk_build_file) if pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS: gtkrc_file = get_ui_file_location(GTKRC_FILE_WINDOWS) if gtkrc_file: Gtk.rc_add_default_file(gtkrc_file) Gtk.rc_reparse_all_for_settings(Gtk.settings_get_default(), True) action_group = Gio.SimpleActionGroup() self.settings.set("gtk_action_group_prefix", "pycam") self.settings.set("gtk_action_group", action_group) self.window = self.gui.get_object("ProjectWindow") self.window.insert_action_group( self.settings.get("gtk_action_group_prefix"), self.settings.get("gtk_action_group")) self.settings.set("main_window", self.window) # show stock items on buttons # increase the initial width of the window (due to hidden elements) self.window.set_default_size(400, -1) # initialize the RecentManager (TODO: check for Windows) if False and pycam.Utils.get_platform( ) == pycam.Utils.OSPlatform.WINDOWS: # The pyinstaller binary for Windows fails mysteriously when trying # to display the stock item. # Error message: Gtk:ERROR:gtkrecentmanager.c:1942:get_icon_fallback: # assertion failed: (retval != NULL) self.recent_manager = None else: try: self.recent_manager = Gtk.recent_manager_get_default() except AttributeError: # GTK 2.12.1 seems to have problems with "RecentManager" on # Windows. Sadly this is the version, that is shipped with the # "appunti" GTK packages for Windows (April 2010). # see http://www.daa.com.au/pipermail/pygtk/2009-May/017052.html self.recent_manager = None # file loading self.last_dirname = None self.last_model_uri = None # define callbacks and accelerator keys for the menu actions for objname, callback, data, accel_key in ( ("OpenModel", self.load_model_file, None, "<Control>o"), ("Quit", self.destroy, None, "<Control>q"), ("GeneralSettings", self.toggle_preferences_window, None, "<Control>p"), ("UndoButton", self.restore_undo_state, None, "<Control>z"), ("HelpIntroduction", self.show_help, "introduction", "F1"), ("HelpSupportedFormats", self.show_help, "supported-formats", None), ("HelpModelTransformations", self.show_help, "model-transformations", None), ("HelpProcessSettings", self.show_help, "process-settings", None), ("HelpBoundsSettings", self.show_help, "bounding-box", None), ("HelpTouchOff", self.show_help, "touch-off", None), ("Help3DView", self.show_help, "3d-view", None), ("HelpServerMode", self.show_help, "server-mode", None), ("HelpCommandLine", self.show_help, "cli-examples", None), ("HelpHotkeys", self.show_help, "keyboard-shortcuts", None), ("ProjectWebsite", self.show_help, "http://pycam.sourceforge.net", None), ("DevelopmentBlog", self.show_help, "http://fab.senselab.org/pycam", None), ("Forum", self.show_help, "http://sourceforge.net/projects/pycam/forums", None), ("BugTracker", self.show_help, "https://github.com/SebKuzminsky/pycam/issues/", None), ("FeatureRequest", self.show_help, "https://github.com/SebKuzminsky/pycam/issues/", None)): item = self.gui.get_object(objname) action = "activate" if data is None: item.connect(action, callback) else: item.connect(action, callback, data) if accel_key: key, mod = Gtk.accelerator_parse(accel_key) accel_path = "<pycam>/%s" % objname item.set_accel_path(accel_path) # Gtk.accel_map_change_entry(accel_path, key, mod, True) FIXME # LinkButton does not work on Windows: https://bugzilla.gnome.org/show_bug.cgi?id=617874 if pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS: def open_url(widget, data=None): webbrowser.open(widget.get_uri()) Gtk.link_button_set_uri_hook(open_url) # configure drag-n-drop for config files and models self.settings.set("configure-drag-drop-func", self.configure_drag_and_drop) self.settings.get("configure-drag-drop-func")(self.window) # other events self.window.connect("destroy", self.destroy) self.window.connect("delete-event", self.destroy) # the settings window self.gui.get_object("CloseSettingsWindow").connect( "clicked", self.toggle_preferences_window, False) self.gui.get_object("ResetPreferencesButton").connect( "clicked", self.reset_preferences) self.preferences_window = self.gui.get_object("GeneralSettingsWindow") self.preferences_window.connect("delete-event", self.toggle_preferences_window, False) self.preferences_window.insert_action_group( self.settings.get("gtk_action_group_prefix"), self.settings.get("gtk_action_group")) self._preferences_window_position = None self._preferences_window_visible = False # "about" window self.about_window = self.gui.get_object("AboutWindow") self.about_window.set_version(VERSION) self.about_window.insert_action_group( self.settings.get("gtk_action_group_prefix"), self.settings.get("gtk_action_group")) self.gui.get_object("About").connect("activate", self.toggle_about_window, True) # we assume, that the last child of the window is the "close" button # TODO: fix this ugly hack! about_window_children = self.gui.get_object( "AboutWindowButtons").get_children() if about_window_children: # it seems to be possible that there are no children - weird :( # see https://github.com/SebKuzminsky/pycam/issues/59 about_window_children[-1].connect("clicked", self.toggle_about_window, False) self.about_window.connect("delete-event", self.toggle_about_window, False) # menu bar uimanager = Gtk.UIManager() self.settings.set("gtk-uimanager", uimanager) self._accel_group = uimanager.get_accel_group() # send a "delete" event on "CTRL-w" for every window self._accel_group.connect( ord('w'), Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.LOCKED, lambda accel_group, window, *args: window.emit( "delete-event", Gdk.Event())) self._accel_group.connect(ord('q'), Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.LOCKED, lambda *args: self.destroy()) self.settings.add_item("gtk-accel-group", lambda: self._accel_group) for obj in self.gui.get_objects(): if isinstance(obj, Gtk.Window): obj.add_accel_group(self._accel_group) # preferences tab preferences_book = self.gui.get_object("PreferencesNotebook") def clear_preferences(): for child in preferences_book.get_children(): preferences_book.remove(child) def add_preferences_item(item, name): preferences_book.append_page(item, Gtk.Label(name)) self.settings.register_ui_section("preferences", add_preferences_item, clear_preferences) for obj_name, label, priority in (("GeneralSettingsPrefTab", "General", -50), ("ProgramsPrefTab", "Programs", 50)): obj = self.gui.get_object(obj_name) obj.unparent() self.settings.register_ui("preferences", label, obj, priority) # general preferences general_prefs = self.gui.get_object("GeneralPreferencesBox") def clear_general_prefs(): for item in general_prefs.get_children(): general_prefs.remove(item) def add_general_prefs_item(item, name): general_prefs.pack_start(item, expand=False, fill=False, padding=3) self.settings.register_ui_section("preferences_general", add_general_prefs_item, clear_general_prefs) # set defaults main_tab = self.gui.get_object("MainTabs") def clear_main_tab(): while main_tab.get_n_pages() > 0: main_tab.remove_page(0) def add_main_tab_item(item, name): main_tab.append_page(item, Gtk.Label(name)) # TODO: move these to plugins, as well self.settings.register_ui_section("main", add_main_tab_item, clear_main_tab) main_window = self.gui.get_object("WindowBox") def clear_main_window(): main_window.foreach(main_window.remove) def add_main_window_item(item, name, **extra_args): # some widgets may want to override the defaults args = {"expand": False, "fill": False, "padding": 3} args.update(extra_args) main_window.pack_start(item, **args) main_tab.unparent() self.settings.register_ui_section("main_window", add_main_window_item, clear_main_window) self.settings.register_ui("main_window", "Tabs", main_tab, -20, args_dict={ "expand": True, "fill": True }) def disable_gui(): self.menubar.set_sensitive(False) main_tab.set_sensitive(False) def enable_gui(): self.menubar.set_sensitive(True) main_tab.set_sensitive(True) # configure locations of external programs for auto_control_name, location_control_name, browse_button, key in ( ("ExternalProgramInkscapeAuto", "ExternalProgramInkscapeControl", "ExternalProgramInkscapeBrowse", "inkscape"), ("ExternalProgramPstoeditAuto", "ExternalProgramPstoeditControl", "ExternalProgramPstoeditBrowse", "pstoedit")): self.gui.get_object(auto_control_name).connect( "clicked", self._locate_external_program, key) location_control = self.gui.get_object(location_control_name) self.settings.add_item("external_program_%s" % key, location_control.get_text, location_control.set_text) self.gui.get_object(browse_button).connect( "clicked", self._browse_external_program_location, key) for objname, callback in ( ("ResetWorkspace", lambda widget: self.reset_workspace()), ("LoadWorkspace", lambda widget: self.load_workspace_dialog()), ("SaveWorkspace", lambda widget: self.save_workspace_dialog(self.last_workspace_uri) ), ("SaveAsWorkspace", lambda widget: self.save_workspace_dialog())): self.gui.get_object(objname).connect("activate", callback) # set the icons (in different sizes) for all windows # Gtk.window_set_default_icon_list(*get_icons_pixbuffers()) FIXME # load menu data gtk_menu_file = get_ui_file_location(GTKMENU_FILE) if gtk_menu_file is None: raise InitializationError( "Failed to load GTK menu specification file: {}".format( gtk_menu_file)) uimanager.add_ui_from_file(gtk_menu_file) # make the actions defined in the GTKBUILD file available in the menu actiongroup = Gtk.ActionGroup("menubar") for action in [ aobj for aobj in self.gui.get_objects() if isinstance(aobj, Gtk.Action) ]: actiongroup.add_action(action) # the "pos" parameter is optional since 2.12 - we can remove it later uimanager.insert_action_group(actiongroup) # the "recent files" sub-menu if self.recent_manager is not None: recent_files_menu = Gtk.RecentChooserMenu(self.recent_manager) recent_files_menu.set_name("RecentFilesMenu") recent_menu_filter = Gtk.RecentFilter() case_converter = pycam.Utils.get_case_insensitive_file_pattern for filter_name, patterns in FILTER_MODEL: if not isinstance(patterns, (list, set, tuple)): patterns = [patterns] # convert it into a mutable list (instead of set/tuple) patterns = list(patterns) for index in range(len(patterns)): patterns[index] = case_converter(patterns[index]) for pattern in patterns: recent_menu_filter.add_pattern(pattern) recent_files_menu.add_filter(recent_menu_filter) recent_files_menu.set_show_numbers(True) # non-local files (without "file://") are not supported. yet recent_files_menu.set_local_only(False) # most recent files to the top recent_files_menu.set_sort_type(Gtk.RECENT_SORT_MRU) # show only ten files recent_files_menu.set_limit(10) uimanager.get_widget("/MenuBar/FileMenu/OpenRecentModelMenu" ).set_submenu(recent_files_menu) recent_files_menu.connect("item-activated", self.load_recent_model_file) else: self.gui.get_object("OpenRecentModel").set_visible(False) # load the menubar and connect functions to its items self.menubar = uimanager.get_widget("/MenuBar") # dict of all merge-ids menu_merges = {} def clear_menu(menu_key): for merge in menu_merges.get(menu_key, []): uimanager.remove_ui(merge) def append_menu_item(menu_key, base_path, widget, name): merge_id = uimanager.new_merge_id() if widget: action_group = widget.props.action_group if action_group not in uimanager.get_action_groups(): uimanager.insert_action_group(action_group, -1) widget_name = widget.get_name() item_type = Gtk.UIManagerItemType.MENU else: widget_name = name item_type = Gtk.UIManagerItemType.SEPARATOR uimanager.add_ui(merge_id, base_path, name, widget_name, item_type, False) if menu_key not in menu_merges: menu_merges[menu_key] = [] menu_merges[menu_key].append(merge_id) def get_menu_funcs(menu_key, base_path): return (lambda widget, name: append_menu_item( menu_key, base_path, widget, name), lambda: clear_menu(menu_key)) for ui_name, base_path in (("view_menu", "/MenuBar/ViewMenu"), ("file_menu", "/MenuBar/FileMenu"), ("edit_menu", "/MenuBar/EditMenu"), ("export_menu", "/MenuBar/FileMenu/ExportMenu")): append_func, clear_func = get_menu_funcs(ui_name, base_path) self.settings.register_ui_section(ui_name, append_func, clear_func) self.settings.register_ui("file_menu", "Quit", self.gui.get_object("Quit"), 100) self.settings.register_ui("file_menu", "QuitSeparator", None, 95) self.settings.register_ui("main_window", "Main", self.menubar, -100) self.settings.set("set_last_filename", self.add_to_recent_file_list) self._event_handlers.extend(( ("history-changed", self._update_undo_button), ("model-change-after", "visual-item-updated"), ("gui-disable", disable_gui), ("gui-enable", enable_gui), ("notify-file-saved", self.add_to_recent_file_list), ("notify-file-opened", self.add_to_recent_file_list), )) for name, target in self._event_handlers: self.settings.register_event(name, target) # allow the task settings control to be updated self.mainloop.update() # register a logging handler for displaying error messages pycam.Utils.log.add_gtk_gui(self.window, logging.ERROR) self.window.show() self.mainloop.update()
# Otherwise it will be added later after writing the file. uri = pycam.Utils.URIHandler(filename) if uri.exists(): # skip this, if the recent manager is not available (e.g. GTK 2.12.1 on Windows) if self.recent_manager: if self.recent_manager.has_item(uri.get_url()): try: self.recent_manager.remove_item(uri.get_url()) except GObject.GError: pass self.recent_manager.add_item(uri.get_url()) # store the directory of the last loaded file if uri.is_local(): self.last_dirname = os.path.dirname(uri.get_local_path()) def get_meta_data(self): filename = "Filename: %s" % str(self.last_model_uri) timestamp = "Timestamp: %s" % str(datetime.datetime.now()) version = "Version: %s" % VERSION result = [] for text in (filename, timestamp, version): result.append("%s %s" % (self.META_DATA_PREFIX, text)) return os.linesep.join(result) if __name__ == "__main__": GUI = ProjectGui() if len(sys.argv) > 1: GUI.load_model_file(sys.argv[1]) get_mainloop().run()
def update(self, *args, **kwargs): get_mainloop().update() if self._progress: return self._progress.update(*args, **kwargs) else: return False
def update(self, text=None, percent=None): if text: self._last_text = text if percent: self._last_percent = percent if percent is not None: percent = min(max(percent, 0.0), 100.0) self._progress_bar.set_fraction(percent / 100.0) if (not percent) and (self._progress_bar.get_fraction() == 0): # use "pulse" mode until we reach 1% of the work to be done self._progress_bar.pulse() # update the GUI current_time = time.time() # Don't update the GUI more often than once per second. # Exception: text-only updates # This restriction improves performance and reduces the # "snappiness" of the GUI. if (self._last_gtk_events_time is None) \ or text \ or (self._last_gtk_events_time + 0.5 <= current_time): # "estimated time of arrival" text time_estimation_suffix = " remaining ..." if self._progress_bar.get_fraction() > 0: total_fraction = ( (self._progress_bar.get_fraction() + self._multi_counter) / max(1, self._multi_maximum)) total_fraction = max(0.0, min(total_fraction, 1.0)) eta_full = (time.time() - self._start_time) / total_fraction if eta_full > 0: eta_delta = eta_full - (time.time() - self._start_time) eta_delta = int(round(eta_delta)) if hasattr(self, "_last_eta_delta"): previous_eta_delta = self._last_eta_delta if eta_delta == previous_eta_delta + 1: # We are currently toggling between two numbers. # We want to avoid screen flicker, thus we just live # with the slight inaccuracy. eta_delta = self._last_eta_delta self._last_eta_delta = eta_delta eta_delta_obj = datetime.timedelta(seconds=eta_delta) eta_text = "%s%s" % (eta_delta_obj, time_estimation_suffix) else: eta_text = None else: eta_text = None if text is not None: lines = [text] else: old_lines = self._progress_bar.get_text().split(os.linesep) # skip the time estimation line lines = [ line for line in old_lines if not line.endswith(time_estimation_suffix) ] if eta_text: lines.append(eta_text) self._progress_bar.set_text(os.linesep.join(lines)) # show the "show_tool_button" ("hide" is called in the progress decorator) # TODO: move "in_progress" somewhere else if self.core.get("toolpath_in_progress"): self._progress_button.show() get_mainloop().update() if not text or (self._start_time + 5 < current_time): # We don't store the timining if the text was changed. # This is especially nice for the snappines during font # initialization. This exception is only valid for the first # five seconds of the operation. self._last_gtk_events_time = current_time # return if the user requested a break return self._cancel_requested
def destroy(self, widget=None, data=None): get_mainloop().stop()