From 70e5e9ca4bb97feda6897cd40b3b5c97cc46f2ad Mon Sep 17 00:00:00 2001
From: necoro <>
Date: Sun, 11 Feb 2007 01:14:30 +0000
Subject: Added usetips Should have fixed segfaults with popups
---
portato/gui/gtk/TreeViewTooltips.py | 423 +++++++++++++++++++++++++++
portato/gui/gtk/glade/portato.glade | 557 ++++++++++++++++++++----------------
portato/gui/gtk/usetips.py | 76 +++++
portato/gui/gtk/windows.py | 57 ++--
4 files changed, 837 insertions(+), 276 deletions(-)
create mode 100644 portato/gui/gtk/TreeViewTooltips.py
create mode 100644 portato/gui/gtk/usetips.py
(limited to 'portato/gui/gtk')
diff --git a/portato/gui/gtk/TreeViewTooltips.py b/portato/gui/gtk/TreeViewTooltips.py
new file mode 100644
index 0000000..1112d3e
--- /dev/null
+++ b/portato/gui/gtk/TreeViewTooltips.py
@@ -0,0 +1,423 @@
+# Copyright (c) 2006, Daniel J. Popowich
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# Send bug reports and contributions to:
+#
+# dpopowich AT astro dot umass dot edu
+#
+
+'''
+TreeViewTooltips.py
+
+Provides TreeViewTooltips, a class which presents tooltips for cells,
+columns and rows in a gtk.TreeView.
+
+------------------------------------------------------------
+ This file includes a demo. Just execute the file:
+
+ python TreeViewTooltips.py
+------------------------------------------------------------
+
+To use, first subclass TreeViewTooltips and implement the get_tooltip()
+method; see below. Then add any number of gtk.TreeVew widgets to a
+TreeViewTooltips instance by calling the add_view() method. Overview
+of the steps:
+
+ # 1. subclass TreeViewTooltips
+ class MyTooltips(TreeViewTooltips):
+
+ # 2. overriding get_tooltip()
+ def get_tooltip(...):
+ ...
+
+ # 3. create an instance
+ mytips = MyTooltips()
+
+ # 4. Build up your gtk.TreeView.
+ myview = gtk.TreeView()
+ ...# create columns, set the model, etc.
+
+ # 5. Add the view to the tooltips
+ mytips.add_view(myview)
+
+How it works: the add_view() method connects the TreeView to the
+"motion-notify" event with the callback set to a private method.
+Whenever the mouse moves across the TreeView the callback will call
+get_tooltip() with the following arguments:
+
+ get_tooltip(view, column, path)
+
+where,
+
+ view: the gtk.TreeView instance.
+ column: the gtk.TreeViewColumn instance that the mouse is
+ currently over.
+ path: the path to the row that the mouse is currently over.
+
+Based on whether or not column and path are checked for specific
+values, get_tooltip can return tooltips for a cell, column, row or the
+whole view:
+
+ Column Checked Path Checked Tooltip For...
+ Y Y cell
+ Y N column
+ N Y row
+ N N view
+
+get_tooltip() should return None if no tooltip should be displayed.
+Otherwise the return value will be coerced to a string (with the str()
+builtin) and stripped; if non-empty, the result will be displayed as
+the tooltip. By default, the tooltip popup window will be displayed
+centered and just below the pointer and will remain shown until the
+pointer leaves the cell (or column, or row, or view, depending on how
+get_tooltip() is implemented).
+
+'''
+
+
+import pygtk
+pygtk.require('2.0')
+
+import gtk
+import gtk.gdk
+import gobject
+
+if gtk.gtk_version < (2, 8):
+ import warnings
+
+ msg = ('''This module was developed and tested with version 2.8.18 of gtk. You are using version %d.%d.%d. Your milage may vary.'''
+ % gtk.gtk_version)
+ warnings.warn(msg)
+
+
+# major, minor, patch
+version = 1, 0, 0
+
+class TreeViewTooltips:
+
+ def __init__(self):
+
+ '''
+ Initialize the tooltip. After initialization there are two
+ attributes available for advanced control:
+
+ window: the popup window that holds the tooltip text, an
+ instance of gtk.Window.
+ label: a gtk.Label that is packed into the window. The
+ tooltip text is set in the label with the
+ set_label() method, so the text can be plain or
+ markup text.
+
+ Be default, the tooltip is enabled. See the enabled/disabled
+ methods.
+ '''
+
+ # create the window
+ self.window = window = gtk.Window(gtk.WINDOW_POPUP)
+ window.set_name('gtk-tooltips')
+ window.set_resizable(False)
+ window.set_border_width(4)
+ window.set_app_paintable(True)
+ window.connect("expose-event", self.__on_expose_event)
+
+
+ # create the label
+ self.label = label = gtk.Label()
+ label.set_line_wrap(True)
+ label.set_alignment(0.5, 0.5)
+ label.set_use_markup(True)
+ label.show()
+ window.add(label)
+
+ # by default, the tooltip is enabled
+ self.__enabled = True
+ # saves the current cell
+ self.__save = None
+ # the timer id for the next tooltip to be shown
+ self.__next = None
+ # flag on whether the tooltip window is shown
+ self.__shown = False
+
+ def enable(self):
+ 'Enable the tooltip'
+
+ self.__enabled = True
+
+ def disable(self):
+ 'Disable the tooltip'
+
+ self.__enabled = False
+
+ def __show(self, tooltip, x, y):
+
+ '''show the tooltip popup with the text/markup given by
+ tooltip.
+
+ tooltip: the text/markup for the tooltip.
+ x, y: the coord. (root window based) of the pointer.
+ '''
+
+ window = self.window
+
+ # set label
+ self.label.set_label(tooltip)
+ # resize window
+ w, h = window.size_request()
+ # move the window
+ window.move(*self.location(x,y,w,h))
+ # show it
+ window.show()
+ self.__shown = True
+
+ def __hide(self):
+ 'hide the tooltip'
+
+ self.__queue_next()
+ self.window.hide()
+ self.__shown = False
+
+ def __leave_handler(self, view, event):
+ 'when the pointer leaves the view, hide the tooltip'
+
+ self.__hide()
+
+ def __motion_handler(self, view, event):
+ 'As the pointer moves across the view, show a tooltip.'
+
+ path = view.get_path_at_pos(int(event.x), int(event.y))
+
+ if self.__enabled and path:
+ path, col, x, y = path
+ tooltip = self.get_tooltip(view, col, path)
+ if tooltip is not None:
+ tooltip = str(tooltip).strip()
+ if tooltip:
+ self.__queue_next((path, col), tooltip,
+ int(event.x_root),
+ int(event.y_root))
+ return
+
+ self.__hide()
+
+ def __queue_next(self, *args):
+
+ 'queue next request to show a tooltip'
+
+ # if args is non-empty it means a request was made to show a
+ # tooltip. if empty, no request is being made, but any
+ # pending requests should be cancelled anyway.
+
+ cell = None
+
+ # if called with args, break them out
+ if args:
+ cell, tooltip, x, y = args
+
+ # if it's the same cell as previously shown, just return
+ if self.__save == cell:
+ return
+
+ # if we have something queued up, cancel it
+ if self.__next:
+ gobject.source_remove(self.__next)
+ self.__next = None
+
+ # if there was a request...
+ if cell:
+ # if the tooltip is already shown, show the new one
+ # immediately
+ if self.__shown:
+ self.__show(tooltip, x, y)
+ # else queue it up in 1/2 second
+ else:
+ self.__next = gobject.timeout_add(500, self.__show,
+ tooltip, x, y)
+
+ # save this cell
+ self.__save = cell
+
+
+ def __on_expose_event(self, window, event):
+
+ # this magic is required so the window appears with a 1-pixel
+ # black border (default gtk Style). This code is a
+ # transliteration of the C implementation of gtk.Tooltips.
+ w, h = window.size_request()
+ window.style.paint_flat_box(window.window, gtk.STATE_NORMAL,
+ gtk.SHADOW_OUT, None, window,
+ 'tooltip', 0, 0, w, h)
+
+ def location(self, x, y, w, h):
+
+ '''Given the x,y coordinates of the pointer and the width and
+ height (w,h) demensions of the tooltip window, return the x, y
+ coordinates of the tooltip window.
+
+ The default location is to center the window on the pointer
+ and 4 pixels below it.
+ '''
+
+ return x - w/2, y + 4
+
+ def add_view(self, view):
+
+ 'add a gtk.TreeView to the tooltip'
+
+ assert isinstance(view, gtk.TreeView), \
+ ('This handler should only be connected to '
+ 'instances of gtk.TreeView')
+
+ view.connect("motion-notify-event", self.__motion_handler)
+ view.connect("leave-notify-event", self.__leave_handler)
+
+ def get_tooltip(self, view, column, path):
+ 'See the module doc string for a description of this method'
+
+ raise NotImplemented, 'Subclass must implement get_tooltip()'
+
+
+if __name__ == '__main__':
+
+ ############################################################
+ # DEMO
+ ############################################################
+
+ # First, subclass TreeViewTooltips
+
+ class DemoTips(TreeViewTooltips):
+
+ def __init__(self, customer_column):
+ # customer_column is an instance of gtk.TreeViewColumn and
+ # is being used in the gtk.TreeView to show customer names.
+ self.cust_col = customer_column
+
+ # call base class init
+ TreeViewTooltips.__init__(self)
+
+ def get_tooltip(self, view, column, path):
+
+ # we have a two column view: customer, phone; we'll make
+ # tooltips cell-based for the customer column, but generic
+ # column-based for the phone column.
+
+ # customer
+ if column is self.cust_col:
+
+ # By checking both column and path we have a
+ # cell-based tooltip.
+ model = view.get_model()
+ customer = model[path][2]
+ return '%s %s\n%s' % (customer.fname,
+ customer.lname,
+ customer.notes)
+ # phone
+ else:
+ return ('Generic Column Tooltip\n'
+ 'Unless otherwise noted, all\narea codes are 888')
+
+ def XX_location(self, x, y, w, h):
+ # rename me to "location" so I override the base class
+ # method. This will demonstrate being able to change
+ # where the tooltip window popups, relative to the
+ # pointer.
+
+ # this will place the tooltip above and to the right
+ return x + 10, y - (h + 10)
+
+ # Here's our customer
+ class Customer:
+
+ def __init__(self, fname, lname, phone, notes):
+ self.fname = fname
+ self.lname = lname
+ self.phone = phone
+ self.notes = notes
+
+ # create a bunch of customers
+ customers = []
+ for fname, lname, phone, notes in [
+ ('Joe', 'Schmoe', '555-1212', 'Likes to Morris dance.'),
+ ('Jane', 'Doe', '555-2323',
+ 'Wonders what the hell\nMorris dancing is.'),
+ ('Phred', 'Phantastic', '900-555-1212', 'Dreams of Betty.'),
+ ('Betty', 'Boop', '555-3434', 'Dreams in b&w.'),
+ ('Red Sox', 'Fan', '555-4545',
+ "Still livin' 2004!\nEspecially after 2006.")]:
+ customers.append(Customer(fname, lname, phone, notes))
+
+ # Build our model and view
+ model = gtk.ListStore(str, str, object)
+ for c in customers:
+ model.append(['%s %s' % (c.fname, c.lname), c.phone, c])
+
+ view = gtk.TreeView(model)
+ view.get_selection().set_mode(gtk.SELECTION_NONE)
+
+ # two columns, name and phone
+ cell = gtk.CellRendererText()
+ cell.set_property('xpad', 20)
+ namecol = gtk.TreeViewColumn('Customer Name', cell, text=0)
+ namecol.set_min_width(200)
+ view.append_column(namecol)
+
+ cell = gtk.CellRendererText()
+ phonecol = gtk.TreeViewColumn('Phone', cell, text=1)
+ view.append_column(phonecol)
+
+ # finally, connect the tooltip, specifying the name column as the
+ # column we want the tooltip to popup over.
+ tips = DemoTips(namecol)
+ tips.add_view(view)
+
+ # We're going to demonstrate enable/disable. First we need a
+ # callback function to connect to the toggled signal.
+ def toggle(button):
+ if button.get_active():
+ tips.disable()
+ else:
+ tips.enable()
+
+ # create a checkbutton and connect our handler
+ check = gtk.CheckButton('Check to disable view tooltips')
+ check.connect('toggled', toggle)
+
+ # a standard gtk.Tooltips to compare to
+ tt = gtk.Tooltips()
+ tt.set_tip(check, ('This is a standard gtk tooltip.\n'
+ 'Compare me to the tooltips above.'))
+
+ # create a VBox to pack the view and checkbutton
+ vbox = gtk.VBox()
+ vbox.pack_start(view)
+ vbox.pack_start(check, False)
+ vbox.show_all()
+
+ # pack the vbox into a simple dialog and run it
+ dialog = gtk.Dialog('TreeViewTooltips Demo')
+ close = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_NONE)
+
+ # add a tooltip for the close button
+ tt.set_tip(close, 'Click to end the demo.')
+
+ dialog.set_default_size(400,400)
+ dialog.vbox.pack_start(vbox)
+ dialog.run()
diff --git a/portato/gui/gtk/glade/portato.glade b/portato/gui/gtk/glade/portato.glade
index 7307dbc..9dc48c4 100644
--- a/portato/gui/gtk/glade/portato.glade
+++ b/portato/gui/gtk/glade/portato.glade
@@ -90,19 +90,30 @@
-
-
@@ -141,6 +163,76 @@
+
+
+
+
+
+
+
+
+
+
@@ -1079,54 +1186,4 @@
-
-
diff --git a/portato/gui/gtk/usetips.py b/portato/gui/gtk/usetips.py
new file mode 100644
index 0000000..6611e09
--- /dev/null
+++ b/portato/gui/gtk/usetips.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+#
+# File: portato/gui/gtk/usetips.py
+# This file is part of the Portato-Project, a graphical portage-frontend.
+#
+# Copyright (C) 2007 René 'Necoro' Neumann
+# This is free software. You may redistribute copies of it under the terms of
+# the GNU General Public License version 2.
+# There is NO WARRANTY, to the extent permitted by law.
+#
+# Written by René 'Necoro' Neumann
+
+from portato.backend import Package
+from portato.backend.flags import invert_use_flag
+
+from TreeViewTooltips import TreeViewTooltips
+
+class UseTips (TreeViewTooltips):
+ """This class handles the display of the so called use-tips,
+ i.e. the tooltips showing the actual use-flags."""
+
+ def __init__ (self, colno, cfg = None):
+ """Constructor.
+
+ @param colno: the number of the column to check
+ @type colno: int
+ @param cfg: a config to look in, whether we should show the tips or not
+ @type cfg: Config"""
+
+ self.colno = colno
+ self.cfg = cfg
+
+ TreeViewTooltips.__init__(self)
+
+ def get_tooltip(self, view, column, path):
+
+ # check config
+ if self.cfg is not None:
+ if not self.cfg.get_boolean("useTips_opt", section = self.cfg.const["gtk_sec"]):
+ return None
+
+ store = view.get_model()
+ it = store.get_iter(path)
+
+ if store.iter_parent(it) is not None:
+ return self.__get_flags(store.get_value(it, self.colno))
+ else: # top items - ignore them
+ return None
+
+ def __get_flags(self, cpv):
+ pkg = Package(cpv)
+ enabled = []
+ disabled = []
+
+ pkg_flags = pkg.get_all_use_flags()
+ if len(pkg_flags) == 0: # no flags - stop here
+ return None
+
+ pkg_flags.sort()
+ for use in pkg_flags:
+ if pkg.is_use_flag_enabled(use):
+ enabled.append(use)
+ else:
+ disabled.append(use)
+
+ string = ""
+
+ if len(enabled) > 0:
+ string = "+"+"\n+".join(enabled)+""
+ if len(disabled) > 0:
+ string = string + "\n"
+
+ if len(disabled) > 0:
+ string = string+"- " + "\n- ".join(disabled) + ""
+
+ return string
diff --git a/portato/gui/gtk/windows.py b/portato/gui/gtk/windows.py
index fb663e0..64ff8a1 100644
--- a/portato/gui/gtk/windows.py
+++ b/portato/gui/gtk/windows.py
@@ -28,12 +28,14 @@ from portato.backend.exceptions import *
from portato.gui.gui_helper import Database, Config, EmergeQueue
from dialogs import *
from wrapper import GtkTree, GtkConsole
+from usetips import UseTips
# for the terminal
import vte
# other
from portage_util import unique_array
+import types
GLADE_FILE = DATA_DIR+"portato.glade"
@@ -171,6 +173,7 @@ class PreferenceWindow (AbstractDialog):
"newUseCheck" : "newuse_opt",
"maskPerVersionCheck" : "maskPerVersion_opt",
"usePerVersionCheck" : "usePerVersion_opt",
+ "useTipsCheck" : ("useTips_opt", "gtk_sec"),
"testPerVersionCheck" : "testingPerVersion_opt"
}
@@ -201,8 +204,13 @@ class PreferenceWindow (AbstractDialog):
hintEB.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#f3f785"))
for box in self.checkboxes:
- self.tree.get_widget(box).\
- set_active(self.cfg.get_boolean(self.checkboxes[box]))
+ val = self.checkboxes[box]
+ if type(val) == types.TupleType:
+ self.tree.get_widget(box).\
+ set_active(self.cfg.get_boolean(val[0], section = self.cfg.const[val[1]]))
+ else:
+ self.tree.get_widget(box).\
+ set_active(self.cfg.get_boolean(val))
for edit in self.edits:
self.tree.get_widget(edit).\
@@ -214,7 +222,11 @@ class PreferenceWindow (AbstractDialog):
"""Sets all options in the Config-instance."""
for box in self.checkboxes:
- self.cfg.set_boolean(self.checkboxes[box], self.tree.get_widget(box).get_active())
+ val = self.checkboxes[box]
+ if type(val) == types.TupleType:
+ self.cfg.set_boolean(val[0], self.tree.get_widget(box).get_active(), section = self.cfg.const[val[1]])
+ else:
+ self.cfg.set_boolean(val, self.tree.get_widget(box).get_active())
for edit in self.edits:
self.cfg.set(self.edits[edit],self.tree.get_widget(edit).get_text())
@@ -250,12 +262,16 @@ class PackageTable:
# the table
self.table = self.tree.get_widget("PackageTable")
+ # the combo vb
+ self.comboVB = self.tree.get_widget("comboVB")
+
# chechboxes
self.installedCheck = self.tree.get_widget("installedCheck")
self.maskedCheck = self.tree.get_widget("maskedCheck")
self.testingCheck = self.tree.get_widget("testingCheck")
# labels
+ self.descLabel = self.tree.get_widget("descLabel")
self.notInSysLabel = self.tree.get_widget("notInSysLabel")
self.missingLabel = self.tree.get_widget("missingLabel")
@@ -295,11 +311,11 @@ class PackageTable:
# version-combo-box
self.vCombo = self.build_vers_combo()
if not self.doEmerge: self.vCombo.set_sensitive(False)
- vb = self.tree.get_widget("comboVB")
- children = vb.get_children()
+ children = self.comboVB.get_children()
if children:
- for c in children: vb.remove(c)
- vb.pack_start(self.vCombo)
+ for c in children:
+ self.comboVB.remove(c)
+ self.comboVB.pack_start(self.vCombo)
# the label (must be here, because it depends on the combo box)
desc = self.actual_package().get_env_var("DESCRIPTION").replace("&","&")
@@ -310,7 +326,6 @@ class PackageTable:
desc = ""+desc+""
use_markup = True
desc = ""+self.actual_package().get_cp()+"\n\n"+desc
- self.descLabel = self.tree.get_widget("descLabel")
self.descLabel.set_use_markup(use_markup)
self.descLabel.set_label(desc)
@@ -332,15 +347,7 @@ class PackageTable:
pkg_flags = pkg.get_all_use_flags()
pkg_flags.sort()
for use in pkg_flags:
- if pkg.is_installed() and use in pkg.get_actual_use_flags(): # flags set during install
- enabled = True
- elif (not pkg.is_installed()) and use in pkg.get_settings("USE").split() and not flags.invert_use_flag(use) in pkg.get_new_use_flags(): # flags that would be set
- enabled = True
- elif use in pkg.get_new_use_flags():
- enabled = True
- else:
- enabled = False
- store.append([enabled, use, backend.get_use_desc(use, self.cp)])
+ store.append([pkg.is_use_flag_enabled(use), use, backend.get_use_desc(use, self.cp)])
return store
@@ -590,12 +597,6 @@ class MainWindow (Window):
self.cfg.modify_external_configs()
- # accelerators - for whatever reason they are not automatically working for popups
- self.accel_group = gtk.AccelGroup()
- self.window.add_accel_group(self.accel_group)
- self.accel_group.connect_group(ord("C"), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, lambda g, a, k, m: self.cb_copy_clicked(a))
- self.accel_group.connect_group(ord("1"), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, lambda g, a, k, m: self.cb_oneshot_clicked(a))
-
# set vpaned position
vpaned = self.tree.get_widget("vpaned")
vpaned.set_position(mHeight/2)
@@ -630,7 +631,7 @@ class MainWindow (Window):
self.packageTable.table.hide_all()
# popups
- self.queuePopup = self.create_popup("queuePopup")
+ self.queuePopup = self.tree.get_widget("queuePopup")
self.consolePopup = self.create_popup("consolePopup")
# set emerge queue
@@ -655,6 +656,9 @@ class MainWindow (Window):
col = gtk.TreeViewColumn("Options", cell, markup = 1)
self.queueList.append_column(col)
+ self.useTips = UseTips(0, self.cfg)
+ self.useTips.add_view(self.queueList)
+
def build_cat_list (self):
"""Builds the category list."""
@@ -711,8 +715,8 @@ class MainWindow (Window):
else: title = ("Console (%s)" % title)
gobject.idle_add(self.notebook.set_tab_label_text, self.termHB, title)
-
+
def cb_cat_list_selection (self, view):
"""Callback for a category-list selection. Updates the package list with the packages in the category."""
# get the selected category
@@ -885,7 +889,8 @@ class MainWindow (Window):
pthinfo = object.get_path_at_pos(x, y)
if pthinfo is not None:
path, col, cellx, celly = pthinfo
- if self.queueTree.is_in_emerge(self.queueTree.get_original().get_iter(path)):
+ it = self.queueTree.get_original().get_iter(path)
+ if self.queueTree.is_in_emerge(it) and self.queueTree.iter_has_parent(it):
object.grab_focus()
object.set_cursor(path, col, 0)
self.queuePopup.popup(None, None, None, event.button, time)
--
cgit v1.2.3-70-g09d2