/* This file is part of the KDE project Copyright (C) 2003 Aaron J. Seigo <aseigo@olympusproject.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <iostream> using namespace std; #include <unistd.h> #include <pwd.h> #include <sys/types.h> #include <tqdir.h> #include <tqimage.h> #include <tqlabel.h> #include <tqlayout.h> #include <tqpainter.h> #include <tqregexp.h> #include <tqsettings.h> #include <tqstyle.h> #include <tqfile.h> #include <dcopclient.h> #include <tdeapplication.h> #include <kcombobox.h> #include <kdialog.h> #include <tdeglobal.h> #include <kiconloader.h> #include <kiconeffect.h> #include <tdelocale.h> #include <tdemessagebox.h> #include <kmimetype.h> #include <kpixmap.h> #include <tderecentdocument.h> #include <kservice.h> #include <kservicegroup.h> #include <kstandarddirs.h> #include <kstdaction.h> #include <tdesycocaentry.h> #include "menuinfo.h" #include "service_mnu.h" #include "kicker.h" #include "tom.h" const int configureMenuID = 1000; const int contextMenuTitleID = 10000; const int destMenuTitleID = 10001; extern "C" { KDE_EXPORT void* init_kickermenu_tom() { TDEGlobal::locale()->insertCatalogue("libkickermenu_tom"); return new TOMFactory; } }; TOMFactory::TOMFactory(TQObject *parent, const char *name) : KLibFactory(parent, name) { } TQObject* TOMFactory::createObject(TQObject *parent, const char *name, const char*, const TQStringList&) { return new TOM((TQWidget*)parent, name); } #include <tqmenudata.h> /* * TODO: switch the backgroundmode when translucency turns on/off * TODO: catch font changes too? */ class runMenuWidget : public TQWidget, public QMenuItem { public: runMenuWidget(TDEPopupMenu* parent, int index) : TQWidget (parent), m_menu(parent), m_index(index) { setFocusPolicy(StrongFocus); TQHBoxLayout* runLayout = new TQHBoxLayout(this); textRect = fontMetrics().boundingRect(i18n("Run:")); runLayout->setSpacing(KDialog::spacingHint()); runLayout->addSpacing((KDialog::spacingHint() * 3) + TDEIcon::SizeMedium + textRect.width()); icon = DesktopIcon("system-run", TDEIcon::SizeMedium); /*TQLabel* l1 = new TQLabel(this); TQPixmap foo = DesktopIcon("system-run", TDEIcon::SizeMedium); cout << "foo is: " << foo.width() << " by " << foo.height() << endl; l1->setPixmap(foo); runLayout->addWidget(l1);*/ /*TQLabel* l2 = new TQLabel(i18n("&Run: "), this); l2->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); l2->setBuddy(this); runLayout->addWidget(l2);*/ m_runEdit = new KHistoryCombo(this); m_runEdit->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Preferred); runLayout->addWidget(m_runEdit, 10); runLayout->addSpacing(KDialog::spacingHint()); TQSettings settings; if (settings.readEntry("/TDEStyle/Settings/MenuTransparencyEngine", "Disabled") != "Disabled") { setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); //l1->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); //l2->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); m_runEdit->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); } else { /*setBackgroundMode(Qt::NoBackground, Qt::NoBackground); l1->setBackgroundMode(Qt::NoBackground, Qt::NoBackground); l2->setBackgroundMode(Qt::NoBackground, Qt::NoBackground); m_runEdit->setBackgroundMode(Qt::NoBackground, Qt::NoBackground);*/ //l1->setAutoMask(true); //l1->setBackgroundMode(Qt::NoBackground, Qt::NoBackground); //l2->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); //m_runEdit->setBackgroundMode(Qt::X11ParentRelative, Qt::X11ParentRelative); } setMinimumHeight(TDEIcon::SizeMedium + 2); } ~runMenuWidget() {} void paintEvent(TQPaintEvent * /*e*/) { TQPainter p(this); TQRect r(rect()); // ew, nasty hack. may result in coredumps due to horrid C-style cast??? kapp->style().drawControl(TQStyle::CE_PopupMenuItem, &p, m_menu, r, palette().active(), TQStyle::Style_Enabled, TQStyleOption(static_cast<TQMenuItem*>(this), 0, TDEIcon::SizeMedium )); p.drawPixmap(KDialog::spacingHint(), 1, icon); p.drawText((KDialog::spacingHint() * 2) + TDEIcon::SizeMedium, textRect.height() + ((height() - textRect.height()) / 2), i18n("Run:")); } void focusInEvent (TQFocusEvent* e) { if (!e || e->gotFocus()) { m_menu->setActiveItem(m_index); m_runEdit->setFocus(); } } void enterEvent(TQEvent*) { focusInEvent(0); } private: TDEPopupMenu* m_menu; KHistoryCombo* m_runEdit; TQPixmap icon; TQRect textRect; int m_index; }; TOM::TOM(TQWidget *parent, const char *name) : KPanelMenu(parent, name), m_maxIndex(0) { disableAutoClear(); m_submenus.setAutoDelete(true); setCaption(i18n("Task-Oriented Menu")); // KMenu legacy: support some menu config options TDEConfig* config = TDEGlobal::config(); config->setGroup("menus"); m_detailedTaskEntries = config->readBoolEntry("DetailedMenuEntries", false); if (m_detailedTaskEntries) { m_detailedNamesFirst = config->readBoolEntry("DetailedEntriesNamesFirst", false); } m_isImmutable = Kicker::kicker()->isImmutable() || config->groupIsImmutable("TOM"); config->setGroup("TOM"); m_largerFont = font(); m_largerFont = config->readFontEntry("Font", &m_largerFont); } TOM::~TOM() { slotClear(); } void TOM::initializeRecentApps(TQPopupMenu* menu) { /* * TODO: make this real * add a KDE-wide "commands run" registry */ if (!m_isImmutable) { menu->insertSeparator(); menu->insertItem(i18n("Configure This Menu"), configureMenuID); } } void TOM::initializeRecentDocs() { m_recentDocsMenu->clear(); m_recentDocsMenu->insertItem(SmallIconSet("history_clear"), i18n("Clear History"), this, TQT_SLOT(clearRecentDocHistory())); m_recentDocsMenu->insertSeparator(); m_recentDocURLs = TDERecentDocument::recentDocuments(); if (m_recentDocURLs.isEmpty()) { setItemEnabled(m_recentDocsMenu->insertItem(i18n("No Entries")), false); return; } int id = 0; for (TQStringList::ConstIterator it = m_recentDocURLs.begin(); it != m_recentDocURLs.end(); ++it) { /* * TODO: make the number of visible items configurable? */ KDesktopFile f(*it, true /* read only */); m_recentDocsMenu->insertItem(DesktopIcon(f.readIcon(), TDEIcon::SizeMedium), f.readName().replace('&', "&&"), id); ++id; } } int TOM::appendTaskGroup(TDEConfig& config, bool inSubMenu) { if (!config.hasGroup("General")) { return 0; } config.setGroup("General"); if (config.readBoolEntry("Hidden", false)) { return 0; } TQString name = config.readEntry("Name", i18n("Unknown")); TQString icon = config.readEntry("Icon"); int numTasks = config.readNumEntry("NumTasks", 0); if (numTasks < 1) { return 0; } TDEPopupMenu* taskGroup; if( inSubMenu ) { taskGroup = new TDEPopupMenu(this); if (icon != TQString::null) { insertItem(DesktopIcon(icon, TDEIcon::SizeMedium), name, taskGroup); } else { insertItem(name, taskGroup); } } else { taskGroup = this; } int foundTasks = 0; for (int i = 0; i < numTasks; ++i) { TQString groupName = TQString("Task%1").arg(i); if (config.hasGroup(groupName)) { config.setGroup(groupName); if (config.readBoolEntry("Hidden", false)) { continue; } TQString name = config.readEntry("Name"); // in case the name contains an ampersand, double 'em up name.replace("&", "&&"); TQString desktopfile = config.readPathEntry("DesktopFile"); KService::Ptr pService = KService::serviceByDesktopPath(desktopfile); if (!pService) { pService = KService::serviceByDesktopName(desktopfile); if (!pService) { continue; } } TQString execName = pService->name(); TQString icon = pService->icon(); if (m_detailedTaskEntries && !execName.isEmpty()) { TQString tmp = i18n("%1 (%2)"); if (m_detailedNamesFirst) { name = tmp.arg(execName).arg(name); } else { name = tmp.arg(name).arg(execName); } } ++m_maxIndex; if (icon.isEmpty()) { taskGroup->insertItem(name, m_maxIndex); } else { TQIconSet iconset = BarIconSet(icon, 22); if (iconset.isNull()) taskGroup->insertItem(name, m_maxIndex); else taskGroup->insertItem(iconset, name, m_maxIndex); } m_tasks.insert(m_maxIndex, pService); ++foundTasks; } } // if we end up with an empty task group, get rid of the menu if (foundTasks == 0) { if (inSubMenu) { delete taskGroup; } return 0; } connect(taskGroup, TQT_SIGNAL(activated(int)), this, TQT_SLOT(runTask(int))); // so we have an actual task group menu with tasks, let's add it if (inSubMenu) { TQObject::connect(taskGroup, TQT_SIGNAL(aboutToShowContextMenu(TDEPopupMenu*, int, TQPopupMenu*)), this, TQT_SLOT(contextualizeRMBmenu(TDEPopupMenu*, int, TQPopupMenu*))); m_submenus.append(taskGroup); taskGroup->setFont(m_largerFont); taskGroup->setKeyboardShortcutsEnabled(true); if (!m_isImmutable && !config.isImmutable()) { taskGroup->insertSeparator(); taskGroup->insertItem("Modify These Tasks", configureMenuID); TQPopupMenu* rmbMenu = taskGroup->contextMenu(); rmbMenu->setFont(m_largerFont); TDEPopupTitle* title = new TDEPopupTitle(); title->setText(i18n("%1 Menu Editor").arg(name)); rmbMenu->insertItem(title, contextMenuTitleID); rmbMenu->insertItem(i18n("Add This Task to Panel")); rmbMenu->insertItem(i18n("Modify This Task...")); rmbMenu->insertItem(i18n("Remove This Task..."), this, TQT_SLOT(removeTask())); rmbMenu->insertItem(i18n("Insert New Task...")); } } return foundTasks; } /* * TODO: track configuration file changes. void TOM::configChanged() { KPanelMenu::configChanged(); setInitialized(false); initialize(); } */ void TOM::initialize() { if (initialized()) { return; } setInitialized(true); m_submenus.clear(); m_tasks.clear(); clear(); /* hack to make items larger. should use something better? m_largerFont = font(); if (m_largerFont.pointSize() < 18) { m_largerFont.setPointSize(18); } setFont(m_largerFont);*/ /*if (!loadSidePixmap()) { m_sidePixmap = m_sideTilePixmap = TQPixmap(); } else { connect(kapp, TQT_SIGNAL(tdedisplayPaletteChanged()), TQT_SLOT(paletteChanged())); }*/ // TASKS insertTitle(i18n("Tasks"), contextMenuTitleID); TQStringList dirs = TDEGlobal::dirs()->findDirs("data", "kicker/tom/"); TQStringList::ConstIterator dIt = dirs.begin(); TQStringList::ConstIterator dEnd = dirs.end(); for (; dIt != dEnd; ++dIt ) { TQDir dir(*dIt); TQStringList entries = dir.entryList("*rc", TQDir::Files); TQStringList::ConstIterator eIt = entries.begin(); TQStringList::ConstIterator eEnd = entries.end(); for (; eIt != eEnd; ++eIt ) { TDEConfig config(*dIt + *eIt); appendTaskGroup(config); } } PanelServiceMenu* moreApps = new PanelServiceMenu(TQString::null, TQString::null, this, "More Applications"); moreApps->setFont(m_largerFont); insertItem(DesktopIcon("misc", TDEIcon::SizeMedium), i18n("More Applications"), moreApps); m_submenus.append(moreApps); if (!m_isImmutable) { insertSeparator(); // TODO: connect to taskgroup edits insertItem("Modify The Above Tasks"); } // DESTINATIONS insertTitle(i18n("Destinations"), destMenuTitleID); int numDests = 0; TQString destinationsConfig = locate("appdata", "tom/destinations"); if (!destinationsConfig.isEmpty() && TQFile::exists(destinationsConfig)) { TDEConfig config(destinationsConfig); numDests = appendTaskGroup(config, false); } if (numDests == 0) { removeItem(destMenuTitleID); } else if (kapp->authorize("run_command")) { insertItem(DesktopIcon("system-run", TDEIcon::SizeMedium), i18n("Run Command..."), this, TQT_SLOT(runCommand())); } // RECENTLY USED ITEMS insertTitle(i18n("Recently Used Items"), contextMenuTitleID); m_recentDocsMenu = new TDEPopupMenu(this, "recentDocs"); m_recentDocsMenu->setFont(m_largerFont); connect(m_recentDocsMenu, TQT_SIGNAL(aboutToShow()), this, TQT_SLOT(initializeRecentDocs())); connect(m_recentDocsMenu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(openRecentDocument(int))); insertItem(DesktopIcon("text-x-generic", TDEIcon::SizeMedium), i18n("Recent Documents"), m_recentDocsMenu); m_submenus.append(m_recentDocsMenu); TDEPopupMenu* recentApps = new TDEPopupMenu(this, "recentApps"); recentApps->setFont(m_largerFont); recentApps->setKeyboardShortcutsEnabled(true); initializeRecentApps(recentApps); insertItem(i18n("Recent Applications"), recentApps); m_submenus.append(recentApps); // SPECIAL ITEMS insertTitle(i18n("Special Items"), contextMenuTitleID); // if we have no destinations, put the run command here if (numDests == 0 && kapp->authorize("run_command")) { insertItem(DesktopIcon("system-run", TDEIcon::SizeMedium), i18n("Run Command..."), this, TQT_SLOT(runCommand())); } TDEConfig* config = TDEGlobal::config(); TQStringList menu_ext = config->readListEntry("Extensions"); if (!menu_ext.isEmpty()) { bool needSeparator = false; for (TQStringList::ConstIterator it = menu_ext.begin(); it != menu_ext.end(); ++it) { MenuInfo info(*it); if (!info.isValid()) { continue; } KPanelMenu *menu = info.load(); if (menu) { ++m_maxIndex; insertItem(DesktopIcon(info.icon(), TDEIcon::SizeMedium), info.name(), menu, m_maxIndex); m_submenus.append(menu); needSeparator = true; } } if (needSeparator) { insertSeparator(); } } TQString username; struct passwd *userInfo = getpwuid(getuid()); if (userInfo) { username = TQString::fromLocal8Bit(userInfo->pw_gecos); if (username.find(',') != -1) { // Remove everything from and including first comma username.truncate(username.find(',')); } if (username.isEmpty()) { username = TQString::fromLocal8Bit(userInfo->pw_name); } } insertItem(DesktopIcon("system-log-out", TDEIcon::SizeMedium), i18n("Logout %1").arg(username), this, TQT_SLOT(logout())); } void TOM::reload() { setInitialized(false); initialize(); } void TOM::contextualizeRMBmenu(TDEPopupMenu* menu, int menuItem, TQPopupMenu* ctxMenu) { if (menuItem == configureMenuID) { menu->hideContextMenu(); return; } ctxMenu->removeItem(contextMenuTitleID); TQString text = menu->text(menuItem); int parens = text.find('(') - 1; if (parens > 0) { text = text.left(parens); } TDEPopupTitle* title = new TDEPopupTitle(); title->setText(i18n("The \"%2\" Task").arg(text)); ctxMenu->insertItem(title, contextMenuTitleID, 0); } void TOM::slotClear() { KPanelMenu::slotClear(); m_submenus.clear(); m_tasks.clear(); } void TOM::slotExec(int /* id */) { // Reimplemented because we have to } void TOM::removeTask() { // TODO: write this change out to the appropriate config file TQString task = TDEPopupMenu::contextMenuFocus()->text(TDEPopupMenu::contextMenuFocusItem()); if (KMessageBox::warningContinueCancel(this, i18n("<qt>Are you sure you want to remove the <strong>%1</strong> task?<p>" "<em>Tip: You can restore the task after it has been removed by selecting the "Modify These Tasks" entry</em></qt>").arg(task), i18n("Remove Task?"),KStdGuiItem::del()) == KMessageBox::Continue) { m_tasks.remove(TDEPopupMenu::contextMenuFocusItem()); TDEPopupMenu::contextMenuFocus()->removeItem(TDEPopupMenu::contextMenuFocusItem()); } } // TODO: should we have a side graphic like the TDE Menu? personally, i don't // think it helps usability. it's nice for branding, but that's it. perhaps a // top image, or something blended with the actual menu background? deffinitely // a job for a graphic artist. /* * if this goes in, it should be shared w/the kmenu * bool TOM::loadSidePixmap() { TDEConfig *config = TDEGlobal::config(); TQColor color = palette().active().highlight(); TQImage image; config->setGroup("WM"); TQColor activeTitle = config->readColorEntry("activeBackground", &color); TQColor inactiveTitle = config->readColorEntry("inactiveBackground", &color); config->setGroup("KMenu"); if (!config->readBoolEntry("Usem_sidePixmap", true)) return false; // figure out which color is most suitable for recoloring to int h1, s1, v1, h2, s2, v2, h3, s3, v3; activeTitle.hsv(&h1, &s1, &v1); inactiveTitle.hsv(&h2, &s2, &v2); palette().active().background().hsv(&h3, &s3, &v3); if ( (abs(h1-h3)+abs(s1-s3)+abs(v1-v3) < abs(h2-h3)+abs(s2-s3)+abs(v2-v3)) && ((abs(h1-h3)+abs(s1-s3)+abs(v1-v3) < 32) || (s1 < 32)) && (s2 > s1)) color = inactiveTitle; else color = activeTitle; // limit max/min brightness int r, g, b; color.rgb(&r, &g, &b); int gray = tqGray(r, g, b); if (gray > 180) { r = (r - (gray - 180) < 0 ? 0 : r - (gray - 180)); g = (g - (gray - 180) < 0 ? 0 : g - (gray - 180)); b = (b - (gray - 180) < 0 ? 0 : b - (gray - 180)); } else if (gray < 76) { r = (r + (76 - gray) > 255 ? 255 : r + (76 - gray)); g = (g + (76 - gray) > 255 ? 255 : g + (76 - gray)); b = (b + (76 - gray) > 255 ? 255 : b + (76 - gray)); } color.setRgb(r, g, b); TQString sideName = config->readEntry("SideName", "kside.png"); TQString sideTileName = config->readEntry("SideTileName", "kside_tile.png"); image.load(locate("data", "kicker/pics/" + sideName)); if (image.isNull()) { return false; } TDEIconEffect::colorize(image, color, 1.0); m_sidePixmap.convertFromImage(image); image.load(locate("data", "kicker/pics/" + sideTileName)); if (image.isNull()) { return false; } TDEIconEffect::colorize(image, color, 1.0); m_sideTilePixmap.convertFromImage(image); if (m_sidePixmap.width() != m_sideTilePixmap.width()) { return false; } // pretile the pixmap to a height of at least 100 pixels if (m_sideTilePixmap.height() < 100) { int tiles = (int)(100 / m_sideTilePixmap.height()) + 1; TQPixmap preTiledPixmap(m_sideTilePixmap.width(), m_sideTilePixmap.height() * tiles); TQPainter p(&preTiledPixmap); p.drawTiledPixmap(preTiledPixmap.rect(), m_sideTilePixmap); m_sideTilePixmap = preTiledPixmap; } return true; } void TOM::paletteChanged() { if (!loadSidePixmap()) m_sidePixmap = m_sideTilePixmap = TQPixmap(); } void TOM::setMinimumSize(const TQSize & s) { KPanelMenu::setMinimumSize(s.width() + m_sidePixmap.width(), s.height()); } void TOM::setMaximumSize(const TQSize & s) { KPanelMenu::setMaximumSize(s.width() + m_sidePixmap.width(), s.height()); } void TOM::setMinimumSize(int w, int h) { KPanelMenu::setMinimumSize(w + m_sidePixmap.width(), h); } void TOM::setMaximumSize(int w, int h) { KPanelMenu::setMaximumSize(w + m_sidePixmap.width(), h); } TQRect TOM::sideImageRect() { return TQStyle::visualRect( TQRect( frameWidth(), frameWidth(), m_sidePixmap.width(), height() - 2*frameWidth() ), this ); } void TOM::resizeEvent(TQResizeEvent * e) { setFrameRect( TQStyle::visualRect( TQRect( m_sidePixmap.width(), 0, width() - m_sidePixmap.width(), height() ), this ) ); } void TOM::paintEvent(TQPaintEvent * e) { if (m_sidePixmap.isNull()) { KPanelMenu::paintEvent(e); return; } TQPainter p(this); style().tqdrawPrimitive( TQStyle::PE_PanelPopup, &p, TQRect( 0, 0, width(), height() ), colorGroup(), TQStyle::Style_Default, TQStyleOption( frameWidth(), 0 ) ); TQRect r = sideImageRect(); r.setBottom( r.bottom() - m_sidePixmap.height() ); p.drawTiledPixmap( r, m_sideTilePixmap ); r = sideImageRect(); r.setTop( r.bottom() - m_sidePixmap.height() ); p.drawPixmap( r, m_sidePixmap ); drawContents( &p ); } TQMouseEvent TOM::translateMouseEvent( TQMouseEvent* e ) { TQRect side = sideImageRect(); if ( !side.contains( e->pos() ) ) return *e; TQPoint newpos( e->pos() ); TQApplication::reverseLayout() ? newpos.setX( newpos.x() - side.width() ) : newpos.setX( newpos.x() + side.width() ); TQPoint newglobal( e->globalPos() ); TQApplication::reverseLayout() ? newglobal.setX( newpos.x() - side.width() ) : newglobal.setX( newpos.x() + side.width() ); return TQMouseEvent( e->type(), newpos, newglobal, e->button(), e->state() ); } void TOM::mousePressEvent(TQMouseEvent * e) { TQMouseEvent newEvent = translateMouseEvent(e); KPanelMenu::mousePressEvent( &newEvent ); } void TOM::mouseReleaseEvent(TQMouseEvent *e) { TQMouseEvent newEvent = translateMouseEvent(e); KPanelMenu::mouseReleaseEvent( &newEvent ); } void TOM::mouseMoveEvent(TQMouseEvent *e) { TQMouseEvent newEvent = translateMouseEvent(e); KPanelMenu::mouseMoveEvent( &newEvent ); }*/ extern int kicker_screen_number; void TOM::runCommand() { TQByteArray data; TQCString appname( "kdesktop" ); if ( kicker_screen_number ) appname.sprintf("kdesktop-screen-%d", kicker_screen_number); kapp->updateRemoteUserTimestamp( appname ); kapp->dcopClient()->send( appname, "KDesktopIface", "popupExecuteCommand()", data ); } void TOM::runTask(int id) { if (!m_tasks.contains(id)) return; kapp->propagateSessionManager(); TDEApplication::startServiceByDesktopPath(m_tasks[id]->desktopEntryPath(), TQStringList(), 0, 0, 0, "", true); } void TOM::clearRecentDocHistory() { TDERecentDocument::clear(); } void TOM::openRecentDocument(int id) { if (id >= 0) { kapp->propagateSessionManager(); KURL u; u.setPath(m_recentDocURLs[id]); KDEDesktopMimeType::run(u, true); } } void TOM::logout() { kapp->requestShutDown(); } #include "tom.moc"